diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx
index eb155e3..1986306 100644
--- a/frontend/src/components/NavBarItem.tsx
+++ b/frontend/src/components/NavBarItem.tsx
@@ -1,6 +1,5 @@
-import React, {useEffect, useRef} from 'react'
+import React, {useEffect, useRef, useState} from 'react'
import Link from 'next/link'
-import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'
@@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) {
}
return
{NavBarItemComponentContents}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx
index 1b9907d..26c3572 100644
--- a/frontend/src/layouts/Authenticated.tsx
+++ b/frontend/src/layouts/Authenticated.tsx
@@ -1,5 +1,4 @@
-import React, { ReactNode, useEffect } from 'react'
-import { useState } from 'react'
+import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
@@ -126,4 +125,4 @@ export default function LayoutAuthenticated({
)
-}
+}
\ No newline at end of file
diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts
index c6a06a5..0f61da0 100644
--- a/frontend/src/menuAside.ts
+++ b/frontend/src/menuAside.ts
@@ -7,7 +7,11 @@ const menuAside: MenuAsideItem[] = [
icon: icon.mdiViewDashboardOutline,
label: 'Dashboard',
},
-
+ {
+ href: '/observation',
+ icon: icon.mdiCamera,
+ label: 'Live Observation',
+ },
{
href: '/users/users-list',
label: 'Users',
@@ -112,4 +116,4 @@ const menuAside: MenuAsideItem[] = [
},
]
-export default menuAside
+export default menuAside
\ No newline at end of file
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index 3d1284d..ccb24ce 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,161 +1,90 @@
-import React, { useEffect, useState } from 'react';
+import React from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import BaseButton from '../components/BaseButton';
-import CardBox from '../components/CardBox';
import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
-import BaseDivider from '../components/BaseDivider';
-import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config';
-import { useAppSelector } from '../stores/hooks';
-import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
-import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
-
+import { mdiTelescope, mdiCamera, mdiDatabaseSearch } from '@mdi/js';
+import BaseIcon from '../components/BaseIcon';
export default function Starter() {
- const [illustrationImage, setIllustrationImage] = useState({
- src: undefined,
- photographer: undefined,
- photographer_url: undefined,
- })
- const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
- const [contentType, setContentType] = useState('image');
- const [contentPosition, setContentPosition] = useState('right');
- const textColor = useAppSelector((state) => state.style.linkColor);
-
- const title = 'Live Sky Viewer PWA'
-
- // Fetch Pexels image/video
- useEffect(() => {
- async function fetchData() {
- const image = await getPexelsImage();
- const video = await getPexelsVideo();
- setIllustrationImage(image);
- setIllustrationVideo(video);
- }
- fetchData();
- }, []);
-
- const imageBlock = (image) => (
-
- );
-
- const videoBlock = (video) => {
- if (video?.video_files?.length > 0) {
- return (
-
-
-
-
)
- }
- };
+ const title = 'JWST Live Sky Explorer'
return (
-
+
-
{getPageTitle('Starter Page')}
+
{getPageTitle('JWST Explorer')}
-
-
- {contentType === 'image' && contentPosition !== 'background'
- ? imageBlock(illustrationImage)
- : null}
- {contentType === 'video' && contentPosition !== 'background'
- ? videoBlock(illustrationVideo)
- : null}
-
-
-
-
-
-
-
-
-
-
-
+
+ {/* Background Decorative Elements */}
+
-
-
-
-
© 2026 {title}. All rights reserved
-
- Privacy Policy
-
-
+
+
+
+
+ JWST LIVE EXPLORER
+
+
+
+ Deploy the world's most powerful infrared eye. Observe the deep universe in real-time through the lens of a physical simulation.
+
+
+
+
+
+
+
+
+
+
IR Spectrum
+
Visualize wavelengths beyond the visible light, piercing through cosmic dust.
+
+
+
Deep Field
+
High-resolution scans of distant galaxies from NASA's latest data releases.
+
+
+
Live Sync
+
Sync with your device's camera for an augmented reality sky identification.
+
+
+
+
+
+
);
}
@@ -163,4 +92,3 @@ export default function Starter() {
Starter.getLayout = function getLayout(page: ReactElement) {
return {page};
};
-
diff --git a/frontend/src/pages/observation.tsx b/frontend/src/pages/observation.tsx
new file mode 100644
index 0000000..f4a2a38
--- /dev/null
+++ b/frontend/src/pages/observation.tsx
@@ -0,0 +1,229 @@
+
+import React, { useEffect, useRef, useState } from 'react';
+import type { ReactElement } from 'react';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import { mdiClose, mdiCamera, mdiTarget, mdiInformationOutline, mdiTelescope } from '@mdi/js';
+import BaseIcon from '../components/BaseIcon';
+import { useAppDispatch, useAppSelector } from '../stores/hooks';
+import { fetch as fetchSkyObjects } from '../stores/sky_objects/sky_objectsSlice';
+import LayoutAuthenticated from '../layouts/Authenticated';
+
+const ObservationPage = () => {
+ const videoRef = useRef(null);
+ const [isCameraActive, setIsCameraActive] = useState(false);
+ const [mode, setMode] = useState<'normal' | 'ir' | 'deep'>('normal');
+ const [scanning, setScanning] = useState(false);
+ const [identifiedObject, setIdentifiedObject] = useState(null);
+ const [telemetry, setTelemetry] = useState({
+ temp: -233,
+ dist: 1.5,
+ focal: 131,
+ });
+
+ const dispatch = useAppDispatch();
+ const { sky_objects } = useAppSelector((state) => state.sky_objects);
+ const router = useRouter();
+
+ useEffect(() => {
+ dispatch(fetchSkyObjects({}));
+ }, [dispatch]);
+
+ const startCamera = async () => {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({
+ video: { facingMode: 'environment' }
+ });
+ if (videoRef.current) {
+ videoRef.current.srcObject = stream;
+ setIsCameraActive(true);
+ }
+ } catch (err) {
+ console.error("Camera access denied:", err);
+ alert("Camera access is required for live observation simulation.");
+ }
+ };
+
+ const stopCamera = () => {
+ if (videoRef.current && videoRef.current.srcObject) {
+ const tracks = (videoRef.current.srcObject as MediaStream).getTracks();
+ tracks.forEach(track => track.stop());
+ setIsCameraActive(false);
+ }
+ };
+
+ useEffect(() => {
+ return () => stopCamera();
+ }, []);
+
+ useEffect(() => {
+ if (scanning) {
+ const timeout = setTimeout(() => {
+ if (sky_objects.length > 0) {
+ const randomObj = sky_objects[Math.floor(Math.random() * sky_objects.length)];
+ setIdentifiedObject(randomObj);
+ }
+ setScanning(false);
+ }, 3000);
+ return () => clearTimeout(timeout);
+ }
+ }, [scanning, sky_objects]);
+
+ // Simulate telemetry fluctuations
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setTelemetry(prev => ({
+ temp: prev.temp + (Math.random() - 0.5),
+ dist: 1.5 + (Math.random() - 0.5) * 0.01,
+ focal: 131.4 + (Math.random() - 0.5),
+ }));
+ }, 2000);
+ return () => clearInterval(interval);
+ }, []);
+
+ const getFilter = () => {
+ switch (mode) {
+ case 'ir': return 'contrast(1.2) brightness(1.1) hue-rotate(180deg) saturate(1.5)';
+ case 'deep': return 'contrast(1.5) brightness(0.8) saturate(0.5) blur(0.5px)';
+ default: return 'none';
+ }
+ };
+
+ return (
+
+
+
JWST | Live Observation
+
+
+ {/* Camera View */}
+
+ {!isCameraActive && (
+
+
+
+
+
Systems Offline
+
+
+ )}
+
+
+
+ {/* JWST Hexagonal Overlay */}
+ {isCameraActive && (
+
+ )}
+
+ {/* Telemetry & UI */}
+ {isCameraActive && (
+
+ {/* Top Bar */}
+
+
+
System Status
+
+
+
TEMP: {telemetry.temp.toFixed(1)}K
+
L2 DIST: {telemetry.dist.toFixed(3)}M km
+
+
+
+
+
+
+ {/* Identification Box */}
+
+
+ {scanning && (
+
+ )}
+
+
+
+
+
+
Active Search
+
+ {scanning ? 'Analyzing Spectrum...' : identifiedObject ? identifiedObject.name : 'No Target Lock'}
+
+
+
+ {identifiedObject && !scanning && (
+
+
COORDINATES: {identifiedObject.ra || 'N/A'}, {identifiedObject.dec || 'N/A'}
+
CLASSIFICATION: {identifiedObject.type || 'STEL-OBJ'}
+
{identifiedObject.description}
+
+ )}
+ {!scanning && (
+
+ )}
+
+
+
+ {/* Bottom Controls */}
+
+ {[
+ { id: 'normal', label: 'VIS', color: 'bg-white/10 text-white' },
+ { id: 'ir', label: 'NIRSpec', color: 'bg-[#E3B341]/20 text-[#E3B341]' },
+ { id: 'deep', label: 'MIRI', color: 'bg-purple-500/20 text-purple-300' }
+ ].map(btn => (
+
+ ))}
+
+
+ )}
+
+ {/* Static Footer (only visible when not active or on desktop) */}
+
+ Mission Protocol: JWST-MAIN-SYS-V2.0.26
+
+
+ );
+};
+
+ObservationPage.getLayout = function getLayout(page: ReactElement) {
+ return {page};
+};
+
+export default ObservationPage;