.lod-label{position:absolute;left:0;right:0;bottom:5%;text-align:center;font:600 clamp(9px,2.6vmin,13px)/1 ui-monospace,monospace;color:#dfe3ffdd;letter-spacing:.02em}
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0d0d16);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 0.6, 2.2);
const mat = new THREE.MeshStandardMaterial({ color: 0x8ecbff, roughness: 0.5 });
const lod = new THREE.LOD();
const levels = [
{ geo: new THREE.IcosahedronGeometry(0.9, 4), dist: 0, label: 'LOD 0 · high' },
{ geo: new THREE.IcosahedronGeometry(0.9, 2), dist: 5, label: 'LOD 1 · medium' },
{ geo: new THREE.IcosahedronGeometry(0.9, 0), dist: 9, label: 'LOD 2 · low' },
];
for (const lv of levels) lod.addLevel(new THREE.Mesh(lv.geo, mat), lv.dist);
scene.add(lod);
const key = new THREE.DirectionalLight(0xffffff, 1.8); key.position.set(3, 4, 5); scene.add(key);
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const label = document.getElementById('lod-label');
function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
camera.lookAt(0, 0, 0);
renderer.setAnimationLoop((t) => {
const z = -1.5 - (Math.sin(t * 0.00035) * 0.5 + 0.5) * 9.5;
lod.position.z = z;
lod.rotation.y = t * 0.0004;
lod.update(camera);
const dist = camera.position.distanceTo(lod.position);
let idx = 0;
for (let i = levels.length - 1; i >= 0; i--) { if (dist >= levels[i].dist) { idx = i; break; } }
label.textContent = levels[idx].label;
renderer.render(scene, camera);
});