| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>๐ Drone Delivery โ OpenEnv</title> |
| <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<span style="color:var(--green)">ENV</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="easy_delivery"> |
| <span class="dot"></span> |
| Easy Delivery |
| <span class="task-tag">10ร10</span> |
| </button> |
| <button class="task-btn" data-task="medium_delivery"> |
| <span class="dot"></span> |
| Medium Delivery |
| <span class="task-tag">14ร14</span> |
| </button> |
| <button class="task-btn" data-task="hard_delivery"> |
| <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> |
|
|
| <script src="/static/script.js?v=1.0.1"></script> |
| </body> |
|
|
| </html> |