Business Card
Let me know what you think — I'd love to hear your feedback.
Get in touch
Let me know what you think — I'd love to hear your feedback.
Get in touch['business-card-front.png', 'business-card-back.png'].forEach(name => { const a = document.createElement('a'); a.href = name; a.download = name; document.body.appendChild(a); a.click(); a.remove(); }); } // ===== Copy front image to clipboard ===== async function copyCards() { const btn = document.getElementById('copyBtn'); const span = btn.querySelector('span'); try { const img = document.getElementById('cardFront'); const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; canvas.getContext('2d').drawImage(img, 0, 0); const blob = await new Promise(r => canvas.toBlob(r, 'image/png')); await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]); span.textContent = 'Copied!'; setTimeout(() => { span.textContent = 'Copy'; }, 1500); } catch { span.textContent = 'Failed'; setTimeout(() => { span.textContent = 'Copy'; }, 1500); } } // ===== Minimal Geometric Animations (full page) ===== (function() { const canvas = document.getElementById('animCanvas'); const ctx = canvas.getContext('2d'); const DPR = window.devicePixelRatio || 1; function resize() { canvas.width = window.innerWidth * DPR; canvas.height = window.innerHeight * DPR; canvas.style.width = window.innerWidth + 'px'; canvas.style.height = window.innerHeight + 'px'; ctx.setTransform(DPR, 0, 0, DPR, 0, 0); } resize(); window.addEventListener('resize', resize); const W = () => window.innerWidth; const H = () => window.innerHeight; // --- Particle system: drifting dots --- const dots = []; for (let i = 0; i < 60; i++) { dots.push({ x: Math.random() * 2000, y: Math.random() * 2000, vx: (Math.random() - 0.5) * 0.3, vy: (Math.random() - 0.5) * 0.3, r: 1 + Math.random() * 1.5, a: 0.1 + Math.random() * 0.15 }); } // --- Traveling geometric shapes --- const shapes = []; function spawnShape() { const type = ['triangle', 'circle', 'square', 'line', 'cross', 'diamond'][Math.floor(Math.random() * 6)]; const edge = Math.floor(Math.random() * 4); let x, y, vx, vy; const speed = 0.3 + Math.random() * 0.5; if (edge === 0) { x = -30; y = Math.random() * H(); vx = speed; vy = (Math.random() - 0.5) * speed * 0.5; } else if (edge === 1) { x = W() + 30; y = Math.random() * H(); vx = -speed; vy = (Math.random() - 0.5) * speed * 0.5; } else if (edge === 2) { x = Math.random() * W(); y = -30; vx = (Math.random() - 0.5) * speed * 0.5; vy = speed; } else { x = Math.random() * W(); y = H() + 30; vx = (Math.random() - 0.5) * speed * 0.5; vy = -speed; } shapes.push({ type, x, y, vx, vy, rot: Math.random() * Math.PI * 2, rotV: (Math.random() - 0.5) * 0.01, size: 8 + Math.random() * 16, a: 0.06 + Math.random() * 0.08 }); } for (let i = 0; i < 12; i++) spawnShape(); // --- Bezier curves drifting --- const curves = []; for (let i = 0; i < 5; i++) { curves.push({ pts: Array.from({ length: 4 }, () => ({ x: Math.random() * 2000, y: Math.random() * 2000, vx: (Math.random() - 0.5) * 0.2, vy: (Math.random() - 0.5) * 0.2 })), a: 0.04 + Math.random() * 0.06 }); } let frame = 0; function draw() { frame++; ctx.clearRect(0, 0, W(), H()); const accent = 'rgba(107,158,255,'; // Draw drifting dots & connections dots.forEach(d => { d.x += d.vx; d.y += d.vy; if (d.x < -50) d.x = W() + 50; if (d.x > W() + 50) d.x = -50; if (d.y < -50) d.y = H() + 50; if (d.y > H() + 50) d.y = -50; ctx.beginPath(); ctx.arc(d.x, d.y, d.r, 0, Math.PI * 2); ctx.fillStyle = accent + d.a + ')'; ctx.fill(); }); // Connections between nearby dots for (let i = 0; i < dots.length; i++) { for (let j = i + 1; j < dots.length; j++) { const dx = dots[i].x - dots[j].x, dy = dots[i].y - dots[j].y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 150) { ctx.beginPath(); ctx.moveTo(dots[i].x, dots[i].y); ctx.lineTo(dots[j].x, dots[j].y); ctx.strokeStyle = accent + (0.06 * (1 - dist / 150)) + ')'; ctx.lineWidth = 0.5; ctx.stroke(); } } } // Draw traveling shapes shapes.forEach(s => { s.x += s.vx; s.y += s.vy; s.rot += s.rotV; ctx.save(); ctx.translate(s.x, s.y); ctx.rotate(s.rot); ctx.strokeStyle = accent + s.a + ')'; ctx.lineWidth = 1; const sz = s.size; if (s.type === 'triangle') { ctx.beginPath(); ctx.moveTo(0, -sz); ctx.lineTo(sz * 0.866, sz * 0.5); ctx.lineTo(-sz * 0.866, sz * 0.5); ctx.closePath(); ctx.stroke(); } else if (s.type === 'circle') { ctx.beginPath(); ctx.arc(0, 0, sz, 0, Math.PI * 2); ctx.stroke(); } else if (s.type === 'square') { ctx.strokeRect(-sz / 2, -sz / 2, sz, sz); } else if (s.type === 'line') { ctx.beginPath(); ctx.moveTo(-sz, 0); ctx.lineTo(sz, 0); ctx.stroke(); } else if (s.type === 'cross') { ctx.beginPath(); ctx.moveTo(-sz / 2, 0); ctx.lineTo(sz / 2, 0); ctx.moveTo(0, -sz / 2); ctx.lineTo(0, sz / 2); ctx.stroke(); } else if (s.type === 'diamond') { ctx.beginPath(); ctx.moveTo(0, -sz); ctx.lineTo(sz, 0); ctx.lineTo(0, sz); ctx.lineTo(-sz, 0); ctx.closePath(); ctx.stroke(); } ctx.restore(); }); // Respawn shapes that left the viewport for (let i = shapes.length - 1; i >= 0; i--) { const s = shapes[i]; if (s.x < -80 || s.x > W() + 80 || s.y < -80 || s.y > H() + 80) { shapes.splice(i, 1); spawnShape(); } } // Draw drifting bezier curves curves.forEach(c => { c.pts.forEach(p => { p.x += p.vx; p.y += p.vy; if (p.x < -100 || p.x > W() + 100) p.vx *= -1; if (p.y < -100 || p.y > H() + 100) p.vy *= -1; }); ctx.beginPath(); ctx.moveTo(c.pts[0].x, c.pts[0].y); ctx.bezierCurveTo(c.pts[1].x, c.pts[1].y, c.pts[2].x, c.pts[2].y, c.pts[3].x, c.pts[3].y); ctx.strokeStyle = accent + c.a + ')'; ctx.lineWidth = 0.8; ctx.stroke(); }); // Subtle grid pulse (every ~200 frames) if (frame % 200 < 40) { const pulse = Math.sin((frame % 200) / 40 * Math.PI) * 0.03; ctx.strokeStyle = accent + pulse + ')'; ctx.lineWidth = 0.3; const step = 80; for (let gx = 0; gx < W(); gx += step) { ctx.beginPath(); ctx.moveTo(gx, 0); ctx.lineTo(gx, H()); ctx.stroke(); } for (let gy = 0; gy < H(); gy += step) { ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(W(), gy); ctx.stroke(); } } requestAnimationFrame(draw); } requestAnimationFrame(draw); })();