Custom Code Experiment •
Sphere 1
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SVG: Animated Glyph - Glitch Icosphere Edition</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background-color: #000;
overflow: hidden;
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.container {
width: 100%; height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
z-index: 1;
}
canvas {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 1;
}
.instructions {
position: absolute;
bottom: 20px;
color: #0ff;
font-family: 'Courier New', monospace;
font-size: 14px;
text-align: center;
width: 100%;
text-shadow: 0 0 10px #0ff, 0 0 20px #0ff;
z-index: 2;
animation: textGlitch 5s infinite;
}
</style>
</head>
<body>
<div class="container">
<canvas id="c"></canvas>
</div>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
// --- Icosphere geometry (subdivision level 2) ---
const t = (1 + Math.sqrt(5)) / 2;
function normalize(v) {
const len = Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
return [v[0]/len, v[1]/len, v[2]/len];
}
// Base icosahedron vertices
let verts = [
[-1,t,0],[1,t,0],[-1,-t,0],[1,-t,0],
[0,-1,t],[0,1,t],[0,-1,-t],[0,1,-t],
[t,0,-1],[t,0,1],[-t,0,-1],[-t,0,1]
].map(normalize);
let faces = [
[0,11,5],[0,5,1],[0,1,7],[0,7,10],[0,10,11],
[1,5,9],[5,11,4],[11,10,2],[10,7,6],[7,1,8],
[3,9,4],[3,4,2],[3,2,6],[3,6,8],[3,8,9],
[4,9,5],[2,4,11],[6,2,10],[8,6,7],[9,8,1]
];
// Subdivide once
function midpoint(a, b, cache, verts) {
const key = [Math.min(a,b), Math.max(a,b)].join('_');
if (cache[key] !== undefined) return cache[key];
const v = normalize([
(verts[a][0]+verts[b][0])/2,
(verts[a][1]+verts[b][1])/2,
(verts[a][2]+verts[b][2])/2
]);
cache[key] = verts.length;
verts.push(v);
return cache[key];
}
function subdivide(verts, faces) {
const cache = {};
const newFaces = [];
for (const [a,b,c] of faces) {
const ab = midpoint(a,b,cache,verts);
const bc = midpoint(b,c,cache,verts);
const ca = midpoint(c,a,cache,verts);
newFaces.push([a,ab,ca],[b,bc,ab],[c,ca,bc],[ab,bc,ca]);
}
return newFaces;
}
faces = subdivide(verts, faces);
faces = subdivide(verts, faces);
// Unique edges
const edgeSet = new Set();
const edges = [];
for (const [a,b,c] of faces) {
for (const [i,j] of [[a,b],[b,c],[c,a]]) {
const key = [Math.min(i,j),Math.max(i,j)].join('_');
if (!edgeSet.has(key)) { edgeSet.add(key); edges.push([i,j]); }
}
}
// --- Projection ---
let rotX = 0, rotY = 0, rotZ = 0;
function rotateXYZ(v, rx, ry, rz) {
let [x,y,z] = v;
// Rotate X
let y2 = y*Math.cos(rx) - z*Math.sin(rx);
let z2 = y*Math.sin(rx) + z*Math.cos(rx);
y=y2; z=z2;
// Rotate Y
let x2 = x*Math.cos(ry) + z*Math.sin(ry);
z2 = -x*Math.sin(ry) + z*Math.cos(ry);
x=x2; z=z2;
// Rotate Z
x2 = x*Math.cos(rz) - y*Math.sin(rz);
y2 = x*Math.sin(rz) + y*Math.cos(rz);
return [x2, y2, z];
}
function project(v, cx, cy, scale) {
const fov = 4;
const z = v[2] + fov;
return [cx + (v[0] / z) * scale, cy + (v[1] / z) * scale];
}
function draw(ts) {
const W = canvas.width, H = canvas.height;
const cx = W/2, cy = H/2;
const scale = Math.min(W,H) * 1.4;
ctx.clearRect(0,0,W,H);
rotX += 0.003;
rotY += 0.007;
rotZ += 0.001;
// Project all vertices
const projected = verts.map(v => {
const rv = rotateXYZ(v, rotX, rotY, rotZ);
return project(rv, cx, cy, scale);
});
// Compute depth for faces (for back-face sorting + shading)
const faceDepths = faces.map((f, i) => {
const rv0 = rotateXYZ(verts[f[0]], rotX, rotY, rotZ);
const rv1 = rotateXYZ(verts[f[1]], rotX, rotY, rotZ);
const rv2 = rotateXYZ(verts[f[2]], rotX, rotY, rotZ);
const z = (rv0[2]+rv1[2]+rv2[2])/3;
// Normal (for backface culling)
const ax = rv1[0]-rv0[0], ay = rv1[1]-rv0[1];
const bx = rv2[0]-rv0[0], by = rv2[1]-rv0[1];
const nz = ax*by - ay*bx; // z component of cross product
return { i, z, visible: nz < 0 };
});
faceDepths.sort((a,b) => a.z - b.z);
// Draw faces
for (const {i, visible} of faceDepths) {
if (!visible) continue;
const [a,b,c] = faces[i];
const pa = projected[a], pb = projected[b], pc = projected[c];
const rv = rotateXYZ(verts[a], rotX, rotY, rotZ);
const brightness = Math.max(0, rv[2]) * 0.5 + 0.05;
ctx.beginPath();
ctx.moveTo(pa[0],pa[1]);
ctx.lineTo(pb[0],pb[1]);
ctx.lineTo(pc[0],pc[1]);
ctx.closePath();
ctx.fillStyle = `rgba(0,${Math.floor(brightness*180)},${Math.floor(brightness*120)},${0.06 + brightness*0.12})`;
ctx.fill();
}
// Draw edges
const edgeDepths = edges.map(([a,b]) => {
const ra = rotateXYZ(verts[a], rotX, rotY, rotZ);
const rb = rotateXYZ(verts[b], rotX, rotY, rotZ);
return (ra[2]+rb[2])/2;
});
for (let ei = 0; ei < edges.length; ei++) {
const [a,b] = edges[ei];
const pa = projected[a], pb = projected[b];
const depth = edgeDepths[ei];
const alpha = 0.08 + Math.max(0, depth+1) * 0.18;
ctx.beginPath();
ctx.moveTo(pa[0],pa[1]);
ctx.lineTo(pb[0],pb[1]);
ctx.strokeStyle = `rgba(0,255,255,${alpha})`;
ctx.lineWidth = 0.5;
ctx.stroke();
}
// Glow outline pass
for (let ei = 0; ei < edges.length; ei++) {
const [a,b] = edges[ei];
const ra = rotateXYZ(verts[a], rotX, rotY, rotZ);
if (ra[2] < 0.3) continue; // only front-facing edges
const pa = projected[a], pb = projected[b];
ctx.beginPath();
ctx.moveTo(pa[0],pa[1]);
ctx.lineTo(pb[0],pb[1]);
ctx.strokeStyle = `rgba(0,255,255,0.12)`;
ctx.lineWidth = 2.5;
ctx.shadowColor = '#0ff';
ctx.shadowBlur = 6;
ctx.stroke();
ctx.shadowBlur = 0;
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
</script>
</body>
</html>