VOXEL — 3D Artist Portfolio :root { –black: #050507; –white: #f0ede8; –acid: #c8ff00; –crimson: #ff2d55; –electric: #00d4ff; –muted: #2a2a35; –glass: rgba(255,255,255,0.04); } *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } html { scroll-behavior: smooth; } body { background: var(–black); color: var(–white); font-family: ‘Space Mono’, monospace; overflow-x: hidden; cursor: none; } /* CUSTOM CURSOR */ #cursor { position: fixed; width: 12px; height: 12px; background: var(–acid); border-radius: 50%; pointer-events: none; z-index: 9999; transform: translate(-50%, -50%); transition: width 0.2s, height 0.2s, background 0.2s; mix-blend-mode: difference; } #cursor-ring { position: fixed; width: 40px; height: 40px; border: 1px solid rgba(200,255,0,0.4); border-radius: 50%; pointer-events: none; z-index: 9998; transform: translate(-50%, -50%); transition: transform 0.1s linear, width 0.3s, height 0.3s, border-color 0.3s; } body:hover #cursor { opacity: 1; } /* NOISE OVERLAY */ body::before { content: ”; position: fixed; inset: 0; background-image: url(“data:image/svg+xml,%3Csvg viewBox=’0 0 256 256′ xmlns=’http://www.w3.org/2000/svg’%3E%3Cfilter id=’noise’%3E%3CfeTurbulence type=’fractalNoise’ baseFrequency=’0.9′ numOctaves=’4′ stitchTiles=’stitch’/%3E%3C/filter%3E%3Crect width=’100%25′ height=’100%25′ filter=’url(%23noise)’ opacity=’0.04’/%3E%3C/svg%3E”); pointer-events: none; z-index: 9990; opacity: 0.35; } /* NAV */ nav { position: fixed; top: 0; left: 0; right: 0; z-index: 100; display: flex; justify-content: space-between; align-items: center; padding: 28px 48px; mix-blend-mode: normal; } .nav-logo { font-family: ‘Bebas Neue’, sans-serif; font-size: 2rem; letter-spacing: 0.15em; color: var(–white); text-decoration: none; } .nav-logo span { color: var(–acid); } .nav-links { display: flex; gap: 40px; list-style: none; } .nav-links a { font-size: 0.72rem; letter-spacing: 0.2em; text-transform: uppercase; color: rgba(240,237,232,0.55); text-decoration: none; transition: color 0.25s; position: relative; } .nav-links a::after { content: ”; position: absolute; bottom: -3px; left: 0; width: 0; height: 1px; background: var(–acid); transition: width 0.3s; } .nav-links a:hover { color: var(–white); } .nav-links a:hover::after { width: 100%; } .nav-cta { font-size: 0.72rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(–black); background: var(–acid); padding: 10px 22px; text-decoration: none; font-family: ‘Space Mono’, monospace; transition: transform 0.2s, box-shadow 0.2s; } .nav-cta:hover { transform: translate(-2px,-2px); box-shadow: 4px 4px 0 var(–crimson); } /* HERO */ #hero { position: relative; height: 100vh; display: flex; align-items: center; justify-content: center; overflow: hidden; } #canvas-container { position: absolute; inset: 0; z-index: 1; } canvas { display: block; } .hero-content { position: relative; z-index: 10; text-align: center; pointer-events: none; } .hero-tag { font-size: 0.68rem; letter-spacing: 0.35em; text-transform: uppercase; color: var(–acid); margin-bottom: 20px; opacity: 0; animation: fadeUp 1s 0.3s forwards; } .hero-title { font-family: ‘Bebas Neue’, sans-serif; font-size: clamp(5rem, 14vw, 13rem); line-height: 0.9; letter-spacing: 0.02em; color: transparent; -webkit-text-stroke: 1px rgba(240,237,232,0.15); mix-blend-mode: screen; opacity: 0; animation: fadeUp 1s 0.5s forwards; pointer-events: none; user-select: none; } .hero-sub { font-size: 0.72rem; letter-spacing: 0.25em; text-transform: uppercase; color: rgba(240,237,232,0.45); margin-top: 28px; opacity: 0; animation: fadeUp 1s 0.7s forwards; } .scroll-hint { position: absolute; bottom: 40px; left: 50%; transform: translateX(-50%); z-index: 10; display: flex; flex-direction: column; align-items: center; gap: 8px; opacity: 0; animation: fadeIn 1s 1.5s forwards; } .scroll-hint span { font-size: 0.6rem; letter-spacing: 0.3em; text-transform: uppercase; color: rgba(240,237,232,0.35); } .scroll-line { width: 1px; height: 50px; background: linear-gradient(to bottom, var(–acid), transparent); animation: scrollPulse 2s infinite; } /* STATS BAR */ .stats-bar { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid rgba(255,255,255,0.06); border-bottom: 1px solid rgba(255,255,255,0.06); } .stat-item { padding: 36px 48px; border-right: 1px solid rgba(255,255,255,0.06); position: relative; overflow: hidden; } .stat-item:last-child { border-right: none; } .stat-item::before { content: ”; position: absolute; inset: 0; background: var(–glass); opacity: 0; transition: opacity 0.3s; } .stat-item:hover::before { opacity: 1; } .stat-num { font-family: ‘Bebas Neue’, sans-serif; font-size: 3.5rem; color: var(–acid); line-height: 1; display: block; } .stat-label { font-size: 0.62rem; letter-spacing: 0.25em; text-transform: uppercase; color: rgba(240,237,232,0.4); margin-top: 6px; display: block; } /* SECTION COMMONS */ section { padding: 120px 48px; } .section-tag { font-size: 0.62rem; letter-spacing: 0.35em; text-transform: uppercase; color: var(–acid); margin-bottom: 16px; display: flex; align-items: center; gap: 12px; } .section-tag::before { content: ”; width: 30px; height: 1px; background: var(–acid); } .section-title { font-family: ‘Syne’, sans-serif; font-size: clamp(2.5rem, 5vw, 4.5rem); font-weight: 800; line-height: 1.05; max-width: 700px; } /* ABOUT */ #about { display: grid; grid-template-columns: 1fr 1fr; gap: 80px; align-items: center; max-width: 1400px; margin: 0 auto; padding: 120px 48px; } .about-text p { font-size: 0.9rem; line-height: 1.9; color: rgba(240,237,232,0.6); margin-top: 32px; max-width: 500px; } .about-text p + p { margin-top: 16px; } .about-visual { position: relative; height: 500px; } .about-canvas { width: 100%; height: 100%; border: 1px solid rgba(255,255,255,0.06); } .about-badge { position: absolute; bottom: -24px; right: -24px; width: 120px; height: 120px; border: 1px solid rgba(255,255,255,0.1); border-radius: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center; background: var(–black); font-size: 0.55rem; letter-spacing: 0.2em; text-transform: uppercase; color: rgba(240,237,232,0.5); text-align: center; animation: spin 20s linear infinite; } .about-badge strong { font-family: ‘Bebas Neue’, sans-serif; font-size: 1.8rem; color: var(–acid); display: block; line-height: 1; animation: spin 20s linear infinite reverse; } /* WORK GRID */ #work { max-width: 1400px; margin: 0 auto; } .work-header { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 60px; } .work-grid { display: grid; grid-template-columns: repeat(12, 1fr); grid-template-rows: auto; gap: 20px; } .work-card { position: relative; overflow: hidden; background: var(–muted); cursor: none; group: true; } .work-card:nth-child(1) { grid-column: span 7; grid-row: span 1; } .work-card:nth-child(2) { grid-column: span 5; grid-row: span 1; } .work-card:nth-child(3) { grid-column: span 4; } .work-card:nth-child(4) { grid-column: span 4; } .work-card:nth-child(5) { grid-column: span 4; } .work-card-inner { width: 100%; padding-top: 70%; position: relative; overflow: hidden; } .work-card:nth-child(1) .work-card-inner { padding-top: 55%; } .work-card:nth-child(2) .work-card-inner { padding-top: 75%; } .work-card-canvas { position: absolute; inset: 0; width: 100%; height: 100%; } .work-card-overlay { position: absolute; inset: 0; background: linear-gradient(to top, rgba(5,5,7,0.9) 0%, transparent 60%); opacity: 0; transition: opacity 0.4s; display: flex; flex-direction: column; justify-content: flex-end; padding: 24px; } .work-card:hover .work-card-overlay { opacity: 1; } .work-card-num { font-size: 0.6rem; letter-spacing: 0.3em; color: var(–acid); margin-bottom: 6px; } .work-card-title { font-family: ‘Syne’, sans-serif; font-size: 1.3rem; font-weight: 800; } .work-card-type { font-size: 0.62rem; letter-spacing: 0.2em; color: rgba(240,237,232,0.5); margin-top: 4px; } /* SERVICES */ #services { max-width: 1400px; margin: 0 auto; } .services-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: rgba(255,255,255,0.06); margin-top: 60px; } .service-card { background: var(–black); padding: 48px 40px; position: relative; overflow: hidden; transition: background 0.3s; } .service-card:hover { background: var(–muted); } .service-num { font-family: ‘Bebas Neue’, sans-serif; font-size: 5rem; color: rgba(255,255,255,0.04); line-height: 1; position: absolute; top: 20px; right: 24px; } .service-icon { width: 44px; height: 44px; border: 1px solid rgba(200,255,0,0.3); display: flex; align-items: center; justify-content: center; margin-bottom: 24px; color: var(–acid); font-size: 1.2rem; transition: background 0.3s, border-color 0.3s; } .service-card:hover .service-icon { background: var(–acid); color: var(–black); border-color: var(–acid); } .service-name { font-family: ‘Syne’, sans-serif; font-size: 1.3rem; font-weight: 700; margin-bottom: 12px; } .service-desc { font-size: 0.78rem; line-height: 1.8; color: rgba(240,237,232,0.45); } /* MARQUEE */ .marquee-section { padding: 40px 0; border-top: 1px solid rgba(255,255,255,0.06); border-bottom: 1px solid rgba(255,255,255,0.06); overflow: hidden; } .marquee-track { display: flex; gap: 60px; animation: marquee 20s linear infinite; white-space: nowrap; } .marquee-item { font-family: ‘Bebas Neue’, sans-serif; font-size: 1.8rem; letter-spacing: 0.1em; color: rgba(240,237,232,0.12); display: flex; align-items: center; gap: 60px; } .marquee-item span { width: 6px; height: 6px; background: var(–acid); border-radius: 50%; display: inline-block; } /* CONTACT */ #contact { max-width: 1400px; margin: 0 auto; text-align: center; padding: 120px 48px 160px; } .contact-title { font-family: ‘Bebas Neue’, sans-serif; font-size: clamp(4rem, 10vw, 9rem); letter-spacing: 0.02em; line-height: 1; margin: 40px 0; background: linear-gradient(135deg, var(–white) 40%, rgba(240,237,232,0.15)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .contact-email { font-size: 0.8rem; letter-spacing: 0.3em; text-transform: uppercase; color: rgba(240,237,232,0.4); text-decoration: none; display: block; margin-bottom: 48px; transition: color 0.3s; } .contact-email:hover { color: var(–acid); } .contact-btn { display: inline-flex; align-items: center; gap: 16px; font-family: ‘Space Mono’, monospace; font-size: 0.75rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(–black); background: var(–acid); padding: 18px 40px; text-decoration: none; transition: transform 0.2s, box-shadow 0.2s; } .contact-btn:hover { transform: translate(-3px,-3px); box-shadow: 6px 6px 0 var(–crimson); } .contact-btn svg { transition: transform 0.3s; } .contact-btn:hover svg { transform: translateX(4px); } /* FOOTER */ footer { border-top: 1px solid rgba(255,255,255,0.06); padding: 32px 48px; display: flex; justify-content: space-between; align-items: center; } .footer-copy { font-size: 0.62rem; letter-spacing: 0.15em; color: rgba(240,237,232,0.25); } .footer-links { display: flex; gap: 32px; } .footer-links a { font-size: 0.62rem; letter-spacing: 0.15em; color: rgba(240,237,232,0.25); text-decoration: none; text-transform: uppercase; transition: color 0.2s; } .footer-links a:hover { color: var(–acid); } /* ANIMATIONS */ @keyframes fadeUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeIn { to { opacity: 1; } } @keyframes scrollPulse { 0%, 100% { opacity: 1; transform: scaleY(1); } 50% { opacity: 0.3; transform: scaleY(0.6); } } @keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } } @keyframes spin { to { transform: rotate(360deg); } } /* REVEAL */ .reveal { opacity: 0; transform: translateY(40px); transition: opacity 0.8s ease, transform 0.8s ease; } .reveal.visible { opacity: 1; transform: translateY(0); } .reveal-delay-1 { transition-delay: 0.1s; } .reveal-delay-2 { transition-delay: 0.2s; } .reveal-delay-3 { transition-delay: 0.3s; }
Hire Me

✦ Creative 3D Artist & Motion Designer

FORM
FLUX

Drag to explore the model

Scroll
12+Years Experience
340Projects Completed
80+Global Clients
22Awards Won

Sculpting Digital Realities Since 2012

I’m a multidisciplinary 3D artist pushing the boundaries between concept and execution. My work lives at the intersection of sculpture, motion, and code — crafting experiences that feel both alien and inevitable.

From hyper-realistic product visualization to surreal abstract worlds, every piece I make is an exploration of form, light, and the poetry hidden in geometry.

12+ years in
the field
3D Modeling Motion Design Product Viz Character Art Concept Art VFX Generative Art World Building
3D Modeling Motion Design Product Viz Character Art Concept Art VFX Generative Art World Building

Featured
Projects

View All →
01 / 2024
Obsidian Throne
Character Design · Sculpture
02 / 2024
Liquid Chrome
Abstract · Motion
03 / 2023
Void Matter
Environment · VFX
04 / 2023
Neon Drift
Product Viz · Light
05 / 2023
Crystal Mind
Concept · Surreal

Services &
Expertise

01
⬡
3D Modeling

From organic characters to hard-surface machines, I create precise, production-ready 3D assets for any medium or scale.

02
◈
Motion & Animation

Fluid, physics-driven animation that breathes life into static models — for film, games, and interactive experiences.

03
◉
Product Visualization

Photorealistic renders that make your product irresistible, with lighting setups and compositions built for impact.

04
⬠
Concept Art

Translating your ideas into compelling visual concepts — world-building, character design, or abstract exploration.

05
⟁
VFX & Simulations

Fluid dynamics, particle systems, and destruction simulations at film quality — crafted with Houdini and Blender.

06
⬧
Interactive 3D/Web

Real-time 3D experiences using Three.js and WebGL — from interactive product viewers to full immersive websites.

Got a project
in mind?

hello@voxel.studio Start a Project
https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js // ===================== // CURSOR // ===================== const cursor = document.getElementById(‘cursor’); const ring = document.getElementById(‘cursor-ring’); let mx = 0, my = 0, rx = 0, ry = 0; document.addEventListener(‘mousemove’, e => { mx = e.clientX; my = e.clientY; }); function animateCursor() { cursor.style.left = mx + ‘px’; cursor.style.top = my + ‘px’; rx += (mx – rx) * 0.12; ry += (my – ry) *.12; ring.style.left = rx + ‘px’; ring.style.top = ry + ‘px’; requestAnimationFrame(animateCursor); } animateCursor(); document.querySelectorAll(‘a, button, .work-card’).forEach(el => { el.addEventListener(‘mouseenter’, () => { cursor.style.width = ‘6px’; cursor.style.height = ‘6px’; ring.style.width = ’60px’; ring.style.height = ’60px’; ring.style.borderColor = ‘rgba(200,255,0,0.8)’; }); el.addEventListener(‘mouseleave’, () => { cursor.style.width = ’12px’; cursor.style.height = ’12px’; ring.style.width = ’40px’; ring.style.height = ’40px’; ring.style.borderColor = ‘rgba(200,255,0,0.4)’; }); }); // ===================== // HERO SCENE // ===================== (function() { const container = document.getElementById(‘canvas-container’); const w = container.clientWidth, h = container.clientHeight; const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(w, h); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; container.appendChild(renderer.domElement); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 100); camera.position.set(0, 0, 5.5); // LIGHTING const ambientLight = new THREE.AmbientLight(0x111122, 1.5); scene.add(ambientLight); const light1 = new THREE.PointLight(0xc8ff00, 80, 30); light1.position.set(3, 3, 3); scene.add(light1); const light2 = new THREE.PointLight(0x00d4ff, 60, 30); light2.position.set(-3, -2, 2); scene.add(light2); const light3 = new THREE.PointLight(0xff2d55, 40, 30); light3.position.set(0, -4, -2); scene.add(light3); // MAIN SCULPTURE — layered torus knots forming a complex shape const group = new THREE.Group(); scene.add(group); // Core knot const knotGeo = new THREE.TorusKnotGeometry(1.2, 0.38, 200, 24, 2, 3); const knotMat = new THREE.MeshPhysicalMaterial({ color: 0x050507, metalness: 1.0, roughness: 0.05, envMapIntensity: 1.2, clearcoat: 1.0, clearcoatRoughness: 0.05, reflectivity: 1.0, }); const knot = new THREE.Mesh(knotGeo, knotMat); group.add(knot); // Wire overlay const wireGeo = new THREE.TorusKnotGeometry(1.25, 0.39, 80, 8, 2, 3); const wireMat = new THREE.MeshBasicMaterial({ color: 0xc8ff00, wireframe: true, transparent: true, opacity: 0.08, }); const wire = new THREE.Mesh(wireGeo, wireMat); group.add(wire); // Outer ring const ringGeo = new THREE.TorusGeometry(2.2, 0.012, 8, 200); const ringMat = new THREE.MeshBasicMaterial({ color: 0xc8ff00, transparent: true, opacity: 0.35 }); const ring3d = new THREE.Mesh(ringGeo, ringMat); ring3d.rotation.x = Math.PI * 0.45; group.add(ring3d); const ring3dB = new THREE.Mesh( new THREE.TorusGeometry(2.6, 0.008, 8, 200), new THREE.MeshBasicMaterial({ color: 0x00d4ff, transparent: true, opacity: 0.2 }) ); ring3dB.rotation.x = Math.PI * 0.3; ring3dB.rotation.z = Math.PI * 0.2; group.add(ring3dB); // Floating particles const particleCount = 400; const pPositions = new Float32Array(particleCount * 3); const pSpeeds = []; for (let i = 0; i { isDragging = true; autoRot = false; prevMouse = { x: e.clientX, y: e.clientY }; }); renderer.domElement.addEventListener(‘touchstart’, e => { isDragging = true; autoRot = false; prevMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY }; }); window.addEventListener(‘mouseup’, () => { isDragging = false; setTimeout(() => autoRot = true, 2000); }); window.addEventListener(‘touchend’, () => { isDragging = false; setTimeout(() => autoRot = true, 2000); }); window.addEventListener(‘mousemove’, e => { if (!isDragging) return; const dx = e.clientX – prevMouse.x; const dy = e.clientY – prevMouse.y; targetRot.y += dx * 0.008; targetRot.x += dy * 0.008; prevMouse = { x: e.clientX, y: e.clientY }; }); window.addEventListener(‘touchmove’, e => { if (!isDragging) return; const dx = e.touches[0].clientX – prevMouse.x; const dy = e.touches[0].clientY – prevMouse.y; targetRot.y += dx * 0.008; targetRot.x += dy * 0.008; prevMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY }; }); // Parallax on mousemove let mouseWorld = { x: 0, y: 0 }; document.addEventListener(‘mousemove’, e => { mouseWorld.x = (e.clientX / window.innerWidth – 0.5) * 2; mouseWorld.y = -(e.clientY / window.innerHeight – 0.5) * 2; }); let t = 0; function animate() { requestAnimationFrame(animate); t += 0.005; if (autoRot) { targetRot.y += 0.004; targetRot.x = Math.sin(t * 0.5) * 0.2; } currentRot.x += (targetRot.x – currentRot.x) * 0.05; currentRot.y += (targetRot.y – currentRot.y) * 0.05; group.rotation.x = currentRot.x; group.rotation.y = currentRot.y; wire.rotation.y = -t * 0.3; // Light pulse light1.intensity = 60 + Math.sin(t * 1.5) * 20; light2.intensity = 50 + Math.cos(t * 1.2) * 15; // Camera parallax camera.position.x += (mouseWorld.x * 0.3 – camera.position.x) * 0.04; camera.position.y += (mouseWorld.y * 0.2 – camera.position.y) * 0.04; camera.lookAt(0, 0, 0); // Animate particles const pos = pGeo.attributes.position.array; for (let i = 0; i { const nw = container.clientWidth, nh = container.clientHeight; camera.aspect = nw / nh; camera.updateProjectionMatrix(); renderer.setSize(nw, nh); }); })(); // ===================== // ABOUT MINI SCENE // ===================== (function() { const canvas = document.getElementById(‘about-canvas’); const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(60, canvas.clientWidth / canvas.clientHeight, 0.1, 50); camera.position.set(0, 0, 4); const light1 = new THREE.PointLight(0xc8ff00, 50, 20); light1.position.set(2, 2, 2); scene.add(light1); const light2 = new THREE.PointLight(0x00d4ff, 30, 20); light2.position.set(-2, -2, 1); scene.add(light2); scene.add(new THREE.AmbientLight(0x112233, 2)); const geo = new THREE.IcosahedronGeometry(1.4, 2); const mat = new THREE.MeshPhysicalMaterial({ color: 0x050507, metalness: 0.9, roughness: 0.1, clearcoat: 1, }); const mesh = new THREE.Mesh(geo, mat); scene.add(mesh); const wireGeo = new THREE.IcosahedronGeometry(1.45, 2); const wireMat = new THREE.MeshBasicMaterial({ color: 0xc8ff00, wireframe: true, transparent: true, opacity: 0.15 }); scene.add(new THREE.Mesh(wireGeo, wireMat)); function resize() { renderer.setSize(canvas.clientWidth, canvas.clientHeight, false); camera.aspect = canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); } resize(); window.addEventListener(‘resize’, resize); let t = 0; function animate() { requestAnimationFrame(animate); t += 0.008; mesh.rotation.y = t; mesh.rotation.x = Math.sin(t * 0.5) * 0.4; renderer.render(scene, camera); } animate(); })(); // ===================== // WORK CARD MINI SCENES // ===================== const cardConfigs = [ { id: ‘c1’, geo: () => new THREE.TorusKnotGeometry(0.9, 0.28, 120, 16, 3, 5), color: 0xff2d55, accent: 0xc8ff00 }, { id: ‘c2’, geo: () => new THREE.SphereGeometry(1, 64, 64), color: 0x00d4ff, accent: 0xffffff }, { id: ‘c3’, geo: () => new THREE.OctahedronGeometry(1.2, 2), color: 0xc8ff00, accent: 0xff2d55 }, { id: ‘c4’, geo: () => new THREE.TorusGeometry(1, 0.35, 32, 100), color: 0x8844ff, accent: 0x00d4ff }, { id: ‘c5’, geo: () => new THREE.IcosahedronGeometry(1.2, 1), color: 0x00ffa3, accent: 0xc8ff00 }, ]; cardConfigs.forEach(cfg => { const canvas = document.getElementById(cfg.id); if (!canvas) return; const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(55, 1, 0.1, 50); camera.position.z = 3.5; const l1 = new THREE.PointLight(cfg.color, 60, 20); l1.position.set(2, 2, 2); scene.add(l1); const l2 = new THREE.PointLight(cfg.accent, 40, 20); l2.position.set(-2, -1, 1); scene.add(l2); scene.add(new THREE.AmbientLight(0x111111, 2)); const mat = new THREE.MeshPhysicalMaterial({ color: 0x080810, metalness: 0.95, roughness: 0.05, clearcoat: 1, }); const mesh = new THREE.Mesh(cfg.geo(), mat); scene.add(mesh); const wireMesh = new THREE.Mesh(cfg.geo(), new THREE.MeshBasicMaterial({ color: cfg.color, wireframe: true, transparent: true, opacity: 0.1 })); scene.add(wireMesh); function resize() { renderer.setSize(canvas.clientWidth, canvas.clientHeight, false); camera.aspect = canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); } resize(); window.addEventListener(‘resize’, resize); let t = Math.random() * 100; const speed = 0.006 + Math.random() * 0.006; function animate() { requestAnimationFrame(animate); t += speed; mesh.rotation.y = t; mesh.rotation.x = Math.sin(t * 0.7) * 0.4; wireMesh.rotation.y = -t * 0.5; l1.intensity = 50 + Math.sin(t * 2) * 15; renderer.render(scene, camera); } animate(); }); // ===================== // SCROLL REVEAL // ===================== const observer = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add(‘visible’); observer.unobserve(e.target); } }); }, { threshold: 0.15 }); document.querySelectorAll(‘.reveal’).forEach(el => observer.observe(el));
Create your own website, free on WordPress.com.Create your own website.
Start free