drone-env / server /static /index.html
manikandan-n-07's picture
Phase 2 validation
d0e61c2
Raw
History Blame Contribute Delete
49.1 kB
<!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>
/* ═══════════════════════════════════════════════════
TOKENS
═══════════════════════════════════════════════════ */
: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;
}
/* ═══════════════════════════════════════════════════
ADVANCED ANIMATIONS
═══════════════════════════════════════════════════ */
@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;
}
/* ═══════════════════════════════════════════════════
NEURAL CONSOLE (TERMINAL)
═══════════════════════════════════════════════════ */
.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);
}
/* Split Console Layout */
.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 BAR
═══════════════════════════════════════════════════ */
.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);
}
/* ═══════════════════════════════════════════════════
RESET + BASE
═══════════════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════
RESET + BASE
═══════════════════════════════════════════════════ */
*,
*::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;
}
/* ── Animated background ── */
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 OVERLAY
═══════════════════════════════════════════════════ */
#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;
}
/* ═══════════════════════════════════════════════════
LAYOUT
═══════════════════════════════════════════════════ */
#app {
position: relative;
z-index: 1;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ── Header ── */
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 grid ── */
.main {
display: grid;
grid-template-columns: 260px 1fr 280px;
gap: 14px;
padding: 14px 18px 20px;
flex: 1;
}
/* ═══════════════════════════════════════════════════
GLASS PANEL
═══════════════════════════════════════════════════ */
.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);
}
/* shimmer top line */
.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 COLUMN
═══════════════════════════════════════════════════ */
.left-col {
display: flex;
flex-direction: column;
gap: 12px;
}
/* Task buttons */
.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 cards */
.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 bar */
.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));
}
/* Control buttons */
.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-play */
.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 — GRID VIEWER
═══════════════════════════════════════════════════ */
.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);
}
/* Path trail */
.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);
}
}
/* Message bar */
.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);
}
/* Progress bar (deliveries) */
.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 COLUMN
═══════════════════════════════════════════════════ */
.right-col {
display: flex;
flex-direction: column;
gap: 12px;
}
/* Reward chart */
.chart-wrap {
position: relative;
height: 160px;
}
/* Episode log */
.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 panel */
.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 distribution mini chart */
.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
═══════════════════════════════════════════════════ */
.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;
}
/* ═══════════════════════════════════════════════════
SCROLLBAR
═══════════════════════════════════════════════════ */
::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.08);
border-radius: 10px;
}
/* ═══════════════════════════════════════════════════
RESPONSIVE
═══════════════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════
RESPONSIVE DESIGN SYSTEM
═══════════════════════════════════════════════════ */
.main {
display: grid;
grid-template-columns: 260px 1fr 280px;
gap: 16px;
padding: 16px;
width: 100%;
max-width: 1600px;
margin: 0 auto;
flex: 1;
}
/* Column containment */
.left-col,
.right-col {
display: flex;
flex-direction: column;
gap: 14px;
min-width: 0;
/* Prevents flex overflow */
}
/* Mission Control Refactor */
.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;
}
/* ──── BREAKPOINTS ──── */
/* TABLET / SMALL LAPTOP */
@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;
}
}
/* MOBILE (STACKED) */
@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;
}
}
/* SMALL MOBILE (TOUCH OPTIMIZED) */
@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 ───────────────────────────────────── -->
<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>
<!-- ── MAIN ─────────────────────────────────────── -->
<div class="main">
<!-- ══ LEFT ══ -->
<div class="left-col">
<!-- Task selector -->
<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>
<!-- Stats -->
<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>
<!-- Battery -->
<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>
<!-- Controls -->
<div class="panel">
<div class="panel-inner">
<div class="panel-title">Controls</div>
<!-- DPAD -->
<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>
<!-- CSV button moved to Mission Control bar -->
</div>
</div>
</div>
<!-- Legend -->
<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><!-- /left -->
<!-- ══ CENTER ══ -->
<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>
<!-- Mission Control Bar (No Overlap) -->
<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>
<!-- Delivery progress -->
<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>
<!-- Antigravity Terminal Logs (Below progress bar) -->
<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>
<!-- Message -->
<div class="msg-bar" id="msgBar">Ready to fly.</div>
</div>
</div><!-- /center -->
<!-- ══ RIGHT ══ -->
<div class="right-col">
<!-- Reward trend chart -->
<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>
<!-- Neural Telemetry Console (Moved from center) -->
<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>
<!-- Episode log -->
<div class="panel">
<div class="panel-inner">
<div class="panel-title">In-Flight Events</div>
<div class="log-list" id="logList"></div>
</div>
</div>
<!-- Analytics -->
<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>
<!-- Action distribution -->
<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><!-- /right -->
</div><!-- /main -->
</div><!-- /app -->
<!-- ═══════════════════════════════════════════════════
TECHNICAL SPECIFICATIONS LEGEND
═══════════════════════════════════════════════════ -->
<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">
<!-- Table injected via JS -->
<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>
<!-- ═══════════════════════════════════════════════════
MISSION COMPLETION MODAL
═══════════════════════════════════════════════════ -->
<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">
<!-- Dynamically filled -->
</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>