Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>3D Driving Portfolio | Graphic Designer</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/[email protected]/examples/js/controls/OrbitControls.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.min.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> | |
<style> | |
body { | |
margin: 0; | |
overflow: hidden; | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
} | |
#canvas-container { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
z-index: 1; | |
} | |
#ui-overlay { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
z-index: 2; | |
pointer-events: none; | |
} | |
.dashboard { | |
background: rgba(0, 0, 0, 0.5); | |
backdrop-filter: blur(10px); | |
border-radius: 15px; | |
padding: 1rem; | |
pointer-events: all; | |
} | |
.speedometer { | |
width: 120px; | |
height: 120px; | |
border: 3px solid rgba(255, 255, 255, 0.2); | |
border-radius: 50%; | |
position: relative; | |
} | |
.speed-needle { | |
position: absolute; | |
width: 3px; | |
height: 50px; | |
background: #f43f5e; | |
top: 10px; | |
left: 50%; | |
transform-origin: bottom center; | |
transform: translateX(-50%) rotate(0deg); | |
transition: transform 0.3s ease-out; | |
} | |
.menu-item { | |
transition: all 0.3s ease; | |
} | |
.menu-item:hover { | |
transform: translateY(-5px); | |
text-shadow: 0 0 10px rgba(255, 255, 255, 0.7); | |
} | |
.world-border { | |
position: absolute; | |
border: 2px dashed rgba(255, 255, 255, 0.3); | |
pointer-events: none; | |
} | |
@keyframes float { | |
0%, 100% { transform: translateY(0); } | |
50% { transform: translateY(-10px); } | |
} | |
.floating { | |
animation: float 3s ease-in-out infinite; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white"> | |
<div id="canvas-container"></div> | |
<div id="ui-overlay" class="flex flex-col justify-between p-6"> | |
<!-- Top Navigation --> | |
<div class="flex justify-between items-start"> | |
<div class="dashboard flex items-center space-x-4"> | |
<div class="text-center"> | |
<div class="speedometer mx-auto mb-2"> | |
<div class="speed-needle" id="speed-needle"></div> | |
</div> | |
<p class="text-sm">SPEED <span id="speed-display">0</span> km/h</p> | |
</div> | |
<div class="text-center"> | |
<div class="text-2xl font-bold" id="distance">0</div> | |
<p class="text-sm">DISTANCE</p> | |
</div> | |
</div> | |
<div class="dashboard p-4"> | |
<h1 class="text-2xl font-bold mb-2">DRIVING PORTFOLIO</h1> | |
<p class="text-sm opacity-80">Graphic Designer & 3D Artist</p> | |
</div> | |
</div> | |
<!-- Center Content --> | |
<div class="flex flex-col items-center justify-center h-full -mt-20"> | |
<div class="text-center max-w-2xl mx-auto bg-black bg-opacity-50 p-8 rounded-xl backdrop-filter backdrop-blur-sm"> | |
<h1 class="text-5xl font-bold mb-4 floating">EXPLORE MY WORLD</h1> | |
<p class="text-lg mb-6">Navigate through this limited 3D environment to discover my graphic design portfolio. Each landmark represents a different aspect of my work.</p> | |
<button id="start-btn" class="bg-rose-500 hover:bg-rose-600 text-white font-bold py-3 px-8 rounded-full transition-all transform hover:scale-105 pointer-events-auto"> | |
START DRIVING | |
</button> | |
</div> | |
</div> | |
<!-- Bottom Navigation --> | |
<div class="flex justify-between items-end"> | |
<div class="dashboard p-4"> | |
<div class="flex space-x-6"> | |
<div class="menu-item pointer-events-auto cursor-pointer"> | |
<i class="fas fa-home text-xl block text-center mb-1"></i> | |
<span class="text-xs">HOME</span> | |
</div> | |
<div class="menu-item pointer-events-auto cursor-pointer"> | |
<i class="fas fa-briefcase text-xl block text-center mb-1"></i> | |
<span class="text-xs">WORK</span> | |
</div> | |
<div class="menu-item pointer-events-auto cursor-pointer"> | |
<i class="fas fa-user text-xl block text-center mb-1"></i> | |
<span class="text-xs">ABOUT</span> | |
</div> | |
<div class="menu-item pointer-events-auto cursor-pointer"> | |
<i class="fas fa-envelope text-xl block text-center mb-1"></i> | |
<span class="text-xs">CONTACT</span> | |
</div> | |
</div> | |
</div> | |
<div class="dashboard p-4"> | |
<div class="flex space-x-4"> | |
<a href="#" class="text-xl hover:text-rose-400 transition-colors pointer-events-auto"> | |
<i class="fab fa-behance"></i> | |
</a> | |
<a href="#" class="text-xl hover:text-rose-400 transition-colors pointer-events-auto"> | |
<i class="fab fa-dribbble"></i> | |
</a> | |
<a href="#" class="text-xl hover:text-rose-400 transition-colors pointer-events-auto"> | |
<i class="fab fa-instagram"></i> | |
</a> | |
<a href="#" class="text-xl hover:text-rose-400 transition-colors pointer-events-auto"> | |
<i class="fab fa-linkedin"></i> | |
</a> | |
</div> | |
</div> | |
</div> | |
<!-- World Border Indicator --> | |
<div class="world-border" id="world-border"></div> | |
</div> | |
<script> | |
// Three.js variables | |
let scene, camera, renderer, car, worldBorder, controls; | |
let isDriving = false; | |
let speed = 0; | |
let maxSpeed = 120; | |
let distance = 0; | |
const worldSize = 200; | |
// Initialize Three.js scene | |
function init() { | |
// Create scene | |
scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0x111111); | |
scene.fog = new THREE.FogExp2(0x111111, 0.002); | |
// Create camera | |
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
camera.position.set(0, 5, 10); | |
// Create renderer | |
renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.shadowMap.enabled = true; | |
document.getElementById('canvas-container').appendChild(renderer.domElement); | |
// Add lights | |
const ambientLight = new THREE.AmbientLight(0x404040); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 1); | |
directionalLight.position.set(1, 1, 1); | |
directionalLight.castShadow = true; | |
scene.add(directionalLight); | |
// Add ground | |
const groundGeometry = new THREE.PlaneGeometry(worldSize, worldSize); | |
const groundMaterial = new THREE.MeshStandardMaterial({ | |
color: 0x333333, | |
roughness: 0.8, | |
metalness: 0.2 | |
}); | |
const ground = new THREE.Mesh(groundGeometry, groundMaterial); | |
ground.rotation.x = -Math.PI / 2; | |
ground.receiveShadow = true; | |
scene.add(ground); | |
// Add grid helper | |
const gridHelper = new THREE.GridHelper(worldSize, 20, 0x555555, 0x333333); | |
scene.add(gridHelper); | |
// Create simple car | |
createSimpleCar(); | |
// Add world border indicator | |
createWorldBorder(); | |
// Add some landmarks | |
createLandmarks(); | |
// Add orbit controls for debugging | |
controls = new THREE.OrbitControls(camera, renderer.domElement); | |
controls.enabled = false; | |
// Event listeners | |
window.addEventListener('resize', onWindowResize); | |
document.addEventListener('keydown', onKeyDown); | |
document.addEventListener('keyup', onKeyUp); | |
document.getElementById('start-btn').addEventListener('click', startDriving); | |
// Start animation loop | |
animate(); | |
} | |
function createSimpleCar() { | |
const carGroup = new THREE.Group(); | |
// Car body | |
const bodyGeometry = new THREE.BoxGeometry(3, 1.5, 6); | |
const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0x3498db }); | |
const body = new THREE.Mesh(bodyGeometry, bodyMaterial); | |
body.position.y = 0.75; | |
body.castShadow = true; | |
carGroup.add(body); | |
// Car cabin | |
const cabinGeometry = new THREE.BoxGeometry(2.5, 1, 3); | |
const cabinMaterial = new THREE.MeshStandardMaterial({ color: 0x2980b9 }); | |
const cabin = new THREE.Mesh(cabinGeometry, cabinMaterial); | |
cabin.position.set(0, 1.5, -0.5); | |
carGroup.add(cabin); | |
// Wheels | |
const wheelGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.4, 16); | |
const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x222222 }); | |
const wheelPositions = [ | |
{ x: -1.5, y: 0.5, z: 2 }, | |
{ x: 1.5, y: 0.5, z: 2 }, | |
{ x: -1.5, y: 0.5, z: -2 }, | |
{ x: 1.5, y: 0.5, z: -2 } | |
]; | |
wheelPositions.forEach(pos => { | |
const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial); | |
wheel.rotation.z = Math.PI / 2; | |
wheel.position.set(pos.x, pos.y, pos.z); | |
wheel.castShadow = true; | |
carGroup.add(wheel); | |
}); | |
// Headlights | |
const headlightGeometry = new THREE.SphereGeometry(0.3, 16, 16); | |
const headlightMaterial = new THREE.MeshStandardMaterial({ | |
color: 0xffffcc, | |
emissive: 0xffff99, | |
emissiveIntensity: 0.5 | |
}); | |
const headlight1 = new THREE.Mesh(headlightGeometry, headlightMaterial); | |
headlight1.position.set(-0.8, 0.8, 3); | |
carGroup.add(headlight1); | |
const headlight2 = new THREE.Mesh(headlightGeometry, headlightMaterial); | |
headlight2.position.set(0.8, 0.8, 3); | |
carGroup.add(headlight2); | |
// Add car to scene | |
carGroup.position.y = 0.5; | |
scene.add(carGroup); | |
car = carGroup; | |
} | |
function createWorldBorder() { | |
const borderSize = worldSize - 5; | |
const borderElement = document.getElementById('world-border'); | |
borderElement.style.width = `${borderSize}px`; | |
borderElement.style.height = `${borderSize}px`; | |
borderElement.style.left = `calc(50% - ${borderSize/2}px)`; | |
borderElement.style.bottom = `calc(50% - ${borderSize/2}px)`; | |
} | |
function createLandmarks() { | |
// Portfolio section | |
const portfolioGeometry = new THREE.BoxGeometry(10, 2, 10); | |
const portfolioMaterial = new THREE.MeshStandardMaterial({ | |
color: 0xf43f5e, | |
emissive: 0xf43f5e, | |
emissiveIntensity: 0.2 | |
}); | |
const portfolio = new THREE.Mesh(portfolioGeometry, portfolioMaterial); | |
portfolio.position.set(30, 1, 30); | |
portfolio.castShadow = true; | |
scene.add(portfolio); | |
// Add text to portfolio | |
const portfolioText = createTextMesh("PORTFOLIO", 0xffffff, 2); | |
portfolioText.position.set(30, 4, 30); | |
scene.add(portfolioText); | |
// About section | |
const aboutGeometry = new THREE.CylinderGeometry(5, 5, 2, 6); | |
const aboutMaterial = new THREE.MeshStandardMaterial({ | |
color: 0x3b82f6, | |
emissive: 0x3b82f6, | |
emissiveIntensity: 0.2 | |
}); | |
const about = new THREE.Mesh(aboutGeometry, aboutMaterial); | |
about.position.set(-40, 1, -40); | |
about.castShadow = true; | |
scene.add(about); | |
// Add text to about | |
const aboutText = createTextMesh("ABOUT", 0xffffff, 2); | |
aboutText.position.set(-40, 4, -40); | |
scene.add(aboutText); | |
// Contact section | |
const contactGeometry = new THREE.SphereGeometry(5, 32, 32); | |
const contactMaterial = new THREE.MeshStandardMaterial({ | |
color: 0x10b981, | |
emissive: 0x10b981, | |
emissiveIntensity: 0.2 | |
}); | |
const contact = new THREE.Mesh(contactGeometry, contactMaterial); | |
contact.position.set(-30, 5, 30); | |
contact.castShadow = true; | |
scene.add(contact); | |
// Add text to contact | |
const contactText = createTextMesh("CONTACT", 0xffffff, 2); | |
contactText.position.set(-30, 11, 30); | |
scene.add(contactText); | |
} | |
function createTextMesh(text, color, size) { | |
const canvas = document.createElement('canvas'); | |
const context = canvas.getContext('2d'); | |
canvas.width = 512; | |
canvas.height = 256; | |
context.fillStyle = '#000000'; | |
context.fillRect(0, 0, canvas.width, canvas.height); | |
context.font = 'Bold 80px Arial'; | |
context.fillStyle = `rgb(${color.r * 255}, ${color.g * 255}, ${color.b * 255})`; | |
context.textAlign = 'center'; | |
context.textBaseline = 'middle'; | |
context.fillText(text, canvas.width/2, canvas.height/2); | |
const texture = new THREE.CanvasTexture(canvas); | |
texture.minFilter = THREE.LinearFilter; | |
texture.wrapS = THREE.ClampToEdgeWrapping; | |
texture.wrapT = THREE.ClampToEdgeWrapping; | |
const material = new THREE.MeshBasicMaterial({ | |
map: texture, | |
transparent: true, | |
side: THREE.DoubleSide | |
}); | |
const geometry = new THREE.PlaneGeometry(size * (canvas.width/canvas.height) * 2, size * 2); | |
const mesh = new THREE.Mesh(geometry, material); | |
return mesh; | |
} | |
function onWindowResize() { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
// Update world border position | |
const borderSize = worldSize - 5; | |
const borderElement = document.getElementById('world-border'); | |
borderElement.style.left = `calc(50% - ${borderSize/2}px)`; | |
borderElement.style.bottom = `calc(50% - ${borderSize/2}px)`; | |
} | |
function onKeyDown(event) { | |
if (!isDriving) return; | |
switch(event.key) { | |
case 'ArrowUp': | |
speed = Math.min(speed + 0.5, maxSpeed); | |
break; | |
case 'ArrowDown': | |
speed = Math.max(speed - 0.5, -maxSpeed/2); | |
break; | |
case 'ArrowLeft': | |
car.rotation.y += 0.05; | |
break; | |
case 'ArrowRight': | |
car.rotation.y -= 0.05; | |
break; | |
} | |
} | |
function onKeyUp(event) { | |
if (!isDriving) return; | |
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { | |
// Gradual slowdown when not pressing acceleration | |
const slowDownInterval = setInterval(() => { | |
if (speed > 0) { | |
speed = Math.max(speed - 0.2, 0); | |
} else if (speed < 0) { | |
speed = Math.min(speed + 0.2, 0); | |
} else { | |
clearInterval(slowDownInterval); | |
} | |
}, 50); | |
} | |
} | |
function startDriving() { | |
isDriving = true; | |
document.getElementById('start-btn').style.display = 'none'; | |
document.querySelector('.text-center.max-w-2xl').style.display = 'none'; | |
// Position camera behind car | |
camera.position.set(0, 5, 10); | |
camera.lookAt(car.position); | |
// Enable first-person controls | |
controls.enabled = false; | |
} | |
function animate() { | |
requestAnimationFrame(animate); | |
if (isDriving) { | |
// Move car based on speed and rotation | |
const direction = new THREE.Vector3(0, 0, -1); | |
direction.applyQuaternion(car.quaternion); | |
car.position.add(direction.multiplyScalar(speed * 0.05)); | |
// Update distance | |
distance += Math.abs(speed) * 0.01; | |
document.getElementById('distance').textContent = Math.floor(distance); | |
// Keep car within world bounds | |
const halfWorld = worldSize / 2 - 5; | |
car.position.x = THREE.MathUtils.clamp(car.position.x, -halfWorld, halfWorld); | |
car.position.z = THREE.MathUtils.clamp(car.position.z, -halfWorld, halfWorld); | |
// Update camera to follow car | |
const cameraOffset = new THREE.Vector3(0, 5, 10); | |
cameraOffset.applyQuaternion(car.quaternion); | |
camera.position.copy(car.position).add(cameraOffset); | |
camera.lookAt(car.position); | |
// Update speedometer | |
updateSpeedometer(); | |
} | |
renderer.render(scene, camera); | |
} | |
function updateSpeedometer() { | |
const speedDisplay = document.getElementById('speed-display'); | |
const speedNeedle = document.getElementById('speed-needle'); | |
const absSpeed = Math.abs(Math.round(speed)); | |
speedDisplay.textContent = absSpeed; | |
// Rotate needle (0km/h = -90deg, maxSpeed = 90deg) | |
const rotation = (absSpeed / maxSpeed) * 180 - 90; | |
speedNeedle.style.transform = `translateX(-50%) rotate(${rotation}deg)`; | |
// Change needle color based on speed | |
if (absSpeed > maxSpeed * 0.8) { | |
speedNeedle.style.background = '#ef4444'; | |
} else if (absSpeed > maxSpeed * 0.5) { | |
speedNeedle.style.background = '#f59e0b'; | |
} else { | |
speedNeedle.style.background = '#f43f5e'; | |
} | |
} | |
// Start the application | |
init(); | |
</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=aminArtistry/ye" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |