<html><head>
<style>
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
font-family: Arial, sans-serif;
}
canvas {
width: 100%;
height: 100%;
}
#ui {
position: absolute;
top: 10px;
left: 10px;
color: white;
}
#btc-emoji {
font-size: 48px;
animation: pulse 2s infinite;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@keyframes pulse {
0% { transform: translate(-50%, -50%) scale(1); }
50% { transform: translate(-50%, -50%) scale(1.2); }
100% { transform: translate(-50%, -50%) scale(1); }
}
</style>
</head>
<body>
<canvas id="glCanvas"></canvas>
<div id="ui">
<div>FPS: <span id="fps">0</span></div>
</div>
<div id="btc-emoji">₿</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
<script>
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl2');
if (!gl) {
console.error('WebGL 2 not supported');
document.body.innerHTML = 'WebGL 2 is not supported in your browser.';
}
const vertexShaderSource = `#version 300 es
in vec4 a_position;
void main() {
gl_Position = a_position;
}`;
const fragmentShaderSource = `#version 300 es
precision highp float;
out vec4 fragColor;
uniform vec3 iResolution;
uniform float iTime;
uniform vec4 iMouse;
vec2 rotate(vec2 a, float b)
{
float c = cos(b);
float s = sin(b);
return vec2(
a.x * c - a.y * s,
a.x * s + a.y * c
);
}
float sdBox( vec3 p, vec3 b )
{
vec3 d = abs(p) - b;
return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
}
float scene(vec3 p)
{
vec3 sp = p + vec3(1,3,2);
// Add a rotating platform
vec2 rotatedXZ = rotate(p.xz, iTime);
float platform = sdBox(vec3(rotatedXZ.x, p.y + 0.5, rotatedXZ.y), vec3(2.0, 0.1, 2.0));
return min(
min(
max(
max(
sdBox(p, vec3(1.)),
-sdBox(p,vec3(.9))
),
abs(mod(length(sp)-iTime*.2,.2)-.1)-.01
),
length(p)-.8
),
platform
);
}
float sceneWithFloor(vec3 p)
{
return min(
scene(p),
1.+p.y
);
}
vec3 trace(vec3 cam, vec3 dir)
{
vec3 accum = vec3(1);
for (int bounce = 0; bounce < 20; bounce++)
{
float tfloor = (cam.y + 1.)/-dir.y;
float t = 0.;
float k = 0.;
for(int i = 0; i < 100; i++)
{
k = scene(cam + t * dir);
t += k;
if (k < 0.001 || (tfloor > 0. && t > tfloor))
break;
}
if (tfloor > 0.)
t = min(t, tfloor);
vec3 h = cam + t * dir;
vec2 o = vec2(.005, 0);
vec3 n = normalize(vec3(
sceneWithFloor(h+o.xyy)-sceneWithFloor(h-o.xyy),
sceneWithFloor(h+o.yxy)-sceneWithFloor(h-o.yxy),
sceneWithFloor(h+o.yyx)-sceneWithFloor(h-o.yyx)
));
if (h.y < -.999)
{
// floor
float A = .5;
float B = max(0.,sceneWithFloor(h+n*A));
float w = clamp(1.-length(h.xz) * .01, 0., 1.);
w = w * .2 + .8;
return accum * vec3(pow(B/A,.7)*.6+.4) * w;
}
else if (length(h) < .85)
{
// ball
float fresnel = mix(.001,1.,pow(1.-dot(-dir, n),5.));
accum *= fresnel;
cam = h + n * .01;
dir = reflect(dir, n);
}
else if (length(h) < 2.) // ew yucky hack
{
// cube
accum *= vec3(.72,.576,.288);
cam = h + n * .01;
dir = reflect(dir, n);
}
else if (abs(h.y + 0.5) < 0.1 && length(h.xz) < 2.0)
{
// platform
accum *= vec3(0.5, 0.5, 0.8); // Blue-ish color for the platform
cam = h + n * .01;
dir = reflect(dir, n);
}
else
{
// sky
return accum * vec3(.8);
}
}
return vec3(0);
}
void mainImage(out vec4 out_color, vec2 fragCoord)
{
vec2 uv = fragCoord / iResolution.xy - .5;
uv.x *= iResolution.x / iResolution.y;
vec3 cam = vec3(0,0,-4);
vec3 dir = normalize(vec3(uv, 1));
cam.yz = rotate(cam.yz, sin(iTime*.1)*.25+.25);
dir.yz = rotate(dir.yz, sin(iTime*.1)*.25+.25);
cam.xz = rotate(cam.xz, iTime*.3);
dir.xz = rotate(dir.xz, iTime*.3);
out_color = vec4(pow(trace(cam,dir),vec3(.45)),1);
}
void main() {
mainImage(fragColor, gl_FragCoord.xy);
}`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(program));
}
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
const resolutionUniformLocation = gl.getUniformLocation(program, "iResolution");
const timeUniformLocation = gl.getUniformLocation(program, "iTime");
const mouseUniformLocation = gl.getUniformLocation(program, "iMouse");
function resizeCanvasToDisplaySize(canvas) {
const displayWidth = canvas.clientWidth;
const displayHeight = canvas.clientHeight;
const needResize = canvas.width !== displayWidth ||
canvas.height !== displayHeight;
if (needResize) {
canvas.width = displayWidth;
canvas.height = displayHeight;
}
return needResize;
}
let mouseX = 0, mouseY = 0;
canvas.addEventListener('mousemove', (event) => {
mouseX = event.clientX;
mouseY = event.clientY;
});
const fpsElement = document.getElementById('fps');
let lastTime = 0;
let frameCount = 0;
function render(time) {
const deltaTime = time - lastTime;
lastTime = time;
frameCount++;
if (deltaTime >= 1000) {
const fps = Math.round((frameCount * 1000) / deltaTime);
fpsElement.textContent = fps;
frameCount = 0;
}
time *= 0.001; // convert to seconds
resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(program);
gl.bindVertexArray(vao);
gl.uniform3f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height, 1);
gl.uniform1f(timeUniformLocation, time);
gl.uniform4f(mouseUniformLocation, mouseX, mouseY, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// Rotate the BTC emoji
const btcEmoji = document.getElementById('btc-emoji');
function rotateBtcEmoji() {
const time = Date.now() * 0.001;
const x = Math.cos(time) * 100;
const y = Math.sin(time) * 100;
btcEmoji.style.transform = `translate(calc(-50% + ${x}px), calc(-50% + ${y}px)) scale(${1 + Math.sin(time * 2) * 0.2})`;
requestAnimationFrame(rotateBtcEmoji);
}
rotateBtcEmoji();
</script>
</body>
</html>