| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Metallic Infinity Möbius Strip</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script> |
| <style> |
| body { |
| margin: 0; |
| overflow: hidden; |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); |
| } |
| #info { |
| position: absolute; |
| bottom: 20px; |
| width: 100%; |
| text-align: center; |
| color: white; |
| font-family: 'Arial', sans-serif; |
| pointer-events: none; |
| text-shadow: 0 0 5px rgba(0,0,0,0.5); |
| } |
| .title { |
| position: absolute; |
| top: 20px; |
| width: 100%; |
| text-align: center; |
| color: white; |
| font-family: 'Arial', sans-serif; |
| font-size: 2.5rem; |
| font-weight: bold; |
| text-shadow: 0 0 15px rgba(255, 215, 0, 0.8); |
| } |
| .controls { |
| position: absolute; |
| top: 80px; |
| right: 20px; |
| background: rgba(0,0,0,0.5); |
| padding: 15px; |
| border-radius: 10px; |
| color: white; |
| font-family: 'Arial', sans-serif; |
| z-index: 100; |
| backdrop-filter: blur(5px); |
| border: 1px solid rgba(255, 215, 0, 0.3); |
| } |
| .control-group { |
| margin-bottom: 12px; |
| } |
| label { |
| display: block; |
| margin-bottom: 5px; |
| color: #ffd700; |
| } |
| input[type="range"] { |
| -webkit-appearance: none; |
| width: 100%; |
| height: 6px; |
| background: rgba(255, 215, 0, 0.2); |
| border-radius: 3px; |
| outline: none; |
| } |
| input[type="range"]::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| width: 16px; |
| height: 16px; |
| background: #ffd700; |
| border-radius: 50%; |
| cursor: pointer; |
| } |
| button { |
| transition: all 0.3s ease; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="title">Metallic Infinity Möbius Strip</div> |
| <div class="controls"> |
| <div class="control-group"> |
| <label for="rotationSpeed">Rotation Speed</label> |
| <input type="range" id="rotationSpeed" min="0" max="0.02" step="0.001" value="0.005"> |
| </div> |
| <div class="control-group"> |
| <label class="flex items-center"> |
| <input type="checkbox" id="wireframe" class="mr-2 h-4 w-4 accent-yellow-400"> |
| <span>Show Wireframe</span> |
| </label> |
| </div> |
| <div class="flex space-x-2 mt-4"> |
| <button id="pauseBtn" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded-lg font-medium shadow-md hover:shadow-lg transform hover:-translate-y-0.5 transition-all"> |
| Pause |
| </button> |
| </div> |
| </div> |
| <div id="info">Drag to rotate | Scroll to zoom</div> |
| <script> |
| |
| const scene = new THREE.Scene(); |
| const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| renderer.setPixelRatio(window.devicePixelRatio); |
| renderer.shadowMap.enabled = true; |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; |
| document.body.appendChild(renderer.domElement); |
| |
| |
| const createInfinityMobiusStrip = (radius = 1, tubeRadius = 0.3, radialSegments = 128, tubularSegments = 128) => { |
| const geometry = new THREE.BufferGeometry(); |
| const vertices = []; |
| const indices = []; |
| const normals = []; |
| const uvs = []; |
| |
| for (let i = 0; i <= radialSegments; i++) { |
| const v = i / radialSegments * Math.PI * 2; |
| |
| for (let j = 0; j <= tubularSegments; j++) { |
| const u = j / tubularSegments * Math.PI * 4; |
| |
| |
| const scale = 1.5; |
| const x = scale * Math.cos(u) / (1 + Math.sin(u)*Math.sin(u)); |
| const y = scale * Math.sin(u) * Math.cos(u) / (1 + Math.sin(u)*Math.sin(u)); |
| |
| |
| const nx = x + tubeRadius * Math.cos(v/2) * Math.cos(u); |
| const ny = y + tubeRadius * Math.cos(v/2) * Math.sin(u); |
| const nz = tubeRadius * Math.sin(v/2); |
| |
| vertices.push(nx, ny, nz); |
| |
| |
| const normal = new THREE.Vector3( |
| Math.cos(u) * Math.cos(v/2), |
| Math.sin(u) * Math.cos(v/2), |
| Math.sin(v/2) |
| ).normalize(); |
| normals.push(normal.x, normal.y, normal.z); |
| |
| uvs.push(j / tubularSegments, i / radialSegments); |
| } |
| } |
| |
| |
| for (let i = 0; i < radialSegments; i++) { |
| for (let j = 0; j < tubularSegments; j++) { |
| const a = i * (tubularSegments + 1) + j; |
| const b = a + tubularSegments + 1; |
| const c = a + 1; |
| const d = b + 1; |
| |
| indices.push(a, b, d); |
| indices.push(a, d, c); |
| } |
| } |
| |
| geometry.setIndex(indices); |
| geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); |
| geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3)); |
| geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2)); |
| |
| return geometry; |
| }; |
| |
| |
| const material = new THREE.MeshPhysicalMaterial({ |
| color: 0xffd700, |
| metalness: 1.0, |
| roughness: 0.1, |
| clearcoat: 1.0, |
| clearcoatRoughness: 0.05, |
| ior: 2.5, |
| specularIntensity: 1.5, |
| envMapIntensity: 2.0, |
| emissive: 0xffcc00, |
| emissiveIntensity: 0.2, |
| side: THREE.DoubleSide, |
| }); |
| |
| const wireframeMaterial = new THREE.MeshBasicMaterial({ |
| color: 0xffffff, |
| wireframe: true, |
| transparent: true, |
| opacity: 0.3 |
| }); |
| |
| |
| const mobiusStrip = new THREE.Mesh(createInfinityMobiusStrip(2, 0.4, 128, 128), material); |
| scene.add(mobiusStrip); |
| mobiusStrip.rotation.x = Math.PI / 4; |
| mobiusStrip.rotation.y = Math.PI / 4; |
| |
| |
| const wireframe = new THREE.Mesh(mobiusStrip.geometry, wireframeMaterial); |
| scene.add(wireframe); |
| wireframe.visible = false; |
| |
| |
| const ambientLight = new THREE.AmbientLight(0x404040, 0.3); |
| scene.add(ambientLight); |
| |
| |
| const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.8); |
| directionalLight1.position.set(2, 2, 2); |
| directionalLight1.castShadow = true; |
| directionalLight1.shadow.mapSize.width = 2048; |
| directionalLight1.shadow.mapSize.height = 2048; |
| scene.add(directionalLight1); |
| |
| |
| const directionalLight2 = new THREE.DirectionalLight(0xff9d00, 0.6); |
| directionalLight2.position.set(-1, -1, -1); |
| scene.add(directionalLight2); |
| |
| |
| const pointLight = new THREE.PointLight(0xffcc00, 2, 15); |
| pointLight.position.set(3, 5, 0); |
| pointLight.castShadow = true; |
| scene.add(pointLight); |
| |
| |
| const pointLight2 = new THREE.PointLight(0xff6600, 1.5, 15); |
| pointLight2.position.set(-3, -5, 0); |
| scene.add(pointLight2); |
| |
| |
| camera.position.set(0, 3, 10); |
| camera.lookAt(0, 0, 0); |
| |
| |
| const controls = new THREE.OrbitControls(camera, renderer.domElement); |
| controls.enableDamping = true; |
| controls.dampingFactor = 0.05; |
| controls.screenSpacePanning = false; |
| controls.minDistance = 5; |
| controls.maxDistance = 20; |
| controls.maxPolarAngle = Math.PI * 0.9; |
| controls.minPolarAngle = Math.PI * 0.1; |
| |
| |
| const cubeTextureLoader = new THREE.CubeTextureLoader(); |
| const envMap = cubeTextureLoader.load([ |
| 'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_px.jpg', |
| 'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_nx.jpg', |
| 'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_py.jpg', |
| 'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_ny.jpg', |
| 'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_pz.jpg', |
| 'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_nz.jpg' |
| ]); |
| scene.background = envMap; |
| scene.environment = envMap; |
| material.envMap = envMap; |
| |
| |
| const rotationSpeedInput = document.getElementById('rotationSpeed'); |
| const wireframeCheckbox = document.getElementById('wireframe'); |
| const pauseBtn = document.getElementById('pauseBtn'); |
| |
| let isPaused = false; |
| let autoRotateSpeed = 0.005; |
| |
| wireframeCheckbox.addEventListener('change', function() { |
| wireframe.visible = this.checked; |
| }); |
| |
| pauseBtn.addEventListener('click', function() { |
| isPaused = !isPaused; |
| pauseBtn.textContent = isPaused ? 'Play' : 'Pause'; |
| pauseBtn.classList.toggle('bg-yellow-700'); |
| pauseBtn.classList.toggle('bg-yellow-600'); |
| }); |
| |
| |
| let time = 0; |
| function animate() { |
| requestAnimationFrame(animate); |
| time += 0.01; |
| |
| if (!isPaused) { |
| |
| mobiusStrip.rotation.x += autoRotateSpeed * 0.8; |
| mobiusStrip.rotation.y += autoRotateSpeed * 0.5; |
| mobiusStrip.rotation.z += autoRotateSpeed * 0.2; |
| |
| |
| pointLight.intensity = 2 + Math.sin(time) * 0.5; |
| pointLight2.intensity = 1.5 + Math.cos(time * 0.8) * 0.3; |
| |
| |
| autoRotateSpeed = parseFloat(rotationSpeedInput.value); |
| |
| wireframe.rotation.copy(mobiusStrip.rotation); |
| } |
| |
| controls.update(); |
| renderer.render(scene, camera); |
| } |
| |
| |
| window.addEventListener('resize', function() { |
| camera.aspect = window.innerWidth / window.innerHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| }); |
| |
| animate(); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=etnom/mobius" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> |
| </html> |