<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SixthSensor</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
            background: #ffffff;
            color: #1a1a1a;
            overflow-x: hidden;
        }
        
        /* Navigation */
        nav {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            background: rgba(255, 255, 255, 0.95);
            backdrop-filter: blur(10px);
            border-bottom: 1px solid #e5e5e5;
            z-index: 1000;
            padding: 20px 40px;
        }
        
        .nav-content {
            max-width: 1200px;
            margin: 0 auto;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        
        .logo {
            font-size: 24px;
            font-weight: 700;
            color: #1a1a1a;
        }
        
        .nav-tabs {
            display: flex;
            gap: 40px;
        }
        
        .nav-tab {
            cursor: pointer;
            padding: 8px 16px;
            border-radius: 6px;
            transition: all 0.3s ease;
            font-weight: 500;
            color: #666;
        }
        
        .nav-tab:hover {
            color: #1a1a1a;
            background: #f5f5f5;
        }
        
        .nav-tab.active {
            color: #1a1a1a;
            background: #e5e5e5;
        }
        
        /* Canvas Container */
        #canvas-container {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 90vh;
            z-index: 0;
        }
        
        #canvas-container::after {
            content: '';
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            height: 100px;
            background: linear-gradient(to bottom, transparent, rgba(255, 255, 255, 0.3));
            pointer-events: none;
            z-index: 1;
        }
        
        canvas {
            display: block;
        }
        
        /* Visibility Controls */
        .visibility-controls {
            position: fixed;
            top: 100px;
            right: 20px;
            background: rgba(255, 255, 255, 0.95);
            backdrop-filter: blur(10px);
            border: 1px solid #e5e5e5;
            border-radius: 8px;
            padding: 16px;
            z-index: 100;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        }
        
        .visibility-controls h3 {
            margin: 0 0 12px 0;
            font-size: 14px;
            font-weight: 600;
            color: #1a1a1a;
        }
        
        .control-item {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
        }
        
        .control-item:last-child {
            margin-bottom: 0;
        }
        
        .control-item input[type="checkbox"] {
            width: 16px;
            height: 16px;
            cursor: pointer;
        }
        
        .control-item label {
            font-size: 13px;
            color: #333;
            cursor: pointer;
            user-select: none;
        }
        
        /* Content */
        .content-wrapper {
            position: relative;
            z-index: 10;
            margin-top: 75vh;
            min-height: calc(100vh - 75vh);
            background: #ffffff;
            padding-top: 40px;
        }
        
        .content-section {
            display: none;
        }
        
        .content-section.active {
            display: block;
        }
        
        /* All pages use centered card layout */
        .content-section.active {
            display: block;
            max-width: 900px;
            margin: 0 auto 60px;
            padding: 0 40px;
        }
        
        .content-card {
            background: rgba(255, 255, 255, 0.98);
            backdrop-filter: blur(10px);
            border-radius: 16px;
            box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
            padding: 60px;
            animation: fadeIn 0.6s ease;
        }
        
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(20px); }
            to { opacity: 1; transform: translateY(0); }
        }
        
        h1 {
            font-size: 48px;
            margin-bottom: 24px;
            font-weight: 700;
        }
        
        h2 {
            font-size: 32px;
            margin: 40px 0 20px;
            font-weight: 600;
        }
        
        p {
            font-size: 18px;
            line-height: 1.8;
            margin-bottom: 20px;
            color: #444;
        }
        
        .feature-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
            gap: 24px;
            margin: 40px 0;
        }
        
        .feature-card {
            padding: 24px;
            background: #f8f8f8;
            border-radius: 12px;
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }
        
        .feature-card:hover {
            transform: translateY(-4px);
            box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
        }
        
        .feature-card h3 {
            font-size: 20px;
            margin-bottom: 12px;
        }
        
        .feature-card p {
            font-size: 14px;
            color: #666;
        }
        
        /* Form */
        form {
            display: flex;
            flex-direction: column;
            gap: 20px;
            margin-top: 40px;
        }
        
        input, textarea {
            padding: 14px;
            border: 1px solid #e5e5e5;
            border-radius: 8px;
            font-size: 16px;
            font-family: inherit;
            transition: border 0.3s ease;
        }
        
        input:focus, textarea:focus {
            outline: none;
            border-color: #1a1a1a;
        }
        
        textarea {
            min-height: 120px;
            resize: vertical;
        }
        
        button {
            padding: 14px 32px;
            background: #1a1a1a;
            color: white;
            border: none;
            border-radius: 8px;
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        
        button:hover {
            background: #333;
            transform: translateY(-2px);
        }
        
        @media (max-width: 768px) {
            #canvas-container {
                height: 80vh;
            }
            
            .content-wrapper {
                margin-top: 80vh;
            }
            
            nav {
                padding: 16px 20px;
            }
            
            .nav-tabs {
                gap: 20px;
            }
            
            .nav-tab {
                padding: 6px 12px;
                font-size: 14px;
            }
            
            .content-section.active {
                padding: 0 20px;
            }
            
            .content-card {
                padding: 40px 24px;
            }
            
            h1 {
                font-size: 36px;
            }
        }
        
        /* Time acceleration indicator */
        #time-indicator {
            position: fixed;
            top: 60vh;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.7);
            backdrop-filter: blur(10px);
            padding: 8px 16px;
            border-radius: 20px;
            color: white;
            font-size: 12px;
            font-weight: 500;
            z-index: 100;
            opacity: 0;
            transition: opacity 0.5s ease;
            pointer-events: none;
        }
        
        #time-indicator.visible {
            opacity: 1;
        }
        
        .progress-bar {
            width: 120px;
            height: 3px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 2px;
            margin-top: 6px;
            overflow: hidden;
        }
        
        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #00ff00, #00cc00);
            border-radius: 2px;
            width: 0%;
            transition: width 0.1s linear;
        }
    </style>
    <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
    <script type="importmap">
    {
        "imports": {
            "three": "https://unpkg.com/three@0.157.0/build/three.module.js",
            "three/addons/": "https://unpkg.com/three@0.157.0/examples/jsm/"
        }
    }
    </script>
</head>
<body>
    <!-- Navigation -->
    <nav>
        <div class="nav-content">
            <div class="logo">SixthSensor</div>
            <div class="nav-tabs">
                <div class="nav-tab active" onclick="showTab('about')">About</div>
                <div class="nav-tab" onclick="showTab('offerings')">Offerings</div>
                <div class="nav-tab" onclick="showTab('case studies')">Case Studies</div>
                <div class="nav-tab" onclick="showTab('inquiries')">Inquiries</div>
            </div>
        </div>
    </nav>

    <!-- Canvas Container -->
    <div id="canvas-container"></div>
    
    <div class="visibility-controls">
        <h3>Visibility</h3>
        <div class="control-item">
            <input type="checkbox" id="showPlanes">
            <label for="showPlanes">Aircraft</label>
        </div>
        <div class="control-item">
            <input type="checkbox" id="showShips">
            <label for="showShips">Ships (experimental)</label>
        </div>
    </div>
    
    <!-- Time Acceleration Indicator -->
    <div id="time-indicator">
        <div id="time-display" style="text-align: center;">60x</div>
        <div class="progress-bar">
            <div class="progress-fill" id="progress-fill"></div>
        </div>
    </div>

    <!-- Content -->
    <div class="content-wrapper">
        <!-- About Section -->
        <div id="about" class="content-section active">
            <div class="content-card">
                <h1>About</h1>
                <p>Under construction. Welcome to my website!
                <br>
                I've designed constellations for various customers. SixthSensor is my startup based in Northern Virginia.</p>
                
                <h2>The Mission</h2>
                <p>Design constellations that do more for a lot less</p>
                
                <div class="feature-grid">
                    <div class="feature-card">
                        <h3>Cost</h3>
                        <p>Fewer satellites required to achieve more mission</p>
                    </div>
                    <div class="feature-card">
                        <h3>Schedule</h3>
                        <p>Achieve mission goals faster than the competition</p>
                    </div>
                    <div class="feature-card">
                        <h3>Risk</h3>
                        <p>Less time to validate conceptual constellations before launch</p>
                    </div>
                </div>
                
                <h2>Customer Testimonials</h2>
                <p>...</p>
            </div>
        </div>

        <!-- Offerings Section -->
        <div id="offerings" class="content-section">
            <div class="content-card">
            <h1>Offerings</h1>
            
            <h2>Constellation Design</h2>
            <p>Design optimal orbits/sizing/launch cadence for your mission. Extract the maximum performance from your satellites.</p>
            
            <h2>Mission Planning</h2>
            <p>Model your mission to validate your constellations performance. 
            Launch scheduling,
            Collection/contact scheduling,
            Predict performance over constellation lifetime</p>
            
            <h2>Due Dilligence</h2>
            <p>Before investing, ensure that physics agrees with your business case</p>
            
            <div class="feature-grid">
                <div class="feature-card">
                    <h3>Optimization</h3>
                    <p>Orbit optimization tailored to your mission</p>
                </div>
                <div class="feature-card">
                    <h3>Consultation</h3>
                    <p>Expert guidance on constellation design challenges</p>
                </div>
                <div class="feature-card">
                    <h3>Simulation</h3>
                    <p>Tailored analysis and visualization software</p>
                </div>
            </div>
            </div>
        </div>

        <!-- Case Studies Section -->
        <div id="case studies" class="content-section">
            <div class="content-card">
            <h1>Case Studies</h1>
            
            <h2>Why Focus on Orbit Selection</h2>
            <p>What if more satellites = worse coverage?</p>
            
            <h2>Beating the Walker Constellation</h2>
            <p>Tried and true, but often not the optimal solution</p>
            
            <h2>Propagating 1M+ satellites real-time</h2>
            <p>https://github.com/ApoPeri/tensorgator</p>
            

        </div>

        <!-- Inquiries Section -->
        <div id="inquiries" class="content-section">
            <div class="content-card">
            <h1>Inquiries</h1>
            <p>Get in touch with us to discuss your mission needs. <br> (Only accepting US based inquiries at this time)</p>
            
            <form onsubmit="handleSubmit(event)">
                <input type="text" placeholder="Name" required>
                <input type="email" placeholder="Email" required>
                <input type="text" placeholder="Organization">
                <textarea placeholder="Message" required></textarea>
                <button type="submit">Send Inquiry</button>
            </form>
            </div>
        </div>
    </div>

    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        let renderer, scene, camera, controls, earth;
        let satelliteMeshes = [];
        let satellites = [];
        let orbitTrails = [];
        let planeRoutes = []; // Visual great circle routes for aircraft
        
        // Visibility state
        let showShips = false;
        let showPlanes = false;
        let sensorCones = [];
        let ships = [];
        let shipMeshes = [];
        let planes = [];
        let planeMeshes = [];
        let debugAxes = [];  // Debug arrows for sensor orientation
        
        // Debug flag for sensor axes
        const SHOW_DEBUG_AXES = false; // Set to true to show debug arrows
        
        // Crosslinks/downlinks and target selection
        let crosslinkLines = [];
        let downlinkLines = [];
        let selectedTargets = [];
        let crosslinkMaterial, downlinkMaterial;
        
        // Performance: throttle expensive updates
        let frameCount = 0;
        const CROSSLINK_UPDATE_INTERVAL = 3; // Update every 3 frames (~20fps)
        const DOWNLINK_UPDATE_INTERVAL = 2;  // Update every 2 frames (~30fps)
        const TRAIL_UPDATE_INTERVAL = 10;    // Update every 10 frames (~6fps)
        
        // Time acceleration variables
        let simulationTime = Date.now(); // Start at current time
        let timeAcceleration = 1; // Start at real-time
        let lastFrameTime = Date.now();
        const startTime = Date.now();
        
        const EARTHRADIUS = 1.0;
        
        // Texture longitude offset - adjust this to align geographic features with lat/lon coordinates
        // If vessels don't line up with coastlines, increase/decrease this value
        // This compensates for how the Earth texture is oriented on the sphere
        const SHIP_PLANE_TEXTURE_LON_OFFSET = 90; // degrees (positive = shift features eastward)
        
        const coneFov = 45; // degrees
        const coneRangeKm = 700; // kilometers
        const PHYSICS = {
            MU: 3.986004418e14,
            EARTH_RADIUS: 6371000,
            EARTH_RADIUS_KM: 6371,
            J2: 1.08262668e-3,
            OMEGA_EARTH: 7.2921159e-5
        };

        const atmosphere = {
            Kr: 0.0025,
            Km: 0.0010,
            ESun: 10.0,
            g: -0.950,
            innerRadius: EARTHRADIUS,
            outerRadius: 1.025 * EARTHRADIUS,
            wavelength: [0.650, 0.570, 0.475],
            scaleDepth: 0.25
        };

        const AtmUniforms = {
            v3LightPosition: { value: new THREE.Vector3(1, 0, 0).normalize() },
            cPs: { value: new THREE.Vector3(1, 0, 0) },
            v3InvWavelength: { value: new THREE.Vector3(
                1 / Math.pow(atmosphere.wavelength[0], 4),
                1 / Math.pow(atmosphere.wavelength[1], 4),
                1 / Math.pow(atmosphere.wavelength[2], 4)
            ) },
            fInnerRadius: { value: atmosphere.innerRadius },
            fInnerRadius2: { value: atmosphere.innerRadius * atmosphere.innerRadius },
            fOuterRadius: { value: atmosphere.outerRadius },
            fOuterRadius2: { value: atmosphere.outerRadius * atmosphere.outerRadius },
            fKrESun: { value: atmosphere.Kr * atmosphere.ESun },
            fKmESun: { value: atmosphere.Km * atmosphere.ESun },
            fKr4PI: { value: atmosphere.Kr * 4.0 * Math.PI },
            fKm4PI: { value: atmosphere.Km * 4.0 * Math.PI },
            fScale: { value: 1 / (atmosphere.outerRadius - atmosphere.innerRadius) },
            fScaleDepth: { value: atmosphere.scaleDepth },
            fScaleOverScaleDepth: { value: 1 / (atmosphere.outerRadius - atmosphere.innerRadius) / atmosphere.scaleDepth },
            g: { value: atmosphere.g },
            g2: { value: atmosphere.g * atmosphere.g },
            tDiffuse: { value: null },
            tDiffuseNight: { value: null },
            fNightScale: { value: 1 }
        };

        const vertexSky = `
uniform vec3 v3LightPosition;
uniform vec3 v3InvWavelength;
uniform vec3 cPs;
uniform float fOuterRadius;
uniform float fOuterRadius2;
uniform float fInnerRadius;
uniform float fInnerRadius2;
uniform float fKrESun;
uniform float fKmESun;
uniform float fKr4PI;
uniform float fKm4PI;
uniform float fScale;
uniform float fScaleDepth;
uniform float fScaleOverScaleDepth;

const int nSamples = 3;
const float fSamples = 3.0;

varying vec3 v3Direction;
varying vec3 c0;
varying vec3 c1;

float scale(float fCos) {
    float x = 1.0 - fCos;
    return fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
}

void main(void) {
    float fCameraHeight = length(cPs);
    float fCameraHeight2 = fCameraHeight * fCameraHeight;
    
    vec3 v3Ray = position - cPs;
    float fFar = length(v3Ray);
    v3Ray /= fFar;

    float B = 2.0 * dot(cPs, v3Ray);
    float C = fCameraHeight2 - fOuterRadius2;
    float fDet = max(0.0, B*B - 4.0*C);
    float fNear = 0.5 * (-B - sqrt(fDet));

    vec3 v3Start = cPs + v3Ray * fNear;
    fFar -= fNear;

    float fStartAngle = dot(v3Ray, v3Start) / fOuterRadius;
    float fStartDepth = exp(-1.0 / fScaleDepth);
    float fStartOffset = fStartDepth * scale(fStartAngle);

    float fSampleLength = fFar / fSamples;
    float fScaledLength = fSampleLength * fScale;
    vec3 v3SampleRay = v3Ray * fSampleLength;
    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5;

    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0);
    for(int i = 0; i < nSamples; i++) {
        float fHeight = length(v3SamplePoint);
        float fDepth = exp(fScaleOverScaleDepth * (fInnerRadius - fHeight));
        float fLightAngle = dot(v3LightPosition, v3SamplePoint) / fHeight;
        float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight;
        float fScatter = (fStartOffset + fDepth * (scale(fLightAngle) - scale(fCameraAngle)));
        vec3 v3Attenuate = exp(-fScatter * (v3InvWavelength * fKr4PI + fKm4PI));
        v3FrontColor += v3Attenuate * (fDepth * fScaledLength);
        v3SamplePoint += v3SampleRay;
    }

    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    c0 = v3FrontColor * (v3InvWavelength * fKrESun);
    c1 = v3FrontColor * fKmESun;
    v3Direction = cPs - position;
}`;

        const fragmentSky = `
uniform vec3 v3LightPosition;
uniform float g;
uniform float g2;

varying vec3 v3Direction;
varying vec3 c0;
varying vec3 c1;

float getMiePhase(float fCos, float fCos2, float g, float g2) {
    return 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + fCos2) / pow(1.0 + g2 - 2.0 * g * fCos, 1.5);
}

float getRayleighPhase(float fCos2) {
    return 0.75 + 0.75 * fCos2;
}

void main (void) {
    float fCos = dot(v3LightPosition, v3Direction) / length(v3Direction);
    float fCos2 = fCos * fCos;
    vec3 color = getRayleighPhase(fCos2) * c0 + getMiePhase(fCos, fCos2, g, g2) * c1;
    gl_FragColor = vec4(color, 1.0);
    gl_FragColor.a = gl_FragColor.b;
}`;

        const vertexGround = `
uniform vec3 v3LightPosition;
uniform vec3 cPs;
uniform vec3 v3InvWavelength;
uniform float fOuterRadius;
uniform float fOuterRadius2;
uniform float fInnerRadius;
uniform float fInnerRadius2;
uniform float fKrESun;
uniform float fKmESun;
uniform float fKr4PI;
uniform float fKm4PI;
uniform float fScale;
uniform float fScaleDepth;
uniform float fScaleOverScaleDepth;

varying vec3 c0;
varying vec3 c1;
varying vec2 vUv;

const int nSamples = 3;
const float fSamples = 3.0;

float scale(float fCos) {
    float x = 1.0 - fCos;
    return fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
}

void main(void) {
    float fCameraHeight = length(cPs);
    float fCameraHeight2 = fCameraHeight*fCameraHeight;
    
    vec3 v3Ray = position - cPs;
    float fFar = length(v3Ray);
    v3Ray /= fFar;

    float B = 2.0 * dot(cPs, v3Ray);
    float C = fCameraHeight2 - fOuterRadius2;
    float fDet = max(0.0, B*B - 4.0 * C);
    float fNear = 0.5 * (-B - sqrt(fDet));

    vec3 v3Start = cPs + v3Ray * fNear;
    fFar -= fNear;

    float fDepth = exp((fInnerRadius - fOuterRadius) / fScaleDepth);
    float fCameraAngle = dot(-v3Ray, position) / length(position);
    float fLightAngle = dot(v3LightPosition, position) / length(position);
    float fCameraScale = scale(fCameraAngle);
    float fLightScale = scale(fLightAngle);
    float fCameraOffset = fDepth*fCameraScale;
    float fTemp = (fLightScale + fCameraScale);

    float fSampleLength = fFar / fSamples;
    float fScaledLength = fSampleLength * fScale;
    vec3 v3SampleRay = v3Ray * fSampleLength;
    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5;

    vec3 v3FrontColor = vec3(0.0, 0.0, 0.0);
    vec3 v3Attenuate;

    for(int i = 0; i < nSamples; i++) {
        float fHeight = length(v3SamplePoint);
        float fDepth = exp(fScaleOverScaleDepth * (fInnerRadius - fHeight));
        float fScatter = fDepth*fTemp - fCameraOffset;
        v3Attenuate = exp(-fScatter * (v3InvWavelength * fKr4PI + fKm4PI));
        v3FrontColor += v3Attenuate * (fDepth * fScaledLength);
        v3SamplePoint += v3SampleRay;
    }

    c0 = v3Attenuate;
    c1 = v3FrontColor * (v3InvWavelength * fKrESun + fKmESun);
    vUv = uv;

    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`;

        const fragmentGround = `
uniform float fNightScale;
uniform sampler2D tDiffuse;
uniform sampler2D tDiffuseNight;

varying vec3 c0;
varying vec3 c1;
varying vec2 vUv;

void main (void) {
    vec3 diffuseTex = texture2D(tDiffuse, vUv).SixthSensor;
    vec3 diffuseNightTex = texture2D(tDiffuseNight, vUv).SixthSensor;
    vec3 day = 0.75 * diffuseTex * c0;
    vec3 night = fNightScale * diffuseNightTex * (1.0 - c0);
    gl_FragColor = vec4(c1, 1.0) + vec4(day + night, 1.0);
}`;

        class Earth3d extends THREE.Group {
            constructor(camera) {
                super();
                this.ground = new THREE.Mesh(
                    new THREE.SphereGeometry(atmosphere.innerRadius, 64, 64),
                    new THREE.ShaderMaterial({
                        uniforms: AtmUniforms,
                        vertexShader: vertexGround,
                        fragmentShader: fragmentGround
                    }));
                this.add(this.ground);
                
                this.sky = new THREE.Mesh(
                    new THREE.SphereGeometry(atmosphere.outerRadius, 64, 64),
                    new THREE.ShaderMaterial({
                        uniforms: AtmUniforms,
                        vertexShader: vertexSky,
                        fragmentShader: fragmentSky,
                        side: THREE.BackSide,
                        transparent: true,
                        depthWrite: false,
                    }));
                this.add(this.sky);
                
                this._sunvect = new THREE.Vector3(1,0,0);
                this.sunvect = new THREE.Vector3(1,0,0);
                this.camera = camera;
                this.parentObj = new THREE.Group();
                this.cameracPs = new THREE.Vector3();
                this.axisY = new THREE.Vector3(0,1,0);
            }
            
            update() {
                this.cameracPs.copy(this.camera.position);
                this.cameracPs.sub(this.parentObj.position);
                this._sunvect.copy(this.sunvect);
                this.cameracPs.applyAxisAngle(this.axisY,-this.rotation.y);
                this._sunvect.applyAxisAngle(this.axisY,-this.rotation.y);
                this.ground.material.uniforms.cPs.value = this.cameracPs;
                this.ground.material.uniforms.v3LightPosition.value = this._sunvect;
                this.sky.material.uniforms.cPs.value = this.cameracPs;
                this.sky.material.uniforms.v3LightPosition.value = this._sunvect;
            }
            
            setSun(sun) {
                this.sunvect.copy(sun);
            }
            
            loadTextures(texDay, texNight) {
                texDay.anisotropy = 16;
                texNight.anisotropy = 16;
                AtmUniforms.tDiffuse.value = texDay;
                AtmUniforms.tDiffuseNight.value = texNight;
            }
        }

        function getJulianDate(date) {
            return date.getTime() / 86400000 + 2440587.5;
        }

        function getSunPosition(julianDate) {
            // High-precision solar position (NOAA/Meeus-style)
            const DEG2RAD = Math.PI / 180;
            const T = (julianDate - 2451545.0) / 36525.0; // Julian centuries from J2000.0

            // Geometric Mean Longitude of the Sun (deg)
            let L0 = 280.46646 + T * (36000.76983 + 0.0003032 * T);
            L0 = ((L0 % 360) + 360) % 360;

            // Mean anomaly of the Sun (deg)
            let M = 357.52911 + T * (35999.05029 - 0.0001537 * T);
            M = ((M % 360) + 360) % 360;
            const Mrad = M * DEG2RAD;

            // Eccentricity of Earth's orbit
            const e = 0.016708634 - T * (0.000042037 + 0.0000001267 * T);

            // Sun's equation of center (deg)
            const C = Math.sin(Mrad) * (1.914602 - T * (0.004817 + 0.000014 * T))
                    + Math.sin(2 * Mrad) * (0.019993 - 0.000101 * T)
                    + Math.sin(3 * Mrad) * 0.000289;

            // True longitude (deg)
            const trueLong = L0 + C;

            // Apparent longitude (deg), include nutation/aberration via Ω term
            const Omega = (125.04 - 1934.136 * T) * DEG2RAD;
            const lambda = (trueLong - 0.00569 - 0.00478 * Math.sin(Omega)) * DEG2RAD;

            // Mean obliquity of the ecliptic (deg)
            const eps0 = 23 + 26 / 60 + (21.448 - 46.8150 * T - 0.00059 * T * T + 0.001813 * T * T * T) / 3600;
            // Corrected (true) obliquity (deg)
            const eps = (eps0 + 0.00256 * Math.cos(Omega)) * DEG2RAD;

            // Convert ecliptic to equatorial (ECI, J2000-based)
            const x = Math.cos(lambda);
            const y = Math.cos(eps) * Math.sin(lambda);
            const z = Math.sin(eps) * Math.sin(lambda);

            // Map to Three.js coordinates (Y-up): ECI z -> Y
            return new THREE.Vector3(x, z, y);
        }

        // Convert lat/lon to 3D position on Earth surface
        // Standard spherical coordinates: lon=0° at +Z, lon=90°E at +X
        // This assumes the Earth texture is oriented with Prime Meridian aligned to +Z axis in initial state
        function latLonToVector3(lat, lon, radius = EARTHRADIUS) {
            const phi = (90 - lat) * (Math.PI / 180);  // Polar angle from north pole (0 to π)
            const theta = lon * (Math.PI / 180);        // Azimuthal angle (longitude in radians)
            
            // Spherical to Cartesian: x = r·sin(φ)·sin(θ), y = r·cos(φ), z = r·sin(φ)·cos(θ)
            const x = radius * Math.sin(phi) * Math.sin(theta);
            const y = radius * Math.cos(phi);
            const z = radius * Math.sin(phi) * Math.cos(theta);
            
            return new THREE.Vector3(x, y, z);
        }

        // Major shipping routes (lat, lon waypoints)
        const shippingRoutes = [

            // Route 1 (Minor, 23 waypoints, 17334 km)
            [
                [-4.67, -34.41],
                [-0.67, -37.20],
                [4.95, -41.07],
                [12.94, -46.80],
                [18.17, -50.77],
                [22.59, -54.37],
                [29.99, -61.19],
                [34.95, -66.57],
                [36.66, -64.84],
                [33.26, -58.40],
                [29.94, -53.05],
                [25.93, -47.40],
                [19.97, -40.09],
                [15.95, -35.67],
                [10.01, -29.58],
                [2.27, -22.11],
                [-5.11, -15.08],
                [-9.97, -10.37],
                [-14.57, -5.70],
                [-20.07, 0.30],
                [-23.83, 4.77],
                [-29.56, 12.53],
                [-32.93, 17.87],
            ],

            // Route 2 (Minor, 20 waypoints, 15567 km)
            [
                [30.52, -180.00],
                [32.43, -173.00],
                [33.96, -165.54],
                [35.01, -157.96],
                [35.58, -150.29],
                [35.66, -142.57],
                [35.25, -134.85],
                [34.34, -127.18],
                [32.59, -118.10],
                [31.30, -118.84],
                [25.03, -126.08],
                [17.01, -133.91],
                [13.07, -137.41],
                [5.06, -144.08],
                [-1.18, -149.13],
                [-8.98, -155.50],
                [-13.22, -159.09],
                [-19.96, -165.19],
                [-26.92, -172.32],
                [-33.21, -180.00],
            ],

            // Route 3 (Minor, 17 waypoints, 12164 km)
            [
                [33.58, -180.00],
                [35.65, -173.24],
                [37.23, -166.55],
                [38.42, -159.70],
                [39.18, -152.72],
                [39.46, -138.55],
                [38.95, -131.46],
                [37.68, -122.62],
                [34.99, -131.19],
                [31.28, -140.17],
                [29.18, -144.42],
                [24.99, -151.79],
                [25.03, -154.53],
                [31.42, -148.76],
                [38.34, -141.14],
                [45.00, -131.40],
                [48.41, -124.75],
            ],

            // Route 4 (Minor, 15 waypoints, 11424 km)
            [
                [21.23, -157.89],
                [12.47, -150.95],
                [7.65, -147.40],
                [0.36, -142.22],
                [-5.12, -138.34],
                [-9.98, -134.83],
                [-14.96, -131.09],
                [-20.63, -126.55],
                [-24.99, -122.75],
                [-30.04, -117.88],
                [-37.64, -108.98],
                [-42.30, -102.00],
                [-45.76, -95.50],
                [-48.73, -88.43],
                [-52.57, -74.69],
            ],

            // Route 5 (Minor, 8 waypoints, 11285 km)
            [
                [8.86, -79.39],
                [6.98, -79.89],
                [6.06, -133.97],
                [5.96, -139.98],
                [5.77, -149.99],
                [5.37, -160.10],
                [5.04, -168.02],
                [5.01, -180.00],
            ],

            // Route 6 (Major, 15 waypoints, 11069 km)
            [
                [35.89, -6.73],
                [35.40, -16.08],
                [34.43, -23.88],
                [32.90, -31.84],
                [30.80, -39.72],
                [27.49, -49.18],
                [25.02, -54.98],
                [20.84, -63.36],
                [18.28, -67.92],
                [31.25, -71.38],
                [39.36, -72.53],
                [39.40, -73.90],
                [28.10, -72.73],
                [21.75, -71.36],
                [20.83, -74.28],
            ],

            // Route 7 (Minor, 14 waypoints, 10616 km)
            [
                [20.20, -154.75],
                [14.32, -145.82],
                [10.98, -141.17],
                [7.57, -136.61],
                [4.09, -132.12],
                [0.58, -127.65],
                [-5.14, -120.35],
                [-8.79, -115.60],
                [-15.03, -106.99],
                [-19.95, -99.48],
                [-23.55, -93.35],
                [-26.29, -88.12],
                [-29.38, -81.34],
                [-33.00, -71.51],
            ],

            // Route 8 (Minor, 14 waypoints, 10356 km)
            [
                [-30.56, -180.00],
                [-25.04, -175.86],
                [-20.22, -172.62],
                [-13.73, -168.58],
                [-9.13, -165.88],
                [-2.32, -162.03],
                [1.76, -159.74],
                [9.95, -155.35],
                [15.22, -152.45],
                [20.14, -149.60],
                [25.00, -146.57],
                [31.19, -142.25],
                [39.94, -134.80],
                [48.41, -124.75],
            ],

            // Route 9 (Major, 17 waypoints, 10002 km)
            [
                [-8.14, -34.86],
                [-8.07, -34.67],
                [16.96, -25.63],
                [23.55, -22.59],
                [30.16, -19.17],
                [35.40, -16.08],
                [38.73, -13.87],
                [45.03, -8.93],
                [48.46, -5.68],
                [50.75, 0.85],
                [53.71, 4.68],
                [57.04, 7.17],
                [58.28, 10.58],
                [54.99, 12.54],
                [56.76, 18.80],
                [59.59, 22.73],
                [60.00, 27.46],
            ],

            // Route 10 (Minor, 12 waypoints, 9864 km)
            [
                [-33.18, -180.00],
                [-26.10, -172.74],
                [-18.11, -166.27],
                [-12.95, -162.49],
                [-7.00, -158.36],
                [0.24, -153.49],
                [8.57, -147.90],
                [14.32, -143.87],
                [19.98, -139.62],
                [25.01, -135.52],
                [30.41, -130.58],
                [37.68, -122.62],
            ],

            // Route 11 (Middle, 22 waypoints, 8899 km)
            [
                [21.41, -157.84],
                [21.40, -157.90],
                [21.40, -157.90],
                [21.46, -158.74],
                [21.40, -157.90],
                [21.40, -157.90],
                [21.41, -157.84],
                [21.32, -157.62],
                [21.39, -147.96],
                [20.80, -138.47],
                [20.06, -131.71],
                [18.61, -122.42],
                [17.60, -117.34],
                [15.77, -109.44],
                [13.40, -100.82],
                [10.74, -92.36],
                [9.31, -88.17],
                [6.75, -81.01],
                [7.53, -79.70],
                [8.27, -79.57],
                [8.90, -79.45],
                [8.98, -79.51],
            ],

            // Route 12 (Middle, 11 waypoints, 8394 km)
            [
                [11.87, 51.97],
                [10.40, 54.14],
                [-3.85, 73.57],
                [-7.02, 76.90],
                [-12.57, 82.95],
                [-17.13, 88.22],
                [-20.45, 92.33],
                [-24.18, 97.34],
                [-29.96, 106.35],
                [-34.33, 114.78],
                [-34.33, 114.78],
            ],

            // Route 13 (Middle, 32 waypoints, 7980 km)
            [
                [35.57, 139.79],
                [35.46, 139.78],
                [35.37, 139.85],
                [35.28, 139.83],
                [35.15, 139.81],
                [35.06, 139.79],
                [35.06, 139.79],
                [35.06, 139.79],
                [28.57, 142.52],
                [27.11, 143.05],
                [23.52, 144.40],
                [17.23, 146.53],
                [8.68, 149.40],
                [-3.05, 153.76],
                [-3.05, 153.76],
                [-6.54, 156.27],
                [-8.95, 154.75],
                [-8.95, 154.75],
                [-10.69, 150.45],
                [-9.07, 144.02],
                [-9.70, 143.18],
                [-9.85, 143.10],
                [-10.37, 142.40],
                [-10.36, 142.31],
                [-10.36, 142.31],
                [-10.36, 142.31],
                [-10.38, 142.28],
                [-10.38, 142.23],
                [-10.43, 142.15],
                [-10.43, 142.08],
                [-8.67, 135.65],
                [-6.59, 132.59],
            ],

            // Route 14 (Minor, 10 waypoints, 7930 km)
            [
                [-17.70, -149.71],
                [-22.96, -145.38],
                [-29.97, -138.88],
                [-34.93, -133.45],
                [-40.80, -125.61],
                [-44.95, -118.52],
                [-50.01, -106.42],
                [-52.65, -96.72],
                [-54.93, -79.94],
                [-56.00, -67.18],
            ],

            // Route 15 (Minor, 8 waypoints, 7695 km)
            [
                [-26.81, 153.09],
                [-20.93, 154.60],
                [-11.03, 154.96],
                [-4.35, 152.45],
                [-2.64, 149.52],
                [9.32, 137.99],
                [25.43, 126.40],
                [30.98, 122.24],
            ],

            // Route 16 (Major, 11 waypoints, 7121 km)
            [
                [31.27, 32.74],
                [31.27, 32.74],
                [29.52, 32.74],
                [27.91, 35.28],
                [25.48, 36.59],
                [13.52, 42.86],
                [12.53, 43.57],
                [11.95, 51.61],
                [7.31, 73.21],
                [5.83, 80.00],
                [9.79, 75.82],
            ],

            // Route 17 (Major, 10 waypoints, 7079 km)
            [
                [43.95, -180.00],
                [43.69, -166.78],
                [42.43, -156.72],
                [40.32, -147.15],
                [37.45, -138.16],
                [34.95, -132.06],
                [30.94, -124.07],
                [24.96, -114.53],
                [19.95, -107.80],
                [19.95, -107.80],
            ],

            // Route 18 (Minor, 10 waypoints, 6924 km)
            [
                [9.08, -180.00],
                [9.21, -179.78],
                [13.73, -172.15],
                [17.73, -164.74],
                [21.44, -157.04],
                [24.81, -149.04],
                [27.87, -140.15],
                [29.90, -132.67],
                [31.48, -124.97],
                [32.57, -117.20],
            ],

            // Route 19 (Minor, 5 waypoints, 6908 km)
            [
                [8.86, -79.39],
                [5.62, -79.81],
                [-5.26, -81.87],
                [-49.31, -76.84],
                [-52.57, -74.69],
            ],

            // Route 20 (Minor, 3 waypoints, 6549 km)
            [
                [5.01, 180.00],
                [4.98, 167.95],
                [19.94, 122.22],
            ],

            // Route 21 (Middle, 14 waypoints, 6486 km)
            [
                [21.90, -71.25],
                [23.72, -67.68],
                [28.20, -57.53],
                [30.17, -52.07],
                [32.51, -44.18],
                [33.76, -38.84],
                [35.22, -30.36],
                [36.04, -21.98],
                [36.27, -13.82],
                [36.00, -6.45],
                [36.00, -6.45],
                [36.00, -6.45],
                [36.00, -6.45],
                [36.08, -5.30],
            ],

            // Route 22 (Middle, 11 waypoints, 6423 km)
            [
                [18.43, -67.82],
                [22.22, -64.08],
                [28.20, -57.53],
                [31.75, -53.08],
                [35.01, -48.49],
                [40.89, -38.24],
                [41.73, -36.45],
                [44.99, -28.32],
                [47.52, -19.72],
                [49.87, -6.32],
                [49.87, -6.32],
            ],

            // Route 23 (Middle, 13 waypoints, 6384 km)
            [
                [21.90, -71.25],
                [25.26, -67.61],
                [29.96, -62.00],
                [34.83, -55.18],
                [37.73, -50.41],
                [41.67, -42.55],
                [41.67, -42.55],
                [41.91, -41.99],
                [44.87, -34.17],
                [47.53, -24.41],
                [49.18, -14.71],
                [49.87, -6.32],
                [49.87, -6.32],
            ],

            // Route 24 (Minor, 9 waypoints, 6373 km)
            [
                [-4.67, -34.41],
                [-1.85, -36.54],
                [4.95, -41.63],
                [9.76, -45.33],
                [13.72, -48.48],
                [22.72, -56.23],
                [29.51, -63.06],
                [34.54, -69.03],
                [38.73, -74.94],
            ],

            // Route 25 (Minor, 8 waypoints, 6368 km)
            [
                [37.68, -122.62],
                [34.99, -135.13],
                [32.52, -142.93],
                [29.57, -150.32],
                [24.96, -159.65],
                [20.00, -168.01],
                [15.81, -174.21],
                [11.57, -180.00],
            ],

            // Route 26 (Minor, 10 waypoints, 6307 km)
            [
                [-4.67, -34.41],
                [-0.28, -37.99],
                [5.00, -42.26],
                [9.99, -46.43],
                [15.57, -51.29],
                [19.25, -54.68],
                [24.01, -59.41],
                [27.57, -63.30],
                [32.97, -69.99],
                [36.87, -75.87],
            ],

            // Route 27 (Middle, 7 waypoints, 6305 km)
            [
                [6.32, 95.15],
                [-7.02, 76.90],
                [-18.87, 58.69],
                [-19.64, 57.55],
                [-19.98, 57.55],
                [-20.56, 55.34],
                [-25.43, 47.09],
            ],

            // Route 28 (Major, 8 waypoints, 6255 km)
            [
                [10.05, -17.61],
                [4.96, -13.72],
                [-2.04, -8.81],
                [-10.01, -3.16],
                [-18.52, 3.30],
                [-25.05, 8.81],
                [-30.03, 13.58],
                [-34.57, 18.54],
            ],

            // Route 29 (Middle, 14 waypoints, 6175 km)
            [
                [-4.59, -34.65],
                [5.02, -39.03],
                [9.20, -40.97],
                [18.04, -45.26],
                [24.99, -48.99],
                [30.17, -52.07],
                [31.75, -53.08],
                [34.83, -55.18],
                [40.70, -59.76],
                [42.55, -61.40],
                [42.67, -61.51],
                [42.67, -61.51],
                [44.62, -63.39],
                [44.62, -63.39],
            ],

            // Route 30 (Middle, 6 waypoints, 6159 km)
            [
                [-33.82, 18.24],
                [-33.80, 18.13],
                [-32.91, 17.46],
                [-31.71, 9.72],
                [-26.93, -20.14],
                [-22.97, -43.15],
            ],

            // Route 31 (Middle, 10 waypoints, 6113 km)
            [
                [-7.92, -34.74],
                [-7.92, -34.74],
                [-11.35, -29.93],
                [-15.37, -24.01],
                [-19.76, -16.92],
                [-24.22, -8.75],
                [-27.17, -2.45],
                [-29.89, 4.33],
                [-31.71, 9.72],
                [-33.95, 18.03],
            ],

            // Route 32 (Minor, 9 waypoints, 5977 km)
            [
                [12.69, 124.22],
                [15.40, 132.80],
                [17.15, 139.27],
                [18.69, 145.83],
                [19.99, 152.50],
                [21.05, 159.27],
                [21.83, 166.14],
                [22.34, 173.10],
                [22.53, 179.97],
            ],

            // Route 33 (Major, 10 waypoints, 5921 km)
            [
                [44.49, -63.71],
                [31.25, -71.38],
                [28.10, -72.73],
                [21.99, -74.76],
                [20.17, -74.05],
                [17.60, -76.25],
                [9.33, -79.95],
                [9.33, -79.95],
                [9.33, -79.95],
                [18.28, -67.92],
            ],

            // Route 34 (Minor, 9 waypoints, 5866 km)
            [
                [30.52, -180.00],
                [32.43, -173.00],
                [33.96, -165.54],
                [35.01, -157.96],
                [35.58, -150.29],
                [35.66, -142.57],
                [35.25, -134.85],
                [34.34, -127.18],
                [32.57, -117.20],
            ],

            // Route 35 (Minor, 8 waypoints, 5811 km)
            [
                [20.08, -180.00],
                [27.79, -171.49],
                [32.44, -165.40],
                [36.40, -159.29],
                [39.96, -152.66],
                [44.89, -140.20],
                [46.98, -132.46],
                [48.41, -124.75],
            ],

            // Route 36 (Middle, 13 waypoints, 5802 km)
            [
                [36.70, -9.19],
                [38.60, -15.80],
                [40.12, -22.84],
                [41.73, -36.45],
                [41.91, -41.99],
                [41.91, -44.12],
                [41.91, -44.12],
                [41.51, -52.70],
                [40.70, -59.76],
                [39.79, -65.05],
                [39.61, -65.91],
                [37.48, -74.43],
                [37.04, -75.89],
            ],

            // Route 37 (Minor, 8 waypoints, 5717 km)
            [
                [9.42, 138.05],
                [14.98, 142.26],
                [19.93, 146.22],
                [26.82, 152.38],
                [35.00, 161.18],
                [39.09, 166.62],
                [42.37, 171.79],
                [46.53, 179.99],
            ],

            // Route 38 (Minor, 8 waypoints, 5705 km)
            [
                [11.57, 180.00],
                [6.01, 172.85],
                [0.99, 166.28],
                [-2.27, 162.02],
                [2.13, 163.71],
                [10.49, 170.95],
                [16.44, 176.42],
                [20.08, 180.00],
            ],

            // Route 39 (Middle, 7 waypoints, 5518 km)
            [
                [-8.78, 115.71],
                [-10.45, 115.48],
                [-24.98, 112.38],
                [-34.32, 114.78],
                [-35.56, 116.65],
                [-38.74, 143.39],
                [-38.76, 143.51],
            ],

            // Route 40 (Middle, 7 waypoints, 5466 km)
            [
                [-19.98, 57.55],
                [-19.64, 57.55],
                [-12.57, 82.95],
                [-6.27, 104.74],
                [-6.17, 105.55],
                [-6.16, 105.60],
                [-5.99, 105.71],
            ],

            // Route 41 (Minor, 9 waypoints, 5449 km)
            [
                [9.42, 138.05],
                [12.92, 141.21],
                [18.33, 146.36],
                [23.09, 151.31],
                [26.74, 155.46],
                [31.35, 161.35],
                [34.86, 166.53],
                [39.98, 175.83],
                [41.84, 179.98],
            ],

            // Route 42 (Major, 7 waypoints, 5338 km)
            [
                [8.82, -79.53],
                [7.37, -79.72],
                [6.59, -81.01],
                [9.14, -87.65],
                [19.95, -107.80],
                [26.62, -115.07],
                [32.59, -117.34],
            ],

            // Route 43 (Major, 8 waypoints, 5332 km)
            [
                [35.38, 139.75],
                [35.17, 139.81],
                [33.71, 138.49],
                [20.36, 122.63],
                [19.75, 121.43],
                [8.06, 109.93],
                [2.97, 105.44],
                [1.10, 104.04],
            ],

            // Route 44 (Middle, 3 waypoints, 5254 km)
            [
                [-4.59, -34.65],
                [11.04, -60.93],
                [18.00, -76.72],
            ],

            // Route 45 (Minor, 6 waypoints, 5141 km)
            [
                [-10.02, 180.00],
                [-10.01, 179.96],
                [-0.03, 153.92],
                [7.64, 140.51],
                [8.66, 138.99],
                [9.42, 138.05],
            ],

            // Route 46 (Minor, 5 waypoints, 5135 km)
            [
                [-10.01, 179.96],
                [-0.03, 153.92],
                [7.64, 140.51],
                [8.66, 138.99],
                [9.32, 137.99],
            ],

            // Route 47 (Middle, 13 waypoints, 5099 km)
            [
                [-3.05, 153.76],
                [-3.05, 153.76],
                [-8.95, 154.75],
                [-10.89, 155.05],
                [-20.80, 154.67],
                [-23.87, 154.08],
                [-26.69, 153.17],
                [-26.91, 153.81],
                [-31.38, 166.98],
                [-34.12, 172.98],
                [-35.08, 174.86],
                [-36.30, 174.89],
                [-36.48, 174.78],
            ],

            // Route 48 (Major, 8 waypoints, 5089 km)
            [
                [34.20, -120.65],
                [34.20, -120.65],
                [37.22, -126.53],
                [40.00, -133.09],
                [43.71, -145.14],
                [45.21, -152.58],
                [46.66, -168.16],
                [46.31, -180.00],
            ],

            // Route 49 (Minor, 7 waypoints, 5048 km)
            [
                [9.42, 138.05],
                [14.53, 144.76],
                [19.94, 152.52],
                [24.96, 160.80],
                [27.94, 166.44],
                [31.69, 174.86],
                [33.57, 179.98],
            ],

            // Route 50 (Minor, 7 waypoints, 5035 km)
            [
                [30.26, -180.00],
                [34.99, -172.80],
                [38.91, -165.45],
                [41.66, -159.07],
                [44.00, -152.29],
                [47.27, -137.57],
                [48.41, -124.75],
            ],

            // Route 51 (Middle, 15 waypoints, 5021 km)
            [
                [-6.54, 156.27],
                [-3.05, 153.76],
                [-3.05, 153.76],
                [-1.77, 153.08],
                [8.68, 149.40],
                [17.23, 146.53],
                [27.11, 143.05],
                [35.06, 139.79],
                [35.06, 139.79],
                [35.06, 139.79],
                [35.28, 139.83],
                [35.37, 139.85],
                [35.37, 139.85],
                [35.46, 139.78],
                [35.57, 139.79],
            ],

            // Route 52 (Minor, 6 waypoints, 4930 km)
            [
                [9.42, 138.05],
                [14.07, 145.32],
                [19.99, 155.29],
                [24.66, 164.59],
                [28.33, 173.48],
                [30.52, 180.00],
            ],

            // Route 53 (Middle, 7 waypoints, 4892 km)
            [
                [47.22, -52.15],
                [52.88, -41.70],
                [57.01, -32.14],
                [62.26, -17.13],
                [65.49, -5.67],
                [69.10, 10.41],
                [71.69, 26.81],
            ],

            // Route 54 (Middle, 122 waypoints, 4870 km)
            [
                [44.62, -63.39],
                [44.62, -63.39],
                [44.04, -58.14],
                [44.04, -58.14],
                [44.04, -58.14],
                [42.60, -49.90],
                [42.56, -49.46],
                [42.54, -49.27],
                [42.53, -49.16],
                [42.52, -49.10],
                [42.30, -47.05],
                [42.29, -46.98],
                [42.29, -46.97],
                [41.94, -44.29],
                [41.94, -44.29],
                [41.93, -44.21],
                [41.92, -44.20],
                [41.91, -44.14],
                [41.91, -44.12],
                [41.91, -44.11],
                [41.90, -44.06],
                [41.90, -44.03],
                [41.89, -43.98],
                [41.88, -43.94],
                [41.88, -43.90],
                [41.87, -43.84],
                [41.87, -43.83],
                [41.86, -43.75],
                [41.86, -43.75],
                [41.67, -42.55],
                [41.25, -40.13],
                [41.25, -40.12],
                [41.24, -40.06],
                [41.24, -40.03],
                [41.23, -40.00],
                [41.22, -39.94],
                [41.22, -39.93],
                [40.97, -38.63],
                [40.95, -38.54],
                [40.89, -38.24],
                [40.87, -38.12],
                [39.77, -33.32],
                [39.62, -32.71],
                [39.59, -32.60],
                [39.56, -32.48],
                [39.53, -32.37],
                [39.50, -32.25],
                [39.47, -32.14],
                [39.44, -32.02],
                [39.41, -31.91],
                [39.38, -31.79],
                [39.34, -31.68],
                [39.31, -31.57],
                [39.28, -31.45],
                [39.25, -31.34],
                [39.22, -31.23],
                [39.19, -31.11],
                [39.16, -31.00],
                [39.13, -30.89],
                [39.01, -30.48],
                [38.81, -29.79],
                [38.60, -29.06],
                [38.52, -28.77],
                [38.42, -28.28],
                [38.52, -28.77],
                [38.60, -29.06],
                [38.81, -29.79],
                [39.01, -30.48],
                [39.13, -30.89],
                [39.16, -31.00],
                [39.19, -31.11],
                [39.22, -31.23],
                [39.25, -31.34],
                [39.28, -31.45],
                [39.31, -31.57],
                [39.34, -31.68],
                [39.38, -31.79],
                [39.41, -31.91],
                [39.44, -32.02],
                [39.47, -32.14],
                [39.50, -32.25],
                [39.53, -32.37],
                [39.56, -32.48],
                [39.59, -32.60],
                [39.62, -32.71],
                [39.77, -33.32],
                [40.87, -38.12],
                [40.89, -38.24],
                [40.95, -38.54],
                [40.97, -38.63],
                [41.22, -39.93],
                [41.22, -39.94],
                [41.23, -40.00],
                [41.24, -40.03],
                [41.24, -40.06],
                [41.25, -40.12],
                [41.25, -40.13],
                [41.67, -42.55],
                [41.86, -43.75],
                [41.86, -43.75],
                [41.87, -43.83],
                [41.87, -43.84],
                [41.88, -43.90],
                [41.88, -43.94],
                [41.89, -43.98],
                [41.90, -44.03],
                [41.90, -44.06],
                [41.91, -44.11],
                [41.91, -44.12],
                [41.91, -44.14],
                [41.92, -44.20],
                [41.93, -44.21],
                [41.94, -44.29],
                [41.94, -44.29],
                [42.29, -46.97],
                [42.29, -46.98],
                [42.30, -47.05],
                [42.52, -49.10],
                [42.53, -49.16],
                [42.54, -49.27],
                [42.56, -49.46],
                [42.60, -49.90],
            ],

            // Route 55 (Middle, 9 waypoints, 4869 km)
            [
                [8.98, -79.51],
                [8.90, -79.45],
                [8.90, -79.45],
                [8.89, -79.46],
                [5.73, -79.94],
                [-5.16, -82.02],
                [-9.22, -81.02],
                [-32.95, -71.75],
                [-32.95, -71.75],
            ],

            // Route 56 (Minor, 11 waypoints, 4799 km)
            [
                [9.42, 138.05],
                [8.58, 134.53],
                [4.85, 125.32],
                [0.92, 119.42],
                [-3.78, 118.15],
                [-5.49, 116.95],
                [-5.73, 114.29],
                [-3.81, 109.41],
                [-2.05, 108.31],
                [-0.24, 106.59],
                [1.17, 103.70],
            ],

            // Route 57 (Minor, 7 waypoints, 4663 km)
            [
                [-3.20, 153.65],
                [3.69, 158.70],
                [9.85, 163.33],
                [14.97, 167.12],
                [19.98, 170.91],
                [25.00, 175.05],
                [30.36, 179.97],
            ],

            // Route 58 (Major, 7 waypoints, 4658 km)
            [
                [50.56, -180.00],
                [50.74, -173.24],
                [49.99, -160.41],
                [47.28, -145.86],
                [42.49, -132.04],
                [39.20, -125.38],
                [37.71, -122.77],
            ],

            // Route 59 (Minor, 7 waypoints, 4653 km)
            [
                [-3.20, 153.65],
                [3.68, 158.70],
                [9.85, 163.33],
                [14.97, 167.12],
                [19.98, 170.91],
                [25.00, 175.05],
                [30.24, 179.97],
            ],

            // Route 60 (Middle, 24 waypoints, 4647 km)
            [
                [1.35, 103.91],
                [1.35, 103.91],
                [1.42, 104.64],
                [0.98, 106.33],
                [-1.87, 108.50],
                [-1.87, 108.50],
                [-1.90, 108.59],
                [-2.10, 109.28],
                [-2.12, 109.38],
                [-2.12, 109.38],
                [-2.27, 109.44],
                [-2.49, 109.55],
                [-3.64, 109.60],
                [-3.64, 109.60],
                [-3.64, 109.60],
                [-3.64, 109.60],
                [-3.64, 109.60],
                [-7.44, 116.40],
                [-7.76, 117.10],
                [-7.86, 125.54],
                [-7.96, 125.72],
                [-8.13, 127.35],
                [-10.04, 135.87],
                [-10.47, 141.87],
            ],

            // Route 61 (Minor, 7 waypoints, 4644 km)
            [
                [19.78, 180.00],
                [18.89, 173.95],
                [17.79, 167.90],
                [16.51, 161.94],
                [15.06, 156.05],
                [12.49, 146.97],
                [9.42, 138.05],
            ],

            // Route 62 (Middle, 16 waypoints, 4634 km)
            [
                [35.57, 139.79],
                [35.46, 139.78],
                [35.37, 139.85],
                [35.37, 139.85],
                [35.28, 139.83],
                [35.06, 139.79],
                [35.46, 139.78],
                [35.06, 139.79],
                [35.06, 139.79],
                [35.06, 139.79],
                [28.57, 142.52],
                [23.52, 144.40],
                [17.23, 146.53],
                [8.68, 149.40],
                [-1.77, 153.08],
                [-3.05, 153.76],
            ],

            // Route 63 (Minor, 6 waypoints, 4550 km)
            [
                [30.39, -180.00],
                [36.17, -173.75],
                [43.09, -164.14],
                [47.40, -156.07],
                [52.10, -143.17],
                [54.32, -133.25],
            ],

            // Route 64 (Middle, 12 waypoints, 4348 km)
            [
                [-5.99, 105.71],
                [-6.16, 105.60],
                [-6.17, 105.55],
                [-6.87, 104.74],
                [-7.00, 105.19],
                [-10.45, 115.48],
                [-11.36, 118.04],
                [-11.11, 122.94],
                [-9.14, 128.22],
                [-10.04, 135.87],
                [-10.47, 141.87],
                [-10.47, 141.87],
            ],

            // Route 65 (Minor, 5 waypoints, 4300 km)
            [
                [41.84, -180.00],
                [46.31, -166.35],
                [48.68, -153.06],
                [49.42, -139.16],
                [48.41, -124.75],
            ],

            // Route 66 (Middle, 5 waypoints, 4173 km)
            [
                [21.32, -157.62],
                [25.58, -147.40],
                [28.76, -137.67],
                [31.18, -127.50],
                [32.75, -117.19],
            ],

            // Route 67 (Minor, 6 waypoints, 4068 km)
            [
                [13.67, 144.68],
                [17.73, 151.31],
                [20.00, 155.31],
                [24.91, 165.15],
                [27.47, 171.20],
                [30.52, 180.00],
            ],

            // Route 68 (Middle, 11 waypoints, 3947 km)
            [
                [21.90, -71.25],
                [21.90, -71.25],
                [37.48, -74.43],
                [37.60, -74.45],
                [38.90, -74.95],
                [40.60, -69.16],
                [41.76, -64.90],
                [41.90, -64.39],
                [42.67, -61.51],
                [44.04, -58.14],
                [46.75, -52.95],
            ],

            // Route 69 (Minor, 10 waypoints, 3903 km)
            [
                [-6.16, 105.51],
                [-3.81, 109.41],
                [-2.05, 108.31],
                [-0.24, 106.59],
                [1.83, 104.77],
                [10.27, 106.94],
                [9.95, 109.95],
                [11.82, 113.94],
                [14.28, 120.51],
                [14.48, 120.82],
            ],

            // Route 70 (Middle, 13 waypoints, 3896 km)
            [
                [20.11, -107.72],
                [9.30, -87.63],
                [6.75, -81.01],
                [7.53, -79.70],
                [7.53, -79.70],
                [7.53, -79.70],
                [8.27, -79.57],
                [8.90, -79.45],
                [8.90, -79.45],
                [8.90, -79.45],
                [7.53, -79.70],
                [7.53, -79.70],
                [6.75, -81.00],
            ],

            // Route 71 (Middle, 4 waypoints, 3839 km)
            [
                [-11.42, 42.92],
                [8.16, 52.75],
                [10.40, 54.14],
                [19.39, 58.69],
            ],

            // Route 72 (Major, 7 waypoints, 3727 km)
            [
                [36.60, -9.49],
                [38.73, -13.87],
                [42.02, -22.14],
                [45.01, -32.73],
                [46.93, -44.37],
                [47.43, -50.42],
                [47.55, -52.87],
            ],

            // Route 73 (Major, 5 waypoints, 3590 km)
            [
                [19.95, -107.80],
                [9.14, -87.65],
                [6.59, -81.01],
                [7.37, -79.72],
                [8.82, -79.53],
            ],

            // Route 74 (Major, 7 waypoints, 3571 km)
            [
                [46.31, 179.93],
                [45.62, 173.73],
                [43.66, 163.39],
                [40.81, 153.83],
                [37.20, 145.05],
                [34.64, 140.02],
                [34.64, 140.02],
            ],

            // Route 75 (Major, 6 waypoints, 3567 km)
            [
                [46.63, -53.30],
                [47.43, -50.42],
                [49.75, -38.89],
                [50.81, -27.11],
                [50.70, -16.13],
                [48.99, -4.85],
            ],

            // Route 76 (Major, 7 waypoints, 3549 km)
            [
                [34.64, 140.02],
                [34.64, 140.02],
                [36.95, 145.46],
                [39.98, 154.51],
                [42.15, 163.78],
                [43.58, 174.12],
                [43.95, 179.94],
            ],

            // Route 77 (Minor, 5 waypoints, 3533 km)
            [
                [41.56, 140.74],
                [41.00, 150.13],
                [39.93, 157.96],
                [37.82, 167.56],
                [33.66, 179.98],
            ],

            // Route 78 (Minor, 5 waypoints, 3405 km)
            [
                [46.54, -180.00],
                [49.97, -170.75],
                [52.16, -162.27],
                [53.95, -150.70],
                [54.40, -132.47],
            ],

            // Route 79 (Middle, 25 waypoints, 3399 km)
            [
                [13.75, 120.89],
                [13.66, 120.17],
                [7.35, 115.64],
                [6.42, 112.61],
                [3.14, 108.22],
                [0.98, 106.33],
                [0.98, 106.33],
                [0.98, 106.33],
                [-0.06, 106.79],
                [-1.87, 108.50],
                [-1.87, 108.50],
                [-1.88, 108.52],
                [-2.12, 109.37],
                [-2.12, 109.38],
                [-2.13, 109.38],
                [-2.27, 109.44],
                [-2.49, 109.55],
                [-2.60, 109.60],
                [-2.61, 109.60],
                [-2.61, 109.60],
                [-3.64, 109.60],
                [-3.64, 109.60],
                [-5.01, 107.05],
                [-5.14, 106.79],
                [-5.99, 105.71],
            ],

            // Route 80 (Major, 11 waypoints, 3357 km)
            [
                [22.21, 114.27],
                [22.23, 114.31],
                [22.22, 114.34],
                [22.21, 114.36],
                [22.13, 114.42],
                [22.11, 114.45],
                [23.93, 119.10],
                [26.70, 121.38],
                [34.80, 129.28],
                [41.60, 140.80],
                [41.60, 140.80],
            ],

            // Route 81 (Middle, 8 waypoints, 3332 km)
            [
                [-5.99, 105.71],
                [-6.16, 105.60],
                [-6.16, 105.60],
                [-7.00, 105.19],
                [-24.98, 112.38],
                [-34.32, 114.78],
                [-34.33, 114.78],
                [-34.33, 114.78],
            ],

            // Route 82 (Middle, 6 waypoints, 3326 km)
            [
                [10.25, -17.64],
                [13.38, -17.94],
                [20.91, -18.17],
                [26.45, -15.23],
                [36.00, -6.45],
                [36.08, -5.30],
            ],

            // Route 83 (Middle, 8 waypoints, 3270 km)
            [
                [-55.98, -67.48],
                [-56.37, -75.15],
                [-52.21, -78.31],
                [-47.44, -78.69],
                [-44.03, -77.90],
                [-35.90, -74.76],
                [-32.95, -71.75],
                [-32.95, -71.75],
            ],

            // Route 84 (Major, 6 waypoints, 3135 km)
            [
                [26.37, 56.70],
                [25.82, 57.17],
                [25.46, 57.46],
                [18.56, 72.77],
                [9.79, 75.82],
                [8.07, 76.92],
            ],

            // Route 85 (Middle, 4 waypoints, 3079 km)
            [
                [-32.52, 30.00],
                [-25.43, 47.09],
                [-20.56, 55.34],
                [-19.98, 57.55],
            ],

            // Route 86 (Middle, 11 waypoints, 3069 km)
            [
                [4.69, 125.19],
                [1.84, 127.00],
                [0.28, 126.37],
                [-1.74, 127.07],
                [-2.75, 125.56],
                [-3.17, 125.54],
                [-3.24, 125.54],
                [-3.90, 125.74],
                [-6.59, 132.59],
                [-8.67, 135.65],
                [-10.47, 141.87],
            ],

            // Route 87 (Major, 12 waypoints, 2968 km)
            [
                [35.38, 139.75],
                [35.17, 139.81],
                [33.71, 138.49],
                [30.51, 130.22],
                [26.70, 121.38],
                [23.93, 119.10],
                [22.11, 114.45],
                [22.13, 114.42],
                [22.21, 114.36],
                [22.22, 114.34],
                [22.23, 114.31],
                [22.21, 114.27],
            ],

            // Route 88 (Major, 4 waypoints, 2952 km)
            [
                [41.70, 143.31],
                [46.25, 155.15],
                [48.49, 163.96],
                [50.55, 179.90],
            ],

            // Route 89 (Middle, 8 waypoints, 2947 km)
            [
                [44.62, -63.39],
                [41.90, -64.39],
                [41.90, -64.39],
                [41.38, -64.56],
                [39.79, -65.05],
                [25.26, -67.61],
                [23.72, -67.68],
                [18.43, -67.82],
            ],

            // Route 90 (Middle, 33 waypoints, 2885 km)
            [
                [13.53, 100.65],
                [12.81, 100.63],
                [12.81, 100.63],
                [2.33, 104.92],
                [1.35, 103.91],
                [1.35, 103.91],
                [0.98, 106.33],
                [0.98, 106.33],
                [-0.06, 106.79],
                [-0.08, 106.80],
                [-0.10, 106.82],
                [-1.83, 108.47],
                [-1.86, 108.49],
                [-1.87, 108.50],
                [-1.87, 108.50],
                [-1.88, 108.52],
                [-1.90, 108.59],
                [-1.91, 108.64],
                [-1.95, 108.79],
                [-2.04, 109.07],
                [-2.08, 109.22],
                [-2.10, 109.28],
                [-2.12, 109.37],
                [-2.12, 109.38],
                [-2.13, 109.38],
                [-2.60, 109.60],
                [-2.61, 109.60],
                [-2.64, 109.60],
                [-3.64, 109.60],
                [-3.64, 109.60],
                [-5.01, 107.05],
                [-5.14, 106.79],
                [-5.91, 106.93],
            ],

            // Route 91 (Major, 4 waypoints, 2849 km)
            [
                [53.94, -165.50],
                [53.34, -146.88],
                [51.76, -136.90],
                [48.47, -124.94],
            ],

            // Route 92 (Middle, 65 waypoints, 2793 km)
            [
                [-16.77, 146.05],
                [-15.90, 145.52],
                [-15.61, 145.52],
                [-14.95, 145.42],
                [-14.40, 144.94],
                [-14.21, 144.69],
                [-14.07, 144.62],
                [-14.03, 144.57],
                [-13.99, 144.49],
                [-13.97, 144.26],
                [-13.85, 143.85],
                [-13.79, 143.83],
                [-13.54, 143.73],
                [-13.34, 143.69],
                [-12.49, 143.50],
                [-12.11, 143.27],
                [-12.08, 143.27],
                [-11.83, 143.33],
                [-11.45, 142.99],
                [-11.45, 142.99],
                [-11.23, 143.02],
                [-10.64, 142.74],
                [-10.62, 142.70],
                [-10.48, 142.58],
                [-10.36, 142.31],
                [-10.36, 142.31],
                [-10.38, 142.28],
                [-10.38, 142.23],
                [-10.43, 142.08],
                [-10.47, 141.87],
                [-10.43, 142.08],
                [-10.38, 142.23],
                [-10.38, 142.28],
                [-10.36, 142.31],
                [-10.36, 142.31],
                [-10.48, 142.58],
                [-10.62, 142.70],
                [-10.64, 142.74],
                [-11.23, 143.02],
                [-11.45, 142.99],
                [-11.45, 142.99],
                [-11.83, 143.33],
                [-12.08, 143.27],
                [-12.11, 143.27],
                [-12.49, 143.50],
                [-13.34, 143.69],
                [-13.54, 143.73],
                [-13.79, 143.83],
                [-13.85, 143.85],
                [-13.97, 144.26],
                [-13.99, 144.49],
                [-14.03, 144.57],
                [-14.07, 144.62],
                [-14.21, 144.69],
                [-14.40, 144.94],
                [-14.95, 145.42],
                [-15.61, 145.52],
                [-15.90, 145.52],
                [-16.77, 146.05],
                [-16.86, 146.07],
                [-17.01, 146.12],
                [-18.01, 146.41],
                [-18.42, 146.70],
                [-21.54, 150.55],
                [-22.62, 152.00],
            ],

            // Route 93 (Minor, 6 waypoints, 2742 km)
            [
                [21.15, -157.67],
                [18.53, -163.16],
                [14.99, -169.90],
                [11.96, -175.21],
                [9.21, -179.78],
                [9.08, -180.00],
            ],

            // Route 94 (Middle, 19 waypoints, 2732 km)
            [
                [13.53, 100.65],
                [12.81, 100.63],
                [12.81, 100.63],
                [2.33, 104.92],
                [0.98, 106.33],
                [-0.08, 106.81],
                [-0.10, 106.82],
                [-1.83, 108.47],
                [-1.85, 108.48],
                [-1.92, 108.65],
                [-1.95, 108.79],
                [-2.04, 109.07],
                [-2.08, 109.22],
                [-2.61, 109.60],
                [-2.64, 109.60],
                [-3.17, 109.60],
                [-3.64, 109.60],
                [-3.64, 109.60],
                [-5.99, 105.71],
            ],

            // Route 95 (Minor, 6 waypoints, 2723 km)
            [
                [13.33, 100.44],
                [7.90, 103.95],
                [7.75, 105.87],
                [11.82, 113.94],
                [14.28, 120.51],
                [14.48, 120.82],
            ],

            // Route 96 (Middle, 22 waypoints, 2672 km)
            [
                [-5.91, 106.93],
                [-5.14, 106.79],
                [-5.14, 106.79],
                [-5.14, 106.79],
                [-5.01, 107.05],
                [-3.64, 109.60],
                [-3.64, 109.60],
                [-2.61, 109.60],
                [-2.61, 109.60],
                [-2.61, 109.60],
                [-2.12, 109.38],
                [-2.12, 109.36],
                [-2.08, 109.22],
                [-1.91, 108.64],
                [-1.86, 108.49],
                [-0.08, 106.80],
                [0.98, 106.33],
                [0.98, 106.33],
                [2.33, 104.92],
                [12.81, 100.63],
                [12.81, 100.63],
                [13.53, 100.65],
            ],

            // Route 97 (Major, 11 waypoints, 2660 km)
            [
                [22.21, 114.27],
                [22.23, 114.31],
                [22.22, 114.34],
                [22.21, 114.36],
                [22.13, 114.42],
                [22.11, 114.45],
                [22.11, 114.45],
                [16.06, 113.39],
                [9.83, 109.63],
                [2.97, 105.44],
                [1.10, 104.04],
            ],

            // Route 98 (Middle, 8 waypoints, 2660 km)
            [
                [-26.69, 153.16],
                [-23.87, 154.08],
                [-20.80, 154.67],
                [-10.89, 155.05],
                [-8.95, 154.75],
                [-3.05, 153.76],
                [-3.05, 153.76],
                [-3.05, 153.76],
            ],

            // Route 99 (Middle, 8 waypoints, 2628 km)
            [
                [44.62, -63.39],
                [41.76, -64.90],
                [41.76, -64.90],
                [41.14, -65.20],
                [39.61, -65.91],
                [21.90, -71.25],
                [21.90, -71.25],
                [21.90, -71.25],
            ],



        ];

        // Major air routes (lat, lon pairs between top airports)
        const airRoutes = [
            // Trans-Atlantic
            [[40.6413, -73.7781], [51.4700, -0.4543]],  // JFK (New York) - LHR (London)
            [[33.9416, -118.4085], [51.4700, -0.4543]], // LAX (Los Angeles) - LHR (London)
            [[33.6407, -84.4277], [49.0097, 2.5479]],   // ATL (Atlanta) - CDG (Paris)
            [[25.7959, -80.2870], [41.2974, 2.0833]],   // MIA (Miami) - BCN (Barcelona)
            [[41.9742, -87.9073], [50.0379, 8.5622]],   // ORD (Chicago) - FRA (Frankfurt)
            
            // Trans-Pacific
            [[37.7749, -122.4194], [35.5494, 139.7798]], // SFO (San Francisco) - HND (Tokyo)
            [[34.0522, -118.2437], [31.1432, 121.8054]], // LAX - PVG (Shanghai)
            [[47.4502, -122.3088], [35.5494, 139.7798]], // SEA (Seattle) - HND (Tokyo)
            [[49.1939, -123.1844], [22.3080, 113.9185]], // YVR (Vancouver) - HKG (Hong Kong)
            [[37.6213, -122.3790], [1.3644, 103.9915]],  // SFO - SIN (Singapore)
            
            // Asia-Pacific
            [[35.5494, 139.7798], [1.3644, 103.9915]],   // HND (Tokyo) - SIN (Singapore)
            [[22.3080, 113.9185], [13.6900, 100.7501]],  // HKG (Hong Kong) - BKK (Bangkok)
            [[31.1432, 121.8054], [37.4602, 126.4407]],  // PVG (Shanghai) - ICN (Seoul)
            [[28.5383, 77.3910], [1.3644, 103.9915]],    // DEL (Delhi) - SIN (Singapore)
            [[35.5494, 139.7798], [37.4602, 126.4407]],  // HND (Tokyo) - ICN (Seoul)
            
            // Europe-Asia
            [[51.4700, -0.4543], [25.2532, 55.3657]],    // LHR (London) - DXB (Dubai)
            [[49.0097, 2.5479], [25.2532, 55.3657]],     // CDG (Paris) - DXB (Dubai)
            [[50.0379, 8.5622], [28.5383, 77.3910]],     // FRA (Frankfurt) - DEL (Delhi)
            [[52.3105, 4.7683], [1.3644, 103.9915]],     // AMS (Amsterdam) - SIN (Singapore)
            [[41.2764, 28.7279], [25.2532, 55.3657]],    // IST (Istanbul) - DXB (Dubai)
            
            // Middle East-Asia
            [[25.2532, 55.3657], [13.6900, 100.7501]],   // DXB (Dubai) - BKK (Bangkok)
            [[25.2532, 55.3657], [22.3080, 113.9185]],   // DXB (Dubai) - HKG (Hong Kong)
            [[25.2532, 55.3657], [31.1432, 121.8054]],   // DXB (Dubai) - PVG (Shanghai)
            
            // Europe Internal
            [[51.4700, -0.4543], [49.0097, 2.5479]],     // London Heathrow (LHR) → Paris Charles de Gaulle (CDG)
            [[48.1186, 11.5673], [41.8026, 12.2387]],    // Munich (MUC) → Rome Fiumicino (FCO)
            [[40.4722, -3.5608], [48.8566, 2.3522]],     // Madrid Barajas (MAD) → Paris (CDG)
            [[41.2974, 2.0833], [51.4700, -0.4543]],     // Barcelona (BCN) → London Heathrow (LHR)
            [[45.6301, 8.7281], [52.3105, 4.7683]],      // Milan Malpensa (MXP) → Amsterdam Schiphol (AMS)
            [[52.5597, 13.2877], [41.8026, 12.2387]],    // Berlin Brandenburg (BER) → Rome Fiumicino (FCO)
            [[37.9364, 23.9445], [50.0379, 8.5622]],     // Athens (ATH) → Frankfurt (FRA)
            [[59.6519, 17.9186], [55.9736, -3.3433]],    // Stockholm Arlanda (ARN) → Edinburgh (EDI)
            
            // Africa Internal
            [[-26.1337, 28.2420], [-1.3192, 36.9278]],   // Johannesburg (JNB) → Nairobi (NBO)
            [[30.1219, 31.4056], [-33.9696, 18.5972]],    // Cairo (CAI) → Cape Town (CPT)
            [[-1.3192, 36.9278], [25.2532, 55.3657]],      // Nairobi (NBO) → Entebbe (EBB)
            [[14.6700, -17.0733], [-26.1337, 28.2420]],      // Dakar (DSS) → Lomé (LFW)
            [[-4.3858, 15.4446], [-6.1657, 39.2026]],     // Kinshasa (FIH) → Dar es Salaam (DAR)
            [[-33.9696, 18.5972], [-29.6144, 31.1197]],   // Cape Town (CPT) → Durban (DUR)
            [[9.0068, 7.2632], [30.1219, 31.4056]],       // Abuja (ABV) → Cairo (CAI)
            [[12.3532, -1.5124], [5.6052, -0.1668]],      // Ouagadougou (OUA) → Accra (ACC)
            [[-1.3192, 36.9278], [-4.3858, 15.4446]],     // Nairobi (NBO) → Kinshasa (FIH)

            
            // Oceania
            [[-33.9461, 151.1772], [1.3644, 103.9915]],  // SYD (Sydney) - SIN (Singapore)
            [[-37.0082, 174.7850], [-33.9461, 151.1772]], // AKL (Auckland) - SYD (Sydney)
            [[-33.9461, 151.1772], [19.8987, -155.6659]],
            // North America Internal
            [[40.6413, -73.7781], [33.9416, -118.4085]], // JFK - LAX
            [[32.8998, -97.0403], [33.6407, -84.4277]],  // DFW (Dallas) - ATL (Atlanta)
            [[33.9416, -118.4085], [19.8987, -155.6659]],
            // North America → South America
            [[25.7933, -80.2906], [-12.0219, -77.1143]], // Miami (MIA) → Lima (LIM)
            [[40.6413, -73.7781], [-34.8222, -58.5358]], // New York (JFK) → Buenos Aires (EZE)
            [[29.9902, -95.3368], [4.7016, -74.1469]],   // Houston (IAH) → Bogotá (BOG)
            [[33.9416, -118.4085], [-23.4319, -46.4695]],// Los Angeles (LAX) → São Paulo (GRU)
            [[25.7933, -80.2906], [9.0714, -79.3835]],   // Miami (MIA) → Panama City (PTY)

            // South America Internal
            [[-23.4319, -46.4695], [-33.3930, -70.7858]], // São Paulo (GRU) → Santiago (SCL)
            [[-12.0219, -77.1143], [-33.3930, -70.7858]], // Lima (LIM) → Santiago (SCL)
            [[4.7016, -74.1469], [-12.0219, -77.1143]],   // Bogotá (BOG) → Lima (LIM)
            [[-34.8222, -58.5358], [-22.9083, -43.1964]], // Buenos Aires (EZE) → Rio de Janeiro (GIG)
            [[-12.0219, -77.1143], [-0.1292, -78.3575]],  // Lima (LIM) → Quito (UIO)

        ];

        // Calculate great circle distance between two lat/lon points (in km)
        function greatCircleDistance(lat1, lon1, lat2, lon2) {
            const R = 6371; // Earth's radius in km
            const phi1 = lat1 * Math.PI / 180;
            const phi2 = lat2 * Math.PI / 180;
            const deltaPhi = (lat2 - lat1) * Math.PI / 180;
            const deltaLambda = (lon2 - lon1) * Math.PI / 180;
            
            const a = Math.sin(deltaPhi/2) * Math.sin(deltaPhi/2) +
                      Math.cos(phi1) * Math.cos(phi2) *
                      Math.sin(deltaLambda/2) * Math.sin(deltaLambda/2);
            const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
            
            return R * c;
        }

        // Spherical linear interpolation for great circle paths
        function slerp(v1, v2, t) {
            const dot = v1.dot(v2);
            const clampedDot = Math.max(-1, Math.min(1, dot));
            const theta = Math.acos(clampedDot) * t;
            
            const relative = v2.clone().sub(v1.clone().multiplyScalar(clampedDot)).normalize();
            return v1.clone().multiplyScalar(Math.cos(theta)).add(relative.multiplyScalar(Math.sin(theta)));
        }

        // Returns true if the straight line between p and q does NOT intersect the Earth sphere
        function hasLineOfSight(p, q, radius = EARTHRADIUS) {
            const u = new THREE.Vector3().subVectors(q, p);
            const A = u.dot(u);
            const B = 2 * p.dot(u);
            const C = p.dot(p) - radius * radius;
            const D = B * B - 4 * A * C;
            if (D <= 0) return true; // no intersection (or tangent)
            const sqrtD = Math.sqrt(D);
            const t1 = (-B - sqrtD) / (2 * A);
            const t2 = (-B + sqrtD) / (2 * A);
            // occluded only if an intersection exists within the segment bounds
            const intersectsWithinSegment = (t1 >= 0 && t1 <= 1) || (t2 >= 0 && t2 <= 1);
            return !intersectsWithinSegment;
        }

        // --- Sensor FOV helpers ---
        // Minimal signed angular difference on a circle
        function angleDiff(a, b) {
            const TWO_PI = Math.PI * 2;
            let d = (a - b) % TWO_PI;
            if (d > Math.PI) d -= TWO_PI;
            if (d < -Math.PI) d += TWO_PI;
            return Math.abs(d);
        }

        // Returns true if a normalized direction in sensor-local space lies within the sensor FOV
        // Local frame convention: sensor looks along -Y. So forward means localDir.y < 0
        function isWithinSensorFOVLocal(localDir, cone) {
            const type = cone.userData.sensorType;
            // Must be in front of sensor
            if (-localDir.y <= 0) return false;

            if (type === 'cone') {
                // Angle between -Y axis and direction
                const halfFovRad = cone.userData.halfFovRad != null
                    ? cone.userData.halfFovRad
                    : (coneFov * Math.PI / 180) / 2;
                const cosAngle = Math.max(-1, Math.min(1, -localDir.y));
                const angle = Math.acos(cosAngle);
                return angle <= halfFovRad;
            }

            if (type === 'rectangular') {
                const h = cone.userData.height ?? 0.3;
                const tanX = (cone.userData.rectWidth ?? 0.1) / h;
                const tanZ = (cone.userData.rectDepth ?? 0.1) / h;
                const y = -localDir.y; // forward distance (normalized units)
                const withinX = Math.abs(localDir.x / y) <= tanX;
                const withinZ = Math.abs(localDir.z / y) <= tanZ;
                return withinX && withinZ;
            }

            if (type === 'axeBlade') {
                const h = cone.userData.height ?? 1;
                const outerR = cone.userData.outerRadius ?? 1;
                const cutoutHalf = ((cone.userData.cutoutAngle ?? 40) * Math.PI / 180) / 2;
                const outerHalf = Math.atan2(outerR, h); // half-angle to outer rim

                // Polar angle from -Y axis
                const theta = Math.acos(Math.max(-1, Math.min(1, -localDir.y)));
                if (theta < cutoutHalf || theta > outerHalf) return false;

                // Azimuth around -Y axis in XZ plane
                const phi = Math.atan2(localDir.z, localDir.x);
                const bladeAngleRad = (cone.userData.bladeAngle ?? 135) * Math.PI / 180;
                const halfBlade = bladeAngleRad / 2;
                const centers = cone.userData.bladeCenters ?? [Math.PI / 2, 3 * Math.PI / 2];
                // Accept if phi is within +/- halfBlade of either center
                return centers.some(c => angleDiff(phi, c) <= halfBlade);
            }

            return false;
        }

        function createShips() {
            ships = [];
            
            // Create multiple ships per route - use all available routes
            const routeIndicesToUse = [...Array(shippingRoutes.length).keys()];
            
            routeIndicesToUse.forEach(routeIndex => {
                const route = shippingRoutes[routeIndex];
                
                // Skip if route doesn't exist or is invalid
                if (!route || route.length < 2) return;
                
                // Calculate total route distance
                let totalDistance = 0;
                for (let i = 0; i < route.length - 1; i++) {
                    const [lat1, lon1] = route[i];
                    const [lat2, lon2] = route[i + 1];
                    totalDistance += greatCircleDistance(lat1, lon1, lat2, lon2);
                }
                
                const shipsPerRoute = 20; // 20 ships per route to show trace
                
                for (let i = 0; i < shipsPerRoute; i++) {
                    ships.push({
                        route: route,
                        routeIndex: routeIndex,
                        progress: i / shipsPerRoute,
                        speed: 25, // 25-40 km/hour (typical cargo ship speed)
                        routeDistance: totalDistance,
                        type: 'ship'
                    });
                }
            });
        }

        function createPlanes() {
            planes = [];
            
            // Create aircraft for each air route
            airRoutes.forEach((route, routeIndex) => {
                const planesPerRoute = 2; // 2 planes per route
                
                // Calculate route distance (simple 2-point great circle)
                const [lat1, lon1] = route[0];
                const [lat2, lon2] = route[1];
                const routeDistance = greatCircleDistance(lat1, lon1, lat2, lon2);
                
                for (let i = 0; i < planesPerRoute; i++) {
                    planes.push({
                        route: route,
                        routeIndex: routeIndex,
                        progress: i / planesPerRoute,
                        speed: 100*(800 + Math.random() * 150), // 800-950 km/hour (typical cruising speed)
                        routeDistance: routeDistance,
                        type: 'plane'
                    });
                }
            });
        }

        function createPlaneRoutes() {
            // Clear existing route lines
            planeRoutes.forEach(line => earth.remove(line));
            planeRoutes = [];
            
            // Create visual great circle routes for each air route
            airRoutes.forEach(route => {
                const [lat1, lon1] = route[0];
                const [lat2, lon2] = route[1];
                
                // Generate points along the great circle
                const segments = 50; // Number of segments for smooth curve
                const points = [];
                
                for (let i = 0; i <= segments; i++) {
                    const t = i / segments;
                    
                    // Calculate position along great circle
                    const phi1 = (90 - lat1) * (Math.PI / 180);
                    const theta1 = (lon1 + SHIP_PLANE_TEXTURE_LON_OFFSET) * (Math.PI / 180);
                    const pos1 = new THREE.Vector3(
                        EARTHRADIUS * Math.sin(phi1) * Math.sin(theta1),
                        EARTHRADIUS * Math.cos(phi1),
                        EARTHRADIUS * Math.sin(phi1) * Math.cos(theta1)
                    );
                    
                    const phi2 = (90 - lat2) * (Math.PI / 180);
                    const theta2 = (lon2 + SHIP_PLANE_TEXTURE_LON_OFFSET) * (Math.PI / 180);
                    const pos2 = new THREE.Vector3(
                        EARTHRADIUS * Math.sin(phi2) * Math.sin(theta2),
                        EARTHRADIUS * Math.cos(phi2),
                        EARTHRADIUS * Math.sin(phi2) * Math.cos(theta2)
                    );
                    
                    // Use slerp for great circle interpolation
                    const position = slerp(pos1.normalize(), pos2.normalize(), t);
                    position.multiplyScalar(EARTHRADIUS * 1.005); // Slightly above surface
                    
                    points.push(position);
                }
                
                // Create line geometry
                const geometry = new THREE.BufferGeometry().setFromPoints(points);
                const material = new THREE.LineBasicMaterial({
                    color: 0xffff00,  // Yellow to match aircraft
                    transparent: true,
                    opacity: 0.1,
                    linewidth: 1
                });
                
                const line = new THREE.Line(geometry, material);
                line.visible = showPlanes; // Match visibility with planes
                earth.add(line); // Add to Earth so it rotates with it
                planeRoutes.push(line);
            });
        }

        function createShipMeshes() {
            shipMeshes.forEach(mesh => scene.remove(mesh));
            shipMeshes = [];
            
            // Create square geometry for ships
            const size = 0.006;
            const shipGeometry = new THREE.PlaneGeometry(size, size);
            const shipMaterial = new THREE.MeshBasicMaterial({ 
                color: 0xf9dfff,
                transparent: true,
                opacity: 0.9,
                side: THREE.DoubleSide
            });
            
            ships.forEach(() => {
                const shipMesh = new THREE.Mesh(shipGeometry, shipMaterial);
                shipMesh.visible = showShips; // Set initial visibility
                earth.add(shipMesh); // Add to earth so they rotate with it
                shipMeshes.push(shipMesh);
            });
        }

        function createPlaneMeshes() {
            planeMeshes.forEach(mesh => scene.remove(mesh));
            planeMeshes = [];
            
            // Create triangle geometry for planes (rotated 90 degrees to lie flat on Earth surface)
            const size = 0.008;
            const planeGeometry = new THREE.BufferGeometry();
            const vertices = new Float32Array([
                0, 0, -size,          // top vertex (pointing forward)
                -size/2, 0, size/2,   // bottom left
                size/2, 0, size/2     // bottom right
            ]);
            planeGeometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
            
            const planeMaterial = new THREE.MeshBasicMaterial({ 
                color: 0xffff00, // Yellow for aircraft
                transparent: true,
                opacity: 0.95,
                side: THREE.DoubleSide
            });
            
            planes.forEach(() => {
                const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
                planeMesh.visible = showPlanes; // Set initial visibility
                earth.add(planeMesh); // Add to earth so they rotate with it
                planeMeshes.push(planeMesh);
            });
        }
        
        // Randomly select ship and plane targets and mark them red
        function selectRandomTargets(numShips = 8, numPlanes = 8) {
            selectedTargets = [];
            const pickRandomIndices = (n, k) => {
                const idx = [...Array(n).keys()];
                for (let i = idx.length - 1; i > 0; i--) {
                    const j = Math.floor(Math.random() * (i + 1));
                    [idx[i], idx[j]] = [idx[j], idx[i]];
                }
                return idx.slice(0, Math.min(k, n));
            };
            
            // Ships
            pickRandomIndices(shipMeshes.length, numShips).forEach(index => {
                const mesh = shipMeshes[index];
                if (!mesh) return;
                mesh.material = mesh.material.clone();
                mesh.material.color.set(0xff0000);
                mesh.material.opacity = 1.0;
                selectedTargets.push({ type: 'ship', index, mesh });
            });
            
            // Planes
            pickRandomIndices(planeMeshes.length, numPlanes).forEach(index => {
                const mesh = planeMeshes[index];
                if (!mesh) return;
                mesh.material = mesh.material.clone();
                mesh.material.color.set(0xff0000);
                mesh.material.opacity = 1.0;
                selectedTargets.push({ type: 'plane', index, mesh });
            });
        }

        function updateShips(deltaTime) {
            if (!showShips) return;
            
            ships.forEach((ship, index) => {
                // Update progress based on absolute speed (km/hour)
                // deltaTime is in ms, speed is in km/hour
                const distanceTraveled = (ship.speed * deltaTime) / 3600000; // Convert to km
                const progressIncrement = distanceTraveled / ship.routeDistance;
                ship.progress += progressIncrement;
                if (ship.progress >= 1) ship.progress = 0; // Loop
                
                // Get current and next waypoint
                const route = ship.route;
                const segmentCount = route.length - 1;
                const totalProgress = ship.progress * segmentCount;
                const currentIndex = Math.floor(totalProgress);
                const nextIndex = (currentIndex + 1) % route.length;
                const segmentProgress = totalProgress - currentIndex;
                
                // Get 3D positions for waypoints (in Earth's local space)
                // Apply texture offset to align with actual Earth texture orientation
                const [lat1, lon1] = route[currentIndex];
                const [lat2, lon2] = route[nextIndex];
                
                const phi1 = (90 - lat1) * (Math.PI / 180);
                const theta1 = (lon1 + SHIP_PLANE_TEXTURE_LON_OFFSET) * (Math.PI / 180);
                const pos1 = new THREE.Vector3(
                    EARTHRADIUS * Math.sin(phi1) * Math.sin(theta1),
                    EARTHRADIUS * Math.cos(phi1),
                    EARTHRADIUS * Math.sin(phi1) * Math.cos(theta1)
                );
                
                const phi2 = (90 - lat2) * (Math.PI / 180);
                const theta2 = (lon2 + SHIP_PLANE_TEXTURE_LON_OFFSET) * (Math.PI / 180);
                const pos2 = new THREE.Vector3(
                    EARTHRADIUS * Math.sin(phi2) * Math.sin(theta2),
                    EARTHRADIUS * Math.cos(phi2),
                    EARTHRADIUS * Math.sin(phi2) * Math.cos(theta2)
                );
                
                // Use spherical interpolation (great circle path)
                const position = slerp(pos1.normalize(), pos2.normalize(), segmentProgress);
                position.multiplyScalar(EARTHRADIUS * 1.003); // Slightly above surface
                
                shipMeshes[index].position.copy(position);
                
                // Orient ship to lie flat (tangent) to Earth's surface
                // Default PlaneGeometry normal is +Z; align it to local radial (up) vector
                const upVec = position.clone().normalize();
                const quat = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), upVec);
                shipMeshes[index].quaternion.copy(quat);
            });
        }

        function updatePlanes(deltaTime) {
            if (!showPlanes) return;
            
            planes.forEach((plane, index) => {
                // Update progress based on absolute speed (km/hour)
                // deltaTime is in ms, speed is in km/hour
                const distanceTraveled = (plane.speed * deltaTime) / 3600000; // Convert to km
                const progressIncrement = distanceTraveled / plane.routeDistance;
                plane.progress += progressIncrement;
                if (plane.progress >= 1) plane.progress = 0;
                
                // Air routes are simple 2-point great circles (in Earth's local space)
                // Apply texture offset to align with actual Earth texture orientation
                const route = plane.route;
                const [lat1, lon1] = route[0];
                const [lat2, lon2] = route[1];
                
                const phi1 = (90 - lat1) * (Math.PI / 180);
                const theta1 = (lon1 + SHIP_PLANE_TEXTURE_LON_OFFSET) * (Math.PI / 180);
                const pos1 = new THREE.Vector3(
                    EARTHRADIUS * Math.sin(phi1) * Math.sin(theta1),
                    EARTHRADIUS * Math.cos(phi1),
                    EARTHRADIUS * Math.sin(phi1) * Math.cos(theta1)
                );
                
                const phi2 = (90 - lat2) * (Math.PI / 180);
                const theta2 = (lon2 + SHIP_PLANE_TEXTURE_LON_OFFSET) * (Math.PI / 180);
                const pos2 = new THREE.Vector3(
                    EARTHRADIUS * Math.sin(phi2) * Math.sin(theta2),
                    EARTHRADIUS * Math.cos(phi2),
                    EARTHRADIUS * Math.sin(phi2) * Math.cos(theta2)
                );
                
                // Use spherical interpolation for great circle path
                const position = slerp(pos1.normalize(), pos2.normalize(), plane.progress);
                position.multiplyScalar(EARTHRADIUS * 1.01); // Higher altitude than ships
                
                planeMeshes[index].position.copy(position);
                
                // Orient triangle to point in direction of travel
                const nextProgress = plane.progress + 0.01;
                const nextPosition = slerp(pos1.normalize(), pos2.normalize(), nextProgress > 1 ? 0 : nextProgress);
                nextPosition.multiplyScalar(EARTHRADIUS * 1.01);
                
                // Calculate direction vector
                const direction = new THREE.Vector3().subVectors(nextPosition, position).normalize();
                const up = position.clone().normalize();
                
                // Create rotation to point triangle in direction of travel
                const quaternion = new THREE.Quaternion();
                const targetMatrix = new THREE.Matrix4().lookAt(position, nextPosition, up);
                quaternion.setFromRotationMatrix(targetMatrix);
                planeMeshes[index].quaternion.copy(quaternion);
            });
        }

        // Draw satellite-to-satellite crosslinks where LoS is clear (no Earth occlusion)
        function updateCrosslinks() {
            // Clear previous lines
            crosslinkLines.forEach(l => { scene.remove(l); if (l.geometry) l.geometry.dispose(); });
            crosslinkLines = [];
            
            const satPositions = satelliteMeshes.map(m => {
                const v = new THREE.Vector3();
                m.getWorldPosition(v);
                return v;
            });
            
            for (let i = 0; i < satPositions.length; i++) {
                for (let j = i + 1; j < satPositions.length; j++) {
                    const p1 = satPositions[i];
                    const p2 = satPositions[j];
                    
                    // Early rejection: skip if too far apart (satellites beyond horizon can't crosslink)
                    const distSq = p1.distanceToSquared(p2);
                    if (distSq > 16) continue; // ~4 Earth radii max practical crosslink distance
                    
                    if (hasLineOfSight(p1, p2)) {
                        const geometry = new THREE.BufferGeometry().setFromPoints([p1, p2]);
                        const line = new THREE.Line(geometry, crosslinkMaterial);
                        scene.add(line);
                        crosslinkLines.push(line);
                    }
                }
            }
        }

        // Draw satellite-to-target downlinks (targets are randomly selected ships/planes and colored red)
        // Only draw links when target is within sensor FOV
        function updateDownlinks() {
            // Clear previous lines
            downlinkLines.forEach(l => { scene.remove(l); if (l.geometry) l.geometry.dispose(); });
            downlinkLines = [];
            
            const satPositions = satelliteMeshes.map(m => {
                const v = new THREE.Vector3();
                m.getWorldPosition(v);
                return v;
            });
            
            // Precompute sensor data for all satellites
            const sensorData = [];
            for (let i = 0; i < satPositions.length; i++) {
                const cone = sensorCones[i];
                if (!cone) continue;
                
                const satPos = satPositions[i];
                const satR = satPos.length();
                const satNorm = satPos.clone().multiplyScalar(1 / satR);
                const invQuat = cone.quaternion.clone().invert();
                const altitudeKm = (satR - 1) * PHYSICS.EARTH_RADIUS_KM;
                const maxRange = (coneRangeKm + altitudeKm) / PHYSICS.EARTH_RADIUS_KM;
                const maxRangeSq = maxRange * maxRange;
                const cosMax = 1 / satR; // horizon limit: acos(1/r)
                
                sensorData.push({ satPos, satNorm, invQuat, cone, maxRangeSq, cosMax, index: i });
            }
            
            selectedTargets.forEach(target => {
                if (!target.mesh || !target.mesh.visible) return; // respect visibility toggles
                const tpos = new THREE.Vector3();
                target.mesh.getWorldPosition(tpos);
                
                for (const sensor of sensorData) {
                    // Early rejection: check distance squared before computing actual distance
                    const distSq = sensor.satPos.distanceToSquared(tpos);
                    if (distSq > sensor.maxRangeSq) continue;

                    // Fast horizon reject using subsatellite dot target normal
                    const tnorm = tpos.clone().normalize();
                    if (sensor.satNorm.dot(tnorm) < sensor.cosMax) continue;
                    
                    // Vector from satellite to target
                    const toTarget = new THREE.Vector3().subVectors(tpos, sensor.satPos);
                    const distance = Math.sqrt(distSq);
                    toTarget.divideScalar(distance); // normalize

                    // Sensor-local FOV acceptance
                    const localDir = toTarget.clone().applyQuaternion(sensor.invQuat);
                    if (!isWithinSensorFOVLocal(localDir, sensor.cone)) continue;
                    
                    // Final robust LOS check
                    if (!hasLineOfSight(sensor.satPos, tpos)) continue;
                    
                    // Draw the link
                    const geometry = new THREE.BufferGeometry().setFromPoints([sensor.satPos, tpos]);
                    const line = new THREE.Line(geometry, downlinkMaterial);
                    scene.add(line);
                    downlinkLines.push(line);
                }
            });
        }

        function getEarthRotation(date) {
            // Greenwich Apparent Sidereal Time (GAST) with small nutation correction
            const DEG2RAD = Math.PI / 180;
            const jd = getJulianDate(date);
            const T = (jd - 2451545.0) / 36525.0;

            // GMST (deg)
            let gmst = 280.46061837 + 360.98564736629 * (jd - 2451545.0)
                     + 0.000387933 * T * T - (T * T * T) / 38710000.0;

            // Small equation-of-equinoxes approximation to get GAST (deg)
            // Using leading terms dependent on Ω and L0
            let L0 = 280.46646 + T * (36000.76983 + 0.0003032 * T);
            L0 = ((L0 % 360) + 360) % 360;
            const Omega = (125.04 - 1934.136 * T) * DEG2RAD;
            const eqeq = 0.00264 * Math.sin(Omega) + 0.000063 * Math.sin(2 * L0 * DEG2RAD); // degrees

            let gast = gmst + eqeq - 35; // GAST in degrees

            // Normalize to 0-360 range
            gast = gast % 360;
            if (gast < 0) gast += 360;

            // Convert to radians (positive so rotation matches day/night texture orientation)
            return gast * DEG2RAD;
        }

        class OrbitPropagator {
            static propagateSingle(satellite, time) {
                const dt = time - satellite.epoch;
                const { a, e, i, raan, argp, M0 } = satellite;
                const n0 = Math.sqrt(PHYSICS.MU / (a * a * a));
                const j2Factor = (n0 * PHYSICS.EARTH_RADIUS * PHYSICS.EARTH_RADIUS * PHYSICS.J2) /
                                (a * a * Math.pow(1 - e * e, 2));
                const dRaanDt = -1.5 * j2Factor * Math.cos(i);
                const dArgpDt = 0.75 * j2Factor * (4 - 5 * Math.pow(Math.sin(i), 2));
                const dMDt = 0.75 * j2Factor * Math.sqrt(1 - e * e) * (2 - 3 * Math.pow(Math.sin(i), 2));
                const raan_t = raan + dRaanDt * dt;
                const argp_t = argp + dArgpDt * dt;
                const M_t = M0 + (n0 + dMDt) * dt;
                const E = this.solveKeplerEquation(M_t, e);
                const sinE = Math.sin(E);
                const cosE = Math.cos(E);
                const sqrtOneMinusE2 = Math.sqrt(1 - e * e);
                const nu = 2 * Math.atan2(sqrtOneMinusE2 * sinE, cosE + e);
                const r = a * (1 - e * cosE);
                const cosNu = Math.cos(nu);
                const sinNu = Math.sin(nu);
                const x_orb = r * cosNu;
                const y_orb = r * sinNu;
                const cosRaan = Math.cos(raan_t);
                const sinRaan = Math.sin(raan_t);
                const cosArgp = Math.cos(argp_t);
                const sinArgp = Math.sin(argp_t);
                const cosI = Math.cos(i);
                const sinI = Math.sin(i);
                const x_eci = (cosRaan * cosArgp - sinRaan * sinArgp * cosI) * x_orb +
                            (-cosRaan * sinArgp - sinRaan * cosArgp * cosI) * y_orb;
                const y_eci = (sinRaan * cosArgp + cosRaan * sinArgp * cosI) * x_orb +
                            (-sinRaan * sinArgp + cosRaan * cosArgp * cosI) * y_orb;
                const z_eci = (sinArgp * sinI) * x_orb + (cosArgp * sinI) * y_orb;
                
                const theta = PHYSICS.OMEGA_EARTH * time;
                const cosTheta = Math.cos(theta);
                const sinTheta = Math.sin(theta);
                const x_ecef = cosTheta * x_eci + sinTheta * y_eci;
                const y_ecef = -sinTheta * x_eci + cosTheta * y_eci;
                const z_ecef = z_eci;
                
                return new THREE.Vector3(
                    x_ecef / PHYSICS.EARTH_RADIUS,
                    z_ecef / PHYSICS.EARTH_RADIUS,
                    -y_ecef / PHYSICS.EARTH_RADIUS
                );
            }
            
            static propagateWithVelocity(satellite, time) {
                const dt = time - satellite.epoch;
                const { a, e, i, raan, argp, M0 } = satellite;
                const n0 = Math.sqrt(PHYSICS.MU / (a * a * a));
                const j2Factor = (n0 * PHYSICS.EARTH_RADIUS * PHYSICS.EARTH_RADIUS * PHYSICS.J2) /
                                (a * a * Math.pow(1 - e * e, 2));
                const dRaanDt = -1.5 * j2Factor * Math.cos(i);
                const dArgpDt = 0.75 * j2Factor * (4 - 5 * Math.pow(Math.sin(i), 2));
                const dMDt = 0.75 * j2Factor * Math.sqrt(1 - e * e) * (2 - 3 * Math.pow(Math.sin(i), 2));
                const raan_t = raan + dRaanDt * dt;
                const argp_t = argp + dArgpDt * dt;
                const M_t = M0 + (n0 + dMDt) * dt;
                const E = this.solveKeplerEquation(M_t, e);
                const sinE = Math.sin(E);
                const cosE = Math.cos(E);
                const sqrtOneMinusE2 = Math.sqrt(1 - e * e);
                const nu = 2 * Math.atan2(sqrtOneMinusE2 * sinE, cosE + e);
                const r = a * (1 - e * cosE);
                const cosNu = Math.cos(nu);
                const sinNu = Math.sin(nu);
                
                // Position in orbital frame
                const x_orb = r * cosNu;
                const y_orb = r * sinNu;
                
                // Velocity in orbital frame
                const vx_orb = -(a * n0 * sinE) / (1 - e * cosE);
                const vy_orb = (a * n0 * sqrtOneMinusE2 * cosE) / (1 - e * cosE);
                
                const cosRaan = Math.cos(raan_t);
                const sinRaan = Math.sin(raan_t);
                const cosArgp = Math.cos(argp_t);
                const sinArgp = Math.sin(argp_t);
                const cosI = Math.cos(i);
                const sinI = Math.sin(i);
                
                // Position in ECI
                const x_eci = (cosRaan * cosArgp - sinRaan * sinArgp * cosI) * x_orb +
                            (-cosRaan * sinArgp - sinRaan * cosArgp * cosI) * y_orb;
                const y_eci = (sinRaan * cosArgp + cosRaan * sinArgp * cosI) * x_orb +
                            (-sinRaan * sinArgp + cosRaan * cosArgp * cosI) * y_orb;
                const z_eci = (sinArgp * sinI) * x_orb + (cosArgp * sinI) * y_orb;
                
                // Velocity in ECI
                const vx_eci = (cosRaan * cosArgp - sinRaan * sinArgp * cosI) * vx_orb +
                            (-cosRaan * sinArgp - sinRaan * cosArgp * cosI) * vy_orb;
                const vy_eci = (sinRaan * cosArgp + cosRaan * sinArgp * cosI) * vx_orb +
                            (-sinRaan * sinArgp + cosRaan * cosArgp * cosI) * vy_orb;
                const vz_eci = (sinArgp * sinI) * vx_orb + (cosArgp * sinI) * vy_orb;
                
                const theta = PHYSICS.OMEGA_EARTH * time;
                const cosTheta = Math.cos(theta);
                const sinTheta = Math.sin(theta);
                
                // Position in ECEF
                const x_ecef = cosTheta * x_eci + sinTheta * y_eci;
                const y_ecef = -sinTheta * x_eci + cosTheta * y_eci;
                const z_ecef = z_eci;
                
                // Velocity in ECEF (accounting for Earth rotation)
                const vx_ecef = cosTheta * vx_eci + sinTheta * vy_eci - PHYSICS.OMEGA_EARTH * y_ecef;
                const vy_ecef = -sinTheta * vx_eci + cosTheta * vy_eci + PHYSICS.OMEGA_EARTH * x_ecef;
                const vz_ecef = vz_eci;
                
                return {
                    position: new THREE.Vector3(
                        x_ecef / PHYSICS.EARTH_RADIUS,
                        z_ecef / PHYSICS.EARTH_RADIUS,
                        -y_ecef / PHYSICS.EARTH_RADIUS
                    ),
                    velocity: new THREE.Vector3(
                        vx_ecef / PHYSICS.EARTH_RADIUS,
                        vz_ecef / PHYSICS.EARTH_RADIUS,
                        -vy_ecef / PHYSICS.EARTH_RADIUS
                    )
                };
            }

            static solveKeplerEquation(M, e) {
                M = M % (2 * Math.PI);
                if (M < 0) M += 2 * Math.PI;
                let E = e < 0.8 ? M : Math.PI;
                for (let i = 0; i < 8; i++) {
                    const f = E - e * Math.sin(E) - M;
                    const df = 1 - e * Math.cos(E);
                    const deltaE = f / df;
                    E -= deltaE;
                    if (Math.abs(deltaE) < 1e-12) break;
                }
                return E;
            }
        }

        function createSatellites() {
            const epoch = Date.now() / 1000;
            satellites = [];
            
            // Create 12 LEO satellites with random inclinations
            for (let i = 1; i < 13; i++) {
                const altitude = 400000 + Math.random() * 600000; // 400-1000 km
                const inclination = Math.random() * Math.PI/2; // 0-180 degrees
                const raan = Math.random() * 2 * Math.PI;
                const argp = Math.random() * 2 * Math.PI;
                const ma = Math.random() * 2 * Math.PI;
                
                satellites.push({
                    a: PHYSICS.EARTH_RADIUS + altitude,
                    e: 0,//0.001 + Math.random() * 0.02, // Low eccentricity
                    i: inclination,
                    raan: raan,
                    argp: argp,
                    M0: ma,
                    epoch: epoch
                });
            }
        }

        function createSatelliteMeshes() {
            const geometry = new THREE.SphereGeometry(0.008, 8, 8);
            
            for (let i = 0; i < satellites.length; i++) {
                const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
                const mesh = new THREE.Mesh(geometry, material);
                scene.add(mesh);
                satelliteMeshes.push(mesh);
            }
        }
        
        function updateOrbitTrails(currentTimeSeconds) {
            // Clear existing trails
            orbitTrails.forEach(trail => scene.remove(trail));
            orbitTrails = [];
            
            const numPoints = 64; // Points for the trail
            const trailDuration = 5 * 60; // 15 minutes in seconds
            
            for (let i = 0; i < satellites.length; i++) {
                const sat = satellites[i];
                const points = [];
                
                // Calculate points from 15 minutes ago to current time
                for (let j = 0; j <= numPoints; j++) {
                    const timeOffset = (j / numPoints) * trailDuration;
                    const trailTime = currentTimeSeconds - trailDuration + timeOffset;
                    
                    const position = OrbitPropagator.propagateSingle(sat, trailTime);
                    points.push(position);
                }
                
                const geometry = new THREE.BufferGeometry().setFromPoints(points);
                const material = new THREE.LineBasicMaterial({ 
                    color: 0x2CFF05, 
                    transparent: true, 
                    opacity: 0.3 
                });
                const line = new THREE.Line(geometry, material);
                scene.add(line);
                orbitTrails.push(line);
            }
        }
        
            /**
             * Creates a custom Three.js geometry resembling a double-sided axe blade.
             * The shape is a cone with a central conical cutout and two azimuthal wedges.
             *
             * @param {number} height The height of the cone from base to apex.
             * @param {number} outerRadius The radius of the cone's base.
             * @param {number} cutoutAngle The full angle (in degrees) of the central conical cutout.
             * @param {number} bladeAngle The angular width (in degrees) of each of the two blades.
             * @param {number} segments The number of segments to build each blade's curve.
             * @returns {THREE.BufferGeometry} The custom axe blade geometry.
             */
/**
 * Creates a custom Three.js geometry resembling a double-sided axe blade.
 */
function createAxeBladeGeometry(height, outerRadius, cutoutAngle, bladeAngle, segments) {
    const geometry = new THREE.BufferGeometry();
    const vertices = [];
    const indices = [];

    const apexIndex = 0;
    vertices.push(0, 0, 0);

    const innerRadius = height * Math.tan((cutoutAngle * Math.PI / 180) / 2);
    const bladeAngleRad = (bladeAngle * Math.PI) / 180;
    const bladeCenters = [Math.PI / 2, 3 * Math.PI / 2];
    let vertexIndexCounter = 1;

    for (const centerAngle of bladeCenters) {
        const startAngle = centerAngle - bladeAngleRad / 2;
        const baseVertexStart = vertexIndexCounter;
        for (let i = 0; i <= segments; i++) {
            const angle = startAngle + (i / segments) * bladeAngleRad;
            const cos = Math.cos(angle);
            const sin = Math.sin(angle);
            vertices.push(outerRadius * cos, -height, outerRadius * sin);
            vertices.push(innerRadius * cos, -height, innerRadius * sin);
        }
        const baseVertexEnd = vertexIndexCounter + (segments * 2);
        for (let i = 0; i < segments; i++) {
            const outer1 = baseVertexStart + i * 2;
            const inner1 = baseVertexStart + i * 2 + 1;
            const outer2 = baseVertexStart + (i + 1) * 2;
            const inner2 = baseVertexStart + (i + 1) * 2 + 1;
            indices.push(apexIndex, outer1, outer2);
            indices.push(apexIndex, inner2, inner1);
            indices.push(outer1, inner1, outer2);
            indices.push(inner1, inner2, outer2);
        }
        indices.push(apexIndex, baseVertexStart + 1, baseVertexStart);
        indices.push(apexIndex, baseVertexEnd, baseVertexEnd + 1);
        vertexIndexCounter = baseVertexEnd + 2;
    }

    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
    geometry.setIndex(indices);
    geometry.computeVertexNormals();
    return geometry;
}

/**
 * Creates sensor meshes (cone, rectangular, axeBlade) and debug arrows.
 */
function createSensorCones() {
    // remove old
    sensorCones.forEach(c => scene.remove(c));
    sensorCones = [];
    debugAxes.forEach(g => scene.remove(g));
    debugAxes = [];

    const baseConeHeight = 0.6;
    const baseConeRadius = 0.2;
    const sensorTypes = ['cone', 'rectangular', 'axeBlade'];

    for (let i = 0; i < satellites.length; i++) {
        let geometry;
        const sensorType = sensorTypes[i % sensorTypes.length];
        let rectWidth, rectDepth;
        let axeParams = null;

        if (sensorType === 'rectangular') {
            rectWidth = baseConeRadius * .5;
            rectDepth = baseConeRadius * 1.2;
            geometry = new THREE.BufferGeometry();
            const vertices = new Float32Array([
                0, 0, 0,
                -rectWidth, -baseConeHeight, -rectDepth,
                rectWidth, -baseConeHeight, -rectDepth,
                rectWidth, -baseConeHeight, rectDepth,
                -rectWidth, -baseConeHeight, rectDepth,
            ]);
            const indices = [0,1,2, 0,2,3, 0,3,4, 0,4,1];
            geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
            geometry.setIndex(indices);
            geometry.computeVertexNormals();
        } else if (sensorType === 'axeBlade') {
            const heightParam = 1, outerRadiusParam = 1, cutoutAngleParam = 40, bladeAngleParam = 135;
            geometry = createAxeBladeGeometry(heightParam, outerRadiusParam, cutoutAngleParam, bladeAngleParam, 32);
            axeParams = { height: heightParam, outerRadius: outerRadiusParam, cutoutAngle: cutoutAngleParam, bladeAngle: bladeAngleParam };
        } else {
            geometry = new THREE.ConeGeometry(baseConeRadius * 2, baseConeHeight, 32);
            geometry.translate(0, -baseConeHeight / 2, 0);
        }

        const mat = new THREE.MeshBasicMaterial({
            color: 0x00ff00,
            transparent: true,
            opacity: 0.2,
            side: THREE.DoubleSide,
            depthWrite: false
        });

        const cone = new THREE.Mesh(geometry, mat);
        cone.userData.sensorType = sensorType;
        // Store FOV parameters for precise checks
        if (sensorType === 'rectangular') {
            cone.userData.height = baseConeHeight;
            cone.userData.rectWidth = rectWidth;
            cone.userData.rectDepth = rectDepth;
        } else if (sensorType === 'axeBlade') {
            Object.assign(cone.userData, axeParams, { bladeCenters: [Math.PI / 2, 3 * Math.PI / 2] });
        } else {
            cone.userData.halfFovRad = (coneFov * Math.PI / 180) / 2;
        }
        cone.userData.prevQuat = cone.quaternion.clone(); // initialize continuity anchor
        scene.add(cone);
        sensorCones.push(cone);

        // Debug axes group for this sensor
        if (SHOW_DEBUG_AXES) {
            const group = new THREE.Group();
            const axisLen = 0.1;
            // ArrowHelper( dir, origin, length, color )
            const xArrow = new THREE.ArrowHelper(new THREE.Vector3(1,0,0), new THREE.Vector3(0,0,0), axisLen, 0xff0000); // red X (in-track)
            const yArrow = new THREE.ArrowHelper(new THREE.Vector3(0,1,0), new THREE.Vector3(0,0,0), axisLen, 0x00ff00); // green Y (down)
            const zArrow = new THREE.ArrowHelper(new THREE.Vector3(0,0,1), new THREE.Vector3(0,0,0), axisLen, 0x0000ff); // blue Z (cross-track)
            group.add(xArrow, yArrow, zArrow);
            scene.add(group);
            debugAxes.push(group);
        }
    }
}

/**
 * Robust orientation + visual debugger for sensors.
 * Fixes spinning and 180° flips by:
 *  - using a fallback chain for in-track
 *  - enforcing quaternion hemisphere continuity by negating components (NOT multiplyScalar)
 */
function updateSensorCones(timeSeconds) {
    const EPS = 1e-8;
    const SMOOTH_FACTOR = 0.28;
    const worldUp = new THREE.Vector3(0, 1, 0);

    for (let i = 0; i < satellites.length; i++) {
        const sat = satellites[i];
        const cone = sensorCones[i];
        const axes = SHOW_DEBUG_AXES ? debugAxes[i] : null;
        if (!cone) continue;

        const sensorType = cone.userData.sensorType;
        let position, velocity;

        if (sensorType === 'axeBlade' || sensorType === 'rectangular') {
            const state = OrbitPropagator.propagateWithVelocity(sat, timeSeconds);
            position = new THREE.Vector3().copy(state.position);
            velocity = new THREE.Vector3().copy(state.velocity);
        } else {
            position = new THREE.Vector3().copy(OrbitPropagator.propagateSingle(sat, timeSeconds));
            velocity = null;
        }

        // place objects
        cone.position.copy(position);
        if (SHOW_DEBUG_AXES && axes) {
            axes.position.copy(position);
        }

        // compute down/nadir
        const nadir = position.clone().negate();
        if (nadir.lengthSq() < EPS) continue;
        nadir.normalize();

        // compute inTrack robustly (projected velocity -> orbit normal trick -> worldUp projection -> arbitrary)
        let inTrack = null;
        if (velocity && velocity.lengthSq() > EPS) {
            const vnorm = velocity.clone().normalize();
            inTrack = vnorm.clone().sub(nadir.clone().multiplyScalar(vnorm.dot(nadir)));
            if (inTrack.lengthSq() > EPS) inTrack.normalize();
            else inTrack = null;
        }

        if (!inTrack && velocity && position.lengthSq() > EPS) {
            const h = position.clone().cross(velocity);
            if (h.lengthSq() > EPS) {
                const orbitNormal = h.normalize();
                inTrack = new THREE.Vector3().crossVectors(nadir, orbitNormal);
                if (inTrack.lengthSq() > EPS) inTrack.normalize();
                else inTrack = null;
            }
        }

        if (!inTrack) {
            inTrack = worldUp.clone().sub(nadir.clone().multiplyScalar(worldUp.dot(nadir)));
            if (inTrack.lengthSq() > EPS) inTrack.normalize();
            else {
                // deterministic arbitrary perpendicular
                const alt = Math.abs(nadir.x) < 0.9 ? new THREE.Vector3(1,0,0) : new THREE.Vector3(0,1,0);
                inTrack = new THREE.Vector3().crossVectors(alt, nadir);
                if (inTrack.lengthSq() > EPS) inTrack.normalize();
                else inTrack.set(1,0,0); // last-ditch fallback
            }
        }

        // Ensure inTrack is prograde relative to velocity if available
        if (velocity && velocity.lengthSq() > EPS) {
            if (inTrack.dot(velocity) < 0) inTrack.negate();
        }

        // cross-track and down
        const down = nadir.clone();
        const crossTrack = new THREE.Vector3().crossVectors(inTrack, down);
        if (crossTrack.lengthSq() < EPS) {
            // ensure crossTrack never degenerates
            crossTrack.copy(new THREE.Vector3(0,0,1).cross(inTrack));
            if (crossTrack.lengthSq() < EPS) crossTrack.set(0,0,1);
        }
        crossTrack.normalize();

        // make basis matrix (X=inTrack, Y=down, Z=crossTrack)
        const rotMatrix = new THREE.Matrix4().makeBasis(inTrack, down, crossTrack);
        const targetQuat = new THREE.Quaternion().setFromRotationMatrix(rotMatrix);

        // Mesh points along local -Y; flip 180° about local X so -Y aligns with 'down'
        const flipXQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), Math.PI);
        const meshTargetQuat = targetQuat.clone().multiply(flipXQuat);

        // --- hemisphere / anti-flip fix ---
        const prevQuat = cone.userData.prevQuat || new THREE.Quaternion();
        if (prevQuat.dot(meshTargetQuat) < 0) {
            // Quaternion.multiplyScalar doesn't exist — negate components instead
            meshTargetQuat.x *= -1;
            meshTargetQuat.y *= -1;
            meshTargetQuat.z *= -1;
            meshTargetQuat.w *= -1;
        }

        // smooth the orientation to avoid sudden flips
        cone.quaternion.slerp(meshTargetQuat, SMOOTH_FACTOR);
        cone.userData.prevQuat = cone.quaternion.clone();

        // rotate debug axes with the sensor so arrows show basis
        if (SHOW_DEBUG_AXES && axes) {
            axes.quaternion.copy(targetQuat);
        }

        // scale sensor based on altitude + FOV (keeps your previous scaling logic)
        const altitude = Math.max(position.length() - EARTHRADIUS, 1e-3);
        const coneHeightNorm = Math.min(coneRangeKm / PHYSICS.EARTH_RADIUS_KM, 10);
        const coneHeight = Math.min(coneHeightNorm, altitude);
        const coneRadius = coneHeight * Math.tan((coneFov * Math.PI / 180) / 2);

        cone.scale.set(
            Math.max(coneRadius / 0.1, 1e-3),
            Math.max(coneHeight / 0.3, 1e-3),
            Math.max(coneRadius / 0.1, 1e-3)
        );
    }
}

/**
 * Updates satellite mesh positions.
 */
function updateSatellites(timeSeconds) {
    for (let i = 0; i < satellites.length; i++) {
        const pos = OrbitPropagator.propagateSingle(satellites[i], timeSeconds);
        satelliteMeshes[i].position.copy(pos);
    }
}



        function init() {
            const container = document.getElementById('canvas-container');
            renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
            const width = window.innerWidth;
            const heightRatio = window.innerWidth <= 768 ? 0.8 : 0.9;
            const height = window.innerHeight * heightRatio;
            renderer.setSize(width, height);
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setClearColor(0xffffff, 1);
            container.appendChild(renderer.domElement);

            scene = new THREE.Scene();
            camera = new THREE.PerspectiveCamera(50, width / height, 0.01, 1000);
            camera.position.set(2.5, 1.5, 2.5);

            controls = new OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.minDistance = 1.5;
            controls.maxDistance = 10;

            const texldr = new THREE.TextureLoader();
            
            // Try loading with error handling
            const diffuse = texldr.load('https://i.imgur.com/fmzPPx4.jpeg');
            const diffuseNight = texldr.load('https://i.imgur.com/p4SHkfw.jpeg');
            
            earth = new Earth3d(camera);
            earth.loadTextures(diffuse, diffuseNight);
            earth.parentObj = earth;
            scene.add(earth);
            // Initialize link materials
            crosslinkMaterial = new THREE.LineBasicMaterial({ color: 0x00ffff, transparent: true, opacity: 0.5 });
            downlinkMaterial = new THREE.LineBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.8, depthTest: true, depthWrite: false });

            createSatellites();
            createSatelliteMeshes();
            createSensorCones();
            createShips();
            createShipMeshes();
            createPlanes();
            createPlaneMeshes();
            createPlaneRoutes();
            // Choose random air and sea targets and mark them red
            selectRandomTargets(25, 25);
            
            // Setup visibility controls
            document.getElementById('showShips').addEventListener('change', (e) => {
                showShips = e.target.checked;
                shipMeshes.forEach(mesh => mesh.visible = showShips);
            });
            
            document.getElementById('showPlanes').addEventListener('change', (e) => {
                showPlanes = e.target.checked;
                planeMeshes.forEach(mesh => mesh.visible = showPlanes);
                planeRoutes.forEach(line => line.visible = showPlanes);
            });

            animate();
        }

        function animate() {
            requestAnimationFrame(animate);
            
            // Calculate frame delta time
            const now = Date.now();
            const frameDelta = now - lastFrameTime;
            lastFrameTime = now;
            
            // Update time acceleration (smoothly transition from 60x to 1x over 5 seconds)
            const elapsed = now - startTime;
            
            if (elapsed < 1500) {
                // Stay at real-time for 5 seconds
                timeAcceleration = 1;
            } else {
                // Ramp up to 10x speed after 5 seconds at real-time
                const rampProgress = Math.min((elapsed - 1500) / 1000, 1); // 1 second ramp
                timeAcceleration = 1 + (99 * rampProgress); // Smoothly go from 1x to 10x
            }
            
            // Update simulation time with acceleration (for satellites only)
            simulationTime += frameDelta * timeAcceleration;
            const timeInSeconds = simulationTime / 1000;
            
            // Use ACTUAL real-world time for sun/Earth (not accelerated)
            const actualCurrentTime = new Date();
            
            // Update indicator
            const indicator = document.getElementById('time-indicator');
            const progressFill = document.getElementById('progress-fill');
            
            if (elapsed < 1500) {
                // Hide indicator during real-time phase
                indicator.classList.remove('visible');
            } else if (elapsed < 2500) {
                // Show ramping up to 10x
                indicator.classList.add('visible');
                const rampProgress = (elapsed - 1500) / 1000;
                progressFill.style.width = (rampProgress * 100) + '%';
                
                const timeDisplay = document.getElementById('time-display');
                const accelDisplay = Math.round(timeAcceleration);
                timeDisplay.textContent = accelDisplay + 'x';
            } else {
                // Show 10x briefly then hide
                if (elapsed < 2500) {
                    indicator.classList.add('visible');
                    progressFill.style.width = '100%';
                    const timeDisplay = document.getElementById('time-display');
                    timeDisplay.textContent = '100x';
                } else {
                    indicator.classList.remove('visible');
                }
            }
            
            // Use actual real-world time for sun position and Earth rotation
            const julianDate = getJulianDate(actualCurrentTime);
            const sunVector = getSunPosition(julianDate);
            earth.setSun(sunVector);
            
            const earthRotation = getEarthRotation(actualCurrentTime);
            earth.rotation.y = earthRotation;
            
            earth.update();
            updateSatellites(timeInSeconds);
            updateSensorCones(timeInSeconds);
            updateShips(frameDelta);
            updatePlanes(frameDelta);
            
            // Throttle expensive updates to improve performance
            if (frameCount % TRAIL_UPDATE_INTERVAL === 0) {
                updateOrbitTrails(timeInSeconds);
            }
            if (frameCount % CROSSLINK_UPDATE_INTERVAL === 0) {
                updateCrosslinks();
            }
            if (frameCount % DOWNLINK_UPDATE_INTERVAL === 0) {
                updateDownlinks();
            }
            
            frameCount++;
            
            controls.update();
            renderer.render(scene, camera);
        }

        window.addEventListener('resize', function() {
            const width = window.innerWidth;
            const heightRatio = window.innerWidth <= 768 ? 0.8 : 0.9;
            const height = window.innerHeight * heightRatio;
            camera.aspect = width / height;
            camera.updateProjectionMatrix();
            renderer.setSize(width, height);
        });

        init();
    </script>

    <script>
        function showTab(tabName) {
            // Hide all sections
            document.querySelectorAll('.content-section').forEach(section => {
                section.classList.remove('active');
            });
            
            // Remove active class from all tabs
            document.querySelectorAll('.nav-tab').forEach(tab => {
                tab.classList.remove('active');
            });
            
            // Show selected section
            document.getElementById(tabName).classList.add('active');
            
            // Add active class to clicked tab
            event.target.classList.add('active');
        }

        function handleSubmit(event) {
            event.preventDefault();
            alert('Thank you for your inquiry! We will get back to you soon.');
            event.target.reset();
        }
    </script>
</body>
</html>