]> git.za3k.com Git - za3k.git/commitdiff
Bees
authorZachary Vance <za3k@za3k.com>
Mon, 30 Mar 2026 17:49:16 +0000 (13:49 -0400)
committerZachary Vance <za3k@za3k.com>
Mon, 27 Apr 2026 16:12:08 +0000 (12:12 -0400)
bee_puzzle/bee-renderer.js [new file with mode: 0644]
bee_puzzle/bumblebee.mp3 [new file with mode: 0644]
bee_puzzle/index.html [new file with mode: 0644]

diff --git a/bee_puzzle/bee-renderer.js b/bee_puzzle/bee-renderer.js
new file mode 100644 (file)
index 0000000..c62a7f9
--- /dev/null
@@ -0,0 +1,317 @@
+// Canvas setup
+const canvas = document.getElementById('hive');
+const ctx = canvas.getContext('2d');
+
+const canvasSize = 650;
+const hiveOffset = 50;
+const hiveSize = 550;
+const hiveCenterX = hiveOffset + hiveSize / 2;
+const hiveCenterY = hiveOffset + hiveSize / 2;
+const beeSize = 18;
+const waggleLength = 200;
+const loopWidth = waggleLength / 5;
+const loopHeight = waggleLength / 2;
+
+// Key points
+const offscreenLeft = { x: -50, y: -50 };
+const offscreenRight = { x: canvasSize + 50, y: -50 };
+const leftDance = { x: hiveOffset + hiveSize * 0.3, y: hiveCenterY };
+const rightDance = { x: hiveOffset + hiveSize * 0.7, y: hiveCenterY };
+
+// Timing
+const FLY_DURATION = 300;
+const WAGGLE_DURATION = 225;
+const ROTATE_DURATION = 225;
+
+let globalTime = 0;
+
+class Bee {
+    constructor() {
+        this.x = -100;
+        this.y = -100;
+        this.angle = 0;
+        this.visible = false;
+        this.isWaggling = false;
+        this.actions = [];
+        this.actionIndex = 0;
+        this.actionStartTime = 0;
+        this.startDelay = 0;
+    }
+    
+    teleportTo(point) {
+        this.actions.push({ type: 'teleport', target: point });
+        return this;
+    }
+    
+    flyTo(point) {
+        this.actions.push({ type: 'fly', target: point, duration: FLY_DURATION });
+        return this;
+    }
+    
+    waggleTo(point) {
+        this.actions.push({ type: 'waggle', target: point, duration: WAGGLE_DURATION });
+        return this;
+    }
+    
+    rotateTo(point, rightSide) {
+        this.actions.push({ type: 'rotate', target: point, rightSide: rightSide, duration: ROTATE_DURATION });
+        return this;
+    }
+    
+    setStartDelay(delay) {
+        this.startDelay = delay;
+        return this;
+    }
+    
+    update(time) {
+        const localTime = time - this.startDelay;
+        if (localTime < 0) {
+            this.visible = false;
+            return;
+        }
+        
+        this.visible = true;
+        this.isWaggling = false;
+        
+        let elapsed = 0;
+        for (let i = 0; i < this.actions.length; i++) {
+            const action = this.actions[i];
+            const duration = action.duration || 0;
+            
+            if (duration === 0) {
+                this.executeAction(action, 0, i);
+                continue;
+            }
+            
+            if (localTime < elapsed + duration) {
+                this.executeAction(action, localTime - elapsed, i);
+                return;
+            }
+            elapsed += duration;
+        }
+        
+        this.visible = false;
+    }
+    
+    executeAction(action, actionTime, actionIndex) {
+        const progress = action.duration ? Math.min(1, actionTime / action.duration) : 1;
+        const eased = progress * progress * (3 - 2 * progress);
+        
+        let prevPos = this.getPreviousPosition(actionIndex);
+        
+        switch (action.type) {
+            case 'teleport':
+                this.x = action.target.x;
+                this.y = action.target.y;
+                this.angle = Math.atan2(action.target.y - prevPos.y, action.target.x - prevPos.x);
+                break;
+                
+            case 'fly':
+                this.x = prevPos.x + (action.target.x - prevPos.x) * eased;
+                this.y = prevPos.y + (action.target.y - prevPos.y) * eased;
+                this.angle = Math.atan2(action.target.y - prevPos.y, action.target.x - prevPos.x);
+                break;
+                
+            case 'waggle':
+                this.x = prevPos.x + (action.target.x - prevPos.x) * progress;
+                this.y = prevPos.y + (action.target.y - prevPos.y) * progress;
+                this.angle = Math.atan2(action.target.y - prevPos.y, action.target.x - prevPos.x);
+                this.isWaggling = true;
+                break;
+                
+            case 'rotate':
+                const midX = (prevPos.x + action.target.x) / 2;
+                const midY = (prevPos.y + action.target.y) / 2;
+                const dx = action.target.x - prevPos.x;
+                const dy = action.target.y - prevPos.y;
+                const dist = Math.sqrt(dx * dx + dy * dy);
+                
+                const perpX = -dy / dist * loopWidth;
+                const perpY = dx / dist * loopWidth;
+                const sign = action.rightSide ? 1 : -1;
+                
+                const arcAngle = progress * Math.PI;
+                const arcProgress = (1 - Math.cos(arcAngle)) / 2;
+                const perpAmount = Math.sin(arcAngle);
+                
+                this.x = prevPos.x + dx * arcProgress + perpX * perpAmount * sign;
+                this.y = prevPos.y + dy * arcProgress + perpY * perpAmount * sign;
+                
+                const velX = dx / 2 * Math.sin(arcAngle) + perpX * Math.cos(arcAngle) * sign;
+                const velY = dy / 2 * Math.sin(arcAngle) + perpY * Math.cos(arcAngle) * sign;
+                this.angle = Math.atan2(velY, velX);
+                break;
+        }
+    }
+    
+    getPreviousPosition(actionIndex) {
+        if (actionIndex === 0) {
+            return { x: this.actions[0].target?.x || 0, y: this.actions[0].target?.y || 0 };
+        }
+        
+        let pos = { x: 0, y: 0 };
+        for (let i = 0; i < actionIndex; i++) {
+            const action = this.actions[i];
+            if (action.target) {
+                pos = { x: action.target.x, y: action.target.y };
+            }
+        }
+        return pos;
+    }
+    
+    draw(time) {
+        if (!this.visible) return;
+        
+        ctx.save();
+        ctx.translate(this.x, this.y);
+        ctx.rotate(this.angle);
+        
+        const size = beeSize;
+        
+        if (this.isWaggling) {
+            const waggleRotation = Math.sin(time * 0.8) * 0.4;
+            ctx.rotate(waggleRotation);
+            const waggleSide = Math.sin(time * 0.8) * 6;
+            ctx.translate(0, waggleSide);
+        }
+        
+        const wingSpeed = this.isWaggling ? 1.2 : 0.4;
+        ctx.fillStyle = 'rgba(200, 220, 255, 0.7)';
+        ctx.beginPath();
+        ctx.ellipse(-size * 0.3, -size * 0.8, size * 0.5, size * 0.3, -0.3, 0, Math.PI * 2);
+        ctx.fill();
+        ctx.beginPath();
+        ctx.ellipse(-size * 0.3, size * 0.8, size * 0.5, size * 0.3, 0.3, 0, Math.PI * 2);
+        ctx.fill();
+        
+        const wingFlap = Math.sin(time * wingSpeed) * (this.isWaggling ? 0.4 : 0.2);
+        ctx.fillStyle = 'rgba(180, 200, 255, 0.6)';
+        ctx.beginPath();
+        ctx.ellipse(-size * 0.2, -size * 0.6 + wingFlap * 15, size * 0.4, size * 0.25, -0.3, 0, Math.PI * 2);
+        ctx.fill();
+        ctx.beginPath();
+        ctx.ellipse(-size * 0.2, size * 0.6 - wingFlap * 15, size * 0.4, size * 0.25, 0.3, 0, Math.PI * 2);
+        ctx.fill();
+        
+        ctx.fillStyle = '#1a1a1a';
+        ctx.beginPath();
+        ctx.ellipse(size * 0.9, 0, size * 0.4, size * 0.35, 0, 0, Math.PI * 2);
+        ctx.fill();
+        
+        ctx.save();
+        if (this.isWaggling) {
+            const abdomenWaggle = Math.sin(time * 0.85) * 0.3;
+            ctx.translate(-size * 0.3, 0);
+            ctx.rotate(abdomenWaggle);
+            ctx.translate(size * 0.3, 0);
+        }
+        
+        const gradient = ctx.createLinearGradient(-size, 0, size, 0);
+        gradient.addColorStop(0, '#FFD700');
+        gradient.addColorStop(0.2, '#1a1a1a');
+        gradient.addColorStop(0.35, '#FFD700');
+        gradient.addColorStop(0.5, '#1a1a1a');
+        gradient.addColorStop(0.65, '#FFD700');
+        gradient.addColorStop(0.8, '#1a1a1a');
+        gradient.addColorStop(1, '#FFD700');
+        
+        ctx.fillStyle = gradient;
+        ctx.beginPath();
+        ctx.ellipse(0, 0, size, size * 0.6, 0, 0, Math.PI * 2);
+        ctx.fill();
+        
+        ctx.fillStyle = '#1a1a1a';
+        ctx.beginPath();
+        ctx.moveTo(-size, 0);
+        ctx.lineTo(-size * 1.3, -size * 0.1);
+        ctx.lineTo(-size * 1.3, size * 0.1);
+        ctx.closePath();
+        ctx.fill();
+        
+        ctx.restore();
+        
+        ctx.fillStyle = '#333';
+        ctx.beginPath();
+        ctx.arc(size * 1.05, -size * 0.15, size * 0.12, 0, Math.PI * 2);
+        ctx.arc(size * 1.05, size * 0.15, size * 0.12, 0, Math.PI * 2);
+        ctx.fill();
+        
+        ctx.fillStyle = 'rgba(255,255,255,0.5)';
+        ctx.beginPath();
+        ctx.arc(size * 1.08, -size * 0.18, size * 0.05, 0, Math.PI * 2);
+        ctx.arc(size * 1.08, size * 0.12, size * 0.05, 0, Math.PI * 2);
+        ctx.fill();
+        
+        ctx.strokeStyle = '#1a1a1a';
+        ctx.lineWidth = 1.5;
+        ctx.beginPath();
+        ctx.moveTo(size * 1.1, -size * 0.25);
+        ctx.quadraticCurveTo(size * 1.4, -size * 0.5, size * 1.3, -size * 0.7);
+        ctx.stroke();
+        ctx.beginPath();
+        ctx.moveTo(size * 1.1, size * 0.25);
+        ctx.quadraticCurveTo(size * 1.4, size * 0.5, size * 1.3, size * 0.7);
+        ctx.stroke();
+        
+        ctx.restore();
+    }
+}
+
+function getWagglePoints(angle, danceCenter) {
+    const angleRad = ((angle - 1) / 5) * Math.PI * 2 - Math.PI / 2;
+    const dx = Math.cos(angleRad) * waggleLength;
+    const dy = Math.sin(angleRad) * waggleLength;
+    return {
+        start: { x: danceCenter.x - dx/2, y: danceCenter.y - dy/2 },
+        end: { x: danceCenter.x + dx/2, y: danceCenter.y + dy/2 }
+    };
+}
+
+function drawHoneycombPattern() {
+    ctx.save();
+    ctx.beginPath();
+    ctx.arc(hiveCenterX, hiveCenterY, hiveSize / 2 - 2, 0, Math.PI * 2);
+    ctx.clip();
+    
+    ctx.globalAlpha = 0.4;
+    ctx.strokeStyle = '#3d2a06';
+    ctx.lineWidth = 1.5;
+    
+    const hexSize = 25;
+    for (let row = -10; row < 25; row++) {
+        for (let col = -10; col < 25; col++) {
+            const x = hiveOffset + col * hexSize * 1.5;
+            const y = hiveOffset + row * hexSize * Math.sqrt(3) + (col % 2) * hexSize * Math.sqrt(3) / 2;
+            
+            ctx.beginPath();
+            for (let i = 0; i < 6; i++) {
+                const a = (Math.PI / 3) * i;
+                const px = x + Math.cos(a) * hexSize;
+                const py = y + Math.sin(a) * hexSize;
+                if (i === 0) ctx.moveTo(px, py);
+                else ctx.lineTo(px, py);
+            }
+            ctx.closePath();
+            ctx.stroke();
+        }
+    }
+    ctx.restore();
+}
+
+function runAnimation(bees, totalDuration) {
+    function animate() {
+        const frameTime = globalTime % totalDuration;
+        
+        ctx.clearRect(0, 0, canvasSize, canvasSize);
+        drawHoneycombPattern();
+        
+        for (const bee of bees) {
+            bee.update(frameTime);
+            bee.draw(globalTime);
+        }
+        
+        globalTime++;
+        requestAnimationFrame(animate);
+    }
+    animate();
+}
diff --git a/bee_puzzle/bumblebee.mp3 b/bee_puzzle/bumblebee.mp3
new file mode 100644 (file)
index 0000000..32228eb
Binary files /dev/null and b/bee_puzzle/bumblebee.mp3 differ
diff --git a/bee_puzzle/index.html b/bee_puzzle/index.html
new file mode 100644 (file)
index 0000000..ee8375e
--- /dev/null
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>🐝</title>
+    <style>
+        * { margin: 0; padding: 0; box-sizing: border-box; }
+        body {
+            background: linear-gradient(135deg, #2d5016 0%, #4a7c23 50%, #6b9b37 100%);
+            min-height: 100vh;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            overflow: hidden;
+        }
+        .container { position: relative; }
+        canvas { background: transparent; position: relative; z-index: 2; }
+        .hive-background {
+            position: absolute;
+            width: 550px; height: 550px;
+            left: 50px; top: 50px;
+            background: radial-gradient(ellipse at center, #8B6914 0%, #6B4F0A 60%, #4a3507 100%);
+            border-radius: 50%;
+            box-shadow: 0 10px 40px rgba(0,0,0,0.4), inset 0 -5px 20px rgba(0,0,0,0.3), inset 0 5px 20px rgba(255,255,255,0.1);
+            border: 4px solid #3d2a06;
+            z-index: 1;
+        }
+        .audio-controls { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); }
+        audio { height: 40px; border-radius: 20px; }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <div class="hive-background"></div>
+        <canvas id="hive" width="650" height="650"></canvas>
+    </div>
+    <div class="audio-controls">
+        <audio controls src="bumblebee.mp3" loop></audio>
+    </div>
+
+    <script src="bee-renderer.js"></script>
+    <script>
+        // Entry and exit points
+        const P0 = {x: -50, y: -50};
+        const P1 = {x: 700, y: -50};
+        
+        // Create 11 bees
+        const bees = [];
+        for (let i = 0; i < 11; i++) bees.push(new Bee());
+        
+        // Bee 0
+        bees[0].teleportTo(P0);
+        bees[0].flyTo({x: 215, y: 225});
+        bees[0].waggleTo({x: 215, y: 425}); bees[0].rotateTo({x: 215, y: 225}, true);
+        bees[0].waggleTo({x: 215, y: 425}); bees[0].rotateTo({x: 215, y: 225}, false);
+        bees[0].waggleTo({x: 215, y: 425}); bees[0].rotateTo({x: 215, y: 225}, true);
+        bees[0].waggleTo({x: 215, y: 425});
+        bees[0].flyTo(P1);
+        
+        // Bee 1
+        bees[1].teleportTo(P0);
+        bees[1].flyTo({x: 530, y: 294});
+        bees[1].waggleTo({x: 340, y: 356}); bees[1].rotateTo({x: 530, y: 294}, true);
+        bees[1].waggleTo({x: 340, y: 356}); bees[1].rotateTo({x: 530, y: 294}, false);
+        bees[1].waggleTo({x: 340, y: 356}); bees[1].rotateTo({x: 530, y: 294}, true);
+        bees[1].waggleTo({x: 340, y: 356});
+        bees[1].flyTo(P1);
+        
+        // Bee 2
+        bees[2].teleportTo(P0);
+        bees[2].flyTo({x: 274, y: 406});
+        bees[2].waggleTo({x: 156, y: 244}); bees[2].rotateTo({x: 274, y: 406}, true);
+        bees[2].waggleTo({x: 156, y: 244}); bees[2].rotateTo({x: 274, y: 406}, false);
+        bees[2].waggleTo({x: 156, y: 244}); bees[2].rotateTo({x: 274, y: 406}, true);
+        bees[2].waggleTo({x: 156, y: 244});
+        bees[2].flyTo(P1);
+        
+        // Bee 3
+        bees[3].teleportTo(P0);
+        bees[3].flyTo({x: 376, y: 406});
+        bees[3].waggleTo({x: 494, y: 244}); bees[3].rotateTo({x: 376, y: 406}, true);
+        bees[3].waggleTo({x: 494, y: 244}); bees[3].rotateTo({x: 376, y: 406}, false);
+        bees[3].waggleTo({x: 494, y: 244}); bees[3].rotateTo({x: 376, y: 406}, true);
+        bees[3].waggleTo({x: 494, y: 244});
+        bees[3].flyTo(P1);
+        
+        // Bee 4
+        bees[4].teleportTo(P0);
+        bees[4].flyTo({x: 120, y: 294});
+        bees[4].waggleTo({x: 310, y: 356}); bees[4].rotateTo({x: 120, y: 294}, true);
+        bees[4].waggleTo({x: 310, y: 356}); bees[4].rotateTo({x: 120, y: 294}, false);
+        bees[4].waggleTo({x: 310, y: 356}); bees[4].rotateTo({x: 120, y: 294}, true);
+        bees[4].waggleTo({x: 310, y: 356});
+        bees[4].flyTo(P1);
+        
+        // Bee 5
+        bees[5].teleportTo(P0);
+        bees[5].flyTo({x: 435, y: 225});
+        bees[5].waggleTo({x: 435, y: 425}); bees[5].rotateTo({x: 435, y: 225}, true);
+        bees[5].waggleTo({x: 435, y: 425}); bees[5].rotateTo({x: 435, y: 225}, false);
+        bees[5].waggleTo({x: 435, y: 425}); bees[5].rotateTo({x: 435, y: 225}, true);
+        bees[5].waggleTo({x: 435, y: 425});
+        bees[5].flyTo(P1);
+        
+        // Bee 6
+        bees[6].teleportTo(P0);
+        bees[6].flyTo({x: 274, y: 406});
+        bees[6].waggleTo({x: 156, y: 244}); bees[6].rotateTo({x: 274, y: 406}, true);
+        bees[6].waggleTo({x: 156, y: 244}); bees[6].rotateTo({x: 274, y: 406}, false);
+        bees[6].waggleTo({x: 156, y: 244}); bees[6].rotateTo({x: 274, y: 406}, true);
+        bees[6].waggleTo({x: 156, y: 244});
+        bees[6].flyTo(P1);
+        
+        // Bee 7
+        bees[7].teleportTo(P0);
+        bees[7].flyTo({x: 340, y: 294});
+        bees[7].waggleTo({x: 530, y: 356}); bees[7].rotateTo({x: 340, y: 294}, true);
+        bees[7].waggleTo({x: 530, y: 356}); bees[7].rotateTo({x: 340, y: 294}, false);
+        bees[7].waggleTo({x: 530, y: 356}); bees[7].rotateTo({x: 340, y: 294}, true);
+        bees[7].waggleTo({x: 530, y: 356});
+        bees[7].flyTo(P1);
+        
+        // Bee 8
+        bees[8].teleportTo(P0);
+        bees[8].flyTo({x: 310, y: 294});
+        bees[8].waggleTo({x: 120, y: 356}); bees[8].rotateTo({x: 310, y: 294}, true);
+        bees[8].waggleTo({x: 120, y: 356}); bees[8].rotateTo({x: 310, y: 294}, false);
+        bees[8].waggleTo({x: 120, y: 356}); bees[8].rotateTo({x: 310, y: 294}, true);
+        bees[8].waggleTo({x: 120, y: 356});
+        bees[8].flyTo(P1);
+        
+        // Bee 9
+        bees[9].teleportTo(P0);
+        bees[9].flyTo({x: 376, y: 406});
+        bees[9].waggleTo({x: 494, y: 244}); bees[9].rotateTo({x: 376, y: 406}, true);
+        bees[9].waggleTo({x: 494, y: 244}); bees[9].rotateTo({x: 376, y: 406}, false);
+        bees[9].waggleTo({x: 494, y: 244}); bees[9].rotateTo({x: 376, y: 406}, true);
+        bees[9].waggleTo({x: 494, y: 244});
+        bees[9].flyTo(P1);
+        
+        // Bee 10
+        bees[10].teleportTo(P0);
+        bees[10].flyTo({x: 215, y: 225});
+        bees[10].waggleTo({x: 215, y: 425}); bees[10].rotateTo({x: 215, y: 225}, true);
+        bees[10].waggleTo({x: 215, y: 425}); bees[10].rotateTo({x: 215, y: 225}, false);
+        bees[10].waggleTo({x: 215, y: 425}); bees[10].rotateTo({x: 215, y: 225}, true);
+        bees[10].waggleTo({x: 215, y: 425});
+        bees[10].flyTo(P1);
+        
+        // Timing
+        bees[0].setStartDelay(0);
+        bees[1].setStartDelay(1538);
+        bees[2].setStartDelay(3075);
+        bees[3].setStartDelay(4613);
+        bees[4].setStartDelay(6150);
+        bees[5].setStartDelay(7688);
+        bees[6].setStartDelay(9225);
+        bees[7].setStartDelay(10763);
+        bees[8].setStartDelay(12300);
+        bees[9].setStartDelay(13838);
+        bees[10].setStartDelay(15375);
+        
+        runAnimation(bees, 18630);
+    </script>
+</body>
+</html>