--- /dev/null
+// 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();
+}
--- /dev/null
+<!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>