Created
June 2, 2026 08:15
-
-
Save gkucmierz/5aecb8cd847adba05b8a7c42466f9e3f to your computer and use it in GitHub Desktop.
Run this code instantly in your browser: https://instacode.app/gist/5aecb8cd847adba05b8a7c42466f9e3f
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Glassmorphism 3D Rotating Cube for Instacode | |
| // Features: Dual-cube perspective engine, global depth sorting, Phong lighting, and specular glass gradients | |
| import { canvas, getContext, onResize } from 'canvas'; | |
| const ctx = getContext('2d'); | |
| let width = 600; | |
| let height = 400; | |
| // Handle canvas resizing | |
| onResize((w, h) => { | |
| width = w; | |
| height = h; | |
| canvas.width = w; | |
| canvas.height = h; | |
| }); | |
| // Setup 3D constants | |
| const CAMERA_DIST = 500; | |
| const FOV = 450; | |
| // Cube builder function | |
| const createCube = (size) => { | |
| const half = size / 2; | |
| return { | |
| vertices: [ | |
| {x: -half, y: -half, z: -half}, // 0 | |
| {x: half, y: -half, z: -half}, // 1 | |
| {x: half, y: half, z: -half}, // 2 | |
| {x: -half, y: half, z: -half}, // 3 | |
| {x: -half, y: -half, z: half}, // 4 | |
| {x: half, y: -half, z: half}, // 5 | |
| {x: half, y: half, z: half}, // 6 | |
| {x: -half, y: half, z: half} // 7 | |
| ], | |
| faces: [ | |
| { indices: [4, 5, 6, 7], name: 'front' }, | |
| { indices: [1, 0, 3, 2], name: 'back' }, | |
| { indices: [0, 1, 5, 4], name: 'top' }, | |
| { indices: [7, 6, 2, 3], name: 'bottom' }, | |
| { indices: [0, 4, 7, 3], name: 'left' }, | |
| { indices: [5, 1, 2, 6], name: 'right' } | |
| ] | |
| }; | |
| }; | |
| const outerCube = createCube(220); | |
| const innerCube = createCube(110); | |
| let angleX = 0.5; | |
| let angleY = 0.5; | |
| let angleZ = 0.2; | |
| // Light source coordinates in 3D space | |
| const lightSource = { x: 300, y: -300, z: -200 }; | |
| function rotateX(point, angle) { | |
| const cos = Math.cos(angle); | |
| const sin = Math.sin(angle); | |
| return { | |
| x: point.x, | |
| y: point.y * cos - point.z * sin, | |
| z: point.y * sin + point.z * cos | |
| }; | |
| } | |
| function rotateY(point, angle) { | |
| const cos = Math.cos(angle); | |
| const sin = Math.sin(angle); | |
| return { | |
| x: point.x * cos - point.z * sin, | |
| y: point.y, | |
| z: point.x * sin + point.z * cos | |
| }; | |
| } | |
| function rotateZ(point, angle) { | |
| const cos = Math.cos(angle); | |
| const sin = Math.sin(angle); | |
| return { | |
| x: point.x * cos - point.y * sin, | |
| y: point.x * sin + point.y * cos, | |
| z: point.z | |
| }; | |
| } | |
| function project(point) { | |
| const scale = FOV / (point.z + CAMERA_DIST); | |
| return { | |
| x: point.x * scale + width / 2, | |
| y: point.y * scale + height / 2, | |
| z: point.z | |
| }; | |
| } | |
| // Compute normal vector for backface culling and lighting | |
| function getNormal(p0, p1, p2) { | |
| const v1 = { x: p1.x - p0.x, y: p1.y - p0.y, z: p1.z - p0.z }; | |
| const v2 = { x: p2.x - p0.x, y: p2.y - p0.y, z: p2.z - p0.z }; | |
| return { | |
| x: v1.y * v2.z - v1.z * v2.y, | |
| y: v1.z * v2.x - v1.x * v2.z, | |
| z: v1.x * v2.y - v1.y * v2.x | |
| }; | |
| } | |
| function normalize(v) { | |
| const len = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); | |
| return { x: v.x / len, y: v.y / len, z: v.z / len }; | |
| } | |
| function dot(v1, v2) { | |
| return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; | |
| } | |
| function draw() { | |
| // Update rotation angles | |
| angleX += 0.007; | |
| angleY += 0.011; | |
| angleZ += 0.005; | |
| // Clear canvas with deep space radial gradient background | |
| const bgGrad = ctx.createRadialGradient(width / 2, height / 2, 50, width / 2, height / 2, Math.max(width, height)); | |
| bgGrad.addColorStop(0, '#1a1829'); // Deep dark violet | |
| bgGrad.addColorStop(0.5, '#0b0a12'); // Dark space blue | |
| bgGrad.addColorStop(1, '#020204'); // Absolute black | |
| ctx.fillStyle = bgGrad; | |
| ctx.fillRect(0, 0, width, height); | |
| // Background stars/dust particle system | |
| ctx.fillStyle = 'rgba(122, 206, 215, 0.1)'; | |
| for (let i = 0; i < 30; i++) { | |
| const x = (Math.sin(i * 99 + angleX * 0.2) * 0.5 + 0.5) * width; | |
| const y = (Math.cos(i * 33 + angleY * 0.2) * 0.5 + 0.5) * height; | |
| ctx.beginPath(); | |
| ctx.arc(x, y, 1.5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| // Pre-process cube geometry | |
| const prepareCubeGeometry = (cube, angX, angY, angZ) => { | |
| const rotated = cube.vertices.map(v => { | |
| let p = rotateX(v, angX); | |
| p = rotateY(p, angY); | |
| p = rotateZ(p, angZ); | |
| return p; | |
| }); | |
| const projected = rotated.map(v => project(v)); | |
| const facesData = cube.faces.map(face => { | |
| const p = face.indices.map(idx => projected[idx]); | |
| const r = face.indices.map(idx => rotated[idx]); | |
| const avgZ = (r[0].z + r[1].z + r[2].z + r[3].z) / 4; | |
| const rawNormal = getNormal(r[0], r[1], r[2]); | |
| const normal = normalize(rawNormal); | |
| const center3D = { | |
| x: (r[0].x + r[1].x + r[2].x + r[3].x) / 4, | |
| y: (r[0].y + r[1].y + r[2].y + r[3].y) / 4, | |
| z: (r[0].z + r[1].z + r[2].z + r[3].z) / 4 | |
| }; | |
| const lightVec = normalize({ | |
| x: lightSource.x - center3D.x, | |
| y: lightSource.y - center3D.y, | |
| z: lightSource.z - center3D.z | |
| }); | |
| const viewVec = normalize({ | |
| x: center3D.x, | |
| y: center3D.y, | |
| z: center3D.z + CAMERA_DIST | |
| }); | |
| const diffuse = Math.max(0, dot(normal, lightVec)); | |
| const reflectVec = { | |
| x: 2 * dot(normal, lightVec) * normal.x - lightVec.x, | |
| y: 2 * dot(normal, lightVec) * normal.y - lightVec.y, | |
| z: 2 * dot(normal, lightVec) * normal.z - lightVec.z | |
| }; | |
| const specular = Math.pow(Math.max(0, dot(reflectVec, viewVec)), 8); | |
| const isFront = dot(normal, viewVec) < 0; | |
| return { | |
| points: p, | |
| avgZ, | |
| diffuse, | |
| specular, | |
| isFront, | |
| name: face.name | |
| }; | |
| }); | |
| return facesData; | |
| }; | |
| // Outer cube (glass shell) and Inner cube (glowing magenta core) | |
| const outerFaces = prepareCubeGeometry(outerCube, angleX, angleY, angleZ); | |
| const innerFaces = prepareCubeGeometry(innerCube, -angleX * 1.2, -angleY * 0.8, angleZ * 1.5); | |
| const allRenderableFaces = []; | |
| outerFaces.forEach(f => allRenderableFaces.push({ ...f, isOuter: true })); | |
| innerFaces.forEach(f => allRenderableFaces.push({ ...f, isOuter: false })); | |
| // Global depth sorting (Painters Algorithm) | |
| allRenderableFaces.sort((a, b) => b.avgZ - a.avgZ); | |
| // Render faces from back to front | |
| allRenderableFaces.forEach(face => { | |
| const pts = face.points; | |
| ctx.beginPath(); | |
| ctx.moveTo(pts[0].x, pts[0].y); | |
| for (let i = 1; i < pts.length; i++) { | |
| ctx.lineTo(pts[i].x, pts[i].y); | |
| } | |
| ctx.closePath(); | |
| if (face.isOuter) { | |
| // --- OUTER GLASS SHELL --- | |
| if (!face.isFront) { | |
| // Back faces of the outer glass cube (subtle lines and low opacity fill) | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.02)'; | |
| ctx.fill(); | |
| ctx.strokeStyle = 'rgba(122, 206, 215, 0.15)'; | |
| ctx.lineWidth = 1; | |
| ctx.stroke(); | |
| } else { | |
| // Front faces of the outer glass cube (specular highlights, gloss gradient) | |
| const grad = ctx.createLinearGradient(pts[0].x, pts[0].y, pts[2].x, pts[2].y); | |
| const opacity = 0.08 + face.specular * 0.15; | |
| const colorCyan = `rgba(122, 206, 215, ${opacity})`; | |
| const colorMagenta = `rgba(235, 94, 155, ${opacity * 0.5})`; | |
| const specColor = `rgba(255, 255, 255, ${0.1 + face.specular * 0.4})`; | |
| grad.addColorStop(0, specColor); | |
| grad.addColorStop(0.3, colorCyan); | |
| grad.addColorStop(1, colorMagenta); | |
| ctx.fillStyle = grad; | |
| ctx.fill(); | |
| // Neon glossy borders | |
| ctx.strokeStyle = `rgba(255, 255, 255, ${0.5 + face.diffuse * 0.3})`; | |
| ctx.lineWidth = 2.0; | |
| ctx.shadowColor = '#7aced7'; | |
| ctx.shadowBlur = 12; | |
| ctx.stroke(); | |
| ctx.shadowBlur = 0; // Reset blur immediately | |
| // Inner bezel highlight (creates thickness illusion) | |
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; | |
| ctx.lineWidth = 1; | |
| ctx.stroke(); | |
| } | |
| } else { | |
| // --- INNER NEON CORE --- | |
| if (!face.isFront) { | |
| // Back faces of the inner core | |
| ctx.fillStyle = 'rgba(235, 94, 155, 0.04)'; | |
| ctx.fill(); | |
| ctx.strokeStyle = 'rgba(235, 94, 155, 0.15)'; | |
| ctx.lineWidth = 0.5; | |
| ctx.stroke(); | |
| } else { | |
| // Front faces of the inner core (bright glowing pink) | |
| const grad = ctx.createLinearGradient(pts[1].x, pts[1].y, pts[3].x, pts[3].y); | |
| grad.addColorStop(0, 'rgba(235, 94, 155, 0.25)'); // Glowing hot pink | |
| grad.addColorStop(1, 'rgba(122, 206, 215, 0.05)'); | |
| ctx.fillStyle = grad; | |
| ctx.fill(); | |
| ctx.strokeStyle = `rgba(235, 94, 155, ${0.7 + face.diffuse * 0.3})`; | |
| ctx.lineWidth = 1.5; | |
| ctx.shadowColor = '#eb5e9b'; | |
| ctx.shadowBlur = 8; | |
| ctx.stroke(); | |
| ctx.shadowBlur = 0; | |
| } | |
| } | |
| }); | |
| // Draw HUD data overlay | |
| ctx.font = '11px monospace'; | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'; | |
| ctx.fillText('CORE: ACTIVE [ROTATION3D]', 20, 30); | |
| ctx.fillStyle = '#7aced7'; | |
| ctx.fillText('GLASSMORPHISM SHADER: ON', 20, 45); | |
| ctx.fillStyle = '#eb5e9b'; | |
| ctx.fillText(`CAM DIST: ${CAMERA_DIST}px`, 20, 60); | |
| requestAnimationFrame(draw); | |
| } | |
| // Start rendering | |
| draw(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment