<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interactive Globe (Revised)</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <!-- UI remains largely the same -->
    <div id="uiContainer">
        <div class="control-box search-box">
            <input type="text" id="searchInput" placeholder="Search for a place...">
            <button id="searchButton">Search</button>
            <p id="status">Initializing...</p> <!-- Start with Initializing -->
        </div>

        <div class="control-box layer-toggles">
            <h4>Layers:</h4>
            <label><input type="checkbox" id="toggleClouds" checked> Clouds</label><br>
            <label><input type="checkbox" id="toggleAtmosphere" checked> Atmosphere</label><br>
             <label><input type="checkbox" id="toggleStars" checked> Stars</label><br>
            <label><input type="checkbox" id="toggleMarkers" checked> Markers</label><br>
            <label><input type="checkbox" id="toggleAutorotate"> Auto-Rotate</label>
        </div>

        <div id="markerInfoBox" class="control-box marker-info" style="display: none;">
            <h4>Marker Info</h4>
            <p id="markerName">Name: -</p>
            <p id="markerCoords">Coords: -</p>
            <button id="closeMarkerInfo">Close</button>
            <button id="removeMarker">Remove</button>
        </div>
    </div>

    <div id="globeContainer"></div>

    <!-- Libraries (Ensure these are accessible) -->
    <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.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/18.6.4/tween.umd.js"></script>
    <!-- Post-processing scripts removed for now -->

    <script src="script.js"></script>
</body>
</html>
/* Use the same style.css as provided in the previous "Advanced" example */
body {
    margin: 0;
    overflow: hidden;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #000;
    color: #fff;
    font-size: 14px;
}

#globeContainer {
    width: 100vw;
    height: 100vh;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
    cursor: grab;
}
#globeContainer:active {
    cursor: grabbing;
}


#uiContainer {
    position: absolute;
    top: 0;
    left: 0;
    padding: 10px;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.control-box {
    background-color: rgba(0, 0, 0, 0.65);
    padding: 12px;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.2);
    max-width: 250px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}

.search-box input {
    padding: 8px;
    margin-right: 5px;
    border: 1px solid #555;
    background-color: #333;
    color: #fff;
    border-radius: 4px;
    width: calc(100% - 80px); /* Adjust based on button width */
}

.search-box button {
    padding: 8px 12px;
    cursor: pointer;
    background-color: #007bff;
    border: none;
    color: white;
    border-radius: 4px;
    transition: background-color 0.2s ease;
}
.search-box button:hover {
    background-color: #0056b3;
}

#status {
    margin-top: 8px;
    font-size: 0.9em;
    min-height: 1.2em;
    color: #ccc;
}

.layer-toggles h4, .marker-info h4 {
    margin-top: 0;
    margin-bottom: 8px;
    border-bottom: 1px solid #555;
    padding-bottom: 5px;
}

.layer-toggles label {
    display: inline-block; /* Changed from block */
    margin-bottom: 5px;
    margin-right: 10px; /* Added spacing */
    cursor: pointer;
}
.layer-toggles input[type="checkbox"] {
    margin-right: 5px;
    vertical-align: middle;
}

.marker-info p {
    margin: 5px 0;
    word-wrap: break-word;
}

.marker-info button {
    margin-top: 10px;
    padding: 5px 10px;
    cursor: pointer;
    margin-right: 5px;
    border-radius: 4px;
    border: none;
}
#closeMarkerInfo {
    background-color: #6c757d;
    color: white;
}
#removeMarker {
    background-color: #dc3545;
    color: white;
}
#closeMarkerInfo:hover { background-color: #5a6268; }
#removeMarker:hover { background-color: #c82333; }

/* Simple loading indicator */
#status:contains("Loading")::after,
#status:contains("Searching")::after,
#status:contains("Initializing")::after {
  content: '...';
  display: inline-block;
  animation: ellipsis 1.2s infinite;
  width: 1.5em; /* Reserve space */
  text-align: left;
}

@keyframes ellipsis {
  0% { content: '.'; }
  33% { content: '..'; }
  66% { content: '...'; }
}
// --- Strict mode and Library Check ---
'use strict';
if (typeof THREE === 'undefined' || typeof TWEEN === 'undefined' || typeof THREE.OrbitControls === 'undefined') {
    console.error("FATAL: Required libraries (Three.js, Tween.js, OrbitControls) not loaded!");
    alert("Error: Could not load 3D libraries. Please check console (F12) and ensure all script tags in HTML are correct and accessible.");
    document.getElementById('status').innerText = "Error: Failed to load 3D components.";
    throw new Error("Missing critical libraries");
}
console.log("Libraries loaded successfully.");

// --- Constants ---
const GLOBE_RADIUS = 5;
const CAMERA_START_DISTANCE = 15;
const CLOUD_ALTITUDE = 0.03;
const ATMOSPHERE_ALTITUDE = 0.4;
const MARKER_ALTITUDE = 0.05;
const MARKER_HEIGHT = 0.2;
const MARKER_RADIAL_SEGMENTS = 16;
const MARKER_COLOR = 0xff4400; // Bright orange-red
const STAR_RADIUS = 90;
const ROTATION_SPEED = 0.0003;
const CLOUD_ROTATION_SPEED = 0.0004;
const PULSE_SPEED = 1.5;

// --- DOM Elements ---
const globeContainer = document.getElementById('globeContainer');
const searchInput = document.getElementById('searchInput');
const searchButton = document.getElementById('searchButton');
const statusElement = document.getElementById('status');
const toggleClouds = document.getElementById('toggleClouds');
const toggleAtmosphere = document.getElementById('toggleAtmosphere');
const toggleStars = document.getElementById('toggleStars'); // Added
const toggleMarkers = document.getElementById('toggleMarkers');
const toggleAutorotate = document.getElementById('toggleAutorotate');
const markerInfoBox = document.getElementById('markerInfoBox');
const markerNameElement = document.getElementById('markerName');
const markerCoordsElement = document.getElementById('markerCoords');
const closeMarkerInfoButton = document.getElementById('closeMarkerInfo');
const removeMarkerButton = document.getElementById('removeMarker');

// --- Three.js Variables ---
let scene, camera, renderer, controls;
let globe, clouds, atmosphere, starField;
let pointLight, ambientLight;
let markersGroup;
let activeMarker = null;
let isAutoRotating = false;
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
let animationFrameId = null; // To manage animation loop

// --- Shaders (Atmosphere - Kept the definition, but ensure it compiles) ---
const vertexShader = `
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
    vNormal = normalize( normalMatrix * normal );
    vPosition = (modelViewMatrix * vec4( position, 1.0 )).xyz;
    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`;
const fragmentShader = `
uniform vec3 uColor;
uniform float uOpacity;
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
    vec3 viewDirection = normalize( -vPosition );
    float intensity = pow( 1.0 - dot( vNormal, viewDirection ), 2.5 );
    gl_FragColor = vec4( uColor, intensity * uOpacity );
}`;

// --- Initialization ---
function init() {
    console.log("init() called");
    statusElement.innerText = "Initializing Scene...";

    // Scene
    scene = new THREE.Scene();
    console.log("Scene created");

    // Camera
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.z = CAMERA_START_DISTANCE;
    console.log("Camera created");

    // Renderer
    try {
        renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false }); // alpha: false might be slightly faster if no transparency needed behind canvas
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setClearColor(0x000000, 1); // Explicitly black
        globeContainer.appendChild(renderer.domElement);
        console.log("Renderer created and attached to DOM");
    } catch (error) {
        console.error("FATAL: Failed to create WebGL Renderer:", error);
        statusElement.innerText = "Error: WebGL not supported or enabled.";
        alert("Error: Could not initialize WebGL. Your browser might not support it, or it might be disabled.");
        return; // Stop initialization if renderer fails
    }


    // Controls
    controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.05;
    controls.minDistance = GLOBE_RADIUS + 0.5; // Keep slightly further away
    controls.maxDistance = 50;
    controls.autoRotate = false;
    console.log("OrbitControls created");

    // Lighting
    ambientLight = new THREE.AmbientLight(0x777777); // Slightly brighter ambient
    scene.add(ambientLight);
    pointLight = new THREE.PointLight(0xffffff, 1.0, 400);
    pointLight.position.set(50, 30, 50); // Adjust light position
    scene.add(pointLight);
    console.log("Lighting added");

    // Starfield (Create first, add later if texture loads)
    createStarfield();

    // Markers Group
    markersGroup = new THREE.Group();
    scene.add(markersGroup);
    console.log("Markers group added");

    // --- Central Globe Creation ---
    createGlobeAndAssets(); // Start loading process

    // Event Listeners
    setupEventListeners();
    console.log("Event listeners setup");

    // Start animation loop ONLY after globe is potentially ready
    // The call is moved inside createGlobeAndAssets's callbacks
}

// --- Asset Creation ---
function createGlobeAndAssets() {
    statusElement.innerText = "Loading Earth texture...";
    const textureLoader = new THREE.TextureLoader();
    const earthTextureUrl = 'https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73909/world.topo.bathy.200412.3x5400x2700.jpg';
    const cloudTextureUrl = 'https://raw.githubusercontent.com/turban/webgl-earth/master/images/fair_clouds_4k.png';

    let globeMeshCreated = false; // Flag to ensure animation starts only once

    // 1. Globe Geometry (defined once)
    const globeGeometry = new THREE.SphereGeometry(GLOBE_RADIUS, 64, 64);
    console.log("Globe geometry created");

    // 2. Globe Material and Mesh (async loading)
    textureLoader.load(
        earthTextureUrl,
        (texture) => { // onLoad
            console.log("Earth texture loaded successfully!");
            statusElement.innerText = "Creating Globe...";
            const globeMaterial = new THREE.MeshStandardMaterial({
                map: texture,
                metalness: 0.1,
                roughness: 0.8,
                name: "GlobeMaterial" // For debugging
            });
            globe = new THREE.Mesh(globeGeometry, globeMaterial);
            globe.name = "Globe";
            scene.add(globe);
            console.log("Globe mesh with texture added to scene.");
            globeMeshCreated = true;
            loadOptionalAssets(textureLoader, cloudTextureUrl); // Load clouds etc. *after* globe
            startAnimationLoop(); // Start animation now
        },
        (xhr) => { // onProgress
            const percentComplete = (xhr.loaded / xhr.total) * 100;
            statusElement.innerText = `Loading Earth: ${Math.round(percentComplete)}%`;
        },
        (error) => { // onError
            console.error('FATAL: Error loading Earth texture:', error);
            statusElement.innerText = "Error loading Earth texture. Using fallback.";
            const fallbackMaterial = new THREE.MeshStandardMaterial({
                color: 0x2266ff, // Bright blue fallback
                metalness: 0.1,
                roughness: 0.8,
                name: "FallbackMaterial"
            });
            globe = new THREE.Mesh(globeGeometry, fallbackMaterial);
            globe.name = "Globe (Fallback)";
            scene.add(globe);
            console.warn("Globe mesh with FALLBACK material added to scene.");
            globeMeshCreated = true;
            loadOptionalAssets(textureLoader, cloudTextureUrl); // Still load other assets
            startAnimationLoop(); // Start animation now
        }
    );

    // Create Atmosphere (add it regardless of texture loading)
    createAtmosphere();
}

function loadOptionalAssets(textureLoader, cloudTextureUrl) {
    console.log("Loading optional assets (Clouds, Stars texture)...");

    // Add Starfield to scene (geometry was created earlier)
    if (starField) {
        textureLoader.load(
            //'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/starfield.jpg',
            'https://star-map.herokuapp.com/map', // Alternative star map source
            (texture) => {
                console.log("Starfield texture loaded.");
                starField.material.map = texture;
                starField.material.needsUpdate = true;
                scene.add(starField); // Add only after texture loaded
                 toggleStars.checked = true; // Ensure checkbox matches
                 starField.visible = true;
            },
            undefined,
            (error) => {
                console.error("Error loading starfield texture:", error);
                toggleStars.checked = false; // Uncheck if loading fails
                toggleStars.disabled = true;
                 if(starField) scene.remove(starField); // Remove mesh if texture fails
                 starField = null;
            }
        );
    }


    // Clouds (only if globe exists)
    if (!globe) return; // Safety check
    statusElement.innerText = "Loading cloud texture...";
    const cloudGeometry = new THREE.SphereGeometry(GLOBE_RADIUS + CLOUD_ALTITUDE, 64, 64);
    textureLoader.load(cloudTextureUrl,
        (texture) => { // onLoad
            console.log("Cloud texture loaded.");
            statusElement.innerText = "Creating clouds...";
            const cloudMaterial = new THREE.MeshStandardMaterial({
                map: texture,
                alphaMap: texture,
                transparent: true,
                opacity: 0.7,
                depthWrite: false,
            });
            clouds = new THREE.Mesh(cloudGeometry, cloudMaterial);
            clouds.name = "Clouds";
            scene.add(clouds);
            console.log("Clouds mesh added to scene.");
             toggleClouds.checked = true; // Ensure checkbox matches
             clouds.visible = true;
             statusElement.innerText = "Globe loaded."; // Update status finally
        },
        undefined, // onProgress (optional)
        (error) => { // onError
            console.error('Error loading Cloud texture:', error);
            statusElement.innerText = "Warning: Could not load clouds.";
             toggleClouds.checked = false; // Uncheck if loading fails
             toggleClouds.disabled = true;
        }
    );
}

function createStarfield() {
    // Create geometry/material but don't load texture/add to scene yet
    try {
        const starGeometry = new THREE.SphereGeometry(STAR_RADIUS, 64, 64);
        const starMaterial = new THREE.MeshBasicMaterial({
            // Map will be loaded in loadOptionalAssets
            side: THREE.BackSide,
            depthWrite: false,
            color: 0xaaaaaa, // Placeholder color until texture loads
            name: "StarMaterial"
        });
        starField = new THREE.Mesh(starGeometry, starMaterial);
        starField.name = "StarField";
        starField.visible = false; // Initially hidden
        console.log("Starfield geometry/material created.");
    } catch(error) {
        console.error("Error creating starfield geometry/material:", error);
        starField = null;
    }
}

function createAtmosphere() {
    // Atmosphere (can be added immediately as it doesn't rely on textures)
    try {
        const atmosphereGeometry = new THREE.SphereGeometry(GLOBE_RADIUS + ATMOSPHERE_ALTITUDE, 64, 64);
        const atmosphereMaterial = new THREE.ShaderMaterial({
            vertexShader: vertexShader,
            fragmentShader: fragmentShader,
            uniforms: {
                uColor: { value: new THREE.Color(0x87ceeb) },
                uOpacity: { value: 0.6 }
            },
            transparent: true,
            blending: THREE.AdditiveBlending,
            side: THREE.BackSide,
            depthWrite: false,
            name: "AtmosphereMaterial"
        });
        atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
        atmosphere.name = "Atmosphere";
        scene.add(atmosphere);
        console.log("Atmosphere mesh added to scene.");
    } catch (error) {
        console.error("Error creating atmosphere shader/mesh:", error);
        statusElement.innerText = "Warning: Atmosphere effect failed.";
        toggleAtmosphere.checked = false;
        toggleAtmosphere.disabled = true;
    }
}

// --- Marker Functions (Mostly unchanged) ---

function latLonToVector3(lat, lon, radius = GLOBE_RADIUS) {
    const phi = (90 - lat) * (Math.PI / 180);
    const theta = (lon + 180) * (Math.PI / 180);
    const x = -(radius * Math.sin(phi) * Math.cos(theta));
    const y = radius * Math.cos(phi);
    const z = radius * Math.sin(phi) * Math.sin(theta);
    return new THREE.Vector3(x, y, z);
}

function vector3ToLatLon(vector) {
    const normalizedVector = vector.clone().normalize();
    const lat = 90 - (Math.acos(normalizedVector.y) * 180 / Math.PI);
    let lon = ((Math.atan2(normalizedVector.z, -normalizedVector.x) * 180 / Math.PI)) - 180;
    if (lon < -180) lon += 360;
    if (lon > 180) lon -= 360;
    return { lat, lon };
}

function addMarker(lat, lon, name = "Selected Location") {
    if (!markersGroup) return null; // Safety check

    const markerPosition = latLonToVector3(lat, lon, GLOBE_RADIUS + MARKER_ALTITUDE);
    const markerBasePosition = latLonToVector3(lat, lon, GLOBE_RADIUS);

    const markerGeometry = new THREE.ConeGeometry(0.05, MARKER_HEIGHT, MARKER_RADIAL_SEGMENTS);
    markerGeometry.translate(0, MARKER_HEIGHT / 2, 0);
    const markerMaterial = new THREE.MeshBasicMaterial({ color: MARKER_COLOR });

    const markerMesh = new THREE.Mesh(markerGeometry, markerMaterial);
    markerMesh.position.copy(markerPosition);
    markerMesh.lookAt(markerBasePosition.multiplyScalar(1.1)); // Point outwards
    markerMesh.rotateX(Math.PI / 2);

    markerMesh.userData = {
        id: THREE.MathUtils.generateUUID(),
        name: name, lat: lat, lon: lon, isMarker: true,
        baseScale: 1.0, pulseTime: Math.random() * PULSE_SPEED
    };

    markersGroup.add(markerMesh);
    console.log(`Marker added: ${name} (${lat.toFixed(4)}, ${lon.toFixed(4)})`);
    return markerMesh;
}

function showMarkerInfo(marker) {
    if (!marker || !marker.userData) return;
    activeMarker = marker;
    markerNameElement.textContent = `Name: ${marker.userData.name}`;
    markerCoordsElement.textContent = `Coords: ${marker.userData.lat.toFixed(4)}, ${marker.userData.lon.toFixed(4)}`;
    markerInfoBox.style.display = 'block';
}

function hideMarkerInfo() {
    activeMarker = null;
    markerInfoBox.style.display = 'none';
}

function removeActiveMarker() {
    if (activeMarker && markersGroup) {
        const markerToRemove = activeMarker; // Keep reference
        hideMarkerInfo(); // Hide box first, setting activeMarker to null
        markersGroup.remove(markerToRemove);
        // Optional: Dispose geometry/material if they are unique and not reused
        // markerToRemove.geometry.dispose();
        // markerToRemove.material.dispose();
        console.log(`Marker removed: ${markerToRemove.userData.name}`);
    }
}


// --- Interaction & Geocoding (Mostly unchanged, added User-Agent reminder) ---

function onGlobeClick(event) {
    if (!globe || !raycaster || !camera || !markersGroup) return; // Safety checks

    // Use offsetX/offsetY for position relative to the container
    const rect = globeContainer.getBoundingClientRect();
    mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
    mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);

    // 1. Check markers
    const markerIntersects = raycaster.intersectObjects(markersGroup.children);
    if (markerIntersects.length > 0 && markerIntersects[0].object.userData.isMarker) {
        const clickedMarker = markerIntersects[0].object;
        showMarkerInfo(clickedMarker);
        animateCameraTo(clickedMarker.userData.lat, clickedMarker.userData.lon);
        return;
    }

    // 2. Check globe
    const globeIntersects = raycaster.intersectObject(globe); // Use the main globe mesh variable
    if (globeIntersects.length > 0) {
        hideMarkerInfo();
        const intersectPoint = globeIntersects[0].point;
        const coords = vector3ToLatLon(intersectPoint);
        addMarker(coords.lat, coords.lon, `Lat: ${coords.lat.toFixed(2)}, Lon: ${coords.lon.toFixed(2)}`);
        // Optional: reverseGeocode(coords.lat, coords.lon, newMarker); // Call if needed
    } else {
        hideMarkerInfo(); // Clicked outside everything
    }
}

function animateCameraTo(lat, lon, duration = 1500) {
    if (!controls || !camera) return;

    const targetSurfacePos = latLonToVector3(lat, lon, GLOBE_RADIUS);
    const targetCamDistance = Math.max(controls.minDistance, camera.position.length());
    const targetCameraPos = latLonToVector3(lat, lon, GLOBE_RADIUS + targetCamDistance - GLOBE_RADIUS );

    TWEEN.removeAll(); // Stop existing tweens

    new TWEEN.Tween(camera.position)
        .to({ x: targetCameraPos.x, y: targetCameraPos.y, z: targetCameraPos.z }, duration)
        .easing(TWEEN.Easing.Quadratic.InOut)
        .start();

    new TWEEN.Tween(controls.target)
        .to({ x: targetSurfacePos.x, y: targetSurfacePos.y, z: targetSurfacePos.z }, duration)
        .easing(TWEEN.Easing.Quadratic.InOut)
        .onComplete(() => controls.update()) // Ensure controls know the new target
        .start();
}

async function searchPlace(query) {
    if (!query) return;
    statusElement.innerText = `Searching for "${query}"...`;
    hideMarkerInfo();

    // ***** IMPORTANT: REPLACE with YOUR User-Agent! *****
    const userAgent = 'InteractiveGlobeDemo/1.2 (your-contact-email@example.com)'; // CHANGE THIS
    // *****************************************************

    if (userAgent.includes('your-contact-email@example.com')) {
         console.warn("Please update the User-Agent string in script.js for Nominatim API calls.");
         statusElement.innerText = "Search disabled: Update User-Agent in code.";
         alert("Developer: Please set a unique User-Agent in script.js before using the search feature (required by Nominatim policy).");
         return;
    }

    const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=1&addressdetails=1`;

    try {
        const response = await fetch(url, { headers: { 'User-Agent': userAgent } });
        if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        const data = await response.json();

        if (data && data.length > 0) {
            const place = data[0];
            const lat = parseFloat(place.lat);
            const lon = parseFloat(place.lon);
            const displayName = place.display_name;
            statusElement.innerText = `Found: ${displayName.substring(0, 50)}...`;

            const markerName = place.address?.name || place.address?.city || place.address?.state || query;
            const newMarker = addMarker(lat, lon, markerName);
            animateCameraTo(lat, lon);
            if (newMarker) showMarkerInfo(newMarker);

        } else {
            statusElement.innerText = `Could not find "${query}".`;
        }
    } catch (error) {
        statusElement.innerText = `Search failed. See console.`;
        console.error("Error during geocoding search:", error);
    }
}

// --- Event Listener Setup ---
function setupEventListeners() {
    window.addEventListener('resize', onWindowResize, false);
    // Click listener is sensitive, use mouseup to avoid firing after drag
    let isDragging = false;
    globeContainer.addEventListener('mousedown', () => { isDragging = false; }, false);
    globeContainer.addEventListener('mousemove', () => { isDragging = true; }, false);
    globeContainer.addEventListener('mouseup', (event) => {
         if (!isDragging) {
             onGlobeClick(event);
         }
         isDragging = false;
     }, false);


    searchButton.addEventListener('click', () => searchPlace(searchInput.value));
    searchInput.addEventListener('keypress', (event) => {
        if (event.key === 'Enter') searchPlace(searchInput.value);
    });

    // Layer Toggles
    toggleClouds.addEventListener('change', () => { if (clouds) clouds.visible = toggleClouds.checked; });
    toggleAtmosphere.addEventListener('change', () => { if (atmosphere) atmosphere.visible = toggleAtmosphere.checked; });
    toggleStars.addEventListener('change', () => { if (starField) starField.visible = toggleStars.checked; });
    toggleMarkers.addEventListener('change', () => { if (markersGroup) markersGroup.visible = toggleMarkers.checked; });
    toggleAutorotate.addEventListener('change', () => {
        isAutoRotating = toggleAutorotate.checked;
        controls.autoRotate = isAutoRotating;
    });

    // Marker Info Box Buttons
    closeMarkerInfoButton.addEventListener('click', hideMarkerInfo);
    removeMarkerButton.addEventListener('click', removeActiveMarker);
}

// --- Window Resize ---
function onWindowResize() {
    if (!camera || !renderer) return;
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
    console.log("Window resized");
}

// --- Animation Loop ---
function startAnimationLoop() {
    if (animationFrameId === null) { // Prevent multiple loops
        console.log("Starting animation loop.");
        animate();
    } else {
        console.log("Animation loop already running.");
    }
}

function animate(time) {
    // Ensure loop continues
    animationFrameId = requestAnimationFrame(animate);

    // Required updates
    TWEEN.update(time);
    controls.update(); // Updates damping, auto-rotate

    const dt = time * 0.001; // Delta time in seconds (useful but optional here)

    // Optional rotations (if not using controls.autoRotate)
    // if (globe && !isAutoRotating) { globe.rotation.y += ROTATION_SPEED; }
    if (clouds && clouds.visible) {
        clouds.rotation.y += CLOUD_ROTATION_SPEED;
    }

    // Marker pulse
    if (markersGroup && markersGroup.visible) {
        markersGroup.children.forEach(marker => {
            if (marker.userData.isMarker) {
                marker.userData.pulseTime += dt || (1/60); // Use delta or estimate
                const pulseScale = marker.userData.baseScale + Math.sin(marker.userData.pulseTime * PULSE_SPEED) * 0.1;
                marker.scale.set(pulseScale, pulseScale, pulseScale);
            }
        });
    }


    // Render the scene
    if (renderer && scene && camera) {
        renderer.render(scene, camera);
    } else {
         console.error("Render call skipped: Renderer, Scene or Camera not ready.");
         // Optionally stop the loop if core components missing: cancelAnimationFrame(animationFrameId); animationFrameId = null;
    }
}

// --- Start ---
try {
    init();
} catch (error) {
    console.error("Error during initialization:", error);
    // Display a user-friendly message if init fails catastrophically
    document.body.innerHTML = `<div style="padding: 20px; color: red; background: black; font-family: sans-serif;">
        <h2>Initialization Failed</h2>
        <p>Could not start the 3D globe. Please check the developer console (F12) for errors.</p>
        <p>Possible issues: WebGL not supported, network error loading resources, or a script error.</p>
        <p>Error details: ${error.message}</p>
    </div>`;
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.