Skip to content

Instantly share code, notes, and snippets.

@gkucmierz
Created June 2, 2026 02:52
Show Gist options
  • Select an option

  • Save gkucmierz/d80987ae2ee4e50c3cc666abd180fd86 to your computer and use it in GitHub Desktop.

Select an option

Save gkucmierz/d80987ae2ee4e50c3cc666abd180fd86 to your computer and use it in GitHub Desktop.
Run this code instantly in your browser: https://instacode.app/gist/d80987ae2ee4e50c3cc666abd180fd86
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