Custom Code Experiment •
Nazar Boncuğu 4: 3D
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>Nazar Boncuğu 4: 3D</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #000; overflow: hidden; width: 100vw; height: 100vh; }
canvas { display: block; }
#overlay {
position: fixed; inset: 0; pointer-events: none;
background: radial-gradient(ellipse at 50% 50%, transparent 30%, rgba(0,0,0,0.72) 100%);
}
</style>
</head>
<body>
<div id="overlay"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
(function () {
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.1;
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x04010d);
scene.fog = new THREE.FogExp2(0x04010d, 0.045);
const camera = new THREE.PerspectiveCamera(42, window.innerWidth / window.innerHeight, 0.1, 200);
camera.position.set(0, 0, 7);
let isDragging = false, prevMouse = { x: 0, y: 0 };
let rotY = 0, rotX = 0.18;
let autoRotate = true;
const pivot = new THREE.Group();
scene.add(pivot);
renderer.domElement.addEventListener('mousedown', e => { isDragging = true; autoRotate = false; prevMouse = { x: e.clientX, y: e.clientY }; });
renderer.domElement.addEventListener('touchstart', e => { isDragging = true; autoRotate = false; prevMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY }; });
window.addEventListener('mouseup', () => { isDragging = false; });
window.addEventListener('touchend', () => { isDragging = false; });
window.addEventListener('mousemove', e => {
if (!isDragging) return;
rotY += (e.clientX - prevMouse.x) * 0.008;
rotX += (e.clientY - prevMouse.y) * 0.008;
rotX = Math.max(-1.1, Math.min(1.1, rotX));
prevMouse = { x: e.clientX, y: e.clientY };
});
window.addEventListener('touchmove', e => {
if (!isDragging) return;
rotY += (e.touches[0].clientX - prevMouse.x) * 0.008;
rotX += (e.touches[0].clientY - prevMouse.y) * 0.008;
prevMouse = { x: e.touches[0].clientX, y: e.touches[0].clientY };
});
const pmrem = new THREE.PMREMGenerator(renderer);
pmrem.compileCubemapShader();
(function buildEnv() {
const envS = new THREE.Scene();
envS.background = new THREE.Color(0x08041a);
const eColors = [0xffd080, 0x3060ff, 0xff4020, 0x20d0a0, 0xc040ff, 0xffffff];
const ePos = [[5,5,5],[-5,5,-5],[5,-5,-5],[-5,-5,5],[0,8,0],[0,-8,0]];
eColors.forEach((c, i) => {
const l = new THREE.PointLight(c, 6, 20);
l.position.set(...ePos[i]);
envS.add(l);
});
const cubeRT = new THREE.WebGLCubeRenderTarget(256);
const cubeCamera = new THREE.CubeCamera(0.1, 50, cubeRT);
envS.add(cubeCamera);
cubeCamera.update(renderer, envS);
scene.environment = pmrem.fromCubemap(cubeRT.texture).texture;
})();
const lerp = (a, b, t) => a + (b - a) * t;
function makeRing(innerR, outerR, color, roughness, metalness, emissive = 0x000000, emissiveIntensity = 0) {
const geo = new THREE.RingGeometry(innerR, outerR, 128, 4);
const points = [
new THREE.Vector2(innerR, -0.045),
new THREE.Vector2(innerR, 0.045),
new THREE.Vector2(outerR, 0.065),
new THREE.Vector2(outerR, -0.065),
];
const shape = new THREE.Shape();
const tGeo = new THREE.TorusGeometry((innerR + outerR) / 2, (outerR - innerR) / 2, 32, 160);
const mat = new THREE.MeshStandardMaterial({ color, roughness, metalness, envMapIntensity: 1.2 });
if (emissive) { mat.emissive = new THREE.Color(emissive); mat.emissiveIntensity = emissiveIntensity; }
const mesh = new THREE.Mesh(tGeo, mat);
mesh.castShadow = true;
mesh.receiveShadow = true;
return mesh;
}
const amulet = new THREE.Group();
pivot.add(amulet);
const discGeo1 = new THREE.CylinderGeometry(2.0, 2.0, 0.38, 128, 1, false);
const discMat1 = new THREE.MeshPhysicalMaterial({
color: 0x0a2070,
roughness: 0.05,
metalness: 0.0,
transmission: 0.55,
ior: 1.52,
envMapIntensity: 2.0,
clearcoat: 1.0,
clearcoatRoughness: 0.08,
});
const disc1 = new THREE.Mesh(discGeo1, discMat1);
disc1.rotation.x = Math.PI / 2;
disc1.position.z = -0.18;
disc1.castShadow = true;
amulet.add(disc1);
const bevel1 = makeRing(1.94, 2.04, 0x0e2a90, 0.1, 0.3);
bevel1.position.z = -0.18;
amulet.add(bevel1);
const goldRing = makeRing(1.62, 1.95, 0xc8901a, 0.18, 0.95);
goldRing.position.z = 0.01;
amulet.add(goldRing);
for (let i = 0; i < 6; i++) {
const r = 1.64 + i * 0.05;
const fr = makeRing(r, r + 0.012, 0xffd060, 0.22, 0.9);
fr.position.z = 0.04;
amulet.add(fr);
}
const discGeo2 = new THREE.CylinderGeometry(1.62, 1.62, 0.28, 128);
const discMat2 = new THREE.MeshPhysicalMaterial({ color: 0xe8dcc4, roughness: 0.3, metalness: 0.0, clearcoat: 0.6, clearcoatRoughness: 0.15 });
const disc2 = new THREE.Mesh(discGeo2, discMat2);
disc2.rotation.x = Math.PI / 2;
disc2.position.z = 0.06;
amulet.add(disc2);
const discGeo3 = new THREE.CylinderGeometry(1.28, 1.28, 0.32, 128);
const discMat3 = new THREE.MeshPhysicalMaterial({ color: 0x1240b8, roughness: 0.06, metalness: 0.0, transmission: 0.4, ior: 1.5, clearcoat: 1.0, clearcoatRoughness: 0.05 });
const disc3 = new THREE.Mesh(discGeo3, discMat3);
disc3.rotation.x = Math.PI / 2;
disc3.position.z = 0.12;
amulet.add(disc3);
const discGeo4 = new THREE.CylinderGeometry(0.92, 0.92, 0.28, 128);
const discMat4 = new THREE.MeshPhysicalMaterial({ color: 0xf0eada, roughness: 0.25, metalness: 0.0, clearcoat: 0.7, clearcoatRoughness: 0.1 });
const disc4 = new THREE.Mesh(discGeo4, discMat4);
disc4.rotation.x = Math.PI / 2;
disc4.position.z = 0.18;
amulet.add(disc4);
const discGeo5 = new THREE.CylinderGeometry(0.64, 0.64, 0.34, 128);
const discMat5 = new THREE.MeshPhysicalMaterial({ color: 0x0a2090, roughness: 0.04, metalness: 0.0, transmission: 0.5, ior: 1.55, clearcoat: 1.0, clearcoatRoughness: 0.04 });
const disc5 = new THREE.Mesh(discGeo5, discMat5);
disc5.rotation.x = Math.PI / 2;
disc5.position.z = 0.22;
amulet.add(disc5);
const pupilGeo = new THREE.SphereGeometry(0.44, 64, 64, 0, Math.PI * 2, 0, Math.PI * 0.55);
const pupilMat = new THREE.MeshPhysicalMaterial({
color: 0x03020a,
roughness: 0.0,
metalness: 0.0,
transmission: 0.18,
ior: 1.7,
clearcoat: 1.0,
clearcoatRoughness: 0.0,
envMapIntensity: 3.0,
});
const pupil = new THREE.Mesh(pupilGeo, pupilMat);
pupil.rotation.x = -Math.PI / 2;
pupil.position.z = 0.28;
amulet.add(pupil);
const specGeo = new THREE.SphereGeometry(0.09, 32, 32);
const specMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.7 });
const spec = new THREE.Mesh(specGeo, specMat);
spec.position.set(-0.14, 0.14, 0.68);
amulet.add(spec);
const spec2 = new THREE.Mesh(new THREE.SphereGeometry(0.04, 16, 16), new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.35 }));
spec2.position.set(0.18, -0.15, 0.64);
amulet.add(spec2);
const rimRing = makeRing(1.98, 2.08, 0xd4a020, 0.12, 0.98);
rimRing.position.z = 0.0;
amulet.add(rimRing);
const beadGroup = new THREE.Group();
amulet.add(beadGroup);
const beadCount = 32;
const beadOrbitR = 2.22;
for (let i = 0; i < beadCount; i++) {
const angle = (i / beadCount) * Math.PI * 2;
const big = i % 3 === 0;
const r = big ? 0.075 : 0.045;
const bGeo = new THREE.SphereGeometry(r, 16, 16);
const bMat = new THREE.MeshStandardMaterial({
color: big ? 0xd4a020 : 0xe8dcc4,
roughness: big ? 0.15 : 0.3,
metalness: big ? 0.9 : 0.1,
});
const bead = new THREE.Mesh(bGeo, bMat);
bead.position.set(
Math.cos(angle) * beadOrbitR,
Math.sin(angle) * beadOrbitR,
0.1
);
beadGroup.add(bead);
}
const spikeGroup = new THREE.Group();
amulet.add(spikeGroup);
const spikeCount = 24;
for (let i = 0; i < spikeCount; i++) {
const angle = (i / spikeCount) * Math.PI * 2;
const isMain = i % 2 === 0;
const h = isMain ? 0.38 : 0.22;
const sGeo = new THREE.ConeGeometry(0.04, h, 8);
const sMat = new THREE.MeshStandardMaterial({ color: isMain ? 0xc8901a : 0x8a6010, roughness: 0.2, metalness: 0.85 });
const spike = new THREE.Mesh(sGeo, sMat);
const dist = 2.28 + h / 2;
spike.position.set(Math.cos(angle) * dist, Math.sin(angle) * dist, 0.0);
spike.rotation.z = -angle - Math.PI / 2;
spikeGroup.add(spike);
}
const tearPoints = [];
for (let i = 0; i <= 20; i++) {
const t = i / 20;
const y = -t * 1.0;
const x = Math.sin(t * Math.PI) * 0.22 * (1 - t * 0.4);
tearPoints.push(new THREE.Vector2(x, y));
}
const tearGeo = new THREE.LatheGeometry(tearPoints, 64);
const tearMat = new THREE.MeshPhysicalMaterial({ color: 0x0e2070, roughness: 0.04, metalness: 0.0, transmission: 0.6, ior: 1.55, clearcoat: 1.0 });
const tear = new THREE.Mesh(tearGeo, tearMat);
tear.position.set(0, -2.65, 0.0);
amulet.add(tear);
const tdotGeo = new THREE.SphereGeometry(0.07, 16, 16);
const tdotMat = new THREE.MeshStandardMaterial({ color: 0xd4a020, roughness: 0.1, metalness: 0.95 });
const tdot = new THREE.Mesh(tdotGeo, tdotMat);
tdot.position.set(0, -2.9, 0.08);
amulet.add(tdot);
for (let i = 0; i < 5; i++) {
const lGeo = new THREE.TorusGeometry(0.12, 0.035, 12, 32);
const lMat = new THREE.MeshStandardMaterial({ color: 0xc8901a, roughness: 0.15, metalness: 0.95 });
const link = new THREE.Mesh(lGeo, lMat);
link.position.set(0, 2.2 + i * 0.26, 0.0);
link.rotation.x = i % 2 === 0 ? Math.PI / 2 : 0;
amulet.add(link);
}
const cornerPositions = [
[-2.7, 2.7], [ 2.7, 2.7],
[-2.7, -2.7], [ 2.7, -2.7],
];
cornerPositions.forEach(([cx, cy]) => {
const cGroup = new THREE.Group();
cGroup.position.set(cx, cy, -0.1);
const angle = Math.atan2(cy, cx) + Math.PI / 4;
cGroup.rotation.z = angle;
const cbGeo = new THREE.CylinderGeometry(0.22, 0.22, 0.12, 32);
const cbMat = new THREE.MeshPhysicalMaterial({ color: 0x0a2070, roughness: 0.08, clearcoat: 1.0, transmission: 0.3, ior: 1.5 });
const cb = new THREE.Mesh(cbGeo, cbMat);
cb.rotation.x = Math.PI / 2;
cGroup.add(cb);
const crGeo = new THREE.TorusGeometry(0.21, 0.025, 8, 32);
const crMat = new THREE.MeshStandardMaterial({ color: 0xd4a020, roughness: 0.15, metalness: 0.95 });
const cr = new THREE.Mesh(crGeo, crMat);
cGroup.add(cr);
const cpGeo = new THREE.SphereGeometry(0.08, 16, 16);
const cpMat = new THREE.MeshBasicMaterial({ color: 0x101820 });
const cp = new THREE.Mesh(cpGeo, cpMat);
cp.position.z = 0.09;
cGroup.add(cp);
amulet.add(cGroup);
});
const particleCount = 600;
const pPositions = new Float32Array(particleCount * 3);
const pColors = new Float32Array(particleCount * 3);
const pSpeeds = new Float32Array(particleCount);
const pAngles = new Float32Array(particleCount);
const pRadii = new Float32Array(particleCount);
for (let i = 0; i < particleCount; i++) {
const angle = Math.random() * Math.PI * 2;
const radius = 3.5 + Math.random() * 5.0;
const z = (Math.random() - 0.5) * 3.0;
pPositions[i * 3] = Math.cos(angle) * radius;
pPositions[i * 3 + 1] = Math.sin(angle) * radius;
pPositions[i * 3 + 2] = z;
pAngles[i] = angle;
pRadii[i] = radius;
pSpeeds[i] = (Math.random() * 0.3 + 0.05) * (Math.random() > 0.5 ? 1 : -1);
const isGold = Math.random() > 0.5;
if (isGold) { pColors[i*3]=0.9; pColors[i*3+1]=0.65; pColors[i*3+2]=0.1; }
else { pColors[i*3]=0.2; pColors[i*3+1]=0.4; pColors[i*3+2]=1.0; }
}
const pGeo = new THREE.BufferGeometry();
pGeo.setAttribute('position', new THREE.BufferAttribute(pPositions, 3));
pGeo.setAttribute('color', new THREE.BufferAttribute(pColors, 3));
const pMat = new THREE.PointsMaterial({ size: 0.045, vertexColors: true, transparent: true, opacity: 0.75, sizeAttenuation: true });
const particles = new THREE.Points(pGeo, pMat);
scene.add(particles);
const ambient = new THREE.AmbientLight(0x0a0820, 1.2);
scene.add(ambient);
const key = new THREE.DirectionalLight(0xffd4a0, 3.5);
key.position.set(-4, 5, 6);
key.castShadow = true;
key.shadow.mapSize.set(2048, 2048);
key.shadow.camera.near = 0.1;
key.shadow.camera.far = 40;
key.shadow.camera.left = key.shadow.camera.bottom = -8;
key.shadow.camera.right = key.shadow.camera.top = 8;
scene.add(key);
const fill = new THREE.DirectionalLight(0x3060ff, 1.4);
fill.position.set(5, -2, 4);
scene.add(fill);
const rim = new THREE.DirectionalLight(0xc08010, 2.2);
rim.position.set(0, -4, -5);
scene.add(rim);
const eyeLight = new THREE.PointLight(0x2050ff, 3.5, 4.0);
eyeLight.position.set(0, 0, 0.6);
amulet.add(eyeLight);
const topLight = new THREE.PointLight(0xffd060, 2.0, 6.0);
topLight.position.set(0, 3.5, 2.0);
scene.add(topLight);
const bgStarCount = 1200;
const bgPos = new Float32Array(bgStarCount * 3);
for (let i = 0; i < bgStarCount; i++) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
const r = 60 + Math.random() * 40;
bgPos[i*3] = r * Math.sin(phi) * Math.cos(theta);
bgPos[i*3+1] = r * Math.sin(phi) * Math.sin(theta);
bgPos[i*3+2] = r * Math.cos(phi);
}
const bgGeo = new THREE.BufferGeometry();
bgGeo.setAttribute('position', new THREE.BufferAttribute(bgPos, 3));
const bgMat = new THREE.PointsMaterial({ color: 0xffffff, size: 0.18, transparent: true, opacity: 0.6 });
scene.add(new THREE.Points(bgGeo, bgMat));
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
let t = 0;
function animate() {
requestAnimationFrame(animate);
t += 0.012;
if (autoRotate) {
rotY += 0.004;
}
pivot.rotation.y = lerp(pivot.rotation.y, rotY, 0.08);
pivot.rotation.x = lerp(pivot.rotation.x, rotX, 0.08);
beadGroup.rotation.z += 0.003;
spikeGroup.rotation.z -= 0.002;
eyeLight.intensity = 3.0 + Math.sin(t * 1.8) * 1.5;
eyeLight.color.setHSL(0.62 + Math.sin(t * 0.4) * 0.04, 1.0, 0.55);
amulet.position.y = Math.sin(t * 0.7) * 0.08;
const pos = pGeo.attributes.position.array;
for (let i = 0; i < particleCount; i++) {
pAngles[i] += pSpeeds[i] * 0.004;
pos[i*3] = Math.cos(pAngles[i]) * pRadii[i];
pos[i*3+1] = Math.sin(pAngles[i]) * pRadii[i];
}
pGeo.attributes.position.needsUpdate = true;
renderer.render(scene, camera);
}
animate();
})();
</script>
</body>
</html>