Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Asteroid Orbit Tracker</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<header class="app-header">
<h1>Asteroid Orbit Tracker 🚀</h1>
</header>

<main class="app-layout">
<section class="card card-orbit">
<h2>Live Orbit Visualization</h2>
<canvas id="orbitCanvas"></canvas>
<p class="hint">
Blue: Low-risk NEO | Orange: Medium-risk NEO
</p>
</section>

<section class="card card-asteroids">
<h2>Nearby Asteroids</h2>
<div id="asteroidList" class="asteroid-list"></div>
<p id="asteroidError" class="error hidden">
Connection error – using mock data.
</p>
</section>

<section class="card card-dashboard">
<h2>Risk Metrics</h2>
<div class="metrics-grid">
<div class="metric-box">
<div id="metricTotal" class="metric-value">0</div>
<div class="metric-label">Total NEOs</div>
</div>
<div class="metric-box">
<div id="metricAvgDistance" class="metric-value">0 LD</div>
<div class="metric-label">Avg Distance</div>
</div>
<div class="metric-box">
<div id="metricAvgVelocity" class="metric-value">0 km/s</div>
<div class="metric-label">Avg Velocity</div>
</div>
</div>
</section>
</main>

<script type="module" src="./src/main.js"></script>
</body>
</html>
31 changes: 31 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "asteroid-frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"chart.js": "^4.4.4",
"react-chartjs-2": "^5.2.0"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.6.1",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.7",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.10",
"vite": "^5.4.1"
}
}
22 changes: 22 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import OrbitCanvas from './components/OrbitCanvas.jsx';
import AsteroidPanel from './components/AsteroidPanel.jsx';
import Dashboard from './components/Dashboard.jsx';

function App() {
return (
<div className="min-h-screen bg-gradient-to-br from-black via-slate-900 to-indigo-900 p-6">
<header className="text-center mb-8">
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">
Asteroid Orbit Tracker 🚀
</h1>
</header>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-8 max-w-7xl mx-auto">
<OrbitCanvas />
<AsteroidPanel />
<Dashboard />
</div>
</div>
);
}

export default App;
44 changes: 44 additions & 0 deletions frontend/src/components/AsteroidPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getAsteroids } from '../services/api.js';

export async function initAsteroidPanel() {
const listEl = document.getElementById('asteroidList');
const errorEl = document.getElementById('asteroidError');
if (!listEl) return;

const asteroids = await getAsteroids();

if (!asteroids || asteroids.length === 0) {
if (errorEl) errorEl.classList.remove('hidden');
return;
}

listEl.innerHTML = '';

asteroids.slice(0, 5).forEach((ast) => {
const item = document.createElement('div');
item.className = 'asteroid-item';

const name = document.createElement('div');
name.className = 'asteroid-name';
name.textContent = ast.name;

const meta1 = document.createElement('div');
meta1.className = 'asteroid-meta';
meta1.textContent = 'Distance: ' + ast.distance + ' LD';

const meta2 = document.createElement('div');
meta2.className = 'asteroid-meta';
meta2.textContent = 'Velocity: ' + ast.velocity + ' km/s';

const risk = document.createElement('div');
risk.className = 'asteroid-risk';
risk.textContent = 'Risk: ' + ast.risk;

item.appendChild(name);
item.appendChild(meta1);
item.appendChild(meta2);
item.appendChild(risk);

listEl.appendChild(item);
});
}
18 changes: 18 additions & 0 deletions frontend/src/components/Dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getStats } from '../services/api.js';

export async function initDashboard() {
const totalEl = document.getElementById('metricTotal');
const distEl = document.getElementById('metricAvgDistance');
const velEl = document.getElementById('metricAvgVelocity');

if (!totalEl || !distEl || !velEl) return;

const stats = await getStats();
const total = stats.total || 0;
const avgDistance = stats.avgDistance || 0;
const avgVelocity = stats.avgVelocity || 0;

totalEl.textContent = total;
distEl.textContent = avgDistance.toFixed(1) + ' LD';
velEl.textContent = avgVelocity.toFixed(1) + ' km/s';
}
65 changes: 65 additions & 0 deletions frontend/src/components/OrbitCanvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
export function initOrbitCanvas() {
const canvas = document.getElementById('orbitCanvas');
if (!canvas) return;

const ctx = canvas.getContext('2d');

function resize() {
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * window.devicePixelRatio;
canvas.height = rect.height * window.devicePixelRatio;
ctx.setTransform(window.devicePixelRatio, 0, 0, window.devicePixelRatio, 0, 0);
}

resize();
window.addEventListener('resize', resize);

let t = 0;

function draw() {
const w = canvas.clientWidth;
const h = canvas.clientHeight;

ctx.fillStyle = 'rgba(2, 6, 23, 0.6)';
ctx.fillRect(0, 0, w, h);

const cx = w / 2;
const cy = h / 2;

ctx.strokeStyle = 'rgba(148, 163, 184, 0.6)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(cx, cy, 80, 0, Math.PI * 2);
ctx.stroke();

ctx.beginPath();
ctx.arc(cx, cy, 140, 0, Math.PI * 2);
ctx.stroke();

ctx.fillStyle = '#22d3ee';
ctx.beginPath();
ctx.arc(cx, cy, 18, 0, Math.PI * 2);
ctx.fill();

const angle1 = t * 0.02;
const x1 = cx + 80 * Math.cos(angle1);
const y1 = cy + 80 * Math.sin(angle1);
ctx.fillStyle = '#3b82f6';
ctx.beginPath();
ctx.arc(x1, y1, 6, 0, Math.PI * 2);
ctx.fill();

const angle2 = t * -0.012;
const x2 = cx + 140 * Math.cos(angle2);
const y2 = cy + 140 * Math.sin(angle2);
ctx.fillStyle = '#f97316';
ctx.beginPath();
ctx.arc(x2, y2, 8, 0, Math.PI * 2);
ctx.fill();

t += 1;
requestAnimationFrame(draw);
}

requestAnimationFrame(draw);
}
9 changes: 9 additions & 0 deletions frontend/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { initOrbitCanvas } from './components/OrbitCanvas.js';
import { initAsteroidPanel } from './components/AsteroidPanel.js';
import { initDashboard } from './components/Dashboard.js';

window.addEventListener('DOMContentLoaded', () => {
initOrbitCanvas();
initAsteroidPanel();
initDashboard();
});
10 changes: 10 additions & 0 deletions frontend/src/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
33 changes: 33 additions & 0 deletions frontend/src/services/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const API_BASE = '';

async function fetchJson(path) {
if (!API_BASE) throw new Error('API disabled');
const res = await fetch(API_BASE + path);
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
}

export async function getAsteroids() {
try {
const data = await fetchJson('/asteroids');
return data.asteroids || data || [];
} catch (e) {
console.warn('Using mock asteroids:', e);
return [
{ id: 1, name: '2026 AB1', distance: 3.4, velocity: 17.1, risk: 'Low' },
{ id: 2, name: 'Apollo-NEO 33', distance: 1.2, velocity: 22.4, risk: 'Medium' },
{ id: 3, name: '2025 QX9', distance: 0.8, velocity: 28.9, risk: 'Medium' },
{ id: 4, name: 'PHA-192', distance: 10.1, velocity: 12.3, risk: 'Low' },
{ id: 5, name: 'Tempel-NEO', distance: 6.7, velocity: 19.5, risk: 'Low' },
];
}
}

export async function getStats() {
try {
return await fetchJson('/stats');
} catch (e) {
console.warn('Using mock stats:', e);
return { total: 5, avgDistance: 4.4, avgVelocity: 20.4 };
}
}
1 change: 1 addition & 0 deletions frontend/src/services/blank.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//blank
Loading