Choose a color, use the mouse to draw a 3D scene, layer by layer. Click "GO" to go to the next layer. Click "X" to reset. Right-click to erase a pixel/voxel.
for(_="te6inF11Nonjbuttjq(${2*z`#fffQtransP);_em;^4+i%NKc.fillJem)IPla6WIWZ(VightU20Drota6CPformB0%_B:CAp[i][$]valuetylefor(-a.offset+1,18,18)i=S;i--;)></div~~((e.page8+~~(i/N)^heU:s.FnerHTMLjmouseJRect(o.,D*,D*0deg) s=B-s:preserve-3d;positij:fixed;width:4040^> <q jclick=0101 JS=YTop)/D)XLeft)/D)=(e,t)=><div22^background:${};>fil6r:brUness(AY(9WX(1VB:WZ(-DICX(4CZ(i F d.wri6`<divbottom:0;rU:5^perspective:70em><p id=s3><p><Fput type=color id=o =#0080ffg()>GO</q='';E=0;r(_r()>x<s>#b{margF:1%}#s{animatij:a Ds FfFi6;border:5px solid}@keyframes a{50%{-3`,p=[],S=D,E=m=0,(r{Qc`,0,0SS),`#ccc`,0i-1S,2),D*i-1,0,2S),p[i]=[]})(),Z=` 1 00 0 0 0N0N0N 01`)+Z[i]&&(p[][K]=(K)+1()_a.downG(e,0,m=1_a.movem&&G(e_upm=0;a.jcj6xtmenu!!G(e,1_G{t?Qf`:+1,p[][]=t||o.};(g{$=S;$--;)&&(+=`B:WYzi}IWXz$}V${E+2}I9-1I81INAX(9CZ(18WY(1V-1I>`_e||r(E+=2)})(1)";G=/[-A-DU-WI-K^_PQzqjNF6]/.exec(_);)with(_.split(G))_=join(shift());eval(_)<3
Zm9yKF89InRlNmluRjExTm9uamJ1dHRqcSgkezIqemAjZmZmUXRyYW5zUCk7X2VtO140K2klTktjLmZpbGxKZW0pSVBsYTZXSVdaKFZpZ2h0VTIwRHJvdGE2Q1Bmb3JtQjAlX0I6Q0FwW2ldWyRdH3ZhbHVlHnR5bGUdZm9yKBwtYS5vZmZzZXQbKzEsMTgsMTgpGmk9UztpLS07KRk+PC9kaXYYfn4oKGUucGFnZRc4K35+KGkvTikWXmhlVToVcy5GbmVySFRNTBRqbW91c2UTSlJlY3QoEm8uHiwSRCoRLEQqEDBkZWcpDyBzHT1CLXMdOnByZXNlcnZlLTNkO3Bvc2l0aWo6Zml4ZWQ7d2lkdGg6Dg40MBU0MF4MPiA8cSBqY2xpY2s9CzAxMDEJSlMdPQgXWRtUb3ApL0QpBxdYG0xlZnQpL0QpBj0oZSx0KT0+BTxkaXYOMhUyXmJhY2tncm91bmQ6JHsffTsEPgRmaWw2cjpiclVuZXNzKANBWSg5D1dYKDFWAkI6V1ooLURJQ1goNA9DWigBHGkgRiBkLndyaTZgPGRpdgxib3R0b206MDtyVTo1XnBlcnNwZWN0aXZlOjcwZW0+PHAgaWQ9cwwBMw8YPjxwPjxGcHV0IHR5cGU9Y29sb3IgaWQ9byAePSMwMDgwZmYLZygpPkdPPC9xCxQ9Jyc7RT0wO3IoX3IoKT54PHMdPiNie21hcmdGOjElfSNze2FuaW1hdGlqOmEgRHMgRmZGaTY7Ym9yZGVyOjVweCBzb2xpZH1Aa2V5ZnJhbWVzIGF7NTAlewEtMw9gLHA9W10sUz1ELEU9bT0wLChyBXscCFFjYCwSMCwwEFMQUyksCGAjY2NjYCwZEjAQaS0xEFMsMiksEkQqaS0xLDAsMhBTKSxwW2ldPVtdfSkoKSxaPWAJMQkwMAkwCQkwCTBOME4wTgkwMWApK1pbaV0mJihwWxZdW0tdPQgRKEspKzEQKBYpGl9hLhNkb3duBUcoZSwwLG09MV9hLhNtb3ZlBW0mJkcoZV8TdXAFbT0wO2EuamNqNnh0bWVudQUhIUcoZSwxX0cFewh0P1FmYDoRBisxEAcaLHBbB11bBl09dHx8by4efTsoZwV7HBkcJD1TOyQtLTspHyYmKBQrPWAEQjpXWXppfUlXWHokfVYke0UrMn1JAzkCLTFJGAM4AjFJGANOQVgoOQ9DWigxOA9XWSgxVi0xST5gX2V8fHIoRSs9Mil9KSgxKSI7Rz0vWwEtH0EtRFUtV0ktS15fUFF6cWpORjZdLy5leGVjKF8pOyl3aXRoKF8uc3BsaXQoRykpXz1qb2luKHNoaWZ0KCkpO2V2YWwoXyk8Mw==
// The JS1K logo
// -------------
// First, loop on the characters of `Z`, containing the the blue pixels of the JS1K logo:
// * ** * *
// * * * * *
// * * * **
// ** ** * * *
for(i in
// UI
// --
// Add the CSS3D scene and the UI in the page, after the canvas.
// The UI contains a color picker (with a default blue value), a GO button to add a layer and a button to clear the scene.
// A style tag is also added to declare an infinite animation and apply it to the scene.
d.write`<div style=transform-style:preserve-3d;position:fixed;width:40em;height:40em;bottom:0;right:5em;perspective:70em><p id=s style=transform-style:preserve-3d;position:fixed;width:40em;height:40em;transform:translateZ(-20em)rotateX(45deg)rotateZ(30deg)></div><p><input type=color id=o value=#0080ff> <button onclick=g()>GO</button> <button onclick=s.innerHTML='';E=0;r();r()>x<style>#b{margin:1%}#s{animation:a 20s infinite;border:5px solid}@keyframes a{50%{transform:translateZ(-20em)rotateX(45deg)rotateZ(-30deg)`,
// Pixels array.
p = [],
// Grid size.
S = 20,
// Elevation and mousedown flag (both are set to 0 on load).
E = m = 0,
// This function `r` resets the grid. It is called on load.
(r = (e, t) => {
for(
// Color the canvas with a semi-transparent white rectangle in order to make the previous layer a bit visible.
c.fillStyle = `#fffc`,
c.fillRect(0, 0, 20 * S, 20 * S),
// Set the lines color.
c.fillStyle = `#ccc`,
// Loop 20 times:
i = S;
i--;
)
// Trace horizontal lines.
c.fillRect(0, 20 * i - 1, 20 * S, 2),
// Trace vertical lines.
c.fillRect(20 * i - 1, 0, 2, 20 * S),
// Reset the pixels array for the current layer.
p[i] = []
})(),
// This is `Z`, the string we're looping on.
Z = `01011010100010100101010100101011011011010101`
)
// If the current char of `Z` is "1":
+Z[i] && (
// Add the pixel's color in `p`.
p[8 + ~~(i / 11)][4 + i % 11] = c.fillStyle = o.value,
// Draw the pixel on the canvas, without touching the grey lines.
c.fillRect(20 * (4 + i % 11) + 1, 20 * (8 + ~~(i / 11)) + 1, 18, 18)
);
// Mouse events
// ------------
// On mousedown, set the mousedown flag to 1 and save the clicked pixel.
a.onmousedown = (e, t) => z(e, 0, m = 1);
// On mousemove, save the hovered pixel is the mousedown flag is set.
a.onmousemove = (e, t) => m && z(e);
// On mouseup, unset the mousedown flag.
onmouseup = (e, t) => m = 0;
// On right click, clear the clicked pixel and return false (to avoid opening the native contextmenu)
a.oncontextmenu = (e, t) => !!z(e, 1);
// This function `z` saves a pixel (or clears it if the param `t` is set):
z = (e, t) => {
// Canvas fill style: white if `t` is set, the color picker's value otherwise.
c.fillStyle = t ? `#fff` : o.value,
// Draw the colored pixel, without touching the grey lines.
c.fillRect(20 * ~~((e.pageX - a.offsetLeft) / 20) + 1, 20 * ~~((e.pageY - a.offsetTop) / 20) + 1, 18, 18),
// Save the pixel's color in `p` (or just the number 1 if `t` is set)
p[~~((e.pageY - a.offsetTop) / 20)][~~((e.pageX - a.offsetLeft) / 20)] = t || o.value;
};
// CSS3D
// -----
// This function `g` (GO) renders the last drawn layer in CSS3D:
(g = (e, t) => {
// Loop on all the pixels of `p`.
for(i = S; i--;)
for(j = S; j--;)
// If the pixel is set:
p[i][j] && (
// Render the corresponding voxel by generating the top, left, right and front faces of a CSS3D cube.
// The top face contains the 3 other faces, so it contains the X and Y offsets.
// The elevation (height of current layer) is also applied to it.
// The 3 other faces use CSS filters to make their color a bit lighter or darker, to add kind of shadows to the scene.
// The back and bottom faces aren't rendered because they are invisible, but also to save bytes and improve performance.
s.innerHTML += `<div style=transform-style:preserve-3d;position:fixed;width:2em;height:2em;background:${p[i][j]};transform:translateY(${2*i}em)translateX(${2*j}em)translateZ(${E+2}em)><div style=transform-style:preserve-3d;position:fixed;width:2em;height:2em;background:${p[i][j]};filter:brightness(90%);transform:rotateY(90deg)translateX(1em)translateZ(-1em)></div><div style=transform-style:preserve-3d;position:fixed;width:2em;height:2em;background:${p[i][j]};filter:brightness(80%);transform:rotateY(90deg)translateX(1em)translateZ(1em)></div><div style=transform-style:preserve-3d;position:fixed;width:2em;height:2em;background:${p[i][j]};filter:brightness(110%);transform:rotateX(90deg)rotateZ(180deg)translateY(1em)translateZ(-1em)>`
);
// If the parameter `e` is not set, increase the elevation by 2em.
e || r(E += 2)
// On load, we don't want to increase the elevation, so we call `g` with e = 1.
})(1)