Created
June 2, 2026 02:52
-
-
Save gkucmierz/d80987ae2ee4e50c3cc666abd180fd86 to your computer and use it in GitHub Desktop.
Run this code instantly in your browser: https://instacode.app/gist/d80987ae2ee4e50c3cc666abd180fd86
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { canvas, onResize } from 'canvas'; | |
| const ctx = canvas.getContext('2d'); | |
| let width = 300; | |
| let height = 150; | |
| // Handle canvas resizing automatically | |
| onResize((w, h) => { | |
| width = w; | |
| height = h; | |
| canvas.width = w; | |
| canvas.height = h; | |
| }); | |
| // Main animation loop | |
| function draw() { | |
| const time = Date.now() * 0.001; // Time in seconds | |
| // 1. Draw smooth background gradient (Deep Indigo to Slate) | |
| const bgGrad = ctx.createLinearGradient(0, 0, 0, height); | |
| bgGrad.addColorStop(0, '#1e1b4b'); // Deep indigo 950 | |
| bgGrad.addColorStop(1, '#0f172a'); // Slate 900 | |
| ctx.fillStyle = bgGrad; | |
| ctx.fillRect(0, 0, width, height); | |
| // Center coordinates of the cat | |
| const centerX = width / 2; | |
| const centerY = height / 2 + 20; // Adjusted lower to fit ears | |
| // 2. Draw soft shadow under the cat | |
| ctx.save(); | |
| ctx.beginPath(); | |
| const shadowGrad = ctx.createRadialGradient(centerX, centerY + 80, 5, centerX, centerY + 80, 100); | |
| shadowGrad.addColorStop(0, 'rgba(0, 0, 0, 0.45)'); | |
| shadowGrad.addColorStop(1, 'rgba(0, 0, 0, 0)'); | |
| ctx.fillStyle = shadowGrad; | |
| ctx.ellipse(centerX, centerY + 80, 80, 15, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // 3. Draw Waving Tail (behind body) | |
| ctx.save(); | |
| ctx.strokeStyle = '#475569'; // Dark Slate 600 | |
| ctx.lineWidth = 12; | |
| ctx.lineCap = 'round'; | |
| ctx.beginPath(); | |
| ctx.moveTo(centerX - 35, centerY + 60); | |
| // Tail swing physics | |
| const tailSwing = Math.sin(time * 3) * 20; | |
| ctx.quadraticCurveTo( | |
| centerX - 80 + tailSwing, centerY + 30, | |
| centerX - 70 + tailSwing * 1.5, centerY - 25 | |
| ); | |
| ctx.stroke(); | |
| ctx.restore(); | |
| // 4. Draw Body with linear gradient | |
| ctx.save(); | |
| const bodyGrad = ctx.createLinearGradient(centerX - 50, centerY, centerX + 50, centerY + 80); | |
| bodyGrad.addColorStop(0, '#94a3b8'); // Slate 400 | |
| bodyGrad.addColorStop(1, '#475569'); // Slate 600 | |
| ctx.fillStyle = bodyGrad; | |
| ctx.beginPath(); | |
| ctx.ellipse(centerX, centerY + 45, 52, 45, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // 5. Draw Head with radial gradient for 3D sphere look | |
| ctx.save(); | |
| const headGrad = ctx.createRadialGradient(centerX - 10, centerY - 25, 10, centerX, centerY - 20, 50); | |
| headGrad.addColorStop(0, '#f1f5f9'); // Slate 100 | |
| headGrad.addColorStop(1, '#64748b'); // Slate 500 | |
| ctx.fillStyle = headGrad; | |
| ctx.beginPath(); | |
| ctx.arc(centerX, centerY - 20, 42, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // 6. Draw Ears | |
| // Left Ear | |
| ctx.save(); | |
| ctx.fillStyle = '#64748b'; | |
| ctx.beginPath(); | |
| ctx.moveTo(centerX - 35, centerY - 43); | |
| ctx.lineTo(centerX - 46, centerY - 75); | |
| ctx.lineTo(centerX - 12, centerY - 54); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Inner left ear (pink) | |
| ctx.fillStyle = '#fda4af'; // Rose 300 | |
| ctx.beginPath(); | |
| ctx.moveTo(centerX - 32, centerY - 45); | |
| ctx.lineTo(centerX - 40, centerY - 68); | |
| ctx.lineTo(centerX - 16, centerY - 52); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // Right Ear | |
| ctx.save(); | |
| ctx.fillStyle = '#475569'; // Darker Slate for depth shadow | |
| ctx.beginPath(); | |
| ctx.moveTo(centerX + 35, centerY - 43); | |
| ctx.lineTo(centerX + 46, centerY - 75); | |
| ctx.lineTo(centerX + 12, centerY - 54); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Inner right ear (pink) | |
| ctx.fillStyle = '#fda4af'; | |
| ctx.beginPath(); | |
| ctx.moveTo(centerX + 32, centerY - 45); | |
| ctx.lineTo(centerX + 40, centerY - 68); | |
| ctx.lineTo(centerX + 16, centerY - 52); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // 7. Draw Eyes (blinking state machine) | |
| // Blinks every few seconds for 150ms | |
| const isBlinking = Math.floor(time * 0.4) % 2 === 0 && (time % 2.5 < 0.15); | |
| ctx.save(); | |
| ctx.fillStyle = '#0f172a'; // Slate 900 | |
| if (isBlinking) { | |
| ctx.strokeStyle = '#0f172a'; | |
| ctx.lineWidth = 4; | |
| ctx.lineCap = 'round'; | |
| // Closed left eye (happy curve) | |
| ctx.beginPath(); | |
| ctx.arc(centerX - 18, centerY - 20, 8, 0.15 * Math.PI, 0.85 * Math.PI); | |
| ctx.stroke(); | |
| // Closed right eye | |
| ctx.beginPath(); | |
| ctx.arc(centerX + 18, centerY - 20, 8, 0.15 * Math.PI, 0.85 * Math.PI); | |
| ctx.stroke(); | |
| } else { | |
| // Open left eye | |
| ctx.beginPath(); | |
| ctx.arc(centerX - 18, centerY - 20, 7, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Open right eye | |
| ctx.beginPath(); | |
| ctx.arc(centerX + 18, centerY - 20, 7, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Glossy highlights | |
| ctx.fillStyle = '#ffffff'; | |
| ctx.beginPath(); | |
| ctx.arc(centerX - 20, centerY - 22, 2.5, 0, Math.PI * 2); | |
| ctx.arc(centerX + 16, centerY - 22, 2.5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| ctx.restore(); | |
| // 8. Draw Blush Cheeks | |
| ctx.save(); | |
| ctx.fillStyle = 'rgba(244, 63, 94, 0.35)'; // Rose 500 with alpha | |
| ctx.beginPath(); | |
| ctx.arc(centerX - 28, centerY - 10, 6, 0, Math.PI * 2); | |
| ctx.arc(centerX + 28, centerY - 10, 6, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // 9. Draw Nose & Mouth | |
| ctx.save(); | |
| ctx.strokeStyle = '#334155'; | |
| ctx.lineWidth = 2.5; | |
| ctx.lineCap = 'round'; | |
| // Tiny pink nose | |
| ctx.fillStyle = '#fda4af'; | |
| ctx.beginPath(); | |
| ctx.moveTo(centerX - 3, centerY - 15); | |
| ctx.lineTo(centerX + 3, centerY - 15); | |
| ctx.lineTo(centerX, centerY - 12); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Classic cat mouth curves (w-shape) | |
| ctx.beginPath(); | |
| ctx.arc(centerX - 4, centerY - 12, 4, Math.PI, 0); | |
| ctx.stroke(); | |
| ctx.beginPath(); | |
| ctx.arc(centerX + 4, centerY - 12, 4, Math.PI, 0); | |
| ctx.stroke(); | |
| ctx.restore(); | |
| // 10. Draw Whiskers | |
| ctx.save(); | |
| ctx.strokeStyle = '#94a3b8'; | |
| ctx.lineWidth = 1.8; | |
| ctx.lineCap = 'round'; | |
| // Left side | |
| ctx.beginPath(); | |
| ctx.moveTo(centerX - 35, centerY - 12); ctx.lineTo(centerX - 54, centerY - 15); | |
| ctx.moveTo(centerX - 35, centerY - 9); ctx.lineTo(centerX - 57, centerY - 9); | |
| ctx.moveTo(centerX - 35, centerY - 6); ctx.lineTo(centerX - 53, centerY - 3); | |
| // Right side | |
| ctx.moveTo(centerX + 35, centerY - 12); ctx.lineTo(centerX + 54, centerY - 15); | |
| ctx.moveTo(centerX + 35, centerY - 9); ctx.lineTo(centerX + 57, centerY - 9); | |
| ctx.moveTo(centerX + 35, centerY - 6); ctx.lineTo(centerX + 53, centerY - 3); | |
| ctx.stroke(); | |
| ctx.restore(); | |
| // 11. Draw Left Paw (Static / breathing movement) | |
| ctx.save(); | |
| ctx.fillStyle = '#64748b'; | |
| const leftPawOffset = Math.sin(time * 3) * 1.5; | |
| ctx.beginPath(); | |
| ctx.ellipse(centerX - 25, centerY + 68 + leftPawOffset, 12, 10, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // 12. Draw Right Paw (Waving!) | |
| // Pivot points translate shoulder coordinate space | |
| ctx.save(); | |
| const shoulderX = centerX + 30; | |
| const shoulderY = centerY + 35; | |
| ctx.translate(shoulderX, shoulderY); | |
| // Wave rotation over time | |
| const waveAngle = -0.5 + Math.sin(time * 8) * 0.45; | |
| ctx.rotate(waveAngle); | |
| // Gradient for the waving paw | |
| const pawGrad = ctx.createLinearGradient(0, 0, 30, -10); | |
| pawGrad.addColorStop(0, '#475569'); | |
| pawGrad.addColorStop(1, '#cbd5e1'); | |
| ctx.fillStyle = pawGrad; | |
| ctx.beginPath(); | |
| ctx.ellipse(18, -4, 18, 11, 0, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // 13. Draw Floating Love Heart | |
| ctx.save(); | |
| const heartX = centerX - 65; | |
| const heartY = centerY - 55 + Math.sin(time * 2.5) * 6; // Float physics | |
| const heartSize = 8; | |
| ctx.translate(heartX, heartY); | |
| ctx.fillStyle = 'rgba(244, 63, 94, 0.85)'; // Rose 500 | |
| ctx.beginPath(); | |
| ctx.moveTo(0, 0); | |
| ctx.bezierCurveTo(-heartSize / 2, -heartSize / 2, -heartSize, 0, 0, heartSize); | |
| ctx.bezierCurveTo(heartSize, 0, heartSize / 2, -heartSize / 2, 0, 0); | |
| ctx.fill(); | |
| ctx.restore(); | |
| requestAnimationFrame(draw); | |
| } | |
| // Kickstart animation loop | |
| requestAnimationFrame(draw); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment