Skip to content

Instantly share code, notes, and snippets.

@felipeabajo
Created January 26, 2026 18:16
Show Gist options
  • Select an option

  • Save felipeabajo/71fce970468e2a10534d4b272e6cfefd to your computer and use it in GitHub Desktop.

Select an option

Save felipeabajo/71fce970468e2a10534d4b272e6cfefd to your computer and use it in GitHub Desktop.
HTML5 Canvas animation
<style>
:root{
--bg1:#0f172a;
--bg2:#111827;
--acc1:#2563eb;
--acc2:#9333ea;
--acc3:#22c55e;
--acc4:#f59e0b;
--white:#e5e7eb;
--muted:#94a3b8;
}
#animation-software-development{
height:15rem !important;
max-height:500px !important;
margin:0;
background: radial-gradient(1200px 800px at 70% 20%, #1e293b 0%, var(--bg1) 55%, var(--bg2) 100%);
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
color: var(--white);
}
.wrap{position:relative; height:100%; width:100%; overflow:hidden}
#scene{position:absolute; inset:0; display:block}
</style>
<div id="animation-software-development">
<div class="wrap">
<canvas id="scene" aria-hidden="true"></canvas>
</div>
</div>
<script>
const TAU = Math.PI * 2;
const rand = (a,b) => a + Math.random()*(b-a);
const cfg = {
speed: 1,
codeLines: [
"<div class=\"app\">",
" <header>Power Platform</header>",
" <section>Dynamics 365</section>",
" <button>Iniciar flujo</button>",
"</div>",
"<entity name=\"Cuenta\">",
" <field name=\"ClienteID\" />",
"</entity>",
],
colors: {
tag: "#2563eb",
attr: "#9333ea",
val: "#22c55e",
text: "#e5e7eb",
accent: "#f59e0b"
}
};
const canvas = document.getElementById('scene');
const ctx = canvas.getContext('2d');
const DPR = Math.min(window.devicePixelRatio || 1, 2);
function resize(){
const {innerWidth:w, innerHeight:h} = window;
canvas.width = Math.floor(w * DPR);
canvas.height = Math.floor(h * DPR);
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
ctx.setTransform(DPR,0,0,DPR,0,0);
}
window.addEventListener('resize', resize);
resize();
class CodeParticle{
constructor(){ this.reset(true); }
reset(initial=false){
const w = canvas.width / DPR, h = canvas.height / DPR;
this.x = rand(-w*0.1, w*1.1);
this.y = initial ? rand(h*0.1, h*0.9) : (h + rand(10,200));
this.vy = -rand(.4, 1.1);
this.vx = rand(-.2, .2);
this.alpha = rand(.4, .95);
this.scale = rand(.7, 1.6);
this.text = this.pickCode();
this.spin = rand(-.004, .004);
this.angle = rand(-.2, .2);
}
pickCode(){
const s = cfg.codeLines[Math.floor(Math.random()*cfg.codeLines.length)];
return s
.replace(/(&?<!?)(\/?)([a-zA-Z!]+)(?=[\s>])/g, '{tag}<$2$3{/}')
.replace(/([a-z-]+)(=)(\"[^\"]*\")/g, '{attr}$1{/}=$3')
.replace(/\"([^\"]*)\"/g, '{val}="$1"{/}')
}
step(dt){
this.x += this.vx * dt * cfg.speed;
this.y += this.vy * dt * cfg.speed;
this.angle += this.spin * dt * cfg.speed;
const h = canvas.height / DPR;
if(this.y < -40) this.reset();
}
draw(ctx){
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.scale(this.scale, this.scale);
drawRichText(ctx, this.text, 0, 0);
ctx.restore();
}
}
function drawRichText(ctx, str, x, y){
const {tag, attr, val, text} = cfg.colors;
const parts = str.split(/(\{(?:tag|attr|val)\}|\{\/\})/g);
let col = text; let cursor = x;
ctx.font = '14px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
for(const p of parts){
if(p === '{tag}') { col = tag; continue; }
if(p === '{attr}') { col = attr; continue; }
if(p === '{val}') { col = val; continue; }
if(p === '{/}') { col = text; continue; }
ctx.fillStyle = col;
ctx.fillText(p, cursor, y);
cursor += ctx.measureText(p).width;
}
}
function drawHand(ctx, x, y, t, left=true){
ctx.save();
ctx.translate(x, y);
const lift = Math.sin(t * 6 + (left?0:Math.PI)) * 3;
ctx.translate(0, lift);
ctx.fillStyle = '#f1c8a0';
ctx.beginPath(); ctx.ellipse(0,0,10,8,0,0,TAU); ctx.fill();
for(let i=0;i<4;i++){
ctx.beginPath(); ctx.roundRect(-10 + i*6, -6, 5, 12, 3); ctx.fill();
}
ctx.restore();
}
function drawPerson(ctx, baseX, baseY, t){
ctx.save();
ctx.translate(baseX, baseY);
ctx.globalAlpha = .15;
ctx.fillStyle = '#000';
ctx.filter = 'blur(8px)';
ctx.beginPath(); ctx.ellipse(0, 62, 120, 18, 0, 0, TAU); ctx.fill();
ctx.filter = 'none'; ctx.globalAlpha = 1;
ctx.fillStyle = '#1f2937';
ctx.beginPath(); ctx.roundRect(-70, -10, 140, 90, 16); ctx.fill();
ctx.fillStyle = '#e0b386';
ctx.fillRect(-12, -26, 24, 18);
ctx.fillStyle = '#d6a06a';
ctx.beginPath(); ctx.ellipse(0, -50, 28, 34, 0, 0, TAU); ctx.fill();
ctx.fillStyle = '#171717';
ctx.beginPath();
ctx.moveTo(-28,-56); ctx.quadraticCurveTo(0,-75,28,-56); ctx.quadraticCurveTo(10,-34,-10,-30); ctx.quadraticCurveTo(-22,-34,-28,-56); ctx.fill();
ctx.globalAlpha = .15; ctx.fillStyle = '#0f0f0f';
ctx.beginPath(); ctx.ellipse(0, -34, 20, 14, 0, 0, TAU); ctx.fill();
ctx.globalAlpha = 1;
ctx.fillStyle = '#0b1020';
ctx.fillRect(-14,-48,10,2);
ctx.fillRect(4,-48,10,2);
ctx.fillStyle = '#111827';
ctx.beginPath(); ctx.ellipse(-9,-44,3,2,0,0,TAU); ctx.fill();
ctx.beginPath(); ctx.ellipse(9,-44,3,2,0,0,TAU); ctx.fill();
ctx.strokeStyle = '#a16207'; ctx.lineWidth = 1.2;
ctx.beginPath(); ctx.moveTo(0,-42); ctx.quadraticCurveTo(2,-38,0,-35); ctx.stroke();
ctx.fillStyle = '#0b1224';
ctx.fillRect(-180, 40, 360, 10);
ctx.fillStyle = '#0f172a';
ctx.beginPath(); ctx.roundRect(-70, 8, 140, 80, 10); ctx.fill();
ctx.save();
const flicker = 0.08 + 0.04*Math.sin(t*8);
const grd = ctx.createLinearGradient(-70,8,-70,88);
grd.addColorStop(0, `rgba(96,165,250,${0.15+flicker})`);
grd.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = grd; ctx.fillRect(-70,8,140,80);
ctx.restore();
ctx.fillStyle = '#2563eb';
ctx.font = 'bold 14px ui-monospace, monospace';
ctx.fillText("</>", -60, 22);
ctx.fillStyle = '#0a0f1d'; ctx.fillRect(-80, 88, 160, 8);
drawHand(ctx, -24, 86, t, true);
drawHand(ctx, 24, 86, t, false);
ctx.restore();
}
const sparks = Array.from({length: 80}, () => ({
x: Math.random(), y: Math.random(), r: Math.random()*1.5+0.3, s: Math.random()*0.6+0.2
}));
const particles = Array.from({length: 28}, ()=> new CodeParticle());
let last = performance.now();
function loop(now){
const dt = Math.min(33, now - last) / 16.6667;
last = now;
const w = canvas.width / DPR, h = canvas.height / DPR;
ctx.clearRect(0,0,w,h);
ctx.globalAlpha = .06;
for(let i=0;i<w;i+=32){ ctx.fillStyle = i%64===0? '#fff' : '#000'; ctx.fillRect(i, 0, 1, h);}
for(let j=0;j<h;j+=32){ ctx.fillStyle = j%64===0? '#fff' : '#000'; ctx.fillRect(0, j, w, 1);}
ctx.globalAlpha = 1;
ctx.save();
for(const s of sparks){
const px = s.x * w, py = (s.y * h + (now*0.01*s.s)) % h;
ctx.globalAlpha = .25 + .25*Math.sin((now*0.004)+(px+py));
ctx.fillStyle = '#93c5fd';
ctx.fillRect(px, py, s.r, s.r);
}
ctx.restore();
drawPerson(ctx, w*0.68, h*0.62, now/1000);
ctx.save();
ctx.translate(w*0.28, h*0.38);
for(const p of particles){ p.step(dt); p.draw(ctx); }
ctx.restore();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment