LakeMinnetonkaSim / index.html
awacke1's picture
Update index.html
2044d3c verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lake Minnetonka - Mound Docks at Dusk</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
background: #1a1a2e;
overflow: hidden;
font-family: 'Georgia', serif;
}
canvas {
display: block;
}
.controls {
position: absolute;
top: 20px;
left: 20px;
color: #d4af37;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 10px;
border: 1px solid #8B4513;
z-index: 100;
}
.controls h3 {
margin: 0 0 10px 0;
color: #daa520;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
}
.controls p {
margin: 5px 0;
font-size: 14px;
text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
}
.title {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
color: #d4af37;
text-align: center;
z-index: 100;
background: rgba(0, 0, 0, 0.8);
padding: 15px 25px;
border-radius: 15px;
border: 2px solid #8B4513;
}
.title h1 {
margin: 0;
font-size: 24px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
color: #daa520;
}
.title p {
margin: 5px 0 0 0;
font-style: italic;
font-size: 16px;
text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
}
</style>
</head>
<body>
<div class="controls">
<h3>Lake Minnetonka Experience</h3>
<p>🎮 Mouse: Look around</p>
<p>⬅️➡️ Arrow keys: Move left/right</p>
<p>⬆️⬇️ Arrow keys: Move forward/back</p>
<p>🔧 WASD: Alternative movement</p>
<p>✨ Watch the fireflies rise at dusk</p>
</div>
<div class="title">
<h1>Mound Docks at Dusk</h1>
<p>"These Mound docks at dusk are almost more than I can bear..."</p>
</div>
<script>
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.fog = true;
document.body.appendChild(renderer.domElement);
// Atmospheric fog
scene.fog = new THREE.Fog(0x2d1810, 50, 800);
// Camera controls
let mouseX = 0, mouseY = 0;
let cameraRotationX = 0, cameraRotationY = 0;
const keys = {};
// Position camera on the dock
camera.position.set(0, 3, 15);
camera.lookAt(0, 0, 0);
// Create bruising dusk sky
const skyGeometry = new THREE.SphereGeometry(1000, 32, 32);
const skyMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 }
},
vertexShader: `
varying vec3 vWorldPosition;
void main() {
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
vWorldPosition = worldPosition.xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float time;
varying vec3 vWorldPosition;
void main() {
vec3 direction = normalize(vWorldPosition);
float elevation = direction.y;
// Bruising sky colors
vec3 darkPurple = vec3(0.1, 0.05, 0.2);
vec3 deepBlue = vec3(0.05, 0.1, 0.3);
vec3 rustGold = vec3(0.8, 0.4, 0.1);
vec3 bloodRed = vec3(0.6, 0.1, 0.1);
// Sun bleeding effect on horizon
float horizonGlow = exp(-abs(elevation) * 2.0);
float sunBleed = sin(direction.x * 2.0 + time * 0.5) * 0.3 + 0.7;
vec3 color = mix(darkPurple, deepBlue, elevation + 0.5);
color = mix(color, rustGold, horizonGlow * sunBleed * 0.8);
color = mix(color, bloodRed, horizonGlow * 0.3);
gl_FragColor = vec4(color, 1.0);
}
`,
side: THREE.BackSide
});
const sky = new THREE.Mesh(skyGeometry, skyMaterial);
scene.add(sky);
// Create water surface with bleeding sun reflection
const waterGeometry = new THREE.PlaneGeometry(1000, 1000, 128, 128);
const waterMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
sunPosition: { value: new THREE.Vector3(100, 10, -200) }
},
vertexShader: `
uniform float time;
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vUv = uv;
// Wave animation
vec3 pos = position;
float wave1 = sin(pos.x * 0.02 + time) * 0.3;
float wave2 = sin(pos.y * 0.015 + time * 1.3) * 0.2;
float wave3 = sin((pos.x + pos.y) * 0.01 + time * 0.8) * 0.4;
pos.z = wave1 + wave2 + wave3;
vPosition = pos;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
uniform float time;
uniform vec3 sunPosition;
varying vec2 vUv;
varying vec3 vPosition;
void main() {
// Base water color
vec3 deepWater = vec3(0.1, 0.2, 0.3);
vec3 shallowWater = vec3(0.2, 0.3, 0.4);
// Sun bleeding reflection - rust and gold
vec3 rustColor = vec3(0.8, 0.3, 0.1);
vec3 goldColor = vec3(1.0, 0.8, 0.2);
// Calculate distance from sun reflection point
vec2 sunReflection = vec2(0.0, -0.3); // Approximate sun position on water
float distToSun = length(vUv - sunReflection);
// Bleeding sun effect
float sunIntensity = exp(-distToSun * 3.0) * (sin(time) * 0.2 + 0.8);
vec3 sunBleed = mix(rustColor, goldColor, sin(time * 2.0) * 0.5 + 0.5);
// Wave reflections
float wave = sin(vPosition.x * 0.1 + time) * sin(vPosition.y * 0.08 + time * 1.2);
vec3 waterColor = mix(deepWater, shallowWater, wave * 0.5 + 0.5);
// Final color with bleeding sun
vec3 finalColor = mix(waterColor, sunBleed, sunIntensity);
gl_FragColor = vec4(finalColor, 0.9);
}
`,
transparent: true
});
waterGeometry.rotateX(-Math.PI / 2);
const water = new THREE.Mesh(waterGeometry, waterMaterial);
water.position.y = 0;
scene.add(water);
// Create wooden pier with weathered texture
function createPier() {
const pierGroup = new THREE.Group();
// Main pier planks
for (let i = 0; i < 20; i++) {
const plankGeometry = new THREE.BoxGeometry(2, 0.1, 0.8);
const plankMaterial = new THREE.MeshLambertMaterial({
color: new THREE.Color(0.4 + Math.random() * 0.2, 0.25, 0.15)
});
const plank = new THREE.Mesh(plankGeometry, plankMaterial);
plank.position.set(0, 0.5, -i * 0.9);
plank.castShadow = true;
pierGroup.add(plank);
}
// Pier supports
for (let i = 0; i < 5; i++) {
const supportGeometry = new THREE.CylinderGeometry(0.1, 0.1, 3);
const supportMaterial = new THREE.MeshLambertMaterial({ color: 0x3d2817 });
const support = new THREE.Mesh(supportGeometry, supportMaterial);
support.position.set(-0.8, -0.5, -i * 4);
pierGroup.add(support);
const support2 = support.clone();
support2.position.x = 0.8;
pierGroup.add(support2);
}
return pierGroup;
}
const pier = createPier();
scene.add(pier);
// Create pontoon boat "hanging its head"
function createPontoon() {
const pontoonGroup = new THREE.Group();
// Pontoon floats
const pontoonGeometry = new THREE.CylinderGeometry(0.3, 0.3, 8);
const pontoonMaterial = new THREE.MeshLambertMaterial({ color: 0x666666 });
const leftPontoon = new THREE.Mesh(pontoonGeometry, pontoonMaterial);
leftPontoon.rotation.z = Math.PI / 2;
leftPontoon.position.set(-1.5, 0.3, -25);
pontoonGroup.add(leftPontoon);
const rightPontoon = leftPontoon.clone();
rightPontoon.position.x = 1.5;
pontoonGroup.add(rightPontoon);
// Deck
const deckGeometry = new THREE.BoxGeometry(4, 0.1, 8);
const deckMaterial = new THREE.MeshLambertMaterial({ color: 0x8B7355 });
const deck = new THREE.Mesh(deckGeometry, deckMaterial);
deck.position.set(0, 0.8, -25);
pontoonGroup.add(deck);
// Mast for morse code slapping
const mastGeometry = new THREE.CylinderGeometry(0.05, 0.05, 6);
const mastMaterial = new THREE.MeshLambertMaterial({ color: 0x654321 });
const mast = new THREE.Mesh(mastGeometry, mastMaterial);
mast.position.set(0, 3.8, -25);
pontoonGroup.add(mast);
// Make pontoon "hang its head" - slight rotation
pontoonGroup.rotation.x = 0.1;
pontoonGroup.position.y = -0.3; // Lower than it was "back then"
return pontoonGroup;
}
const pontoon = createPontoon();
scene.add(pontoon);
// Create plastic chair with sweating beer
function createChairAndBeer() {
const chairGroup = new THREE.Group();
// Plastic chair
const seatGeometry = new THREE.BoxGeometry(1.5, 0.1, 1.5);
const chairMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
const seat = new THREE.Mesh(seatGeometry, chairMaterial);
seat.position.set(3, 1, 8);
chairGroup.add(seat);
// Chair back
const backGeometry = new THREE.BoxGeometry(1.5, 2, 0.1);
const back = new THREE.Mesh(backGeometry, chairMaterial);
back.position.set(3, 2, 7.3);
chairGroup.add(back);
// Chair legs
for (let i = 0; i < 4; i++) {
const legGeometry = new THREE.CylinderGeometry(0.03, 0.03, 1);
const leg = new THREE.Mesh(legGeometry, chairMaterial);
leg.position.set(
3 + (i % 2 ? 0.6 : -0.6),
0.5,
8 + (i < 2 ? 0.6 : -0.6)
);
chairGroup.add(leg);
}
// Beer can
const beerGeometry = new THREE.CylinderGeometry(0.15, 0.15, 0.5);
const beerMaterial = new THREE.MeshLambertMaterial({ color: 0xDAA520 });
const beer = new THREE.Mesh(beerGeometry, beerMaterial);
beer.position.set(3.8, 1.35, 8);
chairGroup.add(beer);
return chairGroup;
}
const chairAndBeer = createChairAndBeer();
scene.add(chairAndBeer);
// Create scattered tackle and rusty magnet
function createTackle() {
const tackleGroup = new THREE.Group();
// Scattered tackle items
for (let i = 0; i < 15; i++) {
const tackleGeometry = new THREE.SphereGeometry(0.1, 8, 8);
const tackleMaterial = new THREE.MeshLambertMaterial({
color: new THREE.Color(Math.random(), Math.random(), Math.random())
});
const tackle = new THREE.Mesh(tackleGeometry, tackleMaterial);
tackle.position.set(
(Math.random() - 0.5) * 10,
0.6,
Math.random() * 5
);
tackleGroup.add(tackle);
}
// Rusty magnet on fraying twine
const magnetGeometry = new THREE.BoxGeometry(0.3, 0.1, 0.3);
const magnetMaterial = new THREE.MeshLambertMaterial({ color: 0x8B4513 });
const magnet = new THREE.Mesh(magnetGeometry, magnetMaterial);
magnet.position.set(-2, 0.6, 5);
tackleGroup.add(magnet);
return tackleGroup;
}
const tackle = createTackle();
scene.add(tackle);
// Create mansions on the shore
function createMansions() {
const mansionGroup = new THREE.Group();
for (let i = 0; i < 5; i++) {
const mansionGeometry = new THREE.BoxGeometry(
8 + Math.random() * 4,
6 + Math.random() * 4,
12 + Math.random() * 6
);
const mansionMaterial = new THREE.MeshLambertMaterial({
color: new THREE.Color(0.9, 0.9, 0.8)
});
const mansion = new THREE.Mesh(mansionGeometry, mansionMaterial);
mansion.position.set(
40 + i * 25 + Math.random() * 10,
mansion.geometry.parameters.height / 2,
-100 + Math.random() * 200
);
mansion.castShadow = true;
mansionGroup.add(mansion);
}
return mansionGroup;
}
const mansions = createMansions();
scene.add(mansions);
// Fireflies rising like embers
const fireflies = [];
const fireflyGeometry = new THREE.SphereGeometry(0.1, 8, 8);
const fireflyMaterial = new THREE.MeshBasicMaterial({
color: 0xFFFF80,
transparent: true,
opacity: 0.8
});
for (let i = 0; i < 100; i++) {
const firefly = new THREE.Mesh(fireflyGeometry, fireflyMaterial);
firefly.position.set(
(Math.random() - 0.5) * 100,
Math.random() * 2,
(Math.random() - 0.5) * 100
);
firefly.userData = {
speed: Math.random() * 0.02 + 0.01,
phase: Math.random() * Math.PI * 2
};
fireflies.push(firefly);
scene.add(firefly);
}
// Lighting setup
const ambientLight = new THREE.AmbientLight(0x404060, 0.3);
scene.add(ambientLight);
// Sunset directional light (bleeding sun)
const sunLight = new THREE.DirectionalLight(0xFF6B35, 0.8);
sunLight.position.set(100, 20, -200);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 2048;
sunLight.shadow.mapSize.height = 2048;
scene.add(sunLight);
// Warm dusk light
const duskLight = new THREE.DirectionalLight(0xDAA520, 0.4);
duskLight.position.set(-50, 30, 100);
scene.add(duskLight);
// Point light for fireflies area
const fireflyLight = new THREE.PointLight(0xFFFF80, 0.5, 30);
fireflyLight.position.set(0, 5, 0);
scene.add(fireflyLight);
// Controls
document.addEventListener('mousemove', (event) => {
mouseX = (event.clientX / window.innerWidth) * 2 - 1;
mouseY = (event.clientY / window.innerHeight) * 2 - 1;
});
document.addEventListener('keydown', (event) => {
keys[event.code] = true;
});
document.addEventListener('keyup', (event) => {
keys[event.code] = false;
});
// Animation loop
let time = 0;
function animate() {
requestAnimationFrame(animate);
time += 0.01;
// Update shaders
skyMaterial.uniforms.time.value = time;
waterMaterial.uniforms.time.value = time;
// Camera controls
cameraRotationY += (mouseX * 0.5 - cameraRotationY) * 0.05;
cameraRotationX += (mouseY * 0.3 - cameraRotationX) * 0.05;
cameraRotationX = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, cameraRotationX));
// Movement
const moveSpeed = 0.3;
if (keys['ArrowUp'] || keys['KeyW']) {
camera.position.z -= Math.cos(cameraRotationY) * moveSpeed;
camera.position.x -= Math.sin(cameraRotationY) * moveSpeed;
}
if (keys['ArrowDown'] || keys['KeyS']) {
camera.position.z += Math.cos(cameraRotationY) * moveSpeed;
camera.position.x += Math.sin(cameraRotationY) * moveSpeed;
}
if (keys['ArrowLeft'] || keys['KeyA']) {
camera.position.x -= Math.cos(cameraRotationY) * moveSpeed;
camera.position.z += Math.sin(cameraRotationY) * moveSpeed;
}
if (keys['ArrowRight'] || keys['KeyD']) {
camera.position.x += Math.cos(cameraRotationY) * moveSpeed;
camera.position.z -= Math.sin(cameraRotationY) * moveSpeed;
}
// Apply camera rotation
camera.rotation.x = cameraRotationX;
camera.rotation.y = cameraRotationY;
// Animate fireflies rising like embers
fireflies.forEach((firefly, index) => {
firefly.position.y += firefly.userData.speed;
firefly.position.x += Math.sin(time + firefly.userData.phase) * 0.01;
firefly.position.z += Math.cos(time + firefly.userData.phase) * 0.01;
// Reset fireflies that get too high
if (firefly.position.y > 20) {
firefly.position.y = 0;
firefly.position.x = (Math.random() - 0.5) * 100;
firefly.position.z = (Math.random() - 0.5) * 100;
}
// Flickering effect
firefly.material.opacity = 0.3 + Math.sin(time * 5 + index) * 0.5;
});
// Gentle pier swaying
pier.rotation.z = Math.sin(time * 0.5) * 0.02;
// Pontoon bobbing (breathing slower)
pontoon.position.y = -0.3 + Math.sin(time * 0.3) * 0.1;
pontoon.rotation.x = 0.1 + Math.sin(time * 0.4) * 0.05;
// Mast slapping animation (morse code)
const mast = pontoon.children.find(child => child.geometry?.parameters?.height === 6);
if (mast) {
mast.rotation.z = Math.sin(time * 2) * 0.1;
}
renderer.render(scene, camera);
}
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Start animation
animate();
</script>
</body>
</html>