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 moon jelly pulses above swaying kelp while fish drift through slanting undersea light.
<!doctype html><meta charset=utf-8><style>body{margin:0;overflow:hidden}canvas{display:block;width:100vw;height:100vh}</style><canvas id=c></canvas><script>
x=c.getContext('2d');M=Math.sin;A=(a,b)=>g.addColorStop(a,b);with(x){F=t=>{n=t*.001;c.width=innerWidth;c.height=innerHeight;setTransform(c.width/1e3,0,0,c.height/600,0,0);
g=createLinearGradient(0,0,0,600);A(0,'#075');A(.5,'#034');A(1,'#001');fillStyle=g;fillRect(0,0,1e3,600);
fillStyle='#8ff1';for(i=0;i<6;i++){j=(i*230+n*18)%1380-190;beginPath();moveTo(j,0);lineTo(j+90,0);lineTo(j+260,600);lineTo(j+70,600);fill()}
fillStyle='#9bb5';for(i=0;i<8;i++){X=(i*173+n*(30+i%3*15))%1200-100;Y=90+(i*73)%350+M(n+i)*8;beginPath();ellipse(X,Y,12,5,0,0,7);moveTo(X-10,Y);lineTo(X-18,Y-6);lineTo(X-18,Y+6);fill()}
fillStyle='#001';beginPath();moveTo(0,570);quadraticCurveTo(250,540,500,570);quadraticCurveTo(750,600,1e3,565);lineTo(1e3,600);lineTo(0,600);fill()
for(i=0;i<15;i++){X=i*70+10;Y=80+30*(i%3);Z=M(n*.7+i)*35;strokeStyle=i%2?'#063':'#073';lineWidth=8-i%3*2;beginPath();moveTo(X,600);bezierCurveTo(X+Z,500,X-Z,Y+100,X+Z/2,Y);stroke()}
X=500+80*M(n*.25);Y=245+16*M(n*.7);R=90+8*M(n*2);lineCap='round';for(k=-4;k<5;k++){Z=X+k*18;strokeStyle=k%2?'#bff8':'#f9f6';lineWidth=k%2?2:5;beginPath();moveTo(Z,Y);bezierCurveTo(Z+M(n+k)*30,Y+100,Z+M(n*1.4+k)*70,Y+230,Z+M(n*.8+k)*80,Y+330);stroke()}
g=createRadialGradient(X,Y-40,10,X,Y,R*1.3);A(0,'#fffd');A(.55,'#a6f8');A(1,'#3ea2');fillStyle=g;shadowColor='#8ff';shadowBlur=25;
beginPath();moveTo(X-R,Y);bezierCurveTo(X-R,Y-R*1.35,X+R,Y-R*1.35,X+R,Y);for(k=0;k<5;k++)quadraticCurveTo(X+R-(2*k+1)*R/5,Y+18,X+R-(2*k+2)*R/5,Y);fill();strokeStyle='#bffc';lineWidth=3;stroke();shadowBlur=0;
fillStyle='#fbe6';for(k=0;k<4;k++){beginPath();ellipse(X+(k-1.5)*22,Y-35+7*M(n*2+k),9,24,.3,0,7);fill()}
requestAnimationFrame(F)}};requestAnimationFrame(F)</script>
wc -c equivalent).The room came from making the scene stateless: a fixed 1000×600 virtual canvas is completely repainted each frame from time alone, with one transform and one recursive paint function. That means the whole subject — water, kelp, fish, bell, rim, tentacles, glow — is just layered analytic geometry sharing a single context, with no DOM scene graph, no asset data, and no persistent objects to manage.