Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Flight Simulator</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&family=Share+Tech+Mono&family=Exo+2:wght@300;400;500;600&display=swap" | |
| rel="stylesheet" /> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://unpkg.com/three@0.160.0/build/three.module.js" | |
| } | |
| } | |
| </script> | |
| <style> | |
| :root { | |
| --hud-primary: #00e5ff; | |
| --hud-secondary: #76ff03; | |
| --hud-warning: #ffab00; | |
| --hud-danger: #ff1744; | |
| --hud-text: #e0f7fa; | |
| --hud-dim: rgba(224, 247, 250, 0.5); | |
| --hud-ghost: rgba(0, 229, 255, 0.08); | |
| --hud-glow: rgba(0, 229, 255, 0.4); | |
| --glass-bg: rgba(2, 12, 20, 0.55); | |
| --glass-border: rgba(0, 229, 255, 0.18); | |
| --glass-shine: rgba(255, 255, 255, 0.04); | |
| --font-mono: "Share Tech Mono", "Courier New", monospace; | |
| --font-ui: "Rajdhani", "Segoe UI", sans-serif; | |
| --font-label: "Exo 2", "Segoe UI", sans-serif; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| overflow: hidden; | |
| font-family: var(--font-ui); | |
| background: #0a1929; | |
| } | |
| #game-container, | |
| canvas { | |
| position: fixed; | |
| inset: 0; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| #game-container { | |
| width: 100vw; | |
| height: 100vh; | |
| background: #87ceeb; | |
| } | |
| /* ── Glass panel base ── */ | |
| .glass { | |
| background: var(--glass-bg); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 10px; | |
| backdrop-filter: blur(16px) saturate(1.4); | |
| -webkit-backdrop-filter: blur(16px) saturate(1.4); | |
| box-shadow: | |
| 0 4px 30px rgba(0, 0, 0, 0.3), | |
| inset 0 1px 0 var(--glass-shine); | |
| } | |
| /* ── HUD Overlay Root ── */ | |
| #ui-root { | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 10; | |
| } | |
| /* ── Top Status Bar ── */ | |
| .top-bar { | |
| position: absolute; | |
| top: 12px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| display: flex; | |
| align-items: center; | |
| gap: 20px; | |
| padding: 6px 20px; | |
| border-radius: 20px; | |
| pointer-events: auto; | |
| animation: fadeSlideDown 0.6s ease both; | |
| } | |
| .top-bar .callsign { | |
| font-family: var(--font-mono); | |
| font-size: 0.72rem; | |
| color: var(--hud-primary); | |
| letter-spacing: 0.18em; | |
| text-transform: uppercase; | |
| } | |
| .top-bar .status-pill { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-family: var(--font-label); | |
| font-size: 0.7rem; | |
| font-weight: 500; | |
| color: var(--hud-dim); | |
| letter-spacing: 0.06em; | |
| } | |
| .status-dot { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: var(--hud-secondary); | |
| box-shadow: 0 0 8px rgba(118, 255, 3, 0.7); | |
| animation: pulse-dot 2s ease infinite; | |
| } | |
| .top-bar .fps-counter { | |
| font-family: var(--font-mono); | |
| font-size: 0.68rem; | |
| color: var(--hud-dim); | |
| } | |
| /* ── Left Telemetry Strip ── */ | |
| .telem-strip { | |
| position: absolute; | |
| left: 16px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| width: 200px; | |
| padding: 14px 16px; | |
| animation: fadeSlideRight 0.5s ease both; | |
| animation-delay: 0.1s; | |
| } | |
| .telem-strip .telem-title { | |
| font-family: var(--font-mono); | |
| font-size: 0.62rem; | |
| color: var(--hud-primary); | |
| letter-spacing: 0.22em; | |
| text-transform: uppercase; | |
| margin-bottom: 12px; | |
| opacity: 0.8; | |
| } | |
| .telem-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: baseline; | |
| padding: 4px 0; | |
| border-bottom: 1px solid rgba(0, 229, 255, 0.06); | |
| } | |
| .telem-row:last-child { | |
| border-bottom: none; | |
| } | |
| .telem-label { | |
| font-family: var(--font-label); | |
| font-size: 0.68rem; | |
| font-weight: 500; | |
| color: var(--hud-dim); | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| } | |
| .telem-value { | |
| font-family: var(--font-mono); | |
| font-size: 0.92rem; | |
| color: #fff; | |
| text-shadow: 0 0 8px rgba(0, 229, 255, 0.3); | |
| } | |
| .telem-value.warning { | |
| color: var(--hud-warning); | |
| text-shadow: 0 0 10px rgba(255, 171, 0, 0.5); | |
| } | |
| .telem-value.danger { | |
| color: var(--hud-danger); | |
| text-shadow: 0 0 10px rgba(255, 23, 68, 0.5); | |
| animation: val-flash 0.6s ease infinite; | |
| } | |
| /* ── Throttle Gauge ── */ | |
| .throttle-gauge { | |
| margin-top: 10px; | |
| padding-top: 8px; | |
| border-top: 1px solid rgba(0, 229, 255, 0.1); | |
| } | |
| .throttle-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 5px; | |
| } | |
| .throttle-header .telem-label { | |
| color: var(--hud-warning); | |
| } | |
| .throttle-track { | |
| height: 4px; | |
| border-radius: 2px; | |
| background: rgba(255, 255, 255, 0.08); | |
| overflow: hidden; | |
| } | |
| .throttle-fill { | |
| height: 100%; | |
| width: 0; | |
| border-radius: inherit; | |
| background: linear-gradient(90deg, var(--hud-primary), var(--hud-secondary)); | |
| box-shadow: 0 0 12px var(--hud-glow); | |
| transition: width 100ms linear; | |
| } | |
| .throttle-fill.high { | |
| background: linear-gradient(90deg, var(--hud-warning), #ff6d00); | |
| box-shadow: 0 0 12px rgba(255, 171, 0, 0.6); | |
| } | |
| .throttle-fill.max { | |
| background: linear-gradient(90deg, var(--hud-danger), #ff6090); | |
| box-shadow: 0 0 14px rgba(255, 23, 68, 0.6); | |
| animation: bar-pulse 0.4s ease infinite; | |
| } | |
| /* ── Speed Tape (left of center) ── */ | |
| .speed-tape { | |
| position: absolute; | |
| left: calc(50% - 190px); | |
| top: 50%; | |
| transform: translateY(-50%); | |
| width: 56px; | |
| height: 220px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-end; | |
| pointer-events: none; | |
| } | |
| .tape-readout { | |
| position: absolute; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| right: 0; | |
| padding: 3px 10px; | |
| background: rgba(0, 0, 0, 0.6); | |
| border: 1px solid var(--hud-primary); | |
| border-radius: 4px; | |
| font-family: var(--font-mono); | |
| font-size: 0.88rem; | |
| color: #fff; | |
| text-shadow: 0 0 6px var(--hud-glow); | |
| z-index: 2; | |
| white-space: nowrap; | |
| } | |
| .tape-scale { | |
| position: absolute; | |
| top: 0; | |
| right: 0; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .tape-tick { | |
| position: absolute; | |
| right: 0; | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| } | |
| .tape-tick .tick-line { | |
| width: 10px; | |
| height: 1px; | |
| background: rgba(224, 247, 250, 0.3); | |
| } | |
| .tape-tick .tick-label { | |
| font-family: var(--font-mono); | |
| font-size: 0.58rem; | |
| color: var(--hud-dim); | |
| min-width: 28px; | |
| text-align: right; | |
| } | |
| /* ── Altitude Tape (right of center) ── */ | |
| .alt-tape { | |
| position: absolute; | |
| right: calc(50% - 190px); | |
| top: 50%; | |
| transform: translateY(-50%); | |
| width: 56px; | |
| height: 220px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-start; | |
| pointer-events: none; | |
| } | |
| .alt-tape .tape-readout { | |
| right: auto; | |
| left: 0; | |
| } | |
| .alt-tape .tape-tick { | |
| right: auto; | |
| left: 0; | |
| flex-direction: row-reverse; | |
| } | |
| .alt-tape .tape-tick .tick-label { | |
| text-align: left; | |
| } | |
| /* ── Attitude Indicator (center) ── */ | |
| .attitude-ring { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| width: min(38vw, 240px); | |
| aspect-ratio: 1; | |
| pointer-events: none; | |
| animation: fadeScale 0.5s ease both; | |
| animation-delay: 0.2s; | |
| } | |
| .horizon-shell { | |
| position: absolute; | |
| inset: 0; | |
| border-radius: 50%; | |
| border: 2px solid rgba(0, 229, 255, 0.25); | |
| overflow: hidden; | |
| box-shadow: | |
| 0 0 30px rgba(0, 229, 255, 0.08), | |
| inset 0 0 40px rgba(0, 0, 0, 0.2); | |
| } | |
| .horizon-ladder { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 140%; | |
| height: 140%; | |
| transform: translate(-50%, -50%); | |
| background: | |
| linear-gradient(180deg, | |
| rgba(100, 180, 255, 0.45) 0%, | |
| rgba(100, 180, 255, 0.12) 46%, | |
| rgba(80, 65, 45, 0.35) 54%, | |
| rgba(80, 65, 45, 0.65) 100%), | |
| repeating-linear-gradient(to bottom, | |
| transparent 0, | |
| transparent 22px, | |
| rgba(255, 255, 255, 0.15) 22px, | |
| rgba(255, 255, 255, 0.15) 23px); | |
| transition: transform 0.08s linear; | |
| } | |
| .pitch-markers { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 150%; | |
| height: 150%; | |
| transform: translate(-50%, -50%); | |
| pointer-events: none; | |
| } | |
| .pitch-line { | |
| position: absolute; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 6px; | |
| font-family: var(--font-mono); | |
| font-size: 0.55rem; | |
| color: rgba(255, 255, 255, 0.7); | |
| text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6); | |
| } | |
| .pitch-line::before, | |
| .pitch-line::after { | |
| content: ""; | |
| height: 1px; | |
| flex: 1; | |
| max-width: 50px; | |
| } | |
| .pitch-line.positive::before, | |
| .pitch-line.positive::after { | |
| background: rgba(100, 200, 255, 0.4); | |
| } | |
| .pitch-line.negative::before, | |
| .pitch-line.negative::after { | |
| background: rgba(120, 90, 60, 0.6); | |
| } | |
| /* Reticle crosshair */ | |
| .reticle { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| width: 40px; | |
| height: 40px; | |
| z-index: 3; | |
| } | |
| .reticle::before, | |
| .reticle::after { | |
| content: ""; | |
| position: absolute; | |
| background: rgba(255, 255, 255, 0.85); | |
| } | |
| .reticle::before { | |
| width: 1px; | |
| height: 52px; | |
| left: 50%; | |
| top: 50%; | |
| transform: translate(-50%, -50%); | |
| } | |
| .reticle::after { | |
| width: 52px; | |
| height: 1px; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| } | |
| .reticle-dot { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| border: 1px solid rgba(255, 255, 255, 0.9); | |
| } | |
| /* Bank angle indicator marks around the ring */ | |
| .bank-marks { | |
| position: absolute; | |
| inset: -4px; | |
| border-radius: 50%; | |
| } | |
| .bank-mark { | |
| position: absolute; | |
| top: 0; | |
| left: 50%; | |
| width: 1px; | |
| height: 8px; | |
| background: rgba(0, 229, 255, 0.4); | |
| transform-origin: 0 calc(50% + 124px); | |
| } | |
| .bank-mark.major { | |
| height: 12px; | |
| background: rgba(0, 229, 255, 0.7); | |
| width: 2px; | |
| } | |
| /* ── Heading / Compass Bar ── */ | |
| .heading-bar { | |
| position: absolute; | |
| bottom: 60px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| pointer-events: none; | |
| animation: fadeSlideUp 0.5s ease both; | |
| animation-delay: 0.25s; | |
| } | |
| .compass-container { | |
| position: relative; | |
| width: min(44vw, 300px); | |
| height: 28px; | |
| border-radius: 6px; | |
| background: var(--glass-bg); | |
| border: 1px solid var(--glass-border); | |
| backdrop-filter: blur(12px); | |
| overflow: hidden; | |
| } | |
| .compass-tape { | |
| position: absolute; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| display: flex; | |
| gap: 0; | |
| transition: transform 0.08s linear; | |
| } | |
| .compass-marking { | |
| width: 28px; | |
| text-align: center; | |
| font-family: var(--font-mono); | |
| font-size: 0.58rem; | |
| font-weight: 600; | |
| color: var(--hud-dim); | |
| } | |
| .compass-marking.major { | |
| color: var(--hud-text); | |
| font-size: 0.66rem; | |
| } | |
| .compass-marking.north { | |
| color: var(--hud-danger); | |
| } | |
| .compass-indicator { | |
| position: absolute; | |
| top: 2px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 0; | |
| height: 0; | |
| border-left: 4px solid transparent; | |
| border-right: 4px solid transparent; | |
| border-top: 6px solid var(--hud-primary); | |
| filter: drop-shadow(0 0 4px var(--hud-glow)); | |
| } | |
| .heading-readout { | |
| position: absolute; | |
| bottom: -20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| font-family: var(--font-mono); | |
| font-size: 0.72rem; | |
| color: var(--hud-primary); | |
| text-shadow: 0 0 6px var(--hud-glow); | |
| letter-spacing: 0.1em; | |
| } | |
| /* ── Controls Tooltip (bottom-right, minimal) ── */ | |
| .controls-tip { | |
| position: absolute; | |
| bottom: 16px; | |
| right: 16px; | |
| padding: 10px 14px; | |
| max-width: 260px; | |
| opacity: 0.35; | |
| transition: opacity 0.3s ease; | |
| pointer-events: auto; | |
| animation: fadeSlideUp 0.5s ease both; | |
| animation-delay: 0.4s; | |
| } | |
| .controls-tip:hover { | |
| opacity: 1; | |
| } | |
| .controls-tip .tip-title { | |
| font-family: var(--font-mono); | |
| font-size: 0.56rem; | |
| color: var(--hud-primary); | |
| letter-spacing: 0.2em; | |
| text-transform: uppercase; | |
| margin-bottom: 8px; | |
| opacity: 0.7; | |
| } | |
| .ctrl-row { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 8px; | |
| padding: 3px 0; | |
| } | |
| .ctrl-keys { | |
| display: flex; | |
| gap: 3px; | |
| } | |
| kbd { | |
| min-width: 22px; | |
| padding: 2px 6px; | |
| border-radius: 4px; | |
| border: 1px solid rgba(0, 229, 255, 0.25); | |
| background: rgba(255, 255, 255, 0.04); | |
| color: var(--hud-text); | |
| font-family: var(--font-mono); | |
| font-size: 0.62rem; | |
| text-align: center; | |
| transition: all 0.15s ease; | |
| } | |
| kbd.active { | |
| background: var(--hud-primary); | |
| color: #000; | |
| border-color: transparent; | |
| box-shadow: 0 0 10px var(--hud-glow); | |
| } | |
| .ctrl-label { | |
| font-family: var(--font-label); | |
| font-size: 0.62rem; | |
| color: var(--hud-dim); | |
| letter-spacing: 0.04em; | |
| } | |
| /* ── Mini Map ── */ | |
| .mini-map { | |
| position: fixed; | |
| bottom: 16px; | |
| left: 16px; | |
| width: 120px; | |
| height: 120px; | |
| border-radius: 10px; | |
| border: 1px solid var(--glass-border); | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(12px); | |
| overflow: hidden; | |
| z-index: 15; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); | |
| animation: fadeSlideUp 0.5s ease both; | |
| animation-delay: 0.35s; | |
| } | |
| .mini-map-canvas { | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .mini-map-label { | |
| position: absolute; | |
| top: 4px; | |
| left: 7px; | |
| font-family: var(--font-mono); | |
| font-size: 0.5rem; | |
| color: var(--hud-primary); | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| opacity: 0.7; | |
| } | |
| /* ── HUD Buttons ── */ | |
| .hud-buttons { | |
| position: fixed; | |
| top: 12px; | |
| right: 14px; | |
| display: flex; | |
| gap: 6px; | |
| z-index: 20; | |
| } | |
| .hud-btn { | |
| pointer-events: auto; | |
| width: 34px; | |
| height: 34px; | |
| border-radius: 8px; | |
| border: 1px solid var(--glass-border); | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(12px); | |
| color: var(--hud-text); | |
| font-size: 0.9rem; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.2s ease; | |
| } | |
| .hud-btn:hover { | |
| background: rgba(0, 229, 255, 0.15); | |
| border-color: var(--hud-primary); | |
| box-shadow: 0 0 12px var(--hud-glow); | |
| } | |
| .hud-btn:active { | |
| transform: scale(0.92); | |
| } | |
| /* ── Altitude Warning ── */ | |
| .altitude-warning { | |
| position: fixed; | |
| top: 24%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| padding: 10px 32px; | |
| background: rgba(255, 23, 68, 0.15); | |
| border: 1px solid var(--hud-danger); | |
| border-radius: 6px; | |
| font-family: var(--font-mono); | |
| font-size: 1rem; | |
| font-weight: 700; | |
| color: var(--hud-danger); | |
| text-transform: uppercase; | |
| letter-spacing: 0.15em; | |
| text-shadow: 0 0 12px rgba(255, 23, 68, 0.5); | |
| animation: warn-flash 0.5s ease infinite; | |
| z-index: 100; | |
| pointer-events: none; | |
| opacity: 0; | |
| transition: opacity 0.2s ease; | |
| } | |
| .altitude-warning.visible { | |
| opacity: 1; | |
| } | |
| /* ── Pause Menu ── */ | |
| .pause-overlay { | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0, 0, 0, 0.65); | |
| backdrop-filter: blur(12px); | |
| z-index: 50; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 0.3s ease; | |
| } | |
| .pause-overlay.visible { | |
| opacity: 1; | |
| pointer-events: auto; | |
| } | |
| .pause-menu { | |
| text-align: center; | |
| padding: 40px 48px; | |
| background: var(--glass-bg); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 16px; | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); | |
| } | |
| .pause-menu h2 { | |
| font-family: var(--font-mono); | |
| font-size: 1.6rem; | |
| color: var(--hud-primary); | |
| letter-spacing: 0.2em; | |
| margin-bottom: 24px; | |
| text-shadow: 0 0 20px var(--hud-glow); | |
| } | |
| .pause-menu button { | |
| display: block; | |
| width: 180px; | |
| margin: 10px auto; | |
| padding: 12px 20px; | |
| border-radius: 8px; | |
| border: 1px solid var(--glass-border); | |
| background: rgba(255, 255, 255, 0.04); | |
| color: var(--hud-text); | |
| font-family: var(--font-ui); | |
| font-size: 0.95rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| letter-spacing: 0.04em; | |
| } | |
| .pause-menu button:hover { | |
| background: rgba(0, 229, 255, 0.15); | |
| border-color: var(--hud-primary); | |
| box-shadow: 0 0 14px var(--hud-glow); | |
| } | |
| .pause-menu button.primary { | |
| background: linear-gradient(135deg, var(--hud-primary), #00b8d4); | |
| color: #000; | |
| border: none; | |
| font-weight: 700; | |
| } | |
| .pause-menu button.primary:hover { | |
| box-shadow: 0 0 20px var(--hud-glow), 0 4px 20px rgba(0, 229, 255, 0.3); | |
| } | |
| /* ── Loading Screen ── */ | |
| .loading-overlay { | |
| position: fixed; | |
| inset: 0; | |
| background: linear-gradient(160deg, #020c14, #0a1929); | |
| z-index: 1000; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| transition: opacity 0.6s ease; | |
| } | |
| .loading-overlay.hidden { | |
| opacity: 0; | |
| pointer-events: none; | |
| } | |
| .loading-title { | |
| font-family: var(--font-mono); | |
| font-size: clamp(1.2rem, 3vw, 2rem); | |
| color: var(--hud-primary); | |
| letter-spacing: 0.3em; | |
| text-transform: uppercase; | |
| margin-bottom: 40px; | |
| text-shadow: 0 0 30px var(--hud-glow); | |
| } | |
| .loading-spinner { | |
| width: 48px; | |
| height: 48px; | |
| border: 2px solid rgba(0, 229, 255, 0.15); | |
| border-top-color: var(--hud-primary); | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| .loading-text { | |
| margin-top: 20px; | |
| font-family: var(--font-label); | |
| font-size: 0.78rem; | |
| color: var(--hud-dim); | |
| letter-spacing: 0.12em; | |
| } | |
| /* ── Mobile Touch Controls ── */ | |
| .touch-controls { | |
| display: none; | |
| position: fixed; | |
| bottom: 20px; | |
| left: 20px; | |
| right: 20px; | |
| z-index: 15; | |
| pointer-events: auto; | |
| } | |
| .touch-row { | |
| display: flex; | |
| justify-content: space-between; | |
| gap: 20px; | |
| } | |
| .touch-btn { | |
| width: 60px; | |
| height: 60px; | |
| border-radius: 50%; | |
| border: 1px solid rgba(0, 229, 255, 0.3); | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(8px); | |
| color: var(--hud-text); | |
| font-size: 1.3rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| user-select: none; | |
| -webkit-user-select: none; | |
| touch-action: manipulation; | |
| transition: all 0.1s ease; | |
| } | |
| .touch-btn:active, | |
| .touch-btn.active { | |
| background: rgba(0, 229, 255, 0.3); | |
| border-color: var(--hud-primary); | |
| box-shadow: 0 0 12px var(--hud-glow); | |
| } | |
| /* ── Animations ── */ | |
| @keyframes fadeSlideDown { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-50%) translateY(-10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| } | |
| @keyframes fadeSlideRight { | |
| from { | |
| opacity: 0; | |
| transform: translateY(-50%) translateX(-14px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(-50%) translateX(0); | |
| } | |
| } | |
| @keyframes fadeSlideUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes fadeScale { | |
| from { | |
| opacity: 0; | |
| transform: translate(-50%, -50%) scale(0.94); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translate(-50%, -50%) scale(1); | |
| } | |
| } | |
| @keyframes spin { | |
| to { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| @keyframes pulse-dot { | |
| 0%, | |
| 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.4; | |
| } | |
| } | |
| @keyframes val-flash { | |
| 0%, | |
| 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.4; | |
| } | |
| } | |
| @keyframes bar-pulse { | |
| 0%, | |
| 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.65; | |
| } | |
| } | |
| @keyframes warn-flash { | |
| 0%, | |
| 100% { | |
| background: rgba(255, 23, 68, 0.12); | |
| } | |
| 50% { | |
| background: rgba(255, 23, 68, 0.28); | |
| } | |
| } | |
| /* ── Responsive ── */ | |
| @media (max-width: 900px) { | |
| .telem-strip { | |
| width: 170px; | |
| padding: 10px 12px; | |
| } | |
| .speed-tape, | |
| .alt-tape { | |
| display: none; | |
| } | |
| .attitude-ring { | |
| width: min(48vw, 200px); | |
| } | |
| .controls-tip { | |
| display: none; | |
| } | |
| } | |
| @media (max-width: 620px) { | |
| .telem-strip { | |
| width: 150px; | |
| left: 8px; | |
| padding: 8px 10px; | |
| } | |
| .attitude-ring { | |
| width: min(50vw, 180px); | |
| } | |
| .mini-map { | |
| width: 80px; | |
| height: 80px; | |
| bottom: 90px; | |
| } | |
| .compass-container { | |
| width: 90vw; | |
| max-width: 260px; | |
| } | |
| .touch-controls { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Loading Screen --> | |
| <div id="loading-overlay" class="loading-overlay"> | |
| <h1 class="loading-title">Flight Simulator</h1> | |
| <div class="loading-spinner"></div> | |
| <p class="loading-text">Initializing systems...</p> | |
| </div> | |
| <div id="game-container"></div> | |
| <!-- HUD Buttons --> | |
| <div class="hud-buttons"> | |
| <button id="btn-fullscreen" class="hud-btn" title="Toggle Fullscreen">⛶</button> | |
| <button id="btn-menu" class="hud-btn" title="Pause Menu">☰</button> | |
| </div> | |
| <!-- Pause Menu Overlay --> | |
| <div id="pause-overlay" class="pause-overlay"> | |
| <div class="pause-menu"> | |
| <h2>PAUSED</h2> | |
| <button id="btn-resume" class="primary">Resume Flight</button> | |
| <button id="btn-restart">Restart</button> | |
| </div> | |
| </div> | |
| <!-- Altitude Warning --> | |
| <div id="altitude-warning" class="altitude-warning"> | |
| ⚠ PULL UP ⚠ | |
| </div> | |
| <div id="ui-root"> | |
| <!-- Top Status Bar --> | |
| <div class="top-bar glass"> | |
| <span class="callsign">FLIGHT SIM</span> | |
| <span class="status-pill" id="flight-status"> | |
| <span class="status-dot"></span> | |
| LIVE | |
| </span> | |
| <span class="fps-counter" id="hud-fps">60 FPS</span> | |
| </div> | |
| <!-- Left Telemetry Strip --> | |
| <div class="telem-strip glass"> | |
| <div class="telem-title">Telemetry</div> | |
| <div class="telem-row"> | |
| <span class="telem-label">SPD</span> | |
| <span class="telem-value" id="hud-speed">0 kn</span> | |
| </div> | |
| <div class="telem-row"> | |
| <span class="telem-label">ALT</span> | |
| <span class="telem-value" id="hud-altitude">0 m</span> | |
| </div> | |
| <div class="telem-row"> | |
| <span class="telem-label">HDG</span> | |
| <span class="telem-value" id="hud-heading">000°</span> | |
| </div> | |
| <div class="telem-row"> | |
| <span class="telem-label">PIT</span> | |
| <span class="telem-value" id="hud-pitch">0°</span> | |
| </div> | |
| <div class="telem-row"> | |
| <span class="telem-label">RLL</span> | |
| <span class="telem-value" id="hud-roll">0°</span> | |
| </div> | |
| <div class="throttle-gauge"> | |
| <div class="throttle-header"> | |
| <span class="telem-label">THR</span> | |
| <span class="telem-value" id="hud-throttle" style="font-size: 0.78rem">0%</span> | |
| </div> | |
| <div class="throttle-track"> | |
| <div id="hud-throttle-bar" class="throttle-fill"></div> | |
| </div> | |
| <div class="throttle-header" style="margin-top: 6px"> | |
| <span class="telem-label">ENG</span> | |
| <span class="telem-value" id="hud-engine-state" | |
| style="font-size: 0.68rem; color: var(--hud-secondary)">Nominal</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Speed Tape --> | |
| <div class="speed-tape"> | |
| <div class="tape-readout" id="speed-readout">0</div> | |
| <div class="tape-scale" id="speed-scale"></div> | |
| </div> | |
| <!-- Altitude Tape --> | |
| <div class="alt-tape"> | |
| <div class="tape-readout" id="alt-readout">0</div> | |
| <div class="tape-scale" id="alt-scale"></div> | |
| </div> | |
| <!-- Attitude Indicator --> | |
| <div class="attitude-ring"> | |
| <div class="bank-marks" id="bank-marks"></div> | |
| <div class="horizon-shell"> | |
| <div id="horizon-ladder" class="horizon-ladder"></div> | |
| <div class="pitch-markers" id="pitch-markers"> | |
| <div class="pitch-line positive" style="top: 25%;">+20</div> | |
| <div class="pitch-line positive" style="top: 35%;">+10</div> | |
| <div class="pitch-line negative" style="top: 65%;">-10</div> | |
| <div class="pitch-line negative" style="top: 75%;">-20</div> | |
| </div> | |
| </div> | |
| <div class="reticle"> | |
| <div class="reticle-dot"></div> | |
| </div> | |
| </div> | |
| <!-- Compass Heading Bar --> | |
| <div class="heading-bar"> | |
| <div class="compass-container"> | |
| <div class="compass-tape" id="compass-tape"></div> | |
| <div class="compass-indicator"></div> | |
| </div> | |
| <span class="heading-readout" id="heading-readout">000°</span> | |
| </div> | |
| <!-- Controls Tooltip --> | |
| <div class="controls-tip glass"> | |
| <div class="tip-title">Controls</div> | |
| <div class="ctrl-row"> | |
| <span class="ctrl-keys"><kbd id="key-w">W</kbd><kbd id="key-s">S</kbd><kbd id="key-a">A</kbd><kbd | |
| id="key-d">D</kbd></span> | |
| <span class="ctrl-label">Pitch/Roll</span> | |
| </div> | |
| <div class="ctrl-row"> | |
| <span class="ctrl-keys"><kbd id="key-q">Q</kbd><kbd id="key-e">E</kbd></span> | |
| <span class="ctrl-label">Yaw</span> | |
| </div> | |
| <div class="ctrl-row"> | |
| <span class="ctrl-keys"><kbd id="key-shift">SHIFT</kbd></span> | |
| <span class="ctrl-label">Boost</span> | |
| </div> | |
| <div class="ctrl-row"> | |
| <span class="ctrl-keys"><kbd id="key-arrowup">UP</kbd><kbd id="key-arrowdown">DOWN</kbd></span> | |
| <span class="ctrl-label">Throttle</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Mini Map --> | |
| <div class="mini-map"> | |
| <span class="mini-map-label">Radar</span> | |
| <canvas id="mini-map-canvas" class="mini-map-canvas"></canvas> | |
| </div> | |
| <!-- Mobile Touch Controls --> | |
| <div class="touch-controls"> | |
| <div class="touch-row"> | |
| <button class="touch-btn" id="touch-yaw-left">↺</button> | |
| <button class="touch-btn" id="touch-pitch-up">▲</button> | |
| <button class="touch-btn" id="touch-yaw-right">↻</button> | |
| </div> | |
| <div class="touch-row"> | |
| <button class="touch-btn" id="touch-roll-left">◄</button> | |
| <button class="touch-btn" id="touch-throttle">◎</button> | |
| <button class="touch-btn" id="touch-roll-right">►</button> | |
| </div> | |
| <div class="touch-row"> | |
| <button class="touch-btn" id="touch-pitch-down">▼</button> | |
| </div> | |
| </div> | |
| <script type="module" src="./src/main.js"></script> | |
| </body> | |
| </html> |