| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>🚁 SkyRelic - Drone Delivery Env</title> |
| <link rel="icon" type="image/svg+xml" href="/favicon.svg" sizes="any"> |
| <link rel="shortcut icon" href="/favicon.ico"> |
| <link |
| href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@300;400;500;700&display=swap" |
| rel="stylesheet" /> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script> |
| <style> |
| |
| |
| |
| :root { |
| --bg0: #06080f; |
| --bg1: #0c1120; |
| --glass: rgba(255, 255, 255, 0.04); |
| --glass-b: rgba(255, 255, 255, 0.09); |
| --glass-hover: rgba(255, 255, 255, 0.07); |
| --border: rgba(255, 255, 255, 0.08); |
| --border-bright: rgba(255, 255, 255, 0.18); |
| |
| --cyan: #00e5ff; |
| --green: #00ffb2; |
| --amber: #ffb300; |
| --red: #ff4d6d; |
| --purple: #b388ff; |
| |
| --text: #e8f4ff; |
| --dim: rgba(232, 244, 255, 0.45); |
| --dimmer: rgba(232, 244, 255, 0.22); |
| |
| --glow-cyan: 0 0 24px rgba(0, 229, 255, 0.35); |
| --glow-green: 0 0 20px rgba(0, 255, 178, 0.35); |
| --glow-amber: 0 0 20px rgba(255, 179, 0, 0.35); |
| --glow-red: 0 0 20px rgba(255, 77, 109, 0.3); |
| |
| --f-display: 'Syne', sans-serif; |
| --f-mono: 'JetBrains Mono', monospace; |
| |
| --cell: 34px; |
| --r: 16px; |
| --r-sm: 10px; |
| } |
| |
| |
| |
| |
| @keyframes glowPulse { |
| 0% { |
| box-shadow: 0 0 5px rgba(0, 229, 255, 0.1); |
| } |
| |
| 100% { |
| box-shadow: 0 0 20px rgba(0, 229, 255, 0.4); |
| } |
| } |
| |
| .glow-on-hover:hover { |
| animation: glowPulse 1.5s infinite alternate; |
| } |
| |
| |
| |
| |
| .console { |
| background: rgba(0, 0, 0, 0.4); |
| border: 1px solid var(--border); |
| border-radius: var(--r-sm); |
| padding: 12px; |
| font-family: var(--f-mono); |
| font-size: 0.65rem; |
| color: var(--green); |
| height: 140px; |
| overflow-y: auto; |
| line-height: 1.5; |
| margin-top: 8px; |
| position: relative; |
| box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.5); |
| scrollbar-width: thin; |
| scrollbar-color: var(--border) transparent; |
| } |
| |
| .console::-webkit-scrollbar { |
| width: 4px; |
| } |
| |
| .console::-webkit-scrollbar-thumb { |
| background: var(--border); |
| border-radius: 4px; |
| } |
| |
| .console::before { |
| content: 'NEURAL_LINK_STABLE'; |
| position: absolute; |
| top: 4px; |
| right: 8px; |
| font-size: 0.5rem; |
| opacity: 0.4; |
| letter-spacing: 2px; |
| } |
| |
| .console-line { |
| margin-bottom: 4px; |
| border-left: 2px solid transparent; |
| padding-left: 6px; |
| } |
| |
| .console-line.info { |
| border-left-color: var(--cyan); |
| color: var(--cyan); |
| } |
| |
| .console-line.success { |
| border-left-color: var(--green); |
| color: var(--green); |
| } |
| |
| .console-line.warn { |
| border-left-color: var(--amber); |
| color: var(--amber); |
| } |
| |
| |
| .console-row { |
| display: flex; |
| gap: 12px; |
| width: 100%; |
| } |
| |
| .console-wrapper { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| } |
| |
| .console-header { |
| font-size: 0.55rem; |
| color: var(--cyan); |
| margin-bottom: 6px; |
| letter-spacing: 1px; |
| opacity: 0.8; |
| display: flex; |
| justify-content: space-between; |
| } |
| |
| .console-header span { |
| opacity: 0.5; |
| } |
| |
| |
| |
| |
| .mission-control { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 12px 16px; |
| background: var(--bg1); |
| border-top: 1px solid var(--border-bright); |
| border-radius: 0 0 var(--r) var(--r); |
| } |
| |
| .control-group { |
| display: flex; |
| gap: 8px; |
| align-items: center; |
| } |
| |
| .status-indicator { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| font-size: 0.6rem; |
| color: var(--dim); |
| } |
| |
| .status-dot { |
| width: 6px; |
| height: 6px; |
| border-radius: 50%; |
| background: var(--dim); |
| } |
| |
| .status-dot.active { |
| background: var(--green); |
| box-shadow: var(--glow-green); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| *, |
| *::before, |
| *::after { |
| box-sizing: border-box; |
| margin: 0; |
| padding: 0; |
| } |
| |
| html, |
| body { |
| height: 100%; |
| background: var(--bg0); |
| color: var(--text); |
| font-family: var(--f-mono); |
| overflow-x: hidden; |
| -webkit-font-smoothing: antialiased; |
| } |
| |
| |
| body::before { |
| content: ''; |
| position: fixed; |
| inset: 0; |
| z-index: 0; |
| background: |
| radial-gradient(ellipse 60% 40% at 20% 30%, rgba(0, 229, 255, 0.05) 0%, transparent 60%), |
| radial-gradient(ellipse 50% 40% at 80% 70%, rgba(0, 255, 178, 0.04) 0%, transparent 60%), |
| radial-gradient(ellipse 40% 30% at 50% 10%, rgba(179, 136, 255, 0.04) 0%, transparent 60%); |
| animation: bgPulse 12s ease-in-out infinite alternate; |
| pointer-events: none; |
| } |
| |
| body::after { |
| content: ''; |
| position: fixed; |
| inset: 0; |
| z-index: 0; |
| background-image: |
| linear-gradient(rgba(0, 229, 255, 0.025) 1px, transparent 1px), |
| linear-gradient(90deg, rgba(0, 229, 255, 0.025) 1px, transparent 1px); |
| background-size: 48px 48px; |
| pointer-events: none; |
| } |
| |
| @keyframes bgPulse { |
| 0% { |
| opacity: 0.6; |
| } |
| |
| 100% { |
| opacity: 1; |
| } |
| } |
| |
| |
| |
| |
| #processing { |
| position: fixed; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| background: rgba(6, 8, 15, 0.85); |
| backdrop-filter: blur(12px); |
| padding: 30px 50px; |
| border-radius: 16px; |
| border: 1px solid var(--border-bright); |
| z-index: 10000; |
| display: none; |
| flex-direction: column; |
| align-items: center; |
| gap: 15px; |
| box-shadow: 0 0 60px rgba(0, 229, 255, 0.25); |
| } |
| |
| .spinner { |
| width: 44px; |
| height: 44px; |
| border: 3px solid var(--glass-b); |
| border-top-color: var(--cyan); |
| border-radius: 50%; |
| animation: spin 0.8s linear infinite; |
| } |
| |
| @keyframes spin { |
| to { |
| transform: rotate(360deg); |
| } |
| } |
| |
| .processing-text { |
| font-family: var(--f-mono); |
| font-size: 0.85rem; |
| color: var(--cyan); |
| text-transform: uppercase; |
| letter-spacing: 3px; |
| font-weight: 700; |
| } |
| |
| |
| |
| |
| #app { |
| position: relative; |
| z-index: 1; |
| min-height: 100vh; |
| display: flex; |
| flex-direction: column; |
| } |
| |
| |
| header { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 16px 28px; |
| background: rgba(12, 17, 32, 0.7); |
| backdrop-filter: blur(24px) saturate(180%); |
| -webkit-backdrop-filter: blur(24px) saturate(180%); |
| border-bottom: 1px solid var(--border); |
| position: sticky; |
| top: 0; |
| z-index: 100; |
| } |
| |
| .logo { |
| font-family: var(--f-display); |
| font-size: 1.25rem; |
| font-weight: 800; |
| letter-spacing: .05em; |
| color: var(--cyan); |
| text-shadow: var(--glow-cyan); |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .critical-flash { |
| color: #ff3d00 !important; |
| text-shadow: 0 0 15px rgba(255, 61, 0, 0.8); |
| animation: pulse-red .8s infinite; |
| } |
| |
| @keyframes pulse-red { |
| 0% { |
| opacity: 1; |
| } |
| |
| 50% { |
| opacity: 0.4; |
| } |
| |
| 100% { |
| opacity: 1; |
| } |
| } |
| |
| .title-icon { |
| font-size: 1.5rem; |
| animation: rotorSpin 3s linear infinite; |
| display: inline-block; |
| } |
| |
| @keyframes rotorSpin { |
| |
| 0%, |
| 100% { |
| transform: rotate(0deg) scale(1); |
| } |
| |
| 50% { |
| transform: rotate(5deg) scale(1.05); |
| } |
| } |
| |
| .header-right { |
| display: flex; |
| gap: 10px; |
| align-items: center; |
| } |
| |
| .hbadge { |
| font-size: .65rem; |
| font-family: var(--f-mono); |
| letter-spacing: .1em; |
| padding: 4px 12px; |
| border-radius: 100px; |
| background: var(--glass); |
| border: 1px solid var(--border); |
| color: var(--dim); |
| } |
| |
| .hbadge.live { |
| border-color: var(--green); |
| color: var(--green); |
| text-shadow: var(--glow-green); |
| } |
| |
| |
| .main { |
| display: grid; |
| grid-template-columns: 260px 1fr 280px; |
| gap: 14px; |
| padding: 14px 18px 20px; |
| flex: 1; |
| } |
| |
| |
| |
| |
| .panel { |
| background: var(--glass); |
| border: 1px solid var(--border); |
| border-radius: var(--r); |
| backdrop-filter: blur(20px) saturate(160%); |
| -webkit-backdrop-filter: blur(20px) saturate(160%); |
| overflow: hidden; |
| position: relative; |
| transition: border-color .25s, box-shadow .25s; |
| } |
| |
| .panel:hover { |
| border-color: var(--border-bright); |
| box-shadow: 0 8px 40px rgba(0, 0, 0, 0.4); |
| } |
| |
| |
| .panel::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 10%; |
| right: 10%; |
| height: 1px; |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent); |
| border-radius: 2px; |
| } |
| |
| .panel-inner { |
| padding: 16px; |
| } |
| |
| .panel-title { |
| font-family: var(--f-display); |
| font-size: .65rem; |
| font-weight: 700; |
| letter-spacing: .18em; |
| text-transform: uppercase; |
| color: var(--cyan); |
| margin-bottom: 14px; |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .panel-title svg { |
| opacity: .7; |
| flex-shrink: 0; |
| } |
| |
| .panel-title::after { |
| content: ''; |
| flex: 1; |
| height: 1px; |
| background: linear-gradient(90deg, var(--border-bright), transparent); |
| } |
| |
| |
| |
| |
| .left-col { |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| } |
| |
| |
| .task-group { |
| display: flex; |
| flex-direction: column; |
| gap: 6px; |
| } |
| |
| .task-btn { |
| width: 100%; |
| font-family: var(--f-display); |
| font-size: .72rem; |
| font-weight: 700; |
| letter-spacing: .06em; |
| padding: 12px 14px; |
| border: 1px solid var(--border); |
| border-radius: var(--r-sm); |
| background: var(--glass); |
| color: var(--dim); |
| cursor: pointer; |
| text-align: left; |
| transition: all .2s; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .task-btn:hover { |
| background: var(--glass-hover); |
| border-color: rgba(0, 229, 255, 0.3); |
| color: var(--text); |
| } |
| |
| .task-btn.active { |
| background: rgba(0, 229, 255, 0.08); |
| border-color: var(--cyan); |
| color: var(--cyan); |
| box-shadow: var(--glow-cyan), inset 0 0 20px rgba(0, 229, 255, 0.05); |
| } |
| |
| .task-btn .dot { |
| width: 7px; |
| height: 7px; |
| border-radius: 50%; |
| background: currentColor; |
| box-shadow: 0 0 8px currentColor; |
| flex-shrink: 0; |
| } |
| |
| .task-tag { |
| margin-left: auto; |
| font-size: .55rem; |
| opacity: .6; |
| padding: 2px 7px; |
| border: 1px solid currentColor; |
| border-radius: 100px; |
| } |
| |
| |
| .stat-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 8px; |
| } |
| |
| .stat-card { |
| background: rgba(255, 255, 255, 0.03); |
| border: 1px solid var(--border); |
| border-radius: var(--r-sm); |
| padding: 10px 12px; |
| display: flex; |
| flex-direction: column; |
| gap: 3px; |
| } |
| |
| .stat-label { |
| font-size: .58rem; |
| letter-spacing: .12em; |
| text-transform: uppercase; |
| color: var(--dim); |
| } |
| |
| .stat-value { |
| font-size: 1.1rem; |
| font-weight: 700; |
| color: var(--text); |
| font-family: var(--f-display); |
| } |
| |
| .stat-value.green { |
| color: var(--green); |
| text-shadow: var(--glow-green); |
| } |
| |
| .stat-value.cyan { |
| color: var(--cyan); |
| text-shadow: var(--glow-cyan); |
| } |
| |
| .stat-value.amber { |
| color: var(--amber); |
| text-shadow: var(--glow-amber); |
| } |
| |
| .stat-value.red { |
| color: var(--red); |
| text-shadow: var(--glow-red); |
| } |
| |
| |
| .battery-wrap { |
| margin-top: 2px; |
| } |
| |
| .battery-label { |
| display: flex; |
| justify-content: space-between; |
| font-size: .6rem; |
| color: var(--dim); |
| margin-bottom: 5px; |
| letter-spacing: .08em; |
| } |
| |
| .battery-track { |
| height: 6px; |
| border-radius: 100px; |
| background: rgba(255, 255, 255, 0.06); |
| overflow: hidden; |
| } |
| |
| .battery-fill { |
| height: 100%; |
| border-radius: 100px; |
| transition: width .4s ease, background .4s ease; |
| background: linear-gradient(90deg, var(--cyan), var(--green)); |
| box-shadow: 0 0 10px rgba(0, 229, 255, 0.5); |
| } |
| |
| .battery-fill.low { |
| background: linear-gradient(90deg, var(--amber), var(--red)); |
| } |
| |
| |
| .btn-grid { |
| display: grid; |
| grid-template-columns: repeat(3, 1fr); |
| gap: 6px; |
| margin-top: 4px; |
| } |
| |
| .ctrl-btn { |
| font-family: var(--f-display); |
| font-size: .65rem; |
| font-weight: 700; |
| letter-spacing: .06em; |
| padding: 9px 6px; |
| border: 1px solid var(--border); |
| border-radius: var(--r-sm); |
| background: var(--glass); |
| color: var(--dim); |
| cursor: pointer; |
| text-align: center; |
| transition: all .18s; |
| user-select: none; |
| } |
| |
| .ctrl-btn:hover { |
| border-color: rgba(0, 229, 255, 0.4); |
| color: var(--cyan); |
| background: rgba(0, 229, 255, 0.06); |
| } |
| |
| .ctrl-btn:active { |
| transform: scale(.94); |
| } |
| |
| .ctrl-btn.primary { |
| border-color: var(--cyan); |
| color: var(--cyan); |
| background: rgba(0, 229, 255, 0.08); |
| } |
| |
| .ctrl-btn.primary:hover { |
| box-shadow: var(--glow-cyan); |
| } |
| |
| .ctrl-btn.success { |
| border-color: var(--green); |
| color: var(--green); |
| background: rgba(0, 255, 178, 0.07); |
| } |
| |
| .ctrl-btn.danger { |
| border-color: var(--red); |
| color: var(--red); |
| background: rgba(255, 77, 109, 0.07); |
| } |
| |
| .ctrl-btn.dir { |
| font-size: .85rem; |
| } |
| |
| .btn-spacer { |
| visibility: hidden; |
| } |
| |
| |
| .auto-row { |
| display: flex; |
| gap: 8px; |
| align-items: center; |
| margin-top: 6px; |
| } |
| |
| .toggle-label { |
| font-size: .65rem; |
| color: var(--dim); |
| letter-spacing: .06em; |
| } |
| |
| .toggle { |
| position: relative; |
| width: 36px; |
| height: 20px; |
| cursor: pointer; |
| } |
| |
| .toggle input { |
| display: none; |
| } |
| |
| .toggle-track { |
| position: absolute; |
| inset: 0; |
| background: rgba(255, 255, 255, 0.08); |
| border-radius: 100px; |
| transition: background .2s; |
| border: 1px solid var(--border); |
| } |
| |
| .toggle input:checked~.toggle-track { |
| background: rgba(0, 229, 255, 0.25); |
| border-color: var(--cyan); |
| } |
| |
| .toggle-thumb { |
| position: absolute; |
| top: 3px; |
| left: 3px; |
| width: 14px; |
| height: 14px; |
| border-radius: 50%; |
| background: var(--dim); |
| transition: all .2s; |
| } |
| |
| .toggle input:checked~.toggle-thumb { |
| left: 19px; |
| background: var(--cyan); |
| box-shadow: var(--glow-cyan); |
| } |
| |
| .speed-label { |
| font-size: .6rem; |
| color: var(--dimmer); |
| } |
| |
| |
| |
| |
| .center-col { |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| } |
| |
| .grid-panel { |
| flex: 1; |
| } |
| |
| .grid-outer { |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| padding: 16px; |
| min-height: 380px; |
| position: relative; |
| } |
| |
| .grid-wrap { |
| display: inline-block; |
| border: 1px solid rgba(0, 229, 255, 0.12); |
| border-radius: 10px; |
| background: rgba(6, 8, 15, 0.6); |
| box-shadow: 0 0 60px rgba(0, 229, 255, 0.06), inset 0 0 30px rgba(0, 0, 0, 0.5); |
| overflow: hidden; |
| position: relative; |
| } |
| |
| .grid-row { |
| display: flex; |
| line-height: 1; |
| } |
| |
| .grid-cell { |
| width: var(--cell); |
| height: var(--cell); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 1.3rem; |
| position: relative; |
| transition: all .2s cubic-bezier(.34, 1.56, .64, 1); |
| user-select: none; |
| } |
| |
| .grid-cell:hover { |
| transform: scale(1.1); |
| z-index: 10; |
| filter: brightness(1.2); |
| } |
| |
| .grid-cell.road { |
| background: rgba(0, 229, 255, 0.02); |
| } |
| |
| .grid-cell.building { |
| background: rgba(255, 255, 255, 0.04); |
| } |
| |
| .grid-cell.tree { |
| background: rgba(0, 255, 178, 0.03); |
| } |
| |
| .grid-cell.obstacle { |
| background: rgba(255, 179, 0, 0.06); |
| } |
| |
| .grid-cell.delivery { |
| background: rgba(255, 179, 0, 0.08); |
| } |
| |
| .grid-cell.drone { |
| background: rgba(0, 229, 255, 0.12); |
| box-shadow: inset 0 0 12px rgba(0, 229, 255, 0.3); |
| } |
| |
| |
| .trail-dot { |
| position: absolute; |
| width: 6px; |
| height: 6px; |
| border-radius: 50%; |
| background: rgba(0, 229, 255, 0.35); |
| pointer-events: none; |
| animation: trailFade 1.5s ease-out forwards; |
| } |
| |
| @keyframes trailFade { |
| to { |
| opacity: 0; |
| transform: scale(0.4); |
| } |
| } |
| |
| |
| .msg-bar { |
| font-size: .75rem; |
| padding: 9px 14px; |
| background: rgba(255, 255, 255, 0.03); |
| border-top: 1px solid var(--border); |
| color: var(--dim); |
| letter-spacing: .04em; |
| min-height: 34px; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .msg-bar.good { |
| color: var(--green); |
| } |
| |
| .msg-bar.warn { |
| color: var(--amber); |
| } |
| |
| .msg-bar.bad { |
| color: var(--red); |
| } |
| |
| |
| .delivery-bar-wrap { |
| padding: 0 16px 14px; |
| } |
| |
| .delivery-bar-label { |
| display: flex; |
| justify-content: space-between; |
| font-size: .6rem; |
| color: var(--dim); |
| margin-bottom: 5px; |
| letter-spacing: .08em; |
| } |
| |
| .delivery-bar-track { |
| height: 4px; |
| border-radius: 100px; |
| background: rgba(255, 255, 255, 0.06); |
| overflow: hidden; |
| } |
| |
| .delivery-bar-fill { |
| height: 100%; |
| border-radius: 100px; |
| background: linear-gradient(90deg, var(--amber), var(--green)); |
| transition: width .5s cubic-bezier(.34, 1.56, .64, 1); |
| box-shadow: 0 0 10px rgba(0, 255, 178, 0.4); |
| } |
| |
| |
| |
| |
| .right-col { |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| } |
| |
| |
| .chart-wrap { |
| position: relative; |
| height: 160px; |
| } |
| |
| |
| .log-list { |
| display: flex; |
| flex-direction: column; |
| gap: 5px; |
| max-height: 200px; |
| overflow-y: auto; |
| scrollbar-width: thin; |
| scrollbar-color: rgba(255, 255, 255, 0.1) transparent; |
| } |
| |
| .log-item { |
| display: flex; |
| gap: 8px; |
| align-items: flex-start; |
| font-size: .65rem; |
| color: var(--dim); |
| padding: 5px 8px; |
| border-radius: 6px; |
| background: rgba(255, 255, 255, 0.02); |
| border: 1px solid transparent; |
| animation: logSlideIn .2s ease; |
| } |
| |
| @keyframes logSlideIn { |
| from { |
| opacity: 0; |
| transform: translateX(6px); |
| } |
| } |
| |
| .log-item.good { |
| color: var(--green); |
| border-color: rgba(0, 255, 178, 0.1); |
| } |
| |
| .log-item.warn { |
| color: var(--amber); |
| border-color: rgba(255, 179, 0, 0.1); |
| } |
| |
| .log-item.bad { |
| color: var(--red); |
| border-color: rgba(255, 77, 109, 0.1); |
| } |
| |
| .log-step { |
| opacity: .5; |
| flex-shrink: 0; |
| } |
| |
| |
| .analytics-grid { |
| display: flex; |
| flex-direction: column; |
| gap: 8px; |
| } |
| |
| .analytics-row { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| font-size: .68rem; |
| padding: 6px 0; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.04); |
| } |
| |
| .analytics-key { |
| color: var(--dim); |
| letter-spacing: .06em; |
| } |
| |
| .analytics-val { |
| font-weight: 500; |
| color: var(--text); |
| font-family: var(--f-display); |
| } |
| |
| .analytics-val.cyan { |
| color: var(--cyan); |
| } |
| |
| .analytics-val.green { |
| color: var(--green); |
| } |
| |
| |
| .action-dist { |
| display: flex; |
| gap: 4px; |
| align-items: flex-end; |
| height: 40px; |
| } |
| |
| .action-bar-wrap { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 3px; |
| flex: 1; |
| } |
| |
| .action-bar { |
| width: 100%; |
| border-radius: 3px 3px 0 0; |
| background: rgba(0, 229, 255, 0.3); |
| transition: height .4s cubic-bezier(.34, 1.56, .64, 1); |
| min-height: 2px; |
| } |
| |
| .action-bar-lbl { |
| font-size: .5rem; |
| color: var(--dimmer); |
| } |
| |
| |
| |
| |
| .legend-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 5px; |
| } |
| |
| .legend-item { |
| display: flex; |
| align-items: center; |
| gap: 7px; |
| font-size: .63rem; |
| color: var(--dim); |
| } |
| |
| .legend-emoji { |
| font-size: 1rem; |
| } |
| |
| |
| |
| |
| ::-webkit-scrollbar { |
| width: 5px; |
| } |
| |
| ::-webkit-scrollbar-track { |
| background: transparent; |
| } |
| |
| ::-webkit-scrollbar-thumb { |
| background: rgba(255, 255, 255, 0.08); |
| border-radius: 10px; |
| } |
| |
| |
| |
| |
| |
| |
| |
| .main { |
| display: grid; |
| grid-template-columns: 260px 1fr 280px; |
| gap: 16px; |
| padding: 16px; |
| width: 100%; |
| max-width: 1600px; |
| margin: 0 auto; |
| flex: 1; |
| } |
| |
| |
| .left-col, |
| .right-col { |
| display: flex; |
| flex-direction: column; |
| gap: 14px; |
| min-width: 0; |
| |
| } |
| |
| |
| .mission-control { |
| display: flex; |
| flex-wrap: wrap; |
| align-items: center; |
| gap: 10px; |
| padding: 12px 16px; |
| background: var(--bg1); |
| border-top: 1px solid var(--border-bright); |
| border-radius: 0 0 var(--r) var(--r); |
| } |
| |
| .control-group { |
| display: flex; |
| flex-wrap: wrap; |
| gap: 8px; |
| align-items: center; |
| } |
| |
| |
| |
| |
| @media (max-width: 1200px) { |
| .main { |
| grid-template-columns: 240px 1fr; |
| } |
| |
| .right-col { |
| grid-column: 1 / -1; |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 14px; |
| } |
| } |
| |
| |
| @media (max-width: 860px) { |
| header { |
| padding: 12px 16px; |
| flex-direction: column; |
| gap: 10px; |
| } |
| |
| .main { |
| grid-template-columns: 1fr; |
| padding: 10px; |
| } |
| |
| .right-col { |
| grid-template-columns: 1fr; |
| } |
| |
| .stat-grid { |
| grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); |
| } |
| |
| .mission-control { |
| justify-content: center; |
| } |
| |
| .control-group { |
| justify-content: center; |
| width: 100%; |
| } |
| |
| .ctrl-btn { |
| flex: 1; |
| min-width: 120px; |
| } |
| |
| .console-row { |
| flex-direction: column; |
| } |
| |
| :root { |
| --cell: 28px; |
| } |
| } |
| |
| |
| @media (max-width: 480px) { |
| :root { |
| --cell: 24px; |
| } |
| |
| .logo { |
| font-size: 1.1rem; |
| } |
| |
| .panel-inner { |
| padding: 12px; |
| } |
| |
| .ctrl-btn.dir { |
| font-size: 1.1rem; |
| padding: 12px 8px; |
| } |
| |
| .stat-value { |
| font-size: 0.95rem; |
| } |
| } |
| </style> |
| </head> |
|
|
| <body> |
|
|
| <div id="processing"> |
| <div class="spinner"></div> |
| <div class="processing-text">Processing...</div> |
| </div> |
|
|
| <div id="loading"> |
| <div class="spinner"></div> |
| <div class="loading-text">Initialising Drone Grid</div> |
| </div> |
|
|
| <div id="app"> |
|
|
| |
| <header> |
| <div class="logo"> |
| <span class="logo-icon">🚁</span> |
| DRONE DELIVERY<span style="color:var(--green)">ENV</span> |
| <span style="font-size:0.9rem; opacity:0.5; margin-left:6px; letter-spacing:0.15em">(Team SkyRelic)</span> |
| </div> |
| <div class="header-right"> |
| <span class="hbadge">OpenEnv v0.1</span> |
| <span class="hbadge live" id="hStatus">● LIVE</span> |
| </div> |
| </header> |
|
|
| |
| <div class="main"> |
|
|
| |
| <div class="left-col"> |
|
|
| |
| <div class="panel"> |
| <div class="panel-inner"> |
| <div class="panel-title">Mission Select</div> |
| <div class="task-group" id="taskGroup"> |
| <button class="task-btn active" data-task="graders:grade_easy"> |
| <span class="dot"></span> |
| Easy Delivery |
| <span class="task-tag">10×10</span> |
| </button> |
| <button class="task-btn" data-task="graders:grade_medium"> |
| <span class="dot"></span> |
| Medium Delivery |
| <span class="task-tag">14×14</span> |
| </button> |
| <button class="task-btn" data-task="graders:grade_hard"> |
| <span class="dot"></span> |
| Hard Delivery |
| <span class="task-tag">18×18</span> |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="panel"> |
| <div class="panel-inner"> |
| <div class="panel-title">Episode Stats</div> |
| <div class="stat-grid"> |
| <div class="stat-card"> |
| <div class="stat-label">Step</div> |
| <div class="stat-value cyan" id="statStep">0</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">Reward</div> |
| <div class="stat-value" id="statReward">0.000</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">Deliveries</div> |
| <div class="stat-value green" id="statDel">0/0</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">Score</div> |
| <div class="stat-value amber" id="statScore">—</div> |
| </div> |
| </div> |
| |
| <div class="battery-wrap" style="margin-top:12px;"> |
| <div class="battery-label"> |
| <span>BATTERY</span> |
| <span id="batPct">100%</span> |
| </div> |
| <div class="battery-track"> |
| <div class="battery-fill" id="batFill" style="width:100%"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="panel"> |
| <div class="panel-inner"> |
| <div class="panel-title">Controls</div> |
| |
| <div class="btn-grid"> |
| <div class="btn-spacer"></div> |
| <button class="ctrl-btn dir" id="btnUp" onclick="doStep('UP')">▲</button> |
| <div class="btn-spacer"></div> |
| <button class="ctrl-btn dir" id="btnLeft" onclick="doStep('LEFT')">◀</button> |
| <div class="btn-spacer"></div> |
| <button class="ctrl-btn dir" id="btnRight" onclick="doStep('RIGHT')">▶</button> |
| <div class="btn-spacer"></div> |
| <button class="ctrl-btn dir" id="btnDown" onclick="doStep('DOWN')">▼</button> |
| <div class="btn-spacer"></div> |
| </div> |
| <div style="display:flex; gap:8px; margin-top:10px;"> |
| <button class="ctrl-btn success" style="flex:1" onclick="doReset()">⟳ Reset</button> |
| <button class="ctrl-btn danger" style="flex:1" onclick="doAnalyse()">⬡ Analyse</button> |
| </div> |
| <div class="auto-row" style="margin-top:10px;"> |
| <label class="toggle"> |
| <input type="checkbox" id="autoToggle" onchange="toggleAuto()" /> |
| <div class="toggle-track"></div> |
| <div class="toggle-thumb"></div> |
| </label> |
| <span class="toggle-label">Autonomous Mode</span> |
| |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="panel"> |
| <div class="panel-inner"> |
| <div class="panel-title">Legend</div> |
| <div class="legend-grid"> |
| <div class="legend-item"><span class="legend-emoji">🚁</span> Drone</div> |
| <div class="legend-item"><span class="legend-emoji">🛣️</span> Road</div> |
| <div class="legend-item"><span class="legend-emoji">📦</span> Package</div> |
| <div class="legend-item"><span class="legend-emoji">✅</span> Delivered</div> |
| <div class="legend-item"><span class="legend-emoji">🏢</span> Building</div> |
| <div class="legend-item"><span class="legend-emoji">🌳</span> Tree</div> |
| <div class="legend-item"><span class="legend-emoji">🚧</span> Obstacle</div> |
| </div> |
| </div> |
| </div> |
|
|
| </div> |
|
|
| |
| <div class="center-col"> |
|
|
| <div class="panel grid-panel"> |
| <div class="panel-inner" style="padding-bottom:6px;"> |
| <div class="panel-title"> |
| Mission Grid |
| <span id="gridInfo" |
| style="font-size:.6rem;color:var(--dim);margin-left:auto;font-family:var(--f-mono);letter-spacing:.05em;"></span> |
| </div> |
| </div> |
| <div class="grid-outer" id="gridOuter"> |
| <div class="grid-wrap" id="gridWrap"> |
| <div style="padding:40px;color:var(--dim);font-size:.8rem;">Loading neural pathways…</div> |
| </div> |
| </div> |
|
|
| |
| <div class="mission-control"> |
| <div class="status-indicator"> |
| <div class="status-dot active"></div> |
| <span>NEURAL_ENGINE: READY</span> |
| </div> |
| <div style="flex: 1"></div> |
| <div class="control-group"> |
| <button class="ctrl-btn success glow-on-hover" id="btnStart" |
| style="min-width:110px; height:36px; font-weight:800;" onclick="startAuto()">START ENGINE</button> |
| <button class="ctrl-btn danger" id="btnStop" style="min-width:110px; height:36px; font-weight:800;" |
| onclick="stopAuto()">STOP ENGINE</button> |
| <div style="width:1px; height:24px; background:var(--border); margin: 0 4px;"></div> |
| <button class="ctrl-btn" id="btnExport" style="padding: 0 14px; height:36px; font-size:0.75rem;" |
| onclick="downloadCSV()">📥 EXPORT AS CSV</button> |
| <button class="ctrl-btn" id="btnDownloadGraph" style="padding: 0 14px; height:36px; font-size:0.75rem;" |
| onclick="downloadGraph()">📈 DOWNLOAD GRAPH</button> |
| </div> |
| </div> |
| |
| <div class="delivery-bar-wrap"> |
| <div class="delivery-bar-label"> |
| <span>PACKAGE DELIVERY PROGRESS</span> |
| <span id="delBarlabel">0 / 0</span> |
| </div> |
| <div class="delivery-bar-track"> |
| <div class="delivery-bar-fill" id="delBarFill" style="width:0%"></div> |
| </div> |
| </div> |
| |
| <div class="panel" style="margin-top:0; border-top:1px solid var(--border);"> |
| <div class="panel-inner" style="padding-top:10px;"> |
| <div class="console-row"> |
| <div class="console-wrapper"> |
| <div class="console-header">TERMINAL_LOGS<span>[SYSTEM]</span></div> |
| <div class="console" id="terminalConsole" style="height:140px; color: var(--text);"> |
| <div class="console-line info">> Awaiting system telemetry...</div> |
| </div> |
| </div> |
| <div class="console-wrapper"> |
| <div class="console-header">NEURAL_TELEMETRY_JSON_STREAM <span>[LIVE]</span></div> |
| <div class="console" id="memoryConsole" style="height:140px; color: var(--cyan); font-size: 0.6rem;"> |
| <div class="console-line info">> Awaiting telemetric JSON stream...</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="msg-bar" id="msgBar">Ready to fly.</div> |
| </div> |
|
|
| </div> |
|
|
| |
| <div class="right-col"> |
|
|
| |
| <div class="panel"> |
| <div class="panel-inner"> |
| <div class="panel-title">Reward Trend</div> |
| <div class="chart-wrap"> |
| <canvas id="rewardChart"></canvas> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="panel"> |
| <div class="panel-inner"> |
| <div class="panel-title">NEURAL_ENGINE_SYSTEM</div> |
| <div class="console" id="neuralConsole" style="height:160px;"> |
| <div class="console-line info">> Neural link active.</div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="panel"> |
| <div class="panel-inner"> |
| <div class="panel-title">In-Flight Events</div> |
| <div class="log-list" id="logList"></div> |
| </div> |
| </div> |
|
|
| |
| <div class="panel"> |
| <div class="panel-inner"> |
| <div class="panel-title">RL Analytics</div> |
| <div class="analytics-grid" id="analyticsGrid"> |
| <div class="analytics-row"> |
| <span class="analytics-key">STATUS</span> |
| <span class="analytics-val" style="color:var(--dim)">Click Analyse</span> |
| </div> |
| </div> |
| |
| <div style="margin-top:12px;"> |
| <div class="panel-title" style="margin-bottom:8px;font-size:.58rem;">Action Distribution</div> |
| <div class="action-dist" id="actionDist"> |
| <div class="action-bar-wrap"> |
| <div class="action-bar" style="height:0px" id="ab-UP"></div> |
| <div class="action-bar-lbl">UP</div> |
| </div> |
| <div class="action-bar-wrap"> |
| <div class="action-bar" style="height:0px" id="ab-DOWN"></div> |
| <div class="action-bar-lbl">DN</div> |
| </div> |
| <div class="action-bar-wrap"> |
| <div class="action-bar" style="height:0px" id="ab-LEFT"></div> |
| <div class="action-bar-lbl">LT</div> |
| </div> |
| <div class="action-bar-wrap"> |
| <div class="action-bar" style="height:0px" id="ab-RIGHT"></div> |
| <div class="action-bar-lbl">RT</div> |
| </div> |
| <div class="action-bar-wrap"> |
| <div class="action-bar" style="height:0px" id="ab-WAIT"></div> |
| <div class="action-bar-lbl">WT</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| </div> |
|
|
| </div> |
| </div> |
|
|
| |
| |
| |
| <div class="legend-section" id="missionLegend"> |
| <div class="legend-header"> |
| <div class="logo">📊 TECHNICAL SPECIFICATIONS LEGEND</div> |
| <div style="font-size: 0.6rem; opacity: 0.7;">LIVE CONFIGURATION VIA CORE/TASKS.PY</div> |
| </div> |
| <div id="legendTableContainer"> |
| |
| <div style="padding: 20px; text-align: center; color: var(--dim);">Loading mission parameters...</div> |
| </div> |
| </div> |
|
|
| <style> |
| .legend-section { |
| max-width: 96%; |
| margin: 20px auto; |
| background: var(--glass-bg); |
| border: 1px solid var(--glass-border); |
| border-radius: 12px; |
| backdrop-filter: blur(10px); |
| padding: 15px; |
| } |
| .legend-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 12px; |
| border-bottom: 1px solid var(--glass-border); |
| padding-bottom: 8px; |
| } |
| .l-table { |
| width: 100%; |
| border-collapse: collapse; |
| font-size: 0.65rem; |
| } |
| .l-table th { |
| text-align: center; |
| color: var(--cyan); |
| padding: 8px; |
| border-bottom: 1px solid var(--glass-border); |
| text-transform: uppercase; |
| letter-spacing: 1px; |
| } |
| .l-table th:first-child { text-align: left; } |
| .l-table td { |
| padding: 8px; |
| text-align: center; |
| border-bottom: 1px solid rgba(255,255,255,0.05); |
| } |
| .l-table td:first-child { text-align: left; } |
| .l-hl { color: var(--green); font-weight: bold; } |
| .l-dim { color: var(--dim); } |
| </style> |
|
|
|
|
| |
| |
| |
| <div id="completionModal" class="modal-overlay" style="display:none;"> |
| <div class="modal-content"> |
| <div class="modal-header"> |
| <div class="logo">🚀 MISSION COMPLETE</div> |
| <button class="close-btn" onclick="closeCompletionModal()">×</button> |
| </div> |
| <div class="modal-body"> |
| <div class="summary-card"> |
| <div class="summary-title" id="summaryStatus">MISSION LOG: SUCCESS</div> |
| <div class="summary-stats"> |
| <div class="s-stat" style="grid-column: span 3; background: var(--glass-hover); border-radius: 8px; margin-bottom: 8px; padding: 10px;"> |
| <span class="s-label" style="font-size: 0.7rem; color: var(--cyan)">FINAL PERFORMANCE SCORE</span> |
| <span class="s-value" id="summaryScore" style="font-size: 1.4rem; color: var(--cyan)">0.000</span> |
| </div> |
| <div class="s-stat"> |
| <span class="s-label">DELIVERIES</span> |
| <span class="s-value" id="summaryDel">0/0</span> |
| </div> |
| <div class="s-stat"> |
| <span class="s-label">STEPS</span> |
| <span class="s-value" id="summarySteps">0</span> |
| </div> |
| <div class="s-stat"> |
| <span class="s-label">MISSION REW</span> |
| <span class="s-value green" id="summaryReward">0.000</span> |
| </div> |
| <div class="s-stat"> |
| <span class="s-label">AVG REW</span> |
| <span class="s-value cyan" id="summaryAvg">0.000</span> |
| </div> |
| <div class="s-stat"> |
| <span class="s-label">EFFICIENCY</span> |
| <span class="s-value amber" id="summaryEfficiency">0.0%</span> |
| </div> |
| <div class="s-stat"> |
| <span class="s-label">TIME</span> |
| <span class="s-value purple" id="summaryTime">0.0s</span> |
| </div> |
| </div> |
| <div class="delivery-list" id="summaryDeliveryList"> |
| |
| </div> |
| </div> |
| </div> |
| <div class="modal-footer"> |
| <button class="ctrl-btn primary" onclick="startNextTask();">START NEXT MISSION</button> |
| </div> |
| </div> |
| </div> |
|
|
| <style> |
| .modal-overlay { |
| position: fixed; |
| inset: 0; |
| background: rgba(0, 0, 0, 0.7); |
| backdrop-filter: blur(10px); |
| z-index: 20000; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| animation: fadeIn 0.3s ease-out; |
| } |
| |
| .modal-content { |
| background: var(--bg1); |
| width: 480px; |
| max-width: 90%; |
| border-radius: var(--r); |
| border: 1px solid var(--border-bright); |
| box-shadow: 0 20px 80px rgba(0, 0, 0, 0.6), var(--glow-cyan); |
| overflow: hidden; |
| animation: scaleIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); |
| } |
| |
| .modal-header { |
| padding: 16px 20px; |
| background: var(--glass-b); |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| border-bottom: 1px solid var(--border); |
| } |
| |
| .close-btn { |
| background: none; |
| border: none; |
| color: var(--dim); |
| font-size: 2rem; |
| cursor: pointer; |
| line-height: 1; |
| } |
| |
| .modal-body { |
| padding: 24px; |
| } |
| |
| .summary-card { |
| background: rgba(0, 0, 0, 0.3); |
| border-radius: var(--r-sm); |
| padding: 20px; |
| border: 1px solid var(--border); |
| } |
| |
| .summary-title { |
| font-family: var(--f-display); |
| font-size: 1.1rem; |
| font-weight: 800; |
| text-align: center; |
| margin-bottom: 20px; |
| color: var(--green); |
| letter-spacing: 2px; |
| } |
| |
| .summary-stats { |
| display: grid; |
| grid-template-columns: repeat(3, 1fr); |
| gap: 12px; |
| margin-bottom: 24px; |
| } |
| |
| .s-stat { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 4px; |
| } |
| |
| .s-label { |
| font-size: 0.55rem; |
| color: var(--dim); |
| letter-spacing: 1px; |
| } |
| |
| .s-value { |
| font-size: 0.9rem; |
| font-weight: 700; |
| } |
| |
| .delivery-list { |
| max-height: 150px; |
| overflow-y: auto; |
| padding-right: 8px; |
| display: flex; |
| flex-direction: column; |
| gap: 8px; |
| border-top: 1px solid var(--border); |
| padding-top: 16px; |
| } |
| |
| .d-item { |
| display: flex; |
| justify-content: space-between; |
| font-size: 0.7rem; |
| color: var(--cyan); |
| background: rgba(0, 229, 255, 0.05); |
| padding: 6px 12px; |
| border-radius: 4px; |
| } |
| |
| .modal-footer { |
| padding: 16px 20px; |
| border-top: 1px solid var(--border); |
| display: flex; |
| justify-content: center; |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; } |
| to { opacity: 1; } |
| } |
| |
| @keyframes scaleIn { |
| from { transform: scale(0.9); opacity: 0; } |
| to { transform: scale(1); opacity: 1; } |
| } |
| </style> |
|
|
| <script src="/static/script.js?v=2.2"></script> |
| </body> |
|
|
| </html> |
|
|