You are a size coder. You came up through 4k intros and JS1k, and you believe that a budget is not a limitation but the entire craft — anyone can be impressive with a megabyte.
I want something worth watching, and I am giving you 2048 bytes to do it in.
The finished HTML file must be 2048 bytes or fewer, counted as UTF-8, including the doctype, every tag, every space and every newline. That is the whole file. There is no second file.
data: URIs are allowed but they count against you like everything else.localStorage, sessionStorage, cookies or indexedDB. It runs in a sandboxed frame where touching them throws.I have seen these a thousand times and I will not be impressed:
Show me something with a subject. Something that is recognisably of something — a place, a weather, a creature, a machine, a mood — and not just maths made visible.
One line. What am I about to see when I open this file?
Exactly one fenced html block, containing the whole file and nothing else.
One short paragraph on the trick that bought you the most room. Not a list of micro-optimisations — the one structural decision that made the rest affordable.
I will run the file through a byte counter, so the number in your ledger is checkable and I am going to check it.
If you cannot get under 2048, hand me the smallest honest version you have and put the real number in the ledger. A 2600-byte file labelled 2600 scores well above a 2600-byte file labelled 1990. Going over is a miss. Lying about going over is the only thing here that is disqualifying.
A hot-air balloon drifts at dawn over purple mountains, with moving clouds and flapping birds.
<!doctype html><meta charset=utf-8>
<style>body{margin:0}canvas{display:block}</style>
<canvas id=c></canvas>
<script>
c.width=innerWidth;c.height=innerHeight
x=c.getContext('2d');S=Math.sin
addEventListener('resize',()=>{c.width=innerWidth;c.height=innerHeight})
function C(a,b,r,d){x.fillStyle=d||x.fillStyle;x.beginPath();x.arc(a,b,r,0,7);x.fill()}
function f(t){
requestAnimationFrame(f)
let W=c.width,H=c.height
let g=x.createLinearGradient(0,0,0,H)
g.addColorStop(0,'#274b7a');g.addColorStop(1,'#ffd29d')
x.fillStyle=g;x.fillRect(0,0,W,H)
C(W*.72,H*.6,34,'#fff0b3');x.fillStyle='rgba(255,244,235,.9)'
for(let i=0;i<5;i++){let a=(i*260+t*.02*(i+1))%(W+200)-100,b=H*(.15+.05*(i%3))
C(a,b,20);C(a+24,b+4,15);C(a-24,b+5,14)}
x.fillStyle='#7a6395';x.beginPath();x.moveTo(0,H*.8)
for(let i=0;i<=8;i++)x.lineTo(i*W/8,H*(.56+.1*S(i*2.4+1)))
x.lineTo(W,H);x.lineTo(0,H);x.closePath();x.fill()
x.fillStyle='#3f6d4e';x.beginPath();x.moveTo(0,H)
for(let i=0;i<=6;i++)x.lineTo(i*W/6,H*(.83+.04*S(i*1.8)))
x.lineTo(W,H);x.closePath();x.fill();x.strokeStyle='#26324a';x.lineWidth=2
for(let i=0;i<4;i++){let a=((t*.04*(1+i*.3)+i*260)%(W+60))-30,b=H*(.18+.05*i)+S(t*.002+i)*7,d=S(t*.012+i)*5
x.beginPath();x.moveTo(a-9,b+d);x.lineTo(a,b);x.lineTo(a+9,b+d);x.stroke()}
let X=W/2+S(t*.0004)*W*.16,Y=H*.36+S(t*.0009)*12,R=58
x.save();x.translate(X,Y);x.beginPath();x.ellipse(0,0,R,68,0,0,7);x.clip()
x.fillStyle='#d94f3d';x.fillRect(-R,-70,R*2,140)
for(let i=-2;i<3;i++){x.fillStyle=i%2?'#f2b544':'#f7e8c9';x.fillRect(i*23-12,-70,23,140)}
x.restore();x.beginPath();x.ellipse(X,Y,R,68,0,0,7);x.strokeStyle='rgba(0,0,0,.18)';x.stroke()
x.strokeStyle='#5d3d22';x.lineWidth=2;x.beginPath();x.moveTo(X-29,Y+58);x.lineTo(X-13,Y+84);x.moveTo(X+29,Y+58);x.lineTo(X+13,Y+84);x.stroke()
C(X,Y+77,5+S(t*.04)*2,'#ffcf5a');C(X,Y+77,3+S(t*.05)*1.5,'#ff8c42')
x.fillStyle='#8a5a33';x.fillRect(X-13,Y+84,26,16)
}
requestAnimationFrame(f)
</script>
\n after each of its 36 lines (1875 characters + 36 newlines).The structural decision was to make the canvas itself the asset: one tiny circle helper plus sine-driven parametric positions generate the sky, clouds, mountains, birds and the striped balloon every frame, so no images, sprites or data tables are stored and the whole scene is just a few dozen drawing calls in a loop.