llamameta's picture
Update index.html
d239247 verified
Raw
History Blame Contribute Delete
21.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bratislava Castle - Cinematic 3D Experience</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#container {
position: relative;
width: 100vw;
height: 100vh;
background: linear-gradient(to bottom, #1a2980, #26d0ce);
}
#canvas {
display: block;
}
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
padding: 20px;
box-sizing: border-box;
color: white;
text-align: center;
pointer-events: none;
z-index: 10;
}
#title {
font-size: 2.5rem;
text-shadow: 0 0 10px rgba(0,0,0,0.7);
margin-bottom: 10px;
letter-spacing: 2px;
}
#subtitle {
font-size: 1.2rem;
max-width: 600px;
margin: 0 auto;
text-shadow: 0 0 5px rgba(0,0,0,0.7);
}
#controls {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0,0,0,0.6);
padding: 15px;
border-radius: 10px;
color: white;
font-size: 0.9rem;
pointer-events: auto;
}
#showcase-btn {
position: absolute;
bottom: 20px;
right: 20px;
padding: 12px 25px;
background: #e63946;
color: white;
border: none;
border-radius: 30px;
font-size: 1rem;
cursor: pointer;
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
transition: all 0.3s ease;
pointer-events: auto;
}
#showcase-btn:hover {
background: #d62828;
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(0,0,0,0.4);
}
#loading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
z-index: 100;
}
.loader {
border: 5px solid #f3f3f3;
border-top: 5px solid #e63946;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#progress-bar {
width: 300px;
height: 10px;
background: #333;
border-radius: 5px;
margin-top: 20px;
overflow: hidden;
}
#progress {
height: 100%;
background: #e63946;
width: 0%;
transition: width 0.3s;
}
#hud {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0,0,0,0.6);
padding: 10px;
border-radius: 5px;
color: white;
font-size: 0.8rem;
}
</style>
</head>
<body>
<div id="container">
<div id="loading">
<div class="loader"></div>
<h2>Creating Bratislava Castle Experience</h2>
<p>Loading 3D assets and textures...</p>
<div id="progress-bar">
<div id="progress"></div>
</div>
</div>
<div id="ui">
<h1 id="title">BRATISLAVA CASTLE</h1>
<p id="subtitle">A cinematic journey through Slovakia's historic landmark with surrounding Danube scenery</p>
</div>
<div id="hud">
<div>Camera Mode: <span id="mode">Orbit</span></div>
<div>Position: <span id="position">0,0,0</span></div>
</div>
<div id="controls">
<strong>CONTROLS:</strong><br>
Orbit Mode: Drag to rotate, Scroll to zoom<br>
Fly Mode: WASD + Mouse, E/Q for up/down, Shift for boost<br>
Press F to toggle modes
</div>
<button id="showcase-btn">START CINEMATIC SHOWCASE</button>
<canvas id="canvas"></canvas>
</div>
<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>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.min.js"></script>
<script>
// Main variables
let scene, camera, renderer, controls, flyControls;
let castle, danube, bridge, cathedral;
let isShowcaseActive = false;
let showcaseStartTime = 0;
let clock = new THREE.Clock();
let mode = 'orbit'; // orbit or fly
// Initialize the scene
function init() {
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
scene.fog = new THREE.Fog(0x87CEEB, 200, 1000);
// Create camera
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 5000);
camera.position.set(0, 50, 200);
// Create renderer
renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas'), antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
// Add lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const sunLight = new THREE.DirectionalLight(0xffffff, 0.8);
sunLight.position.set(100, 200, 100);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 2048;
sunLight.shadow.mapSize.height = 2048;
scene.add(sunLight);
// Create controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
flyControls = new THREE.PointerLockControls(camera, document.body);
// Create environment
createEnvironment();
// Create castle
createCastle();
// Create surrounding scenery
createScenery();
// Event listeners
window.addEventListener('resize', onWindowResize);
document.getElementById('showcase-btn').addEventListener('click', startShowcase);
document.addEventListener('keydown', onKeyDown);
// Hide loading screen after a delay to simulate loading
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
}, 3000);
// Start animation loop
animate();
}
// Create environment elements
function createEnvironment() {
// Ground
const groundGeometry = new THREE.PlaneGeometry(2000, 2000);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x3d8c40,
roughness: 0.9,
metalness: 0.1
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Sky
const skyGeometry = new THREE.SphereGeometry(1000, 32, 32);
const skyMaterial = new THREE.MeshBasicMaterial({
color: 0x87CEEB,
side: THREE.BackSide
});
const sky = new THREE.Mesh(skyGeometry, skyMaterial);
scene.add(sky);
// Sun
const sunGeometry = new THREE.SphereGeometry(20, 32, 32);
const sunMaterial = new THREE.MeshBasicMaterial({ color: 0xFFFF00 });
const sun = new THREE.Mesh(sunGeometry, sunMaterial);
sun.position.set(100, 150, -100);
scene.add(sun);
}
// Create the castle model
function createCastle() {
// Castle base
const baseGeometry = new THREE.BoxGeometry(150, 80, 150);
const baseMaterial = new THREE.MeshStandardMaterial({
color: 0xD2B48C,
roughness: 0.8
});
const base = new THREE.Mesh(baseGeometry, baseMaterial);
base.position.y = 40;
base.castShadow = true;
base.receiveShadow = true;
scene.add(base);
// Castle towers
const towerGeometry = new THREE.CylinderGeometry(25, 25, 100, 32);
const towerMaterial = new THREE.MeshStandardMaterial({
color: 0xA0522D,
roughness: 0.9
});
const tower1 = new THREE.Mesh(towerGeometry, towerMaterial);
tower1.position.set(-60, 90, -60);
tower1.castShadow = true;
scene.add(tower1);
const tower2 = new THREE.Mesh(towerGeometry, towerMaterial);
tower2.position.set(60, 90, -60);
tower2.castShadow = true;
scene.add(tower2);
const tower3 = new THREE.Mesh(towerGeometry, towerMaterial);
tower3.position.set(-60, 90, 60);
tower3.castShadow = true;
scene.add(tower3);
const tower4 = new THREE.Mesh(towerGeometry, towerMaterial);
tower4.position.set(60, 90, 60);
tower4.castShadow = true;
scene.add(tower4);
// Castle roof
const roofGeometry = new THREE.ConeGeometry(100, 40, 4);
const roofMaterial = new THREE.MeshStandardMaterial({
color: 0xB22222,
roughness: 0.7
});
const roof = new THREE.Mesh(roofGeometry, roofMaterial);
roof.position.y = 130;
roof.rotation.y = Math.PI / 4;
roof.castShadow = true;
scene.add(roof);
// Flag
const flagPoleGeometry = new THREE.CylinderGeometry(1, 1, 30, 8);
const flagPoleMaterial = new THREE.MeshStandardMaterial({ color: 0xC0C0C0 });
const flagPole = new THREE.Mesh(flagPoleGeometry, flagPoleMaterial);
flagPole.position.set(0, 155, 0);
scene.add(flagPole);
const flagGeometry = new THREE.PlaneGeometry(20, 12);
const flagMaterial = new THREE.MeshStandardMaterial({
color: 0xFFFFFF,
side: THREE.DoubleSide,
roughness: 0.9
});
const flag = new THREE.Mesh(flagGeometry, flagMaterial);
flag.position.set(10, 160, 0);
flag.rotation.y = Math.PI / 2;
scene.add(flag);
castle = { base, towers: [tower1, tower2, tower3, tower4], roof, flag };
}
// Create surrounding scenery
function createScenery() {
// Danube river
const riverGeometry = new THREE.PlaneGeometry(1000, 100);
const riverMaterial = new THREE.MeshStandardMaterial({
color: 0x1E90FF,
roughness: 0.9,
metalness: 0.8
});
const river = new THREE.Mesh(riverGeometry, riverMaterial);
river.rotation.x = -Math.PI / 2;
river.position.set(0, 0.1, 300);
scene.add(river);
danube = river;
// Bridge (UFO bridge)
const bridgeBaseGeometry = new THREE.BoxGeometry(300, 10, 20);
const bridgeBaseMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });
const bridgeBase = new THREE.Mesh(bridgeBaseGeometry, bridgeBaseMaterial);
bridgeBase.position.set(0, 20, 300);
bridgeBase.castShadow = true;
scene.add(bridgeBase);
// Bridge towers
const towerGeometry = new THREE.CylinderGeometry(5, 5, 50, 16);
const towerMaterial = new THREE.MeshStandardMaterial({ color: 0xA9A9A9 });
const tower1 = new THREE.Mesh(towerGeometry, towerMaterial);
tower1.position.set(-100, 45, 300);
scene.add(tower1);
const tower2 = new THREE.Mesh(towerGeometry, towerMaterial);
tower2.position.set(100, 45, 300);
scene.add(tower2);
// Bridge cables
const cableMaterial = new THREE.LineBasicMaterial({ color: 0xC0C0C0 });
for (let i = 0; i < 10; i++) {
const points = [];
points.push(new THREE.Vector3(-100 + i * 22, 45, 300));
points.push(new THREE.Vector3(0, 70 + Math.sin(i) * 10, 300));
points.push(new THREE.Vector3(100 - i * 22, 45, 300));
const cableGeometry = new THREE.BufferGeometry().setFromPoints(points);
const cable = new THREE.Line(cableGeometry, cableMaterial);
scene.add(cable);
}
bridge = { base: bridgeBase, towers: [tower1, tower2] };
// Cathedral (St. Martin's)
const cathedralBaseGeometry = new THREE.BoxGeometry(60, 80, 40);
const cathedralBaseMaterial = new THREE.MeshStandardMaterial({ color: 0xF5F5DC });
const cathedralBase = new THREE.Mesh(cathedralBaseGeometry, cathedralBaseMaterial);
cathedralBase.position.set(-200, 40, 100);
cathedralBase.castShadow = true;
scene.add(cathedralBase);
const spireGeometry = new THREE.ConeGeometry(15, 60, 8);
const spireMaterial = new THREE.MeshStandardMaterial({ color: 0x708090 });
const spire = new THREE.Mesh(spireGeometry, spireMaterial);
spire.position.set(-200, 110, 100);
scene.add(spire);
cathedral = { base: cathedralBase, spire };
// Trees
for (let i = 0; i < 30; i++) {
const treeTrunkGeometry = new THREE.CylinderGeometry(2, 2, 15, 8);
const treeTrunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
const treeTrunk = new THREE.Mesh(treeTrunkGeometry, treeTrunkMaterial);
const treeTopGeometry = new THREE.ConeGeometry(8, 20, 8);
const treeTopMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });
const treeTop = new THREE.Mesh(treeTopGeometry, treeTopMaterial);
treeTop.position.y = 17.5;
const tree = new THREE.Group();
tree.add(treeTrunk);
tree.add(treeTop);
// Position trees around the castle and river
const angle = Math.random() * Math.PI * 2;
const distance = 100 + Math.random() * 150;
tree.position.set(
Math.cos(angle) * distance,
7.5,
Math.sin(angle) * distance + 200
);
scene.add(tree);
}
// Hills
const hillGeometry = new THREE.ConeGeometry(300, 100, 32);
const hillMaterial = new THREE.MeshStandardMaterial({
color: 0x2E8B57,
roughness: 0.9
});
const hill = new THREE.Mesh(hillGeometry, hillMaterial);
hill.position.set(0, -50, -200);
hill.rotation.x = Math.PI;
scene.add(hill);
}
// Start the cinematic showcase
function startShowcase() {
isShowcaseActive = true;
showcaseStartTime = clock.getElapsedTime();
document.getElementById('showcase-btn').textContent = "SHOWCASE ACTIVE";
document.getElementById('showcase-btn').disabled = true;
}
// Handle showcase animation
function updateShowcase() {
const elapsed = clock.getElapsedTime() - showcaseStartTime;
const progress = elapsed / 20; // 20 second showcase
if (progress >= 1) {
isShowcaseActive = false;
document.getElementById('showcase-btn').textContent = "REPLAY SHOWCASE";
document.getElementById('showcase-btn').disabled = false;
return;
}
// Four movements in the showcase:
// 1. Low over the Danube toward the UFO bridge (0-5s)
// 2. Rise along the castle hill to the Crown Tower (5-10s)
// 3. Hero arc over the Honorary Courtyard (10-15s)
// 4. Pull-back aerial with the sun flaring behind the castle (15-20s)
if (progress < 0.25) {
// Movement 1: Low over the Danube
const t = progress * 4;
camera.position.set(
-200 + t * 200,
10 + Math.sin(t * Math.PI) * 5,
400 - t * 100
);
camera.lookAt(0, 30, 300);
} else if (progress < 0.5) {
// Movement 2: Rise along the castle hill
const t = (progress - 0.25) * 4;
camera.position.set(
-50 + t * 50,
30 + t * 70,
100 - t * 50
);
camera.lookAt(0, 80, 0);
} else if (progress < 0.75) {
// Movement 3: Hero arc over the courtyard
const t = (progress - 0.5) * 4;
const angle = Math.PI + t * Math.PI;
camera.position.set(
Math.cos(angle) * 100,
80 + Math.sin(t * Math.PI) * 30,
Math.sin(angle) * 100
);
camera.lookAt(0, 50, 0);
} else {
// Movement 4: Pull-back aerial with sun flare
const t = (progress - 0.75) * 4;
camera.position.set(
Math.sin(t * 0.5) * 50,
150 + t * 50,
200 + t * 100
);
camera.lookAt(0, 80, 0);
}
}
// Handle window resize
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Handle keyboard input
function onKeyDown(event) {
if (event.key === 'f' || event.key === 'F') {
toggleCameraMode();
}
}
// Toggle between orbit and fly modes
function toggleCameraMode() {
mode = mode === 'orbit' ? 'fly' : 'orbit';
document.getElementById('mode').textContent = mode.charAt(0).toUpperCase() + mode.slice(1);
if (mode === 'fly') {
flyControls.lock();
} else {
flyControls.unlock();
}
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
// Update controls
if (mode === 'orbit' && !isShowcaseActive) {
controls.update();
}
// Update showcase if active
if (isShowcaseActive) {
updateShowcase();
}
// Update HUD
const pos = camera.position;
document.getElementById('position').textContent =
`${pos.x.toFixed(1)}, ${pos.y.toFixed(1)}, ${pos.z.toFixed(1)}`;
// Render scene
renderer.render(scene, camera);
}
// Simulate loading progress
function simulateLoading() {
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 5;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
}
document.getElementById('progress').style.width = progress + '%';
}, 100);
}
// Start loading simulation
simulateLoading();
// Initialize when page loads
window.onload = init;
</script>
</body>
</html>