Meta Description" name="description" />

Share this result

Previews are deleted daily. Get a permanent share link sent to your inbox:
Script
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>Mobile Creative Sandbox</title> <style> body { margin: 0; overflow: hidden; background-color: #87CEEB; font-family: sans-serif; user-select: none; -webkit-user-select: none; touch-action: none; } #ui-container { position: absolute; top: 15px; left: 15px; color: white; text-shadow: 1px 1px 3px rgba(0,0,0,0.8); pointer-events: none; font-size: 14px; } #crosshair { position: absolute; top: 50%; left: 50%; width: 10px; height: 10px; transform: translate(-50%, -50%); color: white; font-size: 24px; pointer-events: none; } /* Virtual Joystick */ #joystick-boundary { position: absolute; bottom: 30px; left: 30px; width: 100px; height: 100px; background: rgba(255, 255, 255, 0.2); border: 2px solid rgba(255, 255, 255, 0.4); border-radius: 50%; touch-action: none; } #joystick-nub { position: absolute; top: 30px; left: 30px; width: 40px; height: 40px; background: rgba(255, 255, 255, 0.6); border-radius: 50%; } /* Build Button */ #build-btn { position: absolute; bottom: 40px; right: 30px; width: 70px; height: 70px; background: #b0413e; color: white; border: 3px solid white; border-radius: 50%; font-weight: bold; font-size: 16px; box-shadow: 0px 4px 10px rgba(0,0,0,0.3); } </style> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> </head> <body> <div id="ui-container"> <b>Earth Sandbox (Mobile)</b><br> Drag right side to look | Use joystick to move </div> <div id="crosshair">+</div> <div id="joystick-boundary"> <div id="joystick-nub"></div> </div> <button id="build-btn">BUILD</button> <script> // --- 1. ENGINE SETUP --- const scene = new THREE.Scene(); scene.background = new THREE.Color(0x87CEEB); 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); document.body.appendChild(renderer.domElement); // Lights scene.add(new THREE.AmbientLight(0xffffff, 0.7)); const sun = new THREE.DirectionalLight(0xffffff, 0.6); sun.position.set(10, 20, 10); scene.add(sun); // --- 2. GROUND GRID --- const blockSize = 2; const blocks = []; const grassGeo = new THREE.BoxGeometry(blockSize, blockSize, blockSize); const grassMat = new THREE.MeshStandardMaterial({ color: 0x557a2b, roughness: 0.8 }); for (let x = -10; x < 10; x++) { for (let z = -10; z < 10; z++) { const block = new THREE.Mesh(grassGeo, grassMat); block.position.set(x * blockSize, -blockSize/2, z * blockSize); scene.add(block); blocks.push(block); } } camera.position.set(0, 4, 8); // --- 3. MOBILE TOUCH LOOK CONTROLS --- let touchStartLook = { x: 0, y: 0 }; let cameraRotation = { y: 0, x: 0 }; window.addEventListener('touchstart', (e) => { for(let i=0; i<e.touches.length; i++) { // If touch is on the right half of screen, look around if (e.touches[i].clientX > window.innerWidth / 2) { touchStartLook.x = e.touches[i].clientX; touchStartLook.y = e.touches[i].clientY; } } }); window.addEventListener('touchmove', (e) => { for(let i=0; i<e.touches.length; i++) { if (e.touches[i].clientX > window.innerWidth / 2) { const deltaX = e.touches[i].clientX - touchStartLook.x; const deltaY = e.touches[i].clientY - touchStartLook.y; cameraRotation.y -= deltaX * 0.005; cameraRotation.x -= deltaY * 0.005; cameraRotation.x = Math.max(-Math.PI/3, Math.min(Math.PI/3, cameraRotation.x)); camera.rotation.order = "YXZ"; camera.rotation.set(cameraRotation.x, cameraRotation.y, 0); touchStartLook.x = e.touches[i].clientX; touchStartLook.y = e.touches[i].clientY; } } }); // --- 4. VIRTUAL JOYSTICK MOVEMENT --- const boundary = document.getElementById('joystick-boundary'); const nub = document.getElementById('joystick-nub'); let moveVector = { x: 0, y: 0 }; let joystickActive = false; boundary.addEventListener('touchstart', (e) => { joystickActive = true; }); window.addEventListener('touchmove', (e) => { if (!joystickActive) return; const touch = e.touches[0]; const bRect = boundary.getBoundingClientRect(); const centerX = bRect.left + bRect.width / 2; const centerY = bRect.top + bRect.height / 2; let dx = touch.clientX - centerX; let dy = touch.clientY - centerY; const dist = Math.sqrt(dx*dx + dy*dy); const maxDist = 30; if (dist > maxDist) { dx = (dx / dist) * maxDist; dy = (dy / dist) * maxDist; } nub.style.transform = `translate(${dx}px, ${dy}px)`; // Normalize values between -1 and 1 moveVector.x = dx / maxDist; moveVector.y = dy / maxDist; }); window.addEventListener('touchend', () => { joystickActive = false; nub.style.transform = 'translate(0px, 0px)'; moveVector = { x: 0, y: 0 }; }); // --- 5. TOUCH BUILDING --- const raycaster = new THREE.Raycaster(); const centerMouse = new THREE.Vector2(0, 0); const brickMat = new THREE.MeshStandardMaterial({ color: 0xb0413e }); document.getElementById('build-btn').addEventListener('touchstart', (e) => { e.preventDefault(); // Stop screen zoom raycaster.setFromCamera(centerMouse, camera); const intersects = raycaster.intersectObjects(blocks); if (intersects.length > 0) { const intersect = intersects[0]; const newBlock = new THREE.Mesh(grassGeo, brickMat); newBlock.position.copy(intersect.object.position).add(intersect.face.normal.multiplyScalar(blockSize)); scene.add(newBlock); blocks.push(newBlock); } }); // --- 6. GAME LOOP --- function animate() { requestAnimationFrame(animate); // Forward/Backward & Strafe based on camera direction const speed = 0.1; const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion); const side = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion); forward.y = 0; // Don't fly into sky side.y = 0; forward.normalize(); side.normalize(); camera.position.addScaledVector(forward, -moveVector.y * speed); camera.position.addScaledVector(side, moveVector.x * speed); renderer.render(scene, camera); } window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); animate(); </script> </body> </html>
Landing Page
This ad does not have a landing page available
Network Timeline
Performance Summary

2

Requests

2

Domains

128KB

Transfer Size

598KB

Content Size

3,406.0ms

Dom Content Loaded

108.0ms

First Paint

3,406.0ms

Load Time
Domain Breakdown
Transfer Size (bytes)
Loading...
Content Size (bytes)
Loading...
Header Size (bytes)
Loading...
Requests
Loading...
Timings (ms)
Loading...
Total Time
Loading...
Content Breakdown
Transfer Size (bytes)
Loading...
Content Size (bytes)
Loading...
Header Size (bytes)
Loading...
Requests
Loading...