Star trek game in html

<!DOCTYPE html>
<html lang=”en”>
<head>
    <meta charset=”UTF-8″>
    <meta name=”viewport” content=”width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no”>
    <title>Super Star Trek: TOS Console</title>
   
    <meta name=”theme-color” content=”#e6b800″>
    <meta name=”mobile-web-app-capable” content=”yes”>
    <meta name=”apple-mobile-web-app-capable” content=”yes”>
    <meta name=”apple-mobile-web-app-status-bar-style” content=”black-translucent”>
    <meta name=”apple-mobile-web-app-title” content=”Star Trek”>
    <link rel=”apple-touch-icon” href=”data:image/svg+xml;charset=utf-8,%3Csvg xmlns=’http://www.w3.org/2000/svg’ viewBox=’0 0 512 512’%3E%3Crect width=’512′ height=’512′ fill=’%23222’/%3E%3Cpath d=’M256 100 L400 400 L256 320 L112 400 Z’ fill=’%23e6b800’/%3E%3C/svg%3E”>
    <link rel=”manifest” href=”data:application/json;charset=utf-8,%7B%22name%22%3A%22Super%20Star%20Trek%22%2C%22short_name%22%3A%22Star%20Trek%22%2C%22start_url%22%3A%22.%22%2C%22display%22%3A%22standalone%22%2C%22background_color%22%3A%22%23222222%22%2C%22theme_color%22%3A%22%23e6b800%22%2C%22icons%22%3A%5B%7B%22src%22%3A%22data%3Aimage%2Fsvg%2Bxml%3Bcharset%3Dutf-8%2C%253Csvg%20xmlns%3D’http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg’%20viewBox%3D’0%200%20512%20512’%253E%253Crect%20width%3D’512’%20height%3D’512’%20fill%3D’%2523222’%2F%253E%253Cpath%20d%3D’M256%20100%20L400%20400%20L256%20320%20L112%20400%20Z’%20fill%3D’%2523e6b800’%2F%253E%253C%2Fsvg%253E%22%2C%22sizes%22%3A%22512×512%22%2C%22type%22%3A%22image%2Fsvg%2Bxml%22%2C%22purpose%22%3A%22any%20maskable%22%7D%5D%7D”>

    <style>
        @import url(‘https://fonts.googleapis.com/css2?family=Oswald:wght@500;700&family=VT323&display=swap’);

        :root {
            –tos-gold: #e6b800;
            –tos-blue: #0077cc;
            –tos-red: #d32f2f;
            –tos-green: #388e3c;
            –tos-orange: #f57c00;
            –tos-purple: #7b1fa2;
            –tos-light-blue: #03a9f4;
           
            –console-bg: #2a2d34;
            –panel-bg: #1a1c20;
            –bezel: #5a5f68;
           
            –font-main: ‘Oswald’, sans-serif;
            –font-screen: ‘VT323’, monospace;
        }

        html, body {
            background-color: var(–console-bg);
            color: #ffffff;
            font-family: var(–font-main);
            text-transform: uppercase;
            margin: 0; padding: 0;
            width: 100vw; height: 100vh; height: 100dvh;
            overflow: hidden; touch-action: none; user-select: none; -webkit-user-select: none;
        }
       
        * { box-sizing: border-box; }
        h1, h2, p { margin: 0; padding: 0; text-transform: uppercase; }

        .font-screen { font-family: var(–font-screen); text-transform: none; }
        .text-center { text-align: center; }

        .tos-btn {
            cursor: pointer; border: 3px solid #000;
            border-top-color: rgba(255,255,255,0.4); border-left-color: rgba(255,255,255,0.4);
            border-bottom-color: rgba(0,0,0,0.6); border-right-color: rgba(0,0,0,0.6);
            color: white; font-family: var(–font-main); font-weight: 700;
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            border-radius: 4px; box-shadow: 2px 2px 5px rgba(0,0,0,0.5);
            transition: transform 0.05s, border-color 0.05s; padding: 2px; text-align: center;
            overflow: hidden; line-height: 1.1;
        }
        .tos-btn:active, .btn-active {
            border-bottom-color: rgba(255,255,255,0.4); border-right-color: rgba(255,255,255,0.4);
            border-top-color: rgba(0,0,0,0.6); border-left-color: rgba(0,0,0,0.6);
            transform: translate(2px, 2px); box-shadow: 0px 0px 2px rgba(0,0,0,0.5);
        }
        .btn-active { filter: brightness(1.3); }

        .btn-gold { background-color: var(–tos-gold); color: #000; }
        .btn-blue { background-color: var(–tos-blue); }
        .btn-red { background-color: var(–tos-red); }
        .btn-green { background-color: var(–tos-green); }
        .btn-orange { background-color: var(–tos-orange); }
        .btn-purple { background-color: var(–tos-purple); }

        .indicator {
            width: 16px; height: 16px; border-radius: 50%; border: 2px solid #000;
            box-shadow: inset -2px -2px 4px rgba(0,0,0,0.5), inset 2px 2px 4px rgba(255,255,255,0.5);
        }
        .ind-red { background-color: #880000; }
        .ind-red.on { background-color: #ff3333; box-shadow: 0 0 10px #ff3333, inset -2px -2px 4px rgba(0,0,0,0.5); }
        .ind-yellow { background-color: #888800; }
        .ind-yellow.on { background-color: #ffff33; box-shadow: 0 0 10px #ffff33, inset -2px -2px 4px rgba(0,0,0,0.5); }
        .ind-green { background-color: #006600; }
        .ind-green.on { background-color: #33ff33; box-shadow: 0 0 10px #33ff33, inset -2px -2px 4px rgba(0,0,0,0.5); }

        #interface-container {
            display: grid; grid-template-columns: 65px 1fr; grid-template-rows: 45px 1fr 150px;
            height: 100%; width: 100%; gap: 6px; padding: 6px;
            background: linear-gradient(to bottom, #3a3f47, #1a1c20);
        }

        #top-bar {
            grid-column: 1 / 3; background-color: var(–panel-bg); border: 3px solid #111; border-radius: 6px;
            display: flex; align-items: center; justify-content: space-between; padding: 0 10px;
            box-shadow: inset 0 0 10px #000;
        }
        .header-title { font-size: clamp(1rem, 4vw, 1.5rem); color: var(–tos-gold); letter-spacing: 1px; }
        .header-lights { display: flex; gap: 8px; }
       
        .header-right { display: flex; align-items: center; gap: 8px; }
        .header-date { font-size: clamp(0.9rem, 3vw, 1.2rem); color: #fff; }
        #btn-quit { display: none; padding: 2px 6px; font-size: 0.75rem; height: 24px; border-width: 2px; }

        #side-panel { grid-column: 1 / 2; display: flex; flex-direction: column; gap: 6px; }
        .stat-box {
            flex: 1; background-color: var(–panel-bg); border: 3px solid #111; border-radius: 4px;
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            box-shadow: inset 0 0 8px #000;
        }
        .stat-label { font-size: clamp(0.5rem, 2vw, 0.65rem); color: #aaa; margin-bottom: -4px; text-align: center; line-height: 1; }
        .stat-val { font-size: clamp(1rem, 4vw, 1.2rem); font-weight: bold; color: var(–tos-light-blue); line-height: 1; margin-top: 2px;}
        .stat-val.warning { color: var(–tos-red); animation: blink 1s infinite; }
        @keyframes blink { 50% { opacity: 0.2; } }

        #main-view {
            grid-column: 2 / 3; background-color: #050a15; border: 8px solid var(–bezel);
            border-radius: 15px 15px 5px 5px; box-shadow: 0 0 0 3px #111, inset 0 0 20px #000;
            display: flex; flex-direction: column; overflow: hidden; position: relative;
        }

        .red-alert-pulse { animation: redAlertCRT 2s infinite; }
        @keyframes redAlertCRT {
            0% { box-shadow: inset 0 0 0px rgba(255, 0, 0, 0); }
            50% { box-shadow: inset 0 0 80px rgba(255, 0, 0, 0.7); }
            100% { box-shadow: inset 0 0 0px rgba(255, 0, 0, 0); }
        }

        #canvas-container { flex: 1; min-height: 0; width: 100%; position: relative; }
        canvas { display: block; width: 100%; height: 100%; cursor: crosshair; touch-action: none; position: absolute; top:0; left:0;}

        #console-log {
            height: 70px; background: rgba(0, 10, 0, 0.9); border-top: 2px solid #333; color: #33ff33;
            padding: 4px 6px; overflow-y: auto; font-size: clamp(0.9rem, 3.5vw, 1.1rem);
            display: flex; flex-direction: column-reverse; z-index: 10;
        }
        .log-entry { margin-bottom: 2px; line-height: 1; }
        .log-alert { color: #ff3333; }
        .log-info { color: #55aaff; }
        .log-success { color: #ffff33; }
        .log-chatter { color: #ff99ff; }
        .log-computer { color: #ffffff; background: #0055ff; padding: 0 4px; display: inline-block;}

        #bottom-bar {
            grid-column: 1 / 3; background-color: var(–panel-bg); border: 3px solid #111; border-radius: 6px;
            padding: 6px; display: grid; grid-template-columns: repeat(3, 1fr) 1.2fr;
            grid-template-rows: repeat(2, 1fr); gap: 6px; box-shadow: inset 0 0 10px #000;
        }

        .btn-label { font-size: clamp(0.8rem, 3.5vw, 1.2rem); line-height: 1; }
        .btn-sub { font-size: clamp(0.5rem, 2vw, 0.65rem); margin-top: 2px; opacity: 0.8; letter-spacing: 0.5px; }
        .btn-shield { grid-column: 4 / 5; grid-row: 1 / 3; }

        .overlay {
            position: absolute; top: 0; left: 0; right: 0; bottom: 0;
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            background: rgba(0,0,0,0.85); z-index: 20; padding: 10px; backdrop-filter: blur(2px);
        }
        .modal {
            background: var(–panel-bg); border: 4px solid var(–bezel); border-radius: 6px;
            padding: 12px; text-align: center; z-index: 30; width: 100%; max-width: 350px;
            box-shadow: 0 0 20px #000;
        }
        .modal-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; margin-bottom: 0.75rem; }
        .modal-btn { padding: 4px 0; font-size: clamp(0.75rem, 3vw, 1rem); white-space: nowrap; line-height: 1.2;}
        .modal-title { font-size: clamp(1.2rem, 5vw, 1.5rem); color: var(–tos-gold); margin-bottom: 0.5rem; line-height: 1;}
        .modal-label { grid-column: span 4; text-align: left; font-size: clamp(0.65rem, 2.5vw, 0.8rem); color: #ccc; margin-top: 0.5rem; margin-bottom: 0; }
        .modal-cancel { width: 100%; padding: 6px; font-size: clamp(1rem, 4vw, 1.2rem); }
        .btn-pill { border-radius: 9999px; padding: 8px 20px; font-size: clamp(1.2rem, 5vw, 1.5rem); margin-top: 1rem; }
       
        .hidden { display: none !important; }

        @media (min-width: 768px) {
            #interface-container { grid-template-columns: 100px 1fr; grid-template-rows: 60px 1fr 100px; padding: 15px; gap: 15px; }
            .header-title { font-size: 2rem; }
            .header-lights { gap: 12px; }
            .indicator { width: 20px; height: 20px; }
            .stat-label { font-size: 0.8rem; }
            .stat-val { font-size: 1.8rem; }
            #main-view { border-width: 12px; }
            #console-log { height: 100px; font-size: 1.3rem; }
            #bottom-bar { grid-template-columns: repeat(6, 1fr) 2fr; grid-template-rows: 1fr; gap: 10px; padding: 10px;}
            .btn-shield { grid-column: auto; grid-row: auto; }
            .btn-label { font-size: 1.5rem; }
            .btn-sub { font-size: 0.8rem; margin-top: 4px;}
            .tos-btn { border-width: 4px; padding: 6px; }
            .modal-grid { gap: 8px; }
            .modal-btn { padding: 8px 0; font-size: 1.1rem; }
            .modal-label { margin-bottom: -4px; }
            #btn-quit { font-size: 1rem; padding: 4px 10px; height: auto;}
        }
    </style>
</head>
<body>

<div id=”interface-container”>
    <div id=”top-bar”>
        <div class=”header-lights”>
            <div id=”ind-1″ class=”indicator ind-red”></div>
            <div id=”ind-2″ class=”indicator ind-yellow”></div>
            <div id=”ind-3″ class=”indicator ind-green on”></div>
        </div>
        <div class=”header-title”>STARFLEET TACTICAL</div>
        <div class=”header-right”>
            <div class=”header-date”>SD: <span id=”ui-stardate”>3000.0</span></div>
            <button id=”btn-quit” class=”tos-btn btn-red”>QUIT</button>
        </div>
    </div>

    <div id=”side-panel”>
        <div class=”stat-box”>
            <span class=”stat-label”>ENERGY</span>
            <span id=”ui-energy” class=”stat-val”>3000</span>
        </div>
        <div class=”stat-box”>
            <span class=”stat-label”>SHIELDS</span>
            <span id=”ui-shields” class=”stat-val”>0</span>
        </div>
        <div class=”stat-box”>
            <span class=”stat-label”>TORPS</span>
            <span id=”ui-torps” class=”stat-val” style=”color:var(–tos-orange)”>10</span>
        </div>
        <div class=”stat-box”>
            <span class=”stat-label”>KLINGONS</span>
            <span id=”ui-klingons” class=”stat-val” style=”color:var(–tos-red)”>0</span>
        </div>
    </div>

    <div id=”main-view”>
        <div id=”canvas-container”>
            <canvas id=”sensorCanvas”></canvas>
           
            <div id=”start-screen” class=”overlay”>
                <h1 style=”font-size: clamp(2rem, 8vw, 3rem); color: var(–tos-gold); text-shadow: 2px 2px 0 #000; margin-bottom: 0.5rem; line-height: 1;”>STAR TREK</h1>
                <div class=”font-screen” style=”color: #ccc; text-align: center; max-width: 320px; font-size: clamp(1rem, 4vw, 1.2rem); margin-bottom: 1rem; line-height: 1.2;”>
                    <p>The Klingon Empire has invaded.</p>
                    <p>You have <span style=”color: #fff; font-weight:bold;”>30 stardates</span> to destroy them.</p>
                </div>
                <button id=”btn-start” class=”tos-btn btn-gold btn-pill”>ENGAGE</button>
            </div>

            <div id=”game-over” class=”overlay hidden”>
                <h1 id=”go-title” style=”font-size: clamp(2rem, 8vw, 3rem); color: var(–tos-red); text-shadow: 2px 2px 0 #000; text-align: center; line-height: 1;”>MISSION FAILED</h1>
                <p id=”go-reason” class=”font-screen” style=”color: #fff; font-size: clamp(1.1rem, 4vw, 1.4rem); margin: 1rem 0; text-align:center;”>The Enterprise was destroyed.</p>
                <button id=”btn-restart” class=”tos-btn btn-gold btn-pill”>RETRY</button>
            </div>

            <div id=”input-modal” class=”overlay hidden”>
                <div class=”modal”>
                    <h2 id=”modal-title” class=”modal-title”>ALLOCATE POWER</h2>
                    <div id=”modal-buttons” class=”modal-grid”></div>
                    <button class=”tos-btn btn-orange modal-cancel” onclick=”closeModal()”>CANCEL</button>
                </div>
            </div>
        </div>
        <div id=”console-log” class=”font-screen”></div>
    </div>

    <div id=”bottom-bar”>
        <button id=”btn-srs” class=”tos-btn btn-blue btn-active”>
            <span class=”btn-label”>SRS</span><span class=”btn-sub”>SENSORS</span>
        </button>
        <button id=”btn-lrs” class=”tos-btn btn-blue”>
            <span class=”btn-label”>LRS</span><span class=”btn-sub”>GALAXY</span>
        </button>
        <button id=”btn-imp” class=”tos-btn btn-gold”>
            <span class=”btn-label”>IMP</span><span class=”btn-sub”>MOVE</span>
        </button>
        <button id=”btn-wrp” class=”tos-btn btn-purple”>
            <span class=”btn-label”>WRP</span><span class=”btn-sub”>WARP</span>
        </button>
        <button id=”btn-pha” class=”tos-btn btn-red text-white”>
            <span class=”btn-label”>PHA</span><span class=”btn-sub”>PHASERS</span>
        </button>
        <button id=”btn-tor” class=”tos-btn btn-orange”>
            <span class=”btn-label”>TOR</span><span class=”btn-sub”>TORPEDO</span>
        </button>
        <button id=”btn-shd” class=”tos-btn btn-green btn-shield”>
            <span class=”btn-label”>SHD</span><span class=”btn-sub”>SHIELDS</span>
        </button>
    </div>
</div>

<script>
    // — WEB AUDIO API SOUND ENGINE —
    const sfx = {
        ctx: null, masterGain: null, redAlertLoop: null,
        init() {
            if (!this.ctx) {
                this.ctx = new (window.AudioContext || window.webkitAudioContext)();
                this.masterGain = this.ctx.createGain();
                this.masterGain.connect(this.ctx.destination);
            }
            if (this.ctx.state === ‘suspended’) this.ctx.resume();
            this.masterGain.gain.setValueAtTime(1, this.ctx.currentTime); // Ensure unmuted
        },
        playOsc(type, fStart, fEnd, dur, vol=0.1) {
            if(!this.ctx || this.ctx.state === ‘suspended’) return;
            const t = this.ctx.currentTime;
            const osc = this.ctx.createOscillator();
            const gain = this.ctx.createGain();
            osc.type = type; osc.connect(gain); gain.connect(this.masterGain);
            osc.frequency.setValueAtTime(fStart, t);
            if(fEnd) osc.frequency.exponentialRampToValueAtTime(fEnd, t + dur);
            gain.gain.setValueAtTime(vol, t);
            gain.gain.exponentialRampToValueAtTime(0.01, t + dur);
            osc.start(t); osc.stop(t + dur);
        },
        click() { this.playOsc(‘sine’, 1200, 800, 0.05, 0.05); },
        error() { this.playOsc(‘sawtooth’, 150, 100, 0.3, 0.1); },
        phaser() { this.playOsc(‘sawtooth’, 800, 100, 0.3, 0.1); },
        torpedo() { this.playOsc(‘square’, 200, 40, 0.6, 0.15); },
        warp() { this.playOsc(‘sine’, 50, 2000, 1.5, 0.1); },
        decloak() { this.playOsc(‘sine’, 1500, 200, 1.2, 0.1); },
        purr() {
            if(!this.ctx || this.ctx.state === ‘suspended’) return;
            const t = this.ctx.currentTime;
            const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain();
            osc.type = ‘triangle’; osc.frequency.value = 60;
            osc.connect(gain); gain.connect(this.masterGain);
            gain.gain.setValueAtTime(0, t); gain.gain.linearRampToValueAtTime(0.2, t + 0.2); gain.gain.linearRampToValueAtTime(0, t + 0.6);
            osc.frequency.setValueAtTime(60, t); osc.frequency.exponentialRampToValueAtTime(40, t + 0.6);
            osc.start(t); osc.stop(t + 0.6);
        },
        noiseBurst() {
            if(!this.ctx || this.ctx.state === ‘suspended’) return;
            const bufferSize = this.ctx.sampleRate * 0.4;
            const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
            const data = buffer.getChannelData(0);
            for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 – 1;
            const noise = this.ctx.createBufferSource(); noise.buffer = buffer;
            const filter = this.ctx.createBiquadFilter(); filter.type = ‘highpass’; filter.frequency.value = 1000;
            const gain = this.ctx.createGain();
            gain.gain.setValueAtTime(0.4, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.4);
            noise.connect(filter); filter.connect(gain); gain.connect(this.masterGain);
            noise.start();
        },
        explosion() {
            if(!this.ctx || this.ctx.state === ‘suspended’) return;
            const bufferSize = this.ctx.sampleRate * 0.5;
            const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
            const data = buffer.getChannelData(0);
            for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 – 1;
            const noise = this.ctx.createBufferSource(); noise.buffer = buffer;
            const filter = this.ctx.createBiquadFilter(); filter.type = ‘lowpass’; filter.frequency.value = 800;
            const gain = this.ctx.createGain();
            gain.gain.setValueAtTime(0.3, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.5);
            noise.connect(filter); filter.connect(gain); gain.connect(this.masterGain);
            noise.start();
        },
        startRedAlert() {
            if(!this.ctx || this.redAlertLoop) return;
            this.redAlertLoop = setInterval(() => {
                if(this.ctx.state === ‘suspended’) return;
                const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain();
                osc.connect(gain); gain.connect(this.masterGain);
                osc.frequency.setValueAtTime(400, this.ctx.currentTime); osc.frequency.linearRampToValueAtTime(800, this.ctx.currentTime + 0.4);
                gain.gain.setValueAtTime(0.05, this.ctx.currentTime); gain.gain.linearRampToValueAtTime(0.01, this.ctx.currentTime + 0.5);
                osc.start(this.ctx.currentTime); osc.stop(this.ctx.currentTime + 0.5);
            }, 1000);
        },
        stopRedAlert() { if(this.redAlertLoop) clearInterval(this.redAlertLoop); this.redAlertLoop = null; },
       
        stopAll() {
            this.stopRedAlert();
            if (this.ctx) {
                this.ctx.suspend();
            }
        }
    };

    // — GAME ENGINE —
    const canvas = document.getElementById(‘sensorCanvas’);
    const ctx = canvas.getContext(‘2d’);
    const canvasContainer = document.getElementById(‘canvas-container’);
    const consoleLog = document.getElementById(‘console-log’);
    const mainView = document.getElementById(‘main-view’);
    const btnQuit = document.getElementById(‘btn-quit’);

    const uiEnergy = document.getElementById(‘ui-energy’), uiShields = document.getElementById(‘ui-shields’);
    const uiTorps = document.getElementById(‘ui-torps’), uiKlingons = document.getElementById(‘ui-klingons’);
    const uiStardate = document.getElementById(‘ui-stardate’);
    const indRed = document.getElementById(‘ind-1’), indYel = document.getElementById(‘ind-2’), indGrn = document.getElementById(‘ind-3’);
    const startScreen = document.getElementById(‘start-screen’), gameOverScreen = document.getElementById(‘game-over’);
    const inputModal = document.getElementById(‘input-modal’), modalButtonsContainer = document.getElementById(‘modal-buttons’);

    const G_SIZE = 8, S_SIZE = 8, MAX_ENERGY = 3000, MAX_TORPS = 10;
    const ENT = ‘E’, KLN = ‘K’, BAS = ‘B’, STR = ‘*’, EMP = ‘.’, ROM = ‘R’, ANO = ‘A’;
   
    let state = ‘START’, mode = ‘SRS’, modalAction = null;
    let isWarping = false, warpStars = [], activeBeams = [];
    let ship = { qX: 0, qY: 0, sX: 0, sY: 0, energy: MAX_ENERGY, shields: 0, torps: MAX_TORPS };
    let galaxy = [], currentQuadrant = [];
    let stardate = 3000.0, endStardate = 3030.0;
    let totalKlingons = 0, totalBases = 0, basesDestroyedByPlayer = 0;
    let tribbles = 0;
    let staticTimer = 0, romulansDecloaked = false;

    const crewQuotes = [
        “Spock: ‘Fascinating.'”, “Spock: ‘Logic dictates we proceed.'”, “Scotty: ‘I’m givin’ her all she’s got!'”,
        “Scotty: ‘The dilithium crystals!'”, “Sulu: ‘Course locked in, sir.'”, “Uhura: ‘Subspace frequencies clear.'”,
        “Chekov: ‘Weapons armed.'”, “Bones: ‘I’m a doctor, not an engineer!'”
    ];

    setInterval(() => {
        if (state === ‘PLAY’ && Math.random() < 0.30 && !isWarping) triggerRandomChatter();
    }, 12000);

    function triggerRandomChatter() {
        const quote = crewQuotes[Math.floor(Math.random() * crewQuotes.length)];
        log(quote, ‘chatter’);
    }

    function log(msg, type = ”) {
        const div = document.createElement(‘div’);
        div.className = `log-entry ${type ? ‘log-‘+type : ”}`;
        div.innerHTML = type === ‘computer’ ? `<span class=”log-computer”>${msg}</span>` : `> ${msg}`;
        consoleLog.prepend(div);
    }

    function initGame() {
        sfx.init(); sfx.click();
        log(‘COMPUTER ONLINE.’, ‘computer’);
        galaxy = []; totalKlingons = 0; totalBases = 0; activeBeams = []; basesDestroyedByPlayer = 0; tribbles = 0;
       
        for (let y = 0; y < G_SIZE; y++) {
            let row = [];
            for (let x = 0; x < G_SIZE; x++) {
                let k = 0, b = 0, s = Math.floor(Math.random() * 8) + 1;
                let rand = Math.random();
                if (rand > 0.95) k = 3; else if (rand > 0.85) k = 2; else if (rand > 0.6) k = 1;
                if (Math.random() > 0.90) b = 1;

                totalKlingons += k; totalBases += b;
                row.push({ k: k, b: b, s: s, explored: false, name: getQuadrantName(x, y) });
            }
            galaxy.push(row);
        }
        if (totalBases === 0) { galaxy[0][0].b = 1; totalBases = 1; }
        if (totalKlingons < 5) { galaxy[1][1].k = 5; totalKlingons += 5; }

        ship = {
            qX: Math.floor(Math.random()*8), qY: Math.floor(Math.random()*8),
            sX: Math.floor(Math.random()*8), sY: Math.floor(Math.random()*8),
            energy: MAX_ENERGY, shields: 0, torps: MAX_TORPS
        };
       
        stardate = 3000.0; consoleLog.innerHTML = ”;
        log(‘SENSORS ONLINE.’, ‘info’);
        log(`ORDERS: DESTROY ${totalKlingons} KLINGONS BY SD ${endStardate}.`, ‘info’);
       
        setupQuadrant();
        state = ‘PLAY’; setMode(‘SRS’);
        startScreen.classList.add(‘hidden’); gameOverScreen.classList.add(‘hidden’);
        btnQuit.style.display = ‘flex’;
       
        updateUI(); resizeCanvas(); requestAnimationFrame(renderLoop);
    }

    function quitGame() {
        sfx.click();
        state = ‘START’;
        isWarping = false;
        activeBeams = [];
        staticTimer = 0;
       
        sfx.stopAll();
        mainView.classList.remove(‘red-alert-pulse’);
        setLights(false, false, true);
       
        btnQuit.style.display = ‘none’;
        inputModal.classList.add(‘hidden’);
        gameOverScreen.classList.add(‘hidden’);
        startScreen.classList.remove(‘hidden’);
       
        log(‘SIMULATION ABORTED.’, ‘computer’);
        ctx.fillStyle = ‘#050a15’;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
    }

    function getQuadrantName(x, y) {
        const regions = [“Antares”, “Rigel”, “Procyon”, “Vega”, “Canopus”, “Altair”, “Sagittarius”, “Pollux”];
        return regions[y] + ” ” + [“I”, “II”, “III”, “IV”][x % 4];
    }

    function setupQuadrant() {
        currentQuadrant = Array(S_SIZE).fill(null).map(() => Array(S_SIZE).fill(EMP));
        let q = galaxy[ship.qY][ship.qX];
        q.explored = true;
        currentQuadrant[ship.sY][ship.sX] = ENT;

        function place(type, count) {
            let placed = 0, attempts = 0;
            while(placed < count && attempts < 50) {
                let rx = Math.floor(Math.random()*S_SIZE), ry = Math.floor(Math.random()*S_SIZE);
                if (currentQuadrant[ry][rx] === EMP) { currentQuadrant[ry][rx] = type; placed++; }
                attempts++;
            }
        }

        place(KLN, q.k); place(BAS, q.b); place(STR, q.s);
        if(Math.random() < 0.2) place(ANO, 1);
        if(Math.random() < 0.15) place(ROM, 1);
       
        romulansDecloaked = false;
        log(`Entering ${q.name}.`, ‘info’);
        checkCondition(); checkDocking();
    }

    function setLights(r, y, g) {
        indRed.className = `indicator ind-red ${r ? ‘on’ : ”}`;
        indYel.className = `indicator ind-yellow ${y ? ‘on’ : ”}`;
        indGrn.className = `indicator ind-green ${g ? ‘on’ : ”}`;
    }

    function checkCondition() {
        if(state !== ‘PLAY’) return;
        let q = galaxy[ship.qY][ship.qX];
        let hasHostiles = q.k > 0 || currentQuadrant.flat().includes(ROM);

        if (hasHostiles) {
            if(!indRed.classList.contains(‘on’)) {
                log(‘RED ALERT.’, ‘computer’); mainView.classList.add(‘red-alert-pulse’); sfx.startRedAlert();
            }
            setLights(true, false, false);
        } else {
            mainView.classList.remove(‘red-alert-pulse’); sfx.stopRedAlert();
            if (ship.energy < 1000) setLights(false, true, false);
            else setLights(false, false, true);
        }
    }

    function checkDocking() {
        let docked = false;
        for (let dy = -1; dy <= 1; dy++) {
            for (let dx = -1; dx <= 1; dx++) {
                let ny = ship.sY + dy, nx = ship.sX + dx;
                if (ny>=0 && ny<S_SIZE && nx>=0 && nx<S_SIZE && currentQuadrant[ny][nx] === BAS) docked = true;
            }
        }
        if (docked) {
            ship.energy = MAX_ENERGY; ship.torps = MAX_TORPS;
            log(‘Docked at Starbase. Resupplied.’, ‘success’); log(‘ENERGY REPLENISHED.’, ‘computer’); sfx.click();
            if (tribbles === 0 && Math.random() < 0.3) {
                tribbles = 2; log(“Captain, some furry creatures came aboard…”, “chatter”);
            }
        }
    }

    function triggerAnomaly() {
        staticTimer = 60;
        sfx.noiseBurst();
        let drain = Math.floor(Math.random() * 200) + 100;
        ship.energy -= drain;
        log(`Ion storm discharge! Lost ${drain} energy!`, ‘alert’);
        if (ship.energy <= 0) gameOver(“HULL BREACH”, “Ion storm destroyed the Enterprise.”);
        updateUI();
    }

    function updateUI() {
        uiEnergy.innerText = Math.floor(ship.energy);
        uiShields.innerText = Math.floor(ship.shields);
        uiTorps.innerText = ship.torps;
        uiKlingons.innerText = totalKlingons;
        uiStardate.innerText = stardate.toFixed(1);
        uiEnergy.className = ship.energy < 500 ? ‘stat-val warning’ : ‘stat-val’;
        uiShields.className = ship.shields < 100 && (galaxy[ship.qY][ship.qX].k > 0 || currentQuadrant.flat().includes(ROM)) ? ‘stat-val warning’ : ‘stat-val’;
    }

    function setMode(newMode) {
        if(state !== ‘PLAY’ || isWarping) return;
        sfx.click(); mode = newMode;
        [‘srs’, ‘lrs’, ‘imp’, ‘wrp’, ‘pha’, ‘tor’].forEach(id => { document.getElementById(‘btn-‘+id).classList.remove(‘btn-active’); });
        document.getElementById(‘btn-‘+mode.toLowerCase())?.classList.add(‘btn-active’);
        draw();
    }

    function resizeCanvas() { canvas.width = canvasContainer.clientWidth; canvas.height = canvasContainer.clientHeight; draw(); }
    window.addEventListener(‘resize’, resizeCanvas);

    // — Drawing Functions —
    function drawEnterprise(ctx, x, y, size) {
        ctx.save(); ctx.translate(x, y); ctx.fillStyle = ‘#e0e5eb’;
        ctx.beginPath(); ctx.arc(0, -size*0.15, size*0.3, 0, Math.PI*2); ctx.fill();
        ctx.fillStyle = ‘#fff’; ctx.beginPath(); ctx.arc(0, -size*0.15, size*0.08, 0, Math.PI*2); ctx.fill();
        ctx.fillStyle = ‘#d0d5db’; ctx.fillRect(-size*0.08, -size*0.1, size*0.16, size*0.4);
        ctx.fillStyle = ‘#a0a5ab’; ctx.beginPath(); ctx.moveTo(-size*0.25, size*0.15); ctx.lineTo(size*0.25, size*0.15); ctx.lineTo(0, -size*0.05); ctx.fill();
        ctx.fillStyle = ‘#d0d5db’; ctx.fillRect(-size*0.32, 0, size*0.12, size*0.45); ctx.fillRect(size*0.20, 0, size*0.12, size*0.45);
        ctx.fillStyle = ‘#ff3333’; ctx.beginPath(); ctx.arc(-size*0.26, 0, size*0.06, Math.PI, 0); ctx.fill(); ctx.beginPath(); ctx.arc(size*0.26, 0, size*0.06, Math.PI, 0); ctx.fill();
        ctx.restore();
    }

    function drawKlingon(ctx, x, y, size) {
        ctx.save(); ctx.translate(x, y);
        let angle = Math.atan2(ship.sY * (canvas.height/8) + (canvas.height/16) – y, ship.sX * (canvas.width/8) + (canvas.width/16) – x);
        ctx.rotate(angle + Math.PI/2);
        ctx.fillStyle = ‘#7cbd5a’;
        ctx.fillRect(-size*0.12, 0, size*0.24, size*0.25); ctx.fillRect(-size*0.04, -size*0.3, size*0.08, size*0.3);
        ctx.beginPath(); ctx.moveTo(0, -size*0.45); ctx.lineTo(size*0.12, -size*0.3); ctx.lineTo(0, -size*0.2); ctx.lineTo(-size*0.12, -size*0.3); ctx.fill();
        ctx.beginPath(); ctx.moveTo(-size*0.12, size*0.1); ctx.lineTo(-size*0.45, size*0.25); ctx.lineTo(-size*0.45, -size*0.05); ctx.closePath(); ctx.fill();
        ctx.beginPath(); ctx.moveTo(size*0.12, size*0.1); ctx.lineTo(size*0.45, size*0.25); ctx.lineTo(size*0.45, -size*0.05); ctx.closePath(); ctx.fill();
        ctx.fillStyle = ‘#ff3333’; ctx.fillRect(-size*0.48, -size*0.05, size*0.06, size*0.15); ctx.fillRect(size*0.42, -size*0.05, size*0.06, size*0.15);
        ctx.restore();
    }

    function drawRomulan(ctx, x, y, size) {
        ctx.save(); ctx.translate(x, y);
        if(!romulansDecloaked) ctx.globalAlpha = 0.15;
        let angle = Math.atan2(ship.sY * (canvas.height/8) + (canvas.height/16) – y, ship.sX * (canvas.width/8) + (canvas.width/16) – x);
        ctx.rotate(angle + Math.PI/2);
        ctx.fillStyle = ‘#99aa99’;
        ctx.beginPath(); ctx.ellipse(0, 0, size*0.4, size*0.25, 0, 0, Math.PI*2); ctx.fill();
        ctx.fillStyle = ‘#cc5555’;
        ctx.beginPath(); ctx.moveTo(0, -size*0.25); ctx.lineTo(size*0.2, size*0.1); ctx.lineTo(-size*0.2, size*0.1); ctx.fill();
        ctx.fillStyle = ‘#778877’; ctx.fillRect(-size*0.05, size*0.2, size*0.1, size*0.2);
        ctx.restore();
    }

    function drawAnomaly(ctx, x, y, size) {
        ctx.save(); ctx.translate(x, y);
        let pulse = Math.sin(Date.now() / 200) * 0.2 + 0.8;
        ctx.shadowBlur = 20 * pulse; ctx.shadowColor = ‘#aa55ff’; ctx.fillStyle = `rgba(170, 85, 255, ${0.6 * pulse})`;
        ctx.beginPath(); ctx.arc(0, 0, size*0.3 * pulse, 0, Math.PI*2); ctx.fill();
        ctx.beginPath(); ctx.arc(0, 0, size*0.15, 0, Math.PI*2); ctx.fillStyle = ‘rgba(255,255,255,0.8)’; ctx.fill();
        ctx.restore();
    }

    function drawStarbase(ctx, x, y, size) {
        ctx.save(); ctx.translate(x, y); ctx.fillStyle = ‘#cccccc’;
        ctx.beginPath(); ctx.arc(0, 0, size*0.15, 0, Math.PI*2); ctx.fill();
        ctx.fillRect(-size*0.35, -size*0.03, size*0.7, size*0.06); ctx.fillRect(-size*0.03, -size*0.35, size*0.06, size*0.7);
        ctx.fillStyle = ‘#4488ff’;
        ctx.beginPath(); ctx.arc(-size*0.35, 0, size*0.08, 0, Math.PI*2); ctx.fill();
        ctx.beginPath(); ctx.arc(size*0.35, 0, size*0.08, 0, Math.PI*2); ctx.fill();
        ctx.beginPath(); ctx.arc(0, -size*0.35, size*0.08, 0, Math.PI*2); ctx.fill();
        ctx.beginPath(); ctx.arc(0, size*0.35, size*0.08, 0, Math.PI*2); ctx.fill();
        ctx.restore();
    }

    function drawStar(ctx, x, y, size) {
        ctx.save(); ctx.translate(x, y); ctx.shadowBlur = 10; ctx.shadowColor = ‘#ffffaa’; ctx.fillStyle = ‘#ffffaa’;
        ctx.beginPath(); ctx.arc(0, 0, size*0.12, 0, Math.PI*2); ctx.fill(); ctx.restore();
    }

    function drawWarpEffect() {
        if(state !== ‘PLAY’) return;
        ctx.fillStyle = ‘rgba(5, 10, 21, 0.4)’; ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = ‘#ffffff’; let cx = canvas.width/2, cy = canvas.height/2;
        warpStars.forEach(s => {
            s.dist *= 1.15; let x = cx + Math.cos(s.angle) * s.dist, y = cy + Math.sin(s.angle) * s.dist, len = s.dist * 0.15;
            ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + Math.cos(s.angle) * len, y + Math.sin(s.angle) * len);
            ctx.strokeStyle = ‘#ffffff’; ctx.lineWidth = 2; ctx.stroke();
            if (s.dist > Math.max(canvas.width, canvas.height)) { s.dist = Math.random() * 50; s.angle = Math.random() * Math.PI * 2; }
        });
        if (isWarping) requestAnimationFrame(drawWarpEffect);
    }

    function drawStatic() {
        let cw = canvas.width, ch = canvas.height, imgData = ctx.createImageData(cw, ch);
        for(let i=0; i<imgData.data.length; i+=4) {
            let v = Math.random() * 255;
            imgData.data[i] = v; imgData.data[i+1] = v; imgData.data[i+2] = v; imgData.data[i+3] = 255;
        }
        ctx.putImageData(imgData, 0, 0);
    }

    function draw() {
        if (state !== ‘PLAY’ || isWarping) return;
        ctx.fillStyle = ‘#050a15’; ctx.fillRect(0, 0, canvas.width, canvas.height);
       
        if (mode === ‘LRS’ || mode === ‘WRP’) drawLRS(); else drawSRS();
        if (staticTimer > 0) drawStatic();
    }

    function drawSRS() {
        let cw = canvas.width, ch = canvas.height, cellW = cw / S_SIZE, cellH = ch / S_SIZE, objSize = Math.min(cellW, cellH) * 0.8;

        if (mode === ‘IMP’ || mode === ‘TOR’) {
            ctx.fillStyle = ‘rgba(255, 255, 255, 0.1)’;
            for (let y = 0; y < S_SIZE; y++) { for (let x = 0; x < S_SIZE; x++) { ctx.fillRect(x*cellW, y*cellH, cellW, cellH); } }
        }

        ctx.fillStyle = ‘rgba(0,0,0,0.2)’; for(let i=0; i<ch; i+=4) ctx.fillRect(0, i, cw, 1);
        ctx.strokeStyle = ‘#2a3b4c’; ctx.lineWidth = 2;
        for (let i = 0; i <= S_SIZE; i++) {
            ctx.beginPath(); ctx.moveTo(i * cellW, 0); ctx.lineTo(i * cellW, ch); ctx.stroke();
            ctx.beginPath(); ctx.moveTo(0, i * cellH); ctx.lineTo(cw, i * cellH); ctx.stroke();
        }

        for (let y = 0; y < S_SIZE; y++) {
            for (let x = 0; x < S_SIZE; x++) {
                let item = currentQuadrant[y][x];
                let px = x * cellW + cellW/2, py = y * cellH + cellH/2;

                if (item === ENT) {
                    drawEnterprise(ctx, px, py, objSize);
                    if (ship.shields > 0) {
                        ctx.strokeStyle = `rgba(100, 200, 255, ${Math.min(1, ship.shields/1000)})`;
                        ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(px, py, objSize*0.5, 0, Math.PI*2); ctx.stroke();
                    }
                }
                else if (item === KLN) drawKlingon(ctx, px, py, objSize);
                else if (item === ROM) drawRomulan(ctx, px, py, objSize);
                else if (item === ANO) drawAnomaly(ctx, px, py, objSize);
                else if (item === BAS) drawStarbase(ctx, px, py, objSize);
                else if (item === STR) drawStar(ctx, px, py, objSize);
            }
        }

        activeBeams.forEach(b => {
            ctx.strokeStyle = b.color; ctx.lineWidth = b.isTorp ? 6 : 4;
            if (b.isTorp) ctx.setLineDash([10, 15]); else ctx.setLineDash([]);
            ctx.beginPath(); ctx.moveTo(b.sx * cellW + cellW/2, b.sy * cellH + cellH/2); ctx.lineTo(b.tx * cellW + cellW/2, b.ty * cellH + cellH/2); ctx.stroke();
            ctx.setLineDash([]);
        });
    }

    function drawLRS() {
        let cw = canvas.width, ch = canvas.height, cellW = cw / G_SIZE, cellH = ch / G_SIZE;
        ctx.strokeStyle = ‘#2a4c2a’; ctx.lineWidth = 2;
        for (let i = 0; i <= G_SIZE; i++) {
            ctx.beginPath(); ctx.moveTo(i * cellW, 0); ctx.lineTo(i * cellW, ch); ctx.stroke();
            ctx.beginPath(); ctx.moveTo(0, i * cellH); ctx.lineTo(cw, i * cellH); ctx.stroke();
        }
        ctx.textAlign = ‘center’; ctx.textBaseline = ‘middle’;
        let fontSize = Math.min(cellW, cellH) * 0.4; ctx.font = `${fontSize}px var(–font-screen)`;

        for (let y = 0; y < G_SIZE; y++) {
            for (let x = 0; x < G_SIZE; x++) {
                let px = x * cellW + cellW/2, py = y * cellH + cellH/2, q = galaxy[y][x];

                if (x === ship.qX && y === ship.qY) {
                    ctx.fillStyle = ‘rgba(100, 200, 255, 0.2)’; ctx.fillRect(x*cellW, y*cellH, cellW, cellH);
                    ctx.strokeStyle = ‘#64c8ff’; ctx.strokeRect(x*cellW, y*cellH, cellW, cellH);
                }

                if (q.explored || (Math.abs(x – ship.qX) <= 1 && Math.abs(y – ship.qY) <= 1)) {
                    q.explored = true;
                    ctx.fillStyle = q.k > 0 ? ‘#ff5555’ : (q.b > 0 ? ‘#55ff55’ : ‘#aaaaaa’);
                    ctx.fillText(`${q.k}${q.b}${q.s}`, px, py);
                } else { ctx.fillStyle = ‘#444’; ctx.fillText(‘???’, px, py); }

                if (mode === ‘WRP’) { ctx.fillStyle = ‘rgba(200, 100, 255, 0.2)’; ctx.fillRect(x*cellW, y*cellH, cellW, cellH); }
            }
        }
    }

    function renderLoop() {
        if (state === ‘PLAY’ && staticTimer > 0) staticTimer–;
        draw();
        requestAnimationFrame(renderLoop);
    }

    canvas.addEventListener(‘pointerdown’, (e) => {
        if (state !== ‘PLAY’ || isWarping || activeBeams.length > 0 || staticTimer > 0) return;
        const rect = canvas.getBoundingClientRect();
        const cx = e.clientX – rect.left, cy = e.clientY – rect.top;

        if (mode === ‘IMP’ || mode === ‘TOR’ || mode === ‘SRS’) {
            let sX = Math.floor(cx / (canvas.width / S_SIZE)), sY = Math.floor(cy / (canvas.height / S_SIZE));
            if (mode === ‘IMP’ || (mode === ‘SRS’ && currentQuadrant[sY][sX] === EMP || currentQuadrant[sY][sX] === ANO)) doImpulse(sX, sY);
            else if (mode === ‘TOR’ || (mode === ‘SRS’ && (currentQuadrant[sY][sX] === KLN || currentQuadrant[sY][sX] === ROM))) doTorpedo(sX, sY);
        } else if (mode === ‘WRP’ || mode === ‘LRS’) {
            let qX = Math.floor(cx / (canvas.width / G_SIZE)), qY = Math.floor(cy / (canvas.height / G_SIZE));
            if (mode === ‘WRP’ || mode === ‘LRS’) doWarp(qX, qY);
        }
    });

    function doImpulse(tx, ty) {
        if (tx === ship.sX && ty === ship.sY) return;
        if (currentQuadrant[ty][tx] !== EMP && currentQuadrant[ty][tx] !== ANO) { sfx.error(); return log(“Error: Sector occupied.”, “alert”); }

        let dist = Math.sqrt((tx-ship.sX)**2 + (ty-ship.sY)**2);
        let cost = Math.floor(dist * 10);
        if (ship.energy < cost) { sfx.error(); return log(“Not enough energy.”, “alert”); }

        ship.energy -= cost;
        let isAnomaly = currentQuadrant[ty][tx] === ANO;
       
        currentQuadrant[ship.sY][ship.sX] = EMP;
        ship.sX = tx; ship.sY = ty;
        currentQuadrant[ship.sY][ship.sX] = ENT;

        stardate += 0.1;
        log(`Impulsed. Cost: ${cost}E`);
       
        if (isAnomaly) triggerAnomaly();
        if(Math.random() < 0.4) triggerRandomChatter();
        checkDocking();
        postAction();
        setMode(‘SRS’);
    }

    function doWarp(tx, ty) {
        if (tx === ship.qX && ty === ship.qY) { log(“Already there.”, “info”); return setMode(‘SRS’); }

        let dist = Math.sqrt((tx-ship.qX)**2 + (ty-ship.qY)**2);
        let cost = Math.floor(dist * 100);
        if (ship.energy < cost) { sfx.error(); return log(`Need ${cost}E for warp.`, “alert”); }

        ship.energy -= cost;
        isWarping = true; sfx.warp();
        log(`Warping to quadrant ${tx},${ty}…`, ‘info’);
       
        warpStars = [];
        for(let i=0; i<50; i++) warpStars.push({angle: Math.random()*Math.PI*2, dist: Math.random()*100 + 10});
        requestAnimationFrame(drawWarpEffect);

        setTimeout(() => {
            if (state !== ‘PLAY’) return;
            isWarping = false;
            ship.qX = tx; ship.qY = ty;
            ship.sX = Math.floor(Math.random()*S_SIZE); ship.sY = Math.floor(Math.random()*S_SIZE);
            stardate += Math.max(0.2, dist * 0.1);
            if(Math.random() < 0.5) triggerRandomChatter();
            setupQuadrant();
            setMode(‘SRS’);
            postAction();
        }, 1200);
    }

    function doTorpedo(tx, ty) {
        if (ship.torps <= 0) { sfx.error(); return log(“No torpedoes!”, “alert”); }
        ship.torps–;
        log(`Torpedo away…`); sfx.torpedo();

        let dx = tx – ship.sX, dy = ty – ship.sY, steps = Math.max(Math.abs(dx), Math.abs(dy));
        let xInc = dx / steps, yInc = dy / steps, cx = ship.sX + xInc, cy = ship.sY + yInc;
        let hit = false;

        activeBeams.push({sx: ship.sX, sy: ship.sY, tx: tx, ty: ty, color: ‘#ffaa00’, isTorp: true});

        setTimeout(() => {
            if (state !== ‘PLAY’) return;
            activeBeams = [];
            for (let i = 0; i < steps; i++) {
                let ix = Math.round(cx), iy = Math.round(cy);
                if (ix < 0 || ix >= S_SIZE || iy < 0 || iy >= S_SIZE) break;

                let target = currentQuadrant[iy][ix];
                if (target === KLN || target === ROM) {
                    log(“Direct hit!”, “success”);
                    destroyKlingon(ix, iy, target); hit = true; break;
                } else if (target === STR) {
                    log(“Hit a star. Absorbed.”, “alert”); sfx.explosion(); hit = true; break;
                } else if (target === ANO) {
                    log(“Torpedo detonated in Anomaly.”, “info”); sfx.explosion(); hit = true; break;
                } else if (target === BAS) {
                    log(“Starbase hit!”, “alert”); log(‘COMPUTER: FRIENDLY FIRE. BASE DESTROYED.’, ‘computer’);
                    sfx.explosion(); currentQuadrant[iy][ix] = EMP; galaxy[ship.qY][ship.qX].b–; hit = true;
                    basesDestroyedByPlayer++;
                    if (basesDestroyedByPlayer >= 2) {
                        setTimeout(() => { if(state===’PLAY’) gameOver(“COURT MARTIAL”, “You destroyed multiple Starbases.<br><br>You face trial for treason.”); }, 800);
                    } else {
                        log(“Starfleet issues formal warning.”, “alert”);
                    }
                    break;
                }
                cx += xInc; cy += yInc;
            }

            if (!hit) log(“Torpedo missed.”);
            stardate += 0.1; postAction(); setMode(‘SRS’);
        }, 600);
    }

    function openModal(action) {
        if (state !== ‘PLAY’ || isWarping || activeBeams.length > 0 || staticTimer > 0) return;
        sfx.click(); modalAction = action; modalButtonsContainer.innerHTML = ”;

        if (action === ‘PHASERS’) {
            document.getElementById(‘modal-title’).innerText = ‘PHASER POWER’;
            modalButtonsContainer.innerHTML = `
                <button class=”tos-btn btn-blue modal-btn” onclick=”modalVal(100)”>100</button>
                <button class=”tos-btn btn-blue modal-btn” onclick=”modalVal(250)”>250</button>
                <button class=”tos-btn btn-blue modal-btn” onclick=”modalVal(500)”>500</button>
                <button class=”tos-btn btn-red modal-btn text-white” onclick=”modalVal(‘MAX’)”>MAX</button>
            `;
        } else if (action === ‘SHIELDS’) {
            document.getElementById(‘modal-title’).innerText = ‘SHIELD POWER’;
            modalButtonsContainer.innerHTML = `
                <div class=”modal-label”>RAISE SHIELDS (USE ENERGY)</div>
                <button class=”tos-btn btn-blue modal-btn” onclick=”modalVal(100)”>+100</button>
                <button class=”tos-btn btn-blue modal-btn” onclick=”modalVal(250)”>+250</button>
                <button class=”tos-btn btn-blue modal-btn” onclick=”modalVal(500)”>+500</button>
                <button class=”tos-btn btn-red modal-btn text-white” onclick=”modalVal(‘MAX’)”>MAX</button>
                <div class=”modal-label” style=”margin-top:0.8rem;”>LOWER SHIELDS (GAIN ENERGY)</div>
                <button class=”tos-btn btn-purple modal-btn” onclick=”modalVal(-100)”>-100</button>
                <button class=”tos-btn btn-purple modal-btn” onclick=”modalVal(-250)”>-250</button>
                <button class=”tos-btn btn-purple modal-btn” onclick=”modalVal(-500)”>-500</button>
                <button class=”tos-btn btn-orange modal-btn text-black” onclick=”modalVal(‘DRAIN’)”>DRAIN</button>
            `;
        }
        inputModal.classList.remove(‘hidden’);
    }

    window.closeModal = function() { sfx.click(); inputModal.classList.add(‘hidden’); modalAction = null; }

    window.modalVal = function(val) {
        if (modalAction === ‘PHASERS’) {
            if (val === ‘MAX’) val = ship.energy;
            if (val > ship.energy) { sfx.error(); log(“Low energy.”, “alert”); return closeModal(); }
            firePhasers(val);
        } else if (modalAction === ‘SHIELDS’) {
            if (val === ‘MAX’) val = Math.min(ship.energy, 2000 – ship.shields);
            if (val === ‘DRAIN’) val = -ship.shields;
            if (val > 0 && val > ship.energy) { sfx.error(); log(“Low energy.”, “alert”); return closeModal(); }
            if (val < 0 && Math.abs(val) > ship.shields) val = -ship.shields;
            setShields(val);
        }
        closeModal();
    }

    function setShields(amount) {
        ship.energy -= amount; ship.shields += amount;
        if (amount > 0) { log(`Shields raised by ${amount}.`); sfx.click(); }
        else if (amount < 0) { log(`Shields lowered by ${Math.abs(amount)}.`); sfx.click(); }
        updateUI(); draw();
    }

    function firePhasers(energySpent) {
        let q = galaxy[ship.qY][ship.qX];
        let roms = currentQuadrant.flat().filter(e => e === ROM).length;
        if (q.k === 0 && roms === 0) { sfx.error(); return log(“No targets.”); }

        sfx.phaser(); ship.energy -= energySpent;
        let energyPerEnemy = energySpent / (q.k + roms);
        log(`Phasers fired: ${energySpent}E`);

        activeBeams = [];
        for (let y = 0; y < S_SIZE; y++) {
            for (let x = 0; x < S_SIZE; x++) {
                let target = currentQuadrant[y][x];
                if (target === KLN || target === ROM) {
                    activeBeams.push({sx: ship.sX, sy: ship.sY, tx: x, ty: y, color: ‘#64c8ff’});
                    let dist = Math.sqrt((x-ship.sX)**2 + (y-ship.sY)**2);
                    let dmgReq = target === ROM ? 200 : 150;
                   
                    let distanceFalloff = Math.max(1, dist * 0.4);
                    let damage = (energyPerEnemy / distanceFalloff) * (Math.random() * 0.4 + 0.8);
                   
                    if (damage >= dmgReq) {
                        log(`Target destroyed!`, “success”);
                        setTimeout(() => { if (state === ‘PLAY’) destroyKlingon(x, y, target); }, 300);
                    } else {
                        log(`Target hit (${Math.floor(damage)} dmg). Failed to penetrate.`, “alert”);
                    }
                }
            }
        }
       
        stardate += 0.1;
        setTimeout(() => { if(state !== ‘PLAY’) return; activeBeams = []; postAction(); setMode(‘SRS’); }, 400);
    }

    function destroyKlingon(x, y, type) {
        if(state !== ‘PLAY’) return;
        sfx.explosion(); currentQuadrant[y][x] = EMP;
        if (type === KLN) {
            galaxy[ship.qY][ship.qX].k–; totalKlingons–;
            if (totalKlingons <= 0) setTimeout(() => { if (state===’PLAY’) checkWin(); }, 500);
        }
        checkCondition();
    }

    function moveHostiles() {
        let coords = [];
        for (let y = 0; y < S_SIZE; y++) {
            for (let x = 0; x < S_SIZE; x++) {
                if (currentQuadrant[y][x] === KLN || currentQuadrant[y][x] === ROM) coords.push({x, y, t: currentQuadrant[y][x]});
            }
        }
        coords.forEach(k => {
            let dx = Math.sign(ship.sX – k.x), dy = Math.sign(ship.sY – k.y);
            if (dx !== 0 && dy !== 0 && currentQuadrant[k.y + dy][k.x + dx] === EMP) {
                currentQuadrant[k.y][k.x] = EMP; currentQuadrant[k.y + dy][k.x + dx] = k.t;
            } else if (dx !== 0 && currentQuadrant[k.y][k.x + dx] === EMP) {
                currentQuadrant[k.y][k.x] = EMP; currentQuadrant[k.y][k.x + dx] = k.t;
            } else if (dy !== 0 && currentQuadrant[k.y + dy][k.x] === EMP) {
                currentQuadrant[k.y][k.x] = EMP; currentQuadrant[k.y + dy][k.x] = k.t;
            }
        });
    }

    function postAction() {
        if (state !== ‘PLAY’ || isWarping) return;
       
        if (tribbles > 0) {
            tribbles = Math.floor(tribbles * 1.5);
            if (Math.random() < 0.3) { sfx.purr(); log(`Tribbles: ${tribbles}`, ‘chatter’); }
            if (tribbles > 10000) {
                let drain = Math.floor(tribbles / 10000);
                ship.energy -= drain;
                log(`Tribbles consuming ${drain} energy!`, ‘alert’);
            }
        }

        let nearAnomaly = false;
        for (let dy = -1; dy <= 1; dy++) {
            for (let dx = -1; dx <= 1; dx++) {
                let ny = ship.sY + dy, nx = ship.sX + dx;
                if (ny>=0 && ny<S_SIZE && nx>=0 && nx<S_SIZE && currentQuadrant[ny][nx] === ANO) nearAnomaly = true;
            }
        }
        if(nearAnomaly && Math.random() < 0.5) triggerAnomaly();

        let q = galaxy[ship.qY][ship.qX];
        let hasHostiles = q.k > 0 || currentQuadrant.flat().includes(ROM);

        if (hasHostiles) {
            moveHostiles();
            activeBeams = [];
            let incomingFire = false;
            let decloakSoundPlayed = false;
           
            for (let y = 0; y < S_SIZE; y++) {
                for (let x = 0; x < S_SIZE; x++) {
                    let target = currentQuadrant[y][x];
                    if (target === KLN || target === ROM) {
                       
                        if(target === ROM && !romulansDecloaked) {
                            romulansDecloaked = true;
                            if(!decloakSoundPlayed) { sfx.decloak(); decloakSoundPlayed=true; }
                            log(“Romulan vessel decloaking!”, “alert”);
                        }

                        incomingFire = true;
                        let color = target === ROM ? ‘#aa00ff’ : ‘#ff3333’;
                        activeBeams.push({sx: x, sy: y, tx: ship.sX, ty: ship.sY, color: color});
                       
                        let dist = Math.sqrt((x-ship.sX)**2 + (y-ship.sY)**2);
                        let baseDmg = target === ROM ? 400 : 300;
                        let damage = Math.floor((baseDmg / Math.max(1, dist * 0.4)) * (Math.random() * 0.5 + 0.5));
                        log(`Incoming fire: ${damage} dmg!`, ‘alert’);

                        if (ship.shields >= damage) {
                            ship.shields -= damage; log(“Shields held.”);
                            setTimeout(() => { if (state === ‘PLAY’) sfx.click(); }, 100);
                        } else {
                            let remainder = damage – ship.shields;
                            ship.shields = 0; ship.energy -= remainder;
                            log(`Shields dropped! Hull hit: ${remainder}E`, ‘alert’);
                            setTimeout(() => { if (state === ‘PLAY’) sfx.explosion(); }, 100);
                        }
                    }
                }
            }
           
            if (incomingFire) {
                setTimeout(() => { if (state === ‘PLAY’) activeBeams = []; }, 400);
            }
        }
        updateUI();
        if (ship.energy <= 0) gameOver(“HULL BREACH”, “Hull integrity failed. The Enterprise is destroyed.<br><br>The Klingon Empire advances unopposed.”);
        else if (stardate > endStardate) gameOver(“TIME EXPIRED”, “Stardate limit exceeded.<br><br>The Federation has surrendered to the Klingon armada.”);
    }

    function checkWin() {
        state = ‘GAMEOVER’; sfx.stopAll(); mainView.classList.remove(‘red-alert-pulse’);
        btnQuit.style.display = ‘none’;
       
        let timeRemaining = endStardate – stardate;
        let score = Math.floor(ship.energy) + Math.floor(timeRemaining * 100) – (basesDestroyedByPlayer * 2000) – (tribbles > 10000 ? 500 : 0);
        let rank = “Ensign”;
       
        if (score >= 4000) rank = “Fleet Admiral”;
        else if (score >= 3000) rank = “Captain”;
        else if (score >= 2000) rank = “Commander”;
        else if (score >= 1000) rank = “Lieutenant”;
        if (basesDestroyedByPlayer > 0 && score >= 3000) rank = “Commander (Demoted)”;
       
        document.getElementById(‘go-title’).innerText = “VICTORY”;
        document.getElementById(‘go-title’).style.color = “var(–tos-green)”;
        document.getElementById(‘go-reason’).innerHTML = `
            The Klingon invasion fleet has been stopped.<br><br>
            <span style=”color:var(–tos-gold)”>Final Score:</span> ${score}<br>
            <span style=”color:var(–tos-light-blue)”>Starfleet Rank:</span> ${rank}
        `;
        gameOverScreen.classList.remove(‘hidden’);
    }

    function gameOver(title, reason) {
        state = ‘GAMEOVER’; sfx.stopAll(); mainView.classList.remove(‘red-alert-pulse’);
        btnQuit.style.display = ‘none’;
        document.getElementById(‘go-title’).innerText = title;
        document.getElementById(‘go-title’).style.color = “var(–tos-red)”;
        document.getElementById(‘go-reason’).innerHTML = reason;
        gameOverScreen.classList.remove(‘hidden’);
    }

    document.getElementById(‘btn-srs’).onclick = () => setMode(‘SRS’);
    document.getElementById(‘btn-lrs’).onclick = () => setMode(‘LRS’);
    document.getElementById(‘btn-imp’).onclick = () => setMode(‘IMP’);
    document.getElementById(‘btn-wrp’).onclick = () => setMode(‘WRP’);
    document.getElementById(‘btn-tor’).onclick = () => setMode(‘TOR’);
    document.getElementById(‘btn-pha’).onclick = () => openModal(‘PHASERS’);
    document.getElementById(‘btn-shd’).onclick = () => openModal(‘SHIELDS’);
    document.getElementById(‘btn-start’).onclick = initGame;
    document.getElementById(‘btn-restart’).onclick = initGame;
    document.getElementById(‘btn-quit’).onclick = quitGame;

    if (‘serviceWorker’ in navigator) {
        window.addEventListener(‘load’, () => {
            const swCode = `self.addEventListener(‘install’, e => self.skipWaiting()); self.addEventListener(‘activate’, e => e.waitUntil(clients.claim())); self.addEventListener(‘fetch’, e => { if (e.request.method === ‘GET’) e.respondWith(fetch(e.request).catch(() => caches.match(e.request))); });`;
            navigator.serviceWorker.register(URL.createObjectURL(new Blob([swCode], { type: ‘application/javascript’ }))).catch(err => console.log(‘SW Registration Failed’));
        });
    }
</script>
</body>
</html>

Leave a Reply