Skip to content

Instantly share code, notes, and snippets.

@simonw
Created April 20, 2026 16:52
Show Gist options
  • Select an option

  • Save simonw/ecaad98efe0f747e27bc0e0ebc669e94 to your computer and use it in GitHub Desktop.

Select an option

Save simonw/ecaad98efe0f747e27bc0e0ebc669e94 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pedaling Pelican</title>
<style>
:root {
--bg-color: #2c3e50;
--panel-color: #ecf0f1;
--accent-color: #e67e22;
--text-color: #2c3e50;
}
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: var(--bg-color);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
}
.canvas-container {
position: relative;
width: 90%;
max-width: 800px;
background: linear-gradient(to bottom, #87CEEB 0%, #E0F7FA 60%, #dcdcdc 60%, #95a5a6 100%);
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
overflow: hidden;
border: 4px solid #34495e;
}
/* Control Panel Styling - OUTSIDE SVG */
.controls {
margin-top: 20px;
padding: 15px 25px;
background-color: var(--panel-color);
border-radius: 50px;
display: flex;
gap: 20px;
align-items: center;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.control-group {
display: flex;
flex-direction: column;
align-items: center;
}
label {
font-size: 0.8rem;
font-weight: bold;
color: var(--text-color);
margin-bottom: 5px;
text-transform: uppercase;
letter-spacing: 1px;
}
input[type=range] {
-webkit-appearance: none;
width: 150px;
height: 6px;
background: #bdc3c7;
border-radius: 5px;
outline: none;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--accent-color);
cursor: pointer;
transition: transform 0.1s;
}
input[type=range]::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
button {
background-color: var(--accent-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: 20px;
font-weight: bold;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s, transform 0.1s;
}
button:hover {
background-color: #d35400;
}
button:active {
transform: scale(0.95);
}
/* Scenery CSS Animation (Allowed as it's not SVG) */
.cloud {
position: absolute;
background: rgba(255, 255, 255, 0.8);
border-radius: 50px;
animation: floatCloud 20s linear infinite;
}
@keyframes floatCloud {
0% { transform: translateX(120%); }
100% { transform: translateX(-120%); }
}
</style>
</head>
<body>
<div class="canvas-container">
<!-- Scenery Elements (HTML/CSS for background movement) -->
<div class="cloud" style="width: 100px; height: 40px; top: 10%; left: 20%; animation-duration: 25s;"></div>
<div class="cloud" style="width: 140px; height: 60px; top: 15%; left: 60%; animation-duration: 18s;"></div>
<svg id="mainSvg" viewBox="0 0 800 500" preserveAspectRatio="xMidYMid slice" style="width: 100%; height: auto; display: block;">
<defs>
<!-- Gradients -->
<linearGradient id="beakGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#f39c12;stop-opacity:1" />
<stop offset="100%" style="stop-color:#e67e22;stop-opacity:1" />
</linearGradient>
<linearGradient id="metalGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#bdc3c7;stop-opacity:1" />
<stop offset="50%" style="stop-color:#95a5a6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#7f8c8d;stop-opacity:1" />
</linearGradient>
<filter id="shadowBlur">
<feGaussianBlur in="SourceGraphic" stdDeviation="2" />
</filter>
</defs>
<!-- Ground Shadow -->
<ellipse cx="400" cy="440" rx="180" ry="15" fill="rgba(0,0,0,0.2)" filter="url(#shadowBlur)" />
<!-- === THE BIKE === -->
<g id="bike" transform="translate(0, 0)">
<!-- Back Wheel -->
<g id="wheelBack" transform="translate(250, 400)">
<circle r="60" fill="none" stroke="#34495e" stroke-width="8" />
<circle r="55" fill="none" stroke="#ecf0f1" stroke-width="4" />
<!-- Spokes Group -->
<g id="spokesBack">
<line x1="0" y1="-55" x2="0" y2="55" stroke="#bdc3c7" stroke-width="2" />
<line x1="-48" y1="-27" x2="48" y2="27" stroke="#bdc3c7" stroke-width="2" />
<line x1="-48" y1="27" x2="48" y2="-27" stroke="#bdc3c7" stroke-width="2" />
</g>
<circle r="8" fill="#7f8c8d" />
</g>
<!-- Front Wheel -->
<g id="wheelFront" transform="translate(550, 400)">
<circle r="60" fill="none" stroke="#34495e" stroke-width="8" />
<circle r="55" fill="none" stroke="#ecf0f1" stroke-width="4" />
<g id="spokesFront">
<line x1="0" y1="-55" x2="0" y2="55" stroke="#bdc3c7" stroke-width="2" />
<line x1="-48" y1="-27" x2="48" y2="27" stroke="#bdc3c7" stroke-width="2" />
<line x1="-48" y1="27" x2="48" y2="-27" stroke="#bdc3c7" stroke-width="2" />
</g>
<circle r="8" fill="#7f8c8d" />
</g>
<!-- Frame -->
<path d="M250 400 L360 400 L310 300 L250 400" fill="none" stroke="#e74c3c" stroke-width="8" stroke-linejoin="round" />
<path d="M310 300 L500 300 L550 400" fill="none" stroke="#e74c3c" stroke-width="8" stroke-linecap="round" />
<path d="M360 400 L450 250" fill="none" stroke="#e74c3c" stroke-width="6" stroke-linecap="round" /> <!-- Seat post -->
<path d="M500 300 L490 220" fill="none" stroke="#e74c3c" stroke-width="6" stroke-linecap="round" /> <!-- Handlebar post -->
<!-- Chain/Gear -->
<circle cx="360" cy="400" r="15" fill="none" stroke="#7f8c8d" stroke-width="3" />
<circle cx="360" cy="400" r="5" fill="#2c3e50" />
<path d="M360 400 L250 400" stroke="#7f8c8d" stroke-width="3" stroke-dasharray="4,2" /> <!-- Chain -->
<!-- Pedal Crank (Left Side - Visual) -->
<g id="pedalCrank" transform="translate(360, 400)">
<line x1="0" y1="0" x2="0" y2="30" stroke="#7f8c8d" stroke-width="5" stroke-linecap="round" />
<rect x="-8" y="25" width="16" height="10" rx="2" fill="#2c3e50" />
</g>
<!-- Handlebars -->
<path d="M490 220 L480 200 L510 200" fill="none" stroke="#bdc3c7" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" />
<circle cx="510" cy="200" r="5" fill="#2c3e50" /> <!-- Grip -->
<!-- Seat -->
<path d="M420 250 Q460 245 470 255" fill="none" stroke="#2c3e50" stroke-width="12" stroke-linecap="round" />
</g>
<!-- === THE PELICAN === -->
<g id="pelican" transform="translate(0, -20)">
<!-- Back Leg (Left Leg) -->
<g id="legLeft" transform="translate(440, 320)">
<path d="M0 0 L-10 40" stroke="#f1c40f" stroke-width="8" stroke-linecap="round" />
<path d="M-10 40 L-20 50 L0 50" fill="none" stroke="#f1c40f" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" />
</g>
<!-- Body -->
<ellipse cx="420" cy="280" rx="70" ry="50" fill="#ffffff" />
<path d="M380 260 Q420 240 460 270" fill="none" stroke="#ecf0f1" stroke-width="2" opacity="0.5"/>
<!-- Neck & Head -->
<g id="headGroup" transform="translate(460, 240) rotate(-10)">
<path d="M0 40 Q10 0 30 -20 Q60 -40 90 -20" fill="none" stroke="#ffffff" stroke-width="28" stroke-linecap="round" />
<!-- Head Detail -->
<circle cx="85" cy="-25" r="16" fill="#ffffff" />
<!-- Beak -->
<path d="M90 -25 L160 -10 L90 5 Z" fill="url(#beakGradient)" stroke="#d35400" stroke-width="1" />
<path d="M90 5 Q110 20 120 25 Q100 25 90 5" fill="#e67e22" /> <!-- Pouch -->
<!-- Eye -->
<circle cx="80" cy="-30" r="3" fill="#000" />
</g>
<!-- Wing (Flapping) -->
<g id="wing" transform="translate(420, 270)">
<path d="M0 0 Q-40 -10 -60 20 Q-20 40 10 20 Z" fill="#ffffff" stroke="#bdc3c7" stroke-width="2" />
<!-- Wing Feathers -->
<path d="M-60 20 L-50 35 M-45 23 L-35 38 M-30 25 L-20 40" stroke="#bdc3c7" stroke-width="2" stroke-linecap="round"/>
</g>
<!-- Front Leg (Right Leg - Pushing Pedal) -->
<g id="legRightGroup" transform="translate(440, 320)">
<line id="thigh" x1="0" y1="0" x2="10" y2="40" stroke="#f1c40f" stroke-width="8" stroke-linecap="round" />
<g id="shinGroup" transform="translate(10, 40)">
<line id="shin" x1="0" y1="0" x2="-5" y2="40" stroke="#f1c40f" stroke-width="7" stroke-linecap="round" />
<g id="footGroup" transform="translate(-5, 40)">
<path d="M0 0 L-10 10 L10 10" fill="none" stroke="#f1c40f" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" />
</g>
</g>
</g>
</g>
</svg>
</div>
<!-- Control Panel -->
<div class="controls">
<button id="toggleBtn">Pause</button>
<div class="control-group">
<label for="speedRange">Speed</label>
<input type="range" id="speedRange" min="0" max="20" value="8">
</div>
<div class="control-group">
<label for="flapRange">Wing Flap</label>
<input type="range" id="flapRange" min="0" max="10" value="5">
</div>
</div>
<script>
// === Configuration & State ===
const state = {
isPlaying: true,
speed: 8, // Pedal speed
flapIntensity: 5, // Wing movement
wheelAngle: 0,
pedalAngle: 0,
wingAngle: 0,
time: 0
};
// === DOM Elements ===
const els = {
wheelBack: document.getElementById('wheelBack'),
wheelFront: document.getElementById('wheelFront'),
pedalCrank: document.getElementById('pedalCrank'),
legRightGroup: document.getElementById('legRightGroup'),
thigh: document.getElementById('thigh'),
shin: document.getElementById('shin'),
shinGroup: document.getElementById('shinGroup'),
footGroup: document.getElementById('footGroup'),
wing: document.getElementById('wing'),
head: document.getElementById('headGroup'),
// Controls
toggleBtn: document.getElementById('toggleBtn'),
speedRange: document.getElementById('speedRange'),
flapRange: document.getElementById('flapRange')
};
// === Animation Logic ===
function toRad(deg) {
return deg * (Math.PI / 180);
}
function updatePhysics() {
if (!state.isPlaying) return;
// Increment Time
state.time += state.speed * 0.05;
// 1. Wheels (Simple Rotation)
state.wheelAngle += state.speed * 2;
const wheelTransform = `rotate(${state.wheelAngle})`;
els.wheelBack.setAttribute('transform', `translate(250, 400) ${wheelTransform}`);
els.wheelFront.setAttribute('transform', `translate(550, 400) ${wheelTransform}`);
// 2. Pedals (Circular Motion)
state.pedalAngle += state.speed * 2;
const pedalRad = toRad(state.pedalAngle);
const crankX = Math.cos(pedalRad) * 30; // Crank length approx 30
const crankY = Math.sin(pedalRad) * 30;
els.pedalCrank.setAttribute('transform', `translate(360, 400) rotate(${state.pedalAngle})`);
// 3. Inverse Kinematics (Simplified) for Right Leg
// Hip is at (440, 300) relative to group, but group is translated to 440, 320
// Let's assume Hip is origin of legRightGroup (0,0)
// Target is Pedal Center + Crank Offset
// Global Coordinates of Pedal Center
const pedalCenterX = 360;
const pedalCenterY = 400;
// Current Pedal Position
const targetX = crankX; // Relative to pedal center
const targetY = crankY; // Relative to pedal center
// We need to map this relative to the Hip (440, 320)
// Hip Offset from Pedal Center: Hip(440,320) - Pedal(360,400) = (80, -80)
const hipOffsetX = 80;
const hipOffsetY = -80;
const localTargetX = targetX - hipOffsetX;
const localTargetY = targetY - hipOffsetY;
// Inverse Kinematics for 2-segment leg
// Segment lengths: Thigh (L1) approx 45, Shin (L2) approx 45
const L1 = 45;
const L2 = 45;
const dist = Math.sqrt(localTargetX**2 + localTargetY**2);
// Clamp distance to avoid breaking
const clampedDist = Math.min(dist, L1 + L2 - 0.1);
// Law of Cosines
// c^2 = a^2 + b^2 - 2ab cos(C)
// Angle at knee (internal)
const cosKnee = (L1**2 + L2**2 - clampedDist**2) / (2 * L1 * L2);
let kneeAngle = Math.acos(Math.max(-1, Math.min(1, cosKnee))); // 0 to PI
// Angle of the vector to target
const targetAngle = Math.atan2(localTargetY, localTargetX);
// Angle of thigh relative to horizontal
// Law of Cosines for thigh angle relative to target line
const cosThighOffset = (L1**2 + clampedDist**2 - L2**2) / (2 * L1 * clampedDist);
const thighOffset = Math.acos(Math.max(-1, Math.min(1, cosThighOffset)));
const thighAngle = targetAngle + thighOffset;
// Apply Transforms to Leg
// Thigh rotation
els.thigh.setAttribute('x2', Math.cos(thighAngle) * L1);
els.thigh.setAttribute('y2', Math.sin(thighAngle) * L1);
// Shin Group moves to end of thigh
els.shinGroup.setAttribute('transform', `translate(${Math.cos(thighAngle)*L1}, ${Math.sin(thighAngle)*L1})`);
// Shin rotation (Thigh angle + Knee Angle)
// Note: Knee bends backwards, so we add or subtract depending on coord system.
// Standard IK usually bends one way. Let's add PI - kneeAngle
const shinGlobalAngle = thighAngle + (Math.PI - kneeAngle);
els.shin.setAttribute('x2', Math.cos(shinGlobalAngle) * L2);
els.shin.setAttribute('y2', Math.sin(shinGlobalAngle) * L2);
// Foot Group moves to end of shin
// We orient the foot to be flat-ish on the pedal
// Pedal is rotating. We want foot to stay horizontal-ish relative to ground,
// or rotate with pedal. Let's rotate with pedal for realism.
els.footGroup.setAttribute('transform', `translate(${Math.cos(shinGlobalAngle)*L2}, ${Math.sin(shinGlobalAngle)*L2}) rotate(${state.pedalAngle * 0.5})`);
// 4. Wing Flapping
// Sine wave based on time
const flapBase = Math.sin(state.time * 0.5);
const flapDeg = flapBase * state.flapIntensity * 5; // Multiplier for range
// Pivot is around body center approx
els.wing.setAttribute('transform', `translate(420, 270) rotate(${flapDeg})`);
// 5. Head Bobbing
const bobY = Math.sin(state.time * 0.2) * 2;
els.head.setAttribute('transform', `translate(460, ${240 + bobY}) rotate(-10)`);
}
function loop() {
updatePhysics();
requestAnimationFrame(loop);
}
// === Event Listeners ===
els.toggleBtn.addEventListener('click', () => {
state.isPlaying = !state.isPlaying;
els.toggleBtn.textContent = state.isPlaying ? "Pause" : "Resume";
});
els.speedRange.addEventListener('input', (e) => {
state.speed = parseFloat(e.target.value);
});
els.flapRange.addEventListener('input', (e) => {
state.flapIntensity = parseFloat(e.target.value);
});
// === Start ===
loop();
</script>
</body>
</html>

2026-04-20T16:49:36 conversation: 01kpnwt8d2bt5qwkm60j9sbkbs id: 01kpnwra0prz6v822cct5b08kq

Model: openrouter/moonshotai/kimi-k2.6

Prompt

Generate an SVG of a pelican riding a bicycle

Response

Here is a complete, interactive HTML application featuring a vector-drawn Pelican on a bicycle.

This solution uses SVG for high-quality rendering and JavaScript (via requestAnimationFrame) for all animations.

How to Use

  1. Copy the code block below.
  2. Save it as an .html file (e.g., pelican.html).
  3. Open it in any modern web browser.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Pedaling Pelican</title>
    <style>
        :root {
            --bg-color: #2c3e50;
            --panel-color: #ecf0f1;
            --accent-color: #e67e22;
            --text-color: #2c3e50;
        }

        body {
            margin: 0;
            padding: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            background-color: var(--bg-color);
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            overflow: hidden;
        }

        .canvas-container {
            position: relative;
            width: 90%;
            max-width: 800px;
            background: linear-gradient(to bottom, #87CEEB 0%, #E0F7FA 60%, #dcdcdc 60%, #95a5a6 100%);
            border-radius: 20px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
            overflow: hidden;
            border: 4px solid #34495e;
        }

        /* Control Panel Styling - OUTSIDE SVG */
        .controls {
            margin-top: 20px;
            padding: 15px 25px;
            background-color: var(--panel-color);
            border-radius: 50px;
            display: flex;
            gap: 20px;
            align-items: center;
            box-shadow: 0 5px 15px rgba(0,0,0,0.2);
        }

        .control-group {
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        label {
            font-size: 0.8rem;
            font-weight: bold;
            color: var(--text-color);
            margin-bottom: 5px;
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        input[type=range] {
            -webkit-appearance: none;
            width: 150px;
            height: 6px;
            background: #bdc3c7;
            border-radius: 5px;
            outline: none;
        }

        input[type=range]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: var(--accent-color);
            cursor: pointer;
            transition: transform 0.1s;
        }

        input[type=range]::-webkit-slider-thumb:hover {
            transform: scale(1.2);
        }

        button {
            background-color: var(--accent-color);
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 20px;
            font-weight: bold;
            cursor: pointer;
            font-size: 1rem;
            transition: background 0.2s, transform 0.1s;
        }

        button:hover {
            background-color: #d35400;
        }

        button:active {
            transform: scale(0.95);
        }

        /* Scenery CSS Animation (Allowed as it's not SVG) */
        .cloud {
            position: absolute;
            background: rgba(255, 255, 255, 0.8);
            border-radius: 50px;
            animation: floatCloud 20s linear infinite;
        }
        
        @keyframes floatCloud {
            0% { transform: translateX(120%); }
            100% { transform: translateX(-120%); }
        }

    </style>
</head>
<body>

    <div class="canvas-container">
        <!-- Scenery Elements (HTML/CSS for background movement) -->
        <div class="cloud" style="width: 100px; height: 40px; top: 10%; left: 20%; animation-duration: 25s;"></div>
        <div class="cloud" style="width: 140px; height: 60px; top: 15%; left: 60%; animation-duration: 18s;"></div>

        <svg id="mainSvg" viewBox="0 0 800 500" preserveAspectRatio="xMidYMid slice" style="width: 100%; height: auto; display: block;">
            <defs>
                <!-- Gradients -->
                <linearGradient id="beakGradient" x1="0%" y1="0%" x2="100%" y2="0%">
                    <stop offset="0%" style="stop-color:#f39c12;stop-opacity:1" />
                    <stop offset="100%" style="stop-color:#e67e22;stop-opacity:1" />
                </linearGradient>
                <linearGradient id="metalGradient" x1="0%" y1="0%" x2="0%" y2="100%">
                    <stop offset="0%" style="stop-color:#bdc3c7;stop-opacity:1" />
                    <stop offset="50%" style="stop-color:#95a5a6;stop-opacity:1" />
                    <stop offset="100%" style="stop-color:#7f8c8d;stop-opacity:1" />
                </linearGradient>
                <filter id="shadowBlur">
                    <feGaussianBlur in="SourceGraphic" stdDeviation="2" />
                </filter>
            </defs>

            <!-- Ground Shadow -->
            <ellipse cx="400" cy="440" rx="180" ry="15" fill="rgba(0,0,0,0.2)" filter="url(#shadowBlur)" />

            <!-- === THE BIKE === -->
            <g id="bike" transform="translate(0, 0)">
                <!-- Back Wheel -->
                <g id="wheelBack" transform="translate(250, 400)">
                    <circle r="60" fill="none" stroke="#34495e" stroke-width="8" />
                    <circle r="55" fill="none" stroke="#ecf0f1" stroke-width="4" />
                    <!-- Spokes Group -->
                    <g id="spokesBack">
                        <line x1="0" y1="-55" x2="0" y2="55" stroke="#bdc3c7" stroke-width="2" />
                        <line x1="-48" y1="-27" x2="48" y2="27" stroke="#bdc3c7" stroke-width="2" />
                        <line x1="-48" y1="27" x2="48" y2="-27" stroke="#bdc3c7" stroke-width="2" />
                    </g>
                    <circle r="8" fill="#7f8c8d" />
                </g>

                <!-- Front Wheel -->
                <g id="wheelFront" transform="translate(550, 400)">
                    <circle r="60" fill="none" stroke="#34495e" stroke-width="8" />
                    <circle r="55" fill="none" stroke="#ecf0f1" stroke-width="4" />
                    <g id="spokesFront">
                        <line x1="0" y1="-55" x2="0" y2="55" stroke="#bdc3c7" stroke-width="2" />
                        <line x1="-48" y1="-27" x2="48" y2="27" stroke="#bdc3c7" stroke-width="2" />
                        <line x1="-48" y1="27" x2="48" y2="-27" stroke="#bdc3c7" stroke-width="2" />
                    </g>
                    <circle r="8" fill="#7f8c8d" />
                </g>

                <!-- Frame -->
                <path d="M250 400 L360 400 L310 300 L250 400" fill="none" stroke="#e74c3c" stroke-width="8" stroke-linejoin="round" />
                <path d="M310 300 L500 300 L550 400" fill="none" stroke="#e74c3c" stroke-width="8" stroke-linecap="round" />
                <path d="M360 400 L450 250" fill="none" stroke="#e74c3c" stroke-width="6" stroke-linecap="round" /> <!-- Seat post -->
                <path d="M500 300 L490 220" fill="none" stroke="#e74c3c" stroke-width="6" stroke-linecap="round" /> <!-- Handlebar post -->
                
                <!-- Chain/Gear -->
                <circle cx="360" cy="400" r="15" fill="none" stroke="#7f8c8d" stroke-width="3" />
                <circle cx="360" cy="400" r="5" fill="#2c3e50" />
                <path d="M360 400 L250 400" stroke="#7f8c8d" stroke-width="3" stroke-dasharray="4,2" /> <!-- Chain -->
                
                <!-- Pedal Crank (Left Side - Visual) -->
                <g id="pedalCrank" transform="translate(360, 400)">
                    <line x1="0" y1="0" x2="0" y2="30" stroke="#7f8c8d" stroke-width="5" stroke-linecap="round" />
                    <rect x="-8" y="25" width="16" height="10" rx="2" fill="#2c3e50" />
                </g>

                <!-- Handlebars -->
                <path d="M490 220 L480 200 L510 200" fill="none" stroke="#bdc3c7" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" />
                <circle cx="510" cy="200" r="5" fill="#2c3e50" /> <!-- Grip -->

                <!-- Seat -->
                <path d="M420 250 Q460 245 470 255" fill="none" stroke="#2c3e50" stroke-width="12" stroke-linecap="round" />
            </g>

            <!-- === THE PELICAN === -->
            <g id="pelican" transform="translate(0, -20)">
                
                <!-- Back Leg (Left Leg) -->
                <g id="legLeft" transform="translate(440, 320)">
                    <path d="M0 0 L-10 40" stroke="#f1c40f" stroke-width="8" stroke-linecap="round" />
                    <path d="M-10 40 L-20 50 L0 50" fill="none" stroke="#f1c40f" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" />
                </g>

                <!-- Body -->
                <ellipse cx="420" cy="280" rx="70" ry="50" fill="#ffffff" />
                <path d="M380 260 Q420 240 460 270" fill="none" stroke="#ecf0f1" stroke-width="2" opacity="0.5"/>

                <!-- Neck & Head -->
                <g id="headGroup" transform="translate(460, 240) rotate(-10)">
                    <path d="M0 40 Q10 0 30 -20 Q60 -40 90 -20" fill="none" stroke="#ffffff" stroke-width="28" stroke-linecap="round" />
                    
                    <!-- Head Detail -->
                    <circle cx="85" cy="-25" r="16" fill="#ffffff" />
                    
                    <!-- Beak -->
                    <path d="M90 -25 L160 -10 L90 5 Z" fill="url(#beakGradient)" stroke="#d35400" stroke-width="1" />
                    <path d="M90 5 Q110 20 120 25 Q100 25 90 5" fill="#e67e22" /> <!-- Pouch -->
                    
                    <!-- Eye -->
                    <circle cx="80" cy="-30" r="3" fill="#000" />
                </g>

                <!-- Wing (Flapping) -->
                <g id="wing" transform="translate(420, 270)">
                    <path d="M0 0 Q-40 -10 -60 20 Q-20 40 10 20 Z" fill="#ffffff" stroke="#bdc3c7" stroke-width="2" />
                    <!-- Wing Feathers -->
                    <path d="M-60 20 L-50 35 M-45 23 L-35 38 M-30 25 L-20 40" stroke="#bdc3c7" stroke-width="2" stroke-linecap="round"/>
                </g>

                <!-- Front Leg (Right Leg - Pushing Pedal) -->
                <g id="legRightGroup" transform="translate(440, 320)">
                    <line id="thigh" x1="0" y1="0" x2="10" y2="40" stroke="#f1c40f" stroke-width="8" stroke-linecap="round" />
                    <g id="shinGroup" transform="translate(10, 40)">
                         <line id="shin" x1="0" y1="0" x2="-5" y2="40" stroke="#f1c40f" stroke-width="7" stroke-linecap="round" />
                         <g id="footGroup" transform="translate(-5, 40)">
                             <path d="M0 0 L-10 10 L10 10" fill="none" stroke="#f1c40f" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" />
                         </g>
                    </g>
                </g>

            </g>
        </svg>
    </div>

    <!-- Control Panel -->
    <div class="controls">
        <button id="toggleBtn">Pause</button>
        <div class="control-group">
            <label for="speedRange">Speed</label>
            <input type="range" id="speedRange" min="0" max="20" value="8">
        </div>
        <div class="control-group">
            <label for="flapRange">Wing Flap</label>
            <input type="range" id="flapRange" min="0" max="10" value="5">
        </div>
    </div>

    <script>
        // === Configuration & State ===
        const state = {
            isPlaying: true,
            speed: 8,         // Pedal speed
            flapIntensity: 5, // Wing movement
            wheelAngle: 0,
            pedalAngle: 0,
            wingAngle: 0,
            time: 0
        };

        // === DOM Elements ===
        const els = {
            wheelBack: document.getElementById('wheelBack'),
            wheelFront: document.getElementById('wheelFront'),
            pedalCrank: document.getElementById('pedalCrank'),
            legRightGroup: document.getElementById('legRightGroup'),
            thigh: document.getElementById('thigh'),
            shin: document.getElementById('shin'),
            shinGroup: document.getElementById('shinGroup'),
            footGroup: document.getElementById('footGroup'),
            wing: document.getElementById('wing'),
            head: document.getElementById('headGroup'),
            
            // Controls
            toggleBtn: document.getElementById('toggleBtn'),
            speedRange: document.getElementById('speedRange'),
            flapRange: document.getElementById('flapRange')
        };

        // === Animation Logic ===
        
        function toRad(deg) {
            return deg * (Math.PI / 180);
        }

        function updatePhysics() {
            if (!state.isPlaying) return;

            // Increment Time
            state.time += state.speed * 0.05;

            // 1. Wheels (Simple Rotation)
            state.wheelAngle += state.speed * 2;
            const wheelTransform = `rotate(${state.wheelAngle})`;
            els.wheelBack.setAttribute('transform', `translate(250, 400) ${wheelTransform}`);
            els.wheelFront.setAttribute('transform', `translate(550, 400) ${wheelTransform}`);

            // 2. Pedals (Circular Motion)
            state.pedalAngle += state.speed * 2;
            const pedalRad = toRad(state.pedalAngle);
            const crankX = Math.cos(pedalRad) * 30; // Crank length approx 30
            const crankY = Math.sin(pedalRad) * 30;
            
            els.pedalCrank.setAttribute('transform', `translate(360, 400) rotate(${state.pedalAngle})`);

            // 3. Inverse Kinematics (Simplified) for Right Leg
            // Hip is at (440, 300) relative to group, but group is translated to 440, 320
            // Let's assume Hip is origin of legRightGroup (0,0)
            // Target is Pedal Center + Crank Offset
            
            // Global Coordinates of Pedal Center
            const pedalCenterX = 360;
            const pedalCenterY = 400;
            
            // Current Pedal Position
            const targetX = crankX; // Relative to pedal center
            const targetY = crankY; // Relative to pedal center

            // We need to map this relative to the Hip (440, 320)
            // Hip Offset from Pedal Center: Hip(440,320) - Pedal(360,400) = (80, -80)
            const hipOffsetX = 80;
            const hipOffsetY = -80;

            const localTargetX = targetX - hipOffsetX;
            const localTargetY = targetY - hipOffsetY;

            // Inverse Kinematics for 2-segment leg
            // Segment lengths: Thigh (L1) approx 45, Shin (L2) approx 45
            const L1 = 45;
            const L2 = 45;
            const dist = Math.sqrt(localTargetX**2 + localTargetY**2);
            
            // Clamp distance to avoid breaking
            const clampedDist = Math.min(dist, L1 + L2 - 0.1);

            // Law of Cosines
            // c^2 = a^2 + b^2 - 2ab cos(C)
            // Angle at knee (internal)
            const cosKnee = (L1**2 + L2**2 - clampedDist**2) / (2 * L1 * L2);
            let kneeAngle = Math.acos(Math.max(-1, Math.min(1, cosKnee))); // 0 to PI
            
            // Angle of the vector to target
            const targetAngle = Math.atan2(localTargetY, localTargetX);
            
            // Angle of thigh relative to horizontal
            // Law of Cosines for thigh angle relative to target line
            const cosThighOffset = (L1**2 + clampedDist**2 - L2**2) / (2 * L1 * clampedDist);
            const thighOffset = Math.acos(Math.max(-1, Math.min(1, cosThighOffset)));
            
            const thighAngle = targetAngle + thighOffset; 

            // Apply Transforms to Leg
            // Thigh rotation
            els.thigh.setAttribute('x2', Math.cos(thighAngle) * L1);
            els.thigh.setAttribute('y2', Math.sin(thighAngle) * L1);
            
            // Shin Group moves to end of thigh
            els.shinGroup.setAttribute('transform', `translate(${Math.cos(thighAngle)*L1}, ${Math.sin(thighAngle)*L1})`);
            
            // Shin rotation (Thigh angle + Knee Angle)
            // Note: Knee bends backwards, so we add or subtract depending on coord system.
            // Standard IK usually bends one way. Let's add PI - kneeAngle
            const shinGlobalAngle = thighAngle + (Math.PI - kneeAngle);
            els.shin.setAttribute('x2', Math.cos(shinGlobalAngle) * L2);
            els.shin.setAttribute('y2', Math.sin(shinGlobalAngle) * L2);

            // Foot Group moves to end of shin
            // We orient the foot to be flat-ish on the pedal
            // Pedal is rotating. We want foot to stay horizontal-ish relative to ground, 
            // or rotate with pedal. Let's rotate with pedal for realism.
            els.footGroup.setAttribute('transform', `translate(${Math.cos(shinGlobalAngle)*L2}, ${Math.sin(shinGlobalAngle)*L2}) rotate(${state.pedalAngle * 0.5})`);


            // 4. Wing Flapping
            // Sine wave based on time
            const flapBase = Math.sin(state.time * 0.5); 
            const flapDeg = flapBase * state.flapIntensity * 5; // Multiplier for range
            
            // Pivot is around body center approx
            els.wing.setAttribute('transform', `translate(420, 270) rotate(${flapDeg})`);

            // 5. Head Bobbing
            const bobY = Math.sin(state.time * 0.2) * 2;
            els.head.setAttribute('transform', `translate(460, ${240 + bobY}) rotate(-10)`);
        }

        function loop() {
            updatePhysics();
            requestAnimationFrame(loop);
        }

        // === Event Listeners ===

        els.toggleBtn.addEventListener('click', () => {
            state.isPlaying = !state.isPlaying;
            els.toggleBtn.textContent = state.isPlaying ? "Pause" : "Resume";
        });

        els.speedRange.addEventListener('input', (e) => {
            state.speed = parseFloat(e.target.value);
        });

        els.flapRange.addEventListener('input', (e) => {
            state.flapIntensity = parseFloat(e.target.value);
        });

        // === Start ===
        loop();

    </script>
</body>
</html>

Token usage

17 input, 5,459 output, {"completion_tokens_details": {"reasoning_tokens": 673}, "cost": 0.02185215, "cost_details": {"upstream_inference_cost": 0.02185215, "upstream_inference_prompt_cost": 1.615e-05, "upstream_inference_completions_cost": 0.021836}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment