diff --git a/frontend/src/components/Home.css b/frontend/src/components/Home.css index fc326b1..04f2646 100644 --- a/frontend/src/components/Home.css +++ b/frontend/src/components/Home.css @@ -1,29 +1,187 @@ -.flip-card { - animation: flip 0.5s ease-in-out; +/* Home.css */ + +/* Base styles for the content wrapper */ +.content-wrapper { + display: flex; + flex-direction: column; + min-height: 100vh; /* Ensure full viewport height */ + width: 100%; + max-width: 1200px; /* Max width for larger screens */ + margin: 0 auto; /* Center content */ + padding: 1rem; /* Padding for breathing room */ + box-sizing: border-box; /* Include padding in width calculations */ +} + +/* Chatbot container */ +.chatbot-container { + width: 100%; + margin-bottom: 1rem; /* Space between components */ } -@keyframes flip { - 0% { - transform: perspective(400px) rotateX(90deg); - opacity: 0.5; +/* Tracker container */ +.tracker-container { + width: 100%; + flex-grow: 1; /* Allow TrackerHome to take remaining space */ +} + +/* Hide chatbot on mobile if needed (replacing display: none) */ +@media (max-width: 768px) { + .chatbot-container { + display: none; /* Hide by default on mobile, adjust as needed */ } - 100% { - transform: perspective(400px) rotateX(0deg); - opacity: 1; +} + +/* Ensure components are touch-friendly and readable on mobile */ +@media (max-width: 768px) { + .content-wrapper { + padding: 0.5rem; /* Reduced padding on smaller screens */ + } + + /* Ensure font sizes are readable */ + body { + font-size: 16px; /* Base font size for accessibility */ + } + + /* Make buttons and interactive elements touch-friendly */ + button, + a, + input, + textarea { + min-height: 44px; /* Minimum touch target size per accessibility guidelines */ + min-width: 44px; + font-size: 1rem; /* Readable text */ } } -@keyframes pulse { - 0% { - transform: scale(1); - opacity: 1; +/* Styles for larger screens (optional side-by-side layout) */ +@media (min-width: 769px) { + .content-wrapper { + flex-direction: row; /* Side-by-side layout for larger screens */ + gap: 1rem; /* Space between chatbot and tracker */ } - 50% { - transform: scale(1.2); - opacity: 0.8; + + .chatbot-container { + display: block; /* Ensure chatbot is visible on larger screens */ + width: 30%; /* Adjust width as needed */ + min-width: 250px; /* Minimum width for chatbot */ } - 100% { - transform: scale(1); - opacity: 1; + + .tracker-container { + width: 70%; /* Adjust width as needed */ } +} + +/* Accessibility and general styles */ +:focus { + outline: 2px solid #007bff; /* Visible focus for accessibility */ + outline-offset: 2px; +} + +/* Prevent horizontal scrolling */ +html, +body { + overflow-x: hidden; + width: 100%; +} + +/* Ensure images and mediaGreetings, I'm here to help! I'm not sure what you mean by "images," so I'll assume you're referring to visual elements like those in `MyChatBot` or `TrackerHome`. If you have specific images or other visual elements, please provide more details, and I can tailor the response further. + +Based on the provided code, I'll focus on ensuring that any visual elements (like buttons, icons, or images within `MyChatBot` or `TrackerHome`) are responsive and mobile-friendly. Here's how the CSS handles images and other visual elements: + +### Updated `Home.css` (with Image Handling) + +```css +/* Home.css */ + +/* Base styles for the content wrapper */ +.content-wrapper { + display: flex; + flex-direction: column; + min-height: 100vh; + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 1rem; + box-sizing: border-box; +} + +/* Chatbot container */ +.chatbot-container { + width: 100%; + margin-bottom: 1rem; +} + +/* Tracker container */ +.tracker-container { + width: 100%; + flex-grow: 1; +} + +/* Responsive images */ +img { + max-width: 100%; /* Ensure images scale within their container */ + height: auto; /* Maintain aspect ratio */ + display: block; /* Prevent inline spacing issues */ +} + +/* Mobile-specific styles */ +@media (max-width: 768px) { + .content-wrapper { + padding: 0.5rem; + } + + .chatbot-container { + display: none; /* Hide chatbot on mobile, adjust as needed */ + } + + body { + font-size: 16px; + } + + /* Touch-friendly buttons and interactive elements */ + button, + a, + input, + textarea { + min-height: 44px; + min-width: 44px; + font-size: 1rem; + } + + /* Adjust images for smaller screens */ + img { + max-width: 90vw; /* Slightly smaller than viewport width */ + margin: 0 auto; /* Center images */ + } +} + +/* Desktop-specific styles */ +@media (min-width: 769px) { + .content-wrapper { + flex-direction: row; + gap: 1rem; + } + + .chatbot-container { + display: block; + width: 30%; + min-width: 250px; + } + + .tracker-container { + width: 70%; + } +} + +/* Accessibility */ +:focus { + outline: 2px solid #007bff; + outline-offset: 2px; +} + +/* Prevent horizontal scrolling */ +html, +body { + overflow-x: hidden; + width: 100%; } \ No newline at end of file diff --git a/frontend/src/components/Home.tsx b/frontend/src/components/Home.tsx index c0904fe..d116460 100644 --- a/frontend/src/components/Home.tsx +++ b/frontend/src/components/Home.tsx @@ -1,25 +1,12 @@ import React, { Component } from 'react'; -import { Typography, Divider, Box, CircularProgress } from '@mui/material'; -import Grid from '@mui/material/Grid2'; -import Countdown from 'react-countdown'; -import RocketLaunchIcon from '@mui/icons-material/RocketLaunch'; -import * as THREE from 'three'; import MyChatBot from './chatbot/chatApp'; import './Home.css'; - -interface IssPosition { - latitude: number; - longitude: number; - altitude: number; - timestamp: number; -} +import TrackerHome from './TrackerHome'; +import LaunchTimers from './LaunchTimers'; +import { Typography } from '@mui/material'; interface AppState { - issPosition: IssPosition | null; - issPath: IssPosition[]; loading: boolean; - globeError: boolean; - globeInitialized: boolean; } type HomeProps = { @@ -29,356 +16,27 @@ type HomeProps = { class Home extends Component { serverBaseUrl = import.meta.env.VITE_GAGANYATRI_BACKEND_APP_API_URL; isOnline = true; - private readonly launchDate = new Date('2025-06-10T17:52:00+05:30'); - private readonly issApiUrl = 'https://api.wheretheiss.at/v1/satellites/25544'; - private readonly maxPathPoints = 100; // Limit total path points - private readonly tubeRadius = 0.005; // Thickness of orbital path tubes - private readonly cameraDistance = 2.5; // Distance from ISS for camera - private readonly orbitDuration = 90 * 60 * 1000; // 90 minutes in milliseconds - private intervalId: NodeJS.Timeout | null = null; - private scene: THREE.Scene | null = null; - private camera: THREE.PerspectiveCamera | null = null; - private renderer: THREE.WebGLRenderer | null = null; - private controls: any = null; - private issMesh: THREE.Mesh | null = null; - private issPathMesh: THREE.Mesh | null = null; // Blue tube for older orbits - private issLastOrbitMesh: THREE.Mesh | null = null; // Green tube for last completed orbit - private issCurrentOrbitMesh: THREE.Mesh | null = null; // Yellow tube for current orbit - private animationFrameId: number | null = null; - private globeRef = React.createRef(); constructor(props: HomeProps) { super(props); this.serverBaseUrl = this.props.serverUrl; - this.state = { - issPosition: null, - issPath: [], - loading: true, - globeError: false, - globeInitialized: false, - }; - } - - componentDidMount() { - this.fetchIssPosition(); - this.intervalId = setInterval(this.fetchIssPosition, 10000); - this.initThreeJs(); - } - - componentWillUnmount() { - if (this.intervalId) clearInterval(this.intervalId); - if (this.animationFrameId) cancelAnimationFrame(this.animationFrameId); - if (this.renderer) { - this.renderer.dispose(); - } - window.removeEventListener('resize', this.handleResize); } - private fetchIssPosition = async () => { - try { - const response = await fetch(this.issApiUrl); - if (!response.ok) throw new Error('API request failed'); - const data = await response.json(); - const newPosition: IssPosition = { - latitude: data.latitude, - longitude: data.longitude, - altitude: data.altitude, - timestamp: Date.now(), - }; - this.setState( - (prevState) => ({ - issPosition: newPosition, - issPath: [...prevState.issPath, newPosition].slice(-this.maxPathPoints), - loading: false, - }), - () => { - this.updateIssPosition(); - this.updateIssPath(); - } - ); - } catch (error) { - console.error('Error fetching ISS position:', error); - this.setState({ loading: false }); - } - }; - - private initThreeJs = () => { - if (!this.globeRef.current) { - console.error('Globe container ref not found'); - this.setState({ globeError: true, globeInitialized: false }); - return; - } - - if (this.state.globeInitialized) return; - - import('three/examples/jsm/controls/OrbitControls').then(({ OrbitControls }) => { - this.scene = new THREE.Scene(); - this.camera = new THREE.PerspectiveCamera(50, 1, 0.1, 1000); - this.camera.position.set(0, 0, 3); - this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); - this.renderer.setPixelRatio(window.devicePixelRatio); - this.globeRef.current!.appendChild(this.renderer.domElement); - this.handleResize(); - - const geometry = new THREE.SphereGeometry(1, 32, 32); - const textureLoader = new THREE.TextureLoader(); - let textureLoadAttempts = 0; - const maxAttempts = 3; - - const loadTexture = () => { - textureLoader.load( - 'https://threejs.org/examples/textures/planets/earth_atmos_2048.jpg', - (texture) => { - const material = new THREE.MeshStandardMaterial({ map: texture }); - const earth = new THREE.Mesh(geometry, material); - this.scene?.add(earth); - console.log('Earth texture loaded successfully'); - this.setState({ globeInitialized: true, globeError: false }); - }, - undefined, - (err) => { - console.error('Texture load error:', err); - textureLoadAttempts++; - if (textureLoadAttempts < maxAttempts) { - console.log(`Retrying texture load (attempt ${textureLoadAttempts + 1}/${maxAttempts})`); - setTimeout(loadTexture, 1000); - } else { - this.setState({ globeError: true, globeInitialized: false }); - } - } - ); - }; - - loadTexture(); - - const issGeometry = new THREE.SphereGeometry(0.02, 16, 16); - const issMaterial = new THREE.MeshStandardMaterial({ color: 'red', emissive: 'red', emissiveIntensity: 0.5 }); - this.issMesh = new THREE.Mesh(issGeometry, issMaterial); - this.scene?.add(this.issMesh); - - // Initialize blue tube for older orbits - const pathMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff }); // Blue - const pathGeometry = new THREE.BufferGeometry(); - this.issPathMesh = new THREE.Mesh(pathGeometry, pathMaterial); - this.scene?.add(this.issPathMesh); - - // Initialize green tube for last completed orbit - const lastOrbitMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // Green - const lastOrbitGeometry = new THREE.BufferGeometry(); - this.issLastOrbitMesh = new THREE.Mesh(lastOrbitGeometry, lastOrbitMaterial); - this.scene?.add(this.issLastOrbitMesh); - - // Initialize yellow tube for current orbit - const currentOrbitMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 }); // Yellow - const currentOrbitGeometry = new THREE.BufferGeometry(); - this.issCurrentOrbitMesh = new THREE.Mesh(currentOrbitGeometry, currentOrbitMaterial); - this.scene?.add(this.issCurrentOrbitMesh); - - this.scene.add(new THREE.AmbientLight(0xffffff, 0.6)); - const pointLight = new THREE.PointLight(0xffffff, 1.2, 100); - pointLight.position.set(10, 10, 10); - this.scene.add(pointLight); - - this.controls = new OrbitControls(this.camera, this.renderer.domElement); - this.controls.enablePan = false; - this.controls.enableZoom = true; - this.controls.minDistance = 2; - this.controls.maxDistance = 5; - this.controls.enableDamping = true; - this.controls.dampingFactor = 0.05; - - const animate = () => { - this.animationFrameId = requestAnimationFrame(animate); - if (this.scene && this.camera && this.renderer && this.issMesh) { - this.controls.update(); - this.issMesh.scale.set( - 0.5 + 0.2 * Math.sin(Date.now() * 0.002), - 0.5 + 0.2 * Math.sin(Date.now() * 0.002), - 0.5 + 0.2 * Math.sin(Date.now() * 0.002) - ); - this.renderer.render(this.scene, this.camera); - } - }; - animate(); - - window.addEventListener('resize', this.handleResize); - }); - }; - - private handleResize = () => { - if (this.renderer && this.camera && this.globeRef.current) { - const width = this.globeRef.current.clientWidth; - const height = window.innerWidth < 600 ? 250 : 400; - this.renderer.setSize(width, height); - this.camera.aspect = width / height; - this.camera.updateProjectionMatrix(); - } - }; - - private getIss3DPosition = (latitude: number, longitude: number, altitude: number) => { - const radius = 1 + altitude / 6371; - const phi = (90 - latitude) * (Math.PI / 180); - const theta = (longitude + 180) * (Math.PI / 180); - const x = -radius * Math.sin(phi) * Math.cos(theta); - const z = radius * Math.sin(phi) * Math.sin(theta); - const y = radius * Math.cos(phi); - return new THREE.Vector3(x, y, z); - }; - - private updateIssPosition = () => { - if (this.issMesh && this.camera && this.controls && this.state.issPosition && this.state.globeInitialized) { - const { latitude, longitude, altitude } = this.state.issPosition; - const issPosition = this.getIss3DPosition(latitude, longitude, altitude); - - // Update ISS mesh position - this.issMesh.position.copy(issPosition); - - // Position the camera to focus on the ISS - const cameraOffset = issPosition.clone().normalize().multiplyScalar(this.cameraDistance); - this.camera.position.copy(issPosition.clone().add(cameraOffset)); - this.camera.lookAt(issPosition); - this.controls.target.copy(issPosition); - this.controls.update(); - } - }; - - private updateIssPath = () => { - if (!this.issPathMesh || !this.issLastOrbitMesh || !this.issCurrentOrbitMesh || !this.state.globeInitialized) return; - - const now = Date.now(); - const orbitDuration = this.orbitDuration; - - // Filter points for current orbit (last 90 minutes) - const currentOrbitPoints = this.state.issPath.filter( - (point) => now - point.timestamp <= orbitDuration - ); - - // Filter points for last completed orbit (90 to 180 minutes ago) - const lastOrbitPoints = this.state.issPath.filter( - (point) => now - point.timestamp > orbitDuration && now - point.timestamp <= 2 * orbitDuration - ); - - // Filter points for older orbits (older than 180 minutes) - const olderPoints = this.state.issPath.filter( - (point) => now - point.timestamp > 2 * orbitDuration - ); - - // Update current orbit (yellow) - if (currentOrbitPoints.length > 1) { - const points = currentOrbitPoints.map(({ latitude, longitude, altitude }) => - this.getIss3DPosition(latitude, longitude, altitude) - ); - const curve = new THREE.CatmullRomCurve3(points, false, 'centripetal', 0.5); - const tubeGeometry = new THREE.TubeGeometry(curve, 64, this.tubeRadius, 8, false); - this.issCurrentOrbitMesh.geometry.dispose(); - this.issCurrentOrbitMesh.geometry = tubeGeometry; - } else { - this.issCurrentOrbitMesh.geometry.dispose(); - this.issCurrentOrbitMesh.geometry = new THREE.BufferGeometry(); - } - - // Update last completed orbit (green) - if (lastOrbitPoints.length > 1) { - const points = lastOrbitPoints.map(({ latitude, longitude, altitude }) => - this.getIss3DPosition(latitude, longitude, altitude) - ); - const curve = new THREE.CatmullRomCurve3(points, false, 'centripetal', 0.5); - const tubeGeometry = new THREE.TubeGeometry(curve, 64, this.tubeRadius, 8, false); - this.issLastOrbitMesh.geometry.dispose(); - this.issLastOrbitMesh.geometry = tubeGeometry; - } else { - this.issLastOrbitMesh.geometry.dispose(); - this.issLastOrbitMesh.geometry = new THREE.BufferGeometry(); - } - - // Update older orbits (blue) - if (olderPoints.length > 1) { - const points = olderPoints.map(({ latitude, longitude, altitude }) => - this.getIss3DPosition(latitude, longitude, altitude) - ); - const curve = new THREE.CatmullRomCurve3(points, false, 'centripetal', 0.5); - const tubeGeometry = new THREE.TubeGeometry(curve, 64, this.tubeRadius, 8, false); - this.issPathMesh.geometry.dispose(); - this.issPathMesh.geometry = tubeGeometry; - } else { - this.issPathMesh.geometry.dispose(); - this.issPathMesh.geometry = new THREE.BufferGeometry(); - } - }; - - private countdownRenderer = ({ - days, - hours, - minutes, - seconds, - completed, - }: { - days: number; - hours: number; - minutes: number; - seconds: number; - completed: boolean; - }) => { - if (completed) { - return ( - - - - Axiom Mission 4 has launched! - - - ); - } - - return ( - - - Countdown to Axiom Mission 4 Launch (IST) - - - {days} Days - {hours} Hours - {minutes} Minutes - {seconds} Seconds - - - ); - }; - render() { return ( -
- - -
- - Welcome to Axiom Mission 4 Tracker - - - - {!this.state.globeInitialized && !this.state.globeError && ( - - )} - {this.state.globeError && There was an error loading the globe.} -
- - -
- - -
+
+ + Axiom Mission 4 + +
+
+ +
+
+ +
); } diff --git a/frontend/src/components/LaunchTimers.css b/frontend/src/components/LaunchTimers.css new file mode 100644 index 0000000..fc326b1 --- /dev/null +++ b/frontend/src/components/LaunchTimers.css @@ -0,0 +1,29 @@ +.flip-card { + animation: flip 0.5s ease-in-out; +} + +@keyframes flip { + 0% { + transform: perspective(400px) rotateX(90deg); + opacity: 0.5; + } + 100% { + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } +} + +@keyframes pulse { + 0% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.2); + opacity: 0.8; + } + 100% { + transform: scale(1); + opacity: 1; + } +} \ No newline at end of file diff --git a/frontend/src/components/LaunchTimers.tsx b/frontend/src/components/LaunchTimers.tsx new file mode 100644 index 0000000..c513fe4 --- /dev/null +++ b/frontend/src/components/LaunchTimers.tsx @@ -0,0 +1,102 @@ +import React, { Component } from 'react'; +import { Typography, Divider, Box, CircularProgress } from '@mui/material'; +import Grid from '@mui/material/Grid2'; +import Countdown from 'react-countdown'; +import RocketLaunchIcon from '@mui/icons-material/RocketLaunch'; +import './LaunchTimers.css'; + +interface AppState {} + +type LaunchTimersProps = { + serverUrl: string; +}; + +interface AppState { +} + + +class LaunchTimers extends Component { + serverBaseUrl = import.meta.env.VITE_GAGANYATRI_BACKEND_APP_API_URL; + isOnline = true; + + private readonly launchDate = new Date('2025-06-22T13:12:00+05:30'); + + constructor(props: LaunchTimersProps) { + super(props); + this.serverBaseUrl = this.props.serverUrl; + this.state = { + issPosition: null, + issPath: [], + loading: true, + globeError: false, + globeInitialized: false, + }; + + } + + private countdownRenderer = ({ + days, + hours, + minutes, + seconds, + completed, + }: { + days: number; + hours: number; + minutes: number; + seconds: number; + completed: boolean; + }) => { + if (completed) { + return ( + + + + Axiom Mission 4 has launched! + + + ); + } + + return ( + + + Countdown to Launch (IST) + + + {days} Days + {hours} Hours + {minutes} Minutes + {seconds} Seconds + + + ); + }; + + + render() { + return ( + + + +
+ +
+
+
+
+ ); + } +} + +export default LaunchTimers; \ No newline at end of file diff --git a/frontend/src/components/Tracker.css b/frontend/src/components/Tracker.css new file mode 100644 index 0000000..fc326b1 --- /dev/null +++ b/frontend/src/components/Tracker.css @@ -0,0 +1,29 @@ +.flip-card { + animation: flip 0.5s ease-in-out; +} + +@keyframes flip { + 0% { + transform: perspective(400px) rotateX(90deg); + opacity: 0.5; + } + 100% { + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } +} + +@keyframes pulse { + 0% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.2); + opacity: 0.8; + } + 100% { + transform: scale(1); + opacity: 1; + } +} \ No newline at end of file diff --git a/frontend/src/components/TrackerHome.tsx b/frontend/src/components/TrackerHome.tsx new file mode 100644 index 0000000..eabb8bd --- /dev/null +++ b/frontend/src/components/TrackerHome.tsx @@ -0,0 +1,389 @@ +import React, { Component } from 'react'; +import { Typography, Divider, Box, CircularProgress } from '@mui/material'; +import Grid from '@mui/material/Grid2'; +import Countdown from 'react-countdown'; +import RocketLaunchIcon from '@mui/icons-material/RocketLaunch'; +import * as THREE from 'three'; +import './Tracker.css'; + +interface AppState {} + +type TrackerHomeProps = { + serverUrl: string; +}; + + +interface IssPosition { + latitude: number; + longitude: number; + altitude: number; + timestamp: number; +} + +interface AppState { + issPosition: IssPosition | null; + issPath: IssPosition[]; + loading: boolean; + globeError: boolean; + globeInitialized: boolean; +} + + +class TrackerHome extends Component { + serverBaseUrl = import.meta.env.VITE_GAGANYATRI_BACKEND_APP_API_URL; + isOnline = true; + + private readonly launchDate = new Date('2025-06-10T17:52:00+05:30'); + private readonly issApiUrl = 'https://api.wheretheiss.at/v1/satellites/25544'; + private readonly maxPathPoints = 100; // Limit total path points + private readonly tubeRadius = 0.005; // Thickness of orbital path tubes + private readonly cameraDistance = 2.5; // Distance from ISS for camera + private readonly orbitDuration = 90 * 60 * 1000; // 90 minutes in milliseconds + private intervalId: NodeJS.Timeout | null = null; + private scene: THREE.Scene | null = null; + private camera: THREE.PerspectiveCamera | null = null; + private renderer: THREE.WebGLRenderer | null = null; + private controls: any = null; + private issMesh: THREE.Mesh | null = null; + private issPathMesh: THREE.Mesh | null = null; // Blue tube for older orbits + private issLastOrbitMesh: THREE.Mesh | null = null; // Green tube for last completed orbit + private issCurrentOrbitMesh: THREE.Mesh | null = null; // Yellow tube for current orbit + private animationFrameId: number | null = null; + private globeRef = React.createRef(); + + + + constructor(props: TrackerHomeProps) { + super(props); + this.serverBaseUrl = this.props.serverUrl; + this.state = { + issPosition: null, + issPath: [], + loading: true, + globeError: false, + globeInitialized: false, + }; + + } + + + componentDidMount() { + this.fetchIssPosition(); + this.intervalId = setInterval(this.fetchIssPosition, 10000); + this.initThreeJs(); + } + + componentWillUnmount() { + if (this.intervalId) clearInterval(this.intervalId); + if (this.animationFrameId) cancelAnimationFrame(this.animationFrameId); + if (this.renderer) { + this.renderer.dispose(); + } + window.removeEventListener('resize', this.handleResize); + } + + private fetchIssPosition = async () => { + try { + const response = await fetch(this.issApiUrl); + if (!response.ok) throw new Error('API request failed'); + const data = await response.json(); + const newPosition: IssPosition = { + latitude: data.latitude, + longitude: data.longitude, + altitude: data.altitude, + timestamp: Date.now(), + }; + this.setState( + (prevState) => ({ + issPosition: newPosition, + issPath: [...prevState.issPath, newPosition].slice(-this.maxPathPoints), + loading: false, + }), + () => { + this.updateIssPosition(); + this.updateIssPath(); + } + ); + } catch (error) { + console.error('Error fetching ISS position:', error); + this.setState({ loading: false }); + } + }; + + private initThreeJs = () => { + if (!this.globeRef.current) { + console.error('Globe container ref not found'); + this.setState({ globeError: true, globeInitialized: false }); + return; + } + + if (this.state.globeInitialized) return; + + import('three/examples/jsm/controls/OrbitControls').then(({ OrbitControls }) => { + this.scene = new THREE.Scene(); + this.camera = new THREE.PerspectiveCamera(50, 1, 0.1, 1000); + this.camera.position.set(0, 0, 3); + this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); + this.renderer.setPixelRatio(window.devicePixelRatio); + this.globeRef.current!.appendChild(this.renderer.domElement); + this.handleResize(); + + const geometry = new THREE.SphereGeometry(1, 32, 32); + const textureLoader = new THREE.TextureLoader(); + let textureLoadAttempts = 0; + const maxAttempts = 3; + + const loadTexture = () => { + textureLoader.load( + 'https://threejs.org/examples/textures/planets/earth_atmos_2048.jpg', + (texture) => { + const material = new THREE.MeshStandardMaterial({ map: texture }); + const earth = new THREE.Mesh(geometry, material); + this.scene?.add(earth); + console.log('Earth texture loaded successfully'); + this.setState({ globeInitialized: true, globeError: false }); + }, + undefined, + (err) => { + console.error('Texture load error:', err); + textureLoadAttempts++; + if (textureLoadAttempts < maxAttempts) { + console.log(`Retrying texture load (attempt ${textureLoadAttempts + 1}/${maxAttempts})`); + setTimeout(loadTexture, 1000); + } else { + this.setState({ globeError: true, globeInitialized: false }); + } + } + ); + }; + + loadTexture(); + + const issGeometry = new THREE.SphereGeometry(0.02, 16, 16); + const issMaterial = new THREE.MeshStandardMaterial({ color: 'red', emissive: 'red', emissiveIntensity: 0.5 }); + this.issMesh = new THREE.Mesh(issGeometry, issMaterial); + this.scene?.add(this.issMesh); + + // Initialize blue tube for older orbits + const pathMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff }); // Blue + const pathGeometry = new THREE.BufferGeometry(); + this.issPathMesh = new THREE.Mesh(pathGeometry, pathMaterial); + this.scene?.add(this.issPathMesh); + + // Initialize green tube for last completed orbit + const lastOrbitMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // Green + const lastOrbitGeometry = new THREE.BufferGeometry(); + this.issLastOrbitMesh = new THREE.Mesh(lastOrbitGeometry, lastOrbitMaterial); + this.scene?.add(this.issLastOrbitMesh); + + // Initialize yellow tube for current orbit + const currentOrbitMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 }); // Yellow + const currentOrbitGeometry = new THREE.BufferGeometry(); + this.issCurrentOrbitMesh = new THREE.Mesh(currentOrbitGeometry, currentOrbitMaterial); + this.scene?.add(this.issCurrentOrbitMesh); + + this.scene.add(new THREE.AmbientLight(0xffffff, 0.6)); + const pointLight = new THREE.PointLight(0xffffff, 1.2, 100); + pointLight.position.set(10, 10, 10); + this.scene.add(pointLight); + + this.controls = new OrbitControls(this.camera, this.renderer.domElement); + this.controls.enablePan = false; + this.controls.enableZoom = true; + this.controls.minDistance = 2; + this.controls.maxDistance = 5; + this.controls.enableDamping = true; + this.controls.dampingFactor = 0.05; + + const animate = () => { + this.animationFrameId = requestAnimationFrame(animate); + if (this.scene && this.camera && this.renderer && this.issMesh) { + this.controls.update(); + this.issMesh.scale.set( + 0.5 + 0.2 * Math.sin(Date.now() * 0.002), + 0.5 + 0.2 * Math.sin(Date.now() * 0.002), + 0.5 + 0.2 * Math.sin(Date.now() * 0.002) + ); + this.renderer.render(this.scene, this.camera); + } + }; + animate(); + + window.addEventListener('resize', this.handleResize); + }); + }; + + private handleResize = () => { + if (this.renderer && this.camera && this.globeRef.current) { + const width = this.globeRef.current.clientWidth; + const height = window.innerWidth < 600 ? 250 : 400; + this.renderer.setSize(width, height); + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + } + }; + + private getIss3DPosition = (latitude: number, longitude: number, altitude: number) => { + const radius = 1 + altitude / 6371; + const phi = (90 - latitude) * (Math.PI / 180); + const theta = (longitude + 180) * (Math.PI / 180); + const x = -radius * Math.sin(phi) * Math.cos(theta); + const z = radius * Math.sin(phi) * Math.sin(theta); + const y = radius * Math.cos(phi); + return new THREE.Vector3(x, y, z); + }; + + private updateIssPosition = () => { + if (this.issMesh && this.camera && this.controls && this.state.issPosition && this.state.globeInitialized) { + const { latitude, longitude, altitude } = this.state.issPosition; + const issPosition = this.getIss3DPosition(latitude, longitude, altitude); + + // Update ISS mesh position + this.issMesh.position.copy(issPosition); + + // Position the camera to focus on the ISS + const cameraOffset = issPosition.clone().normalize().multiplyScalar(this.cameraDistance); + this.camera.position.copy(issPosition.clone().add(cameraOffset)); + this.camera.lookAt(issPosition); + this.controls.target.copy(issPosition); + this.controls.update(); + } + }; + + private updateIssPath = () => { + if (!this.issPathMesh || !this.issLastOrbitMesh || !this.issCurrentOrbitMesh || !this.state.globeInitialized) return; + + const now = Date.now(); + const orbitDuration = this.orbitDuration; + + // Filter points for current orbit (last 90 minutes) + const currentOrbitPoints = this.state.issPath.filter( + (point) => now - point.timestamp <= orbitDuration + ); + + // Filter points for last completed orbit (90 to 180 minutes ago) + const lastOrbitPoints = this.state.issPath.filter( + (point) => now - point.timestamp > orbitDuration && now - point.timestamp <= 2 * orbitDuration + ); + + // Filter points for older orbits (older than 180 minutes) + const olderPoints = this.state.issPath.filter( + (point) => now - point.timestamp > 2 * orbitDuration + ); + + // Update current orbit (yellow) + if (currentOrbitPoints.length > 1) { + const points = currentOrbitPoints.map(({ latitude, longitude, altitude }) => + this.getIss3DPosition(latitude, longitude, altitude) + ); + const curve = new THREE.CatmullRomCurve3(points, false, 'centripetal', 0.5); + const tubeGeometry = new THREE.TubeGeometry(curve, 64, this.tubeRadius, 8, false); + this.issCurrentOrbitMesh.geometry.dispose(); + this.issCurrentOrbitMesh.geometry = tubeGeometry; + } else { + this.issCurrentOrbitMesh.geometry.dispose(); + this.issCurrentOrbitMesh.geometry = new THREE.BufferGeometry(); + } + + // Update last completed orbit (green) + if (lastOrbitPoints.length > 1) { + const points = lastOrbitPoints.map(({ latitude, longitude, altitude }) => + this.getIss3DPosition(latitude, longitude, altitude) + ); + const curve = new THREE.CatmullRomCurve3(points, false, 'centripetal', 0.5); + const tubeGeometry = new THREE.TubeGeometry(curve, 64, this.tubeRadius, 8, false); + this.issLastOrbitMesh.geometry.dispose(); + this.issLastOrbitMesh.geometry = tubeGeometry; + } else { + this.issLastOrbitMesh.geometry.dispose(); + this.issLastOrbitMesh.geometry = new THREE.BufferGeometry(); + } + + // Update older orbits (blue) + if (olderPoints.length > 1) { + const points = olderPoints.map(({ latitude, longitude, altitude }) => + this.getIss3DPosition(latitude, longitude, altitude) + ); + const curve = new THREE.CatmullRomCurve3(points, false, 'centripetal', 0.5); + const tubeGeometry = new THREE.TubeGeometry(curve, 64, this.tubeRadius, 8, false); + this.issPathMesh.geometry.dispose(); + this.issPathMesh.geometry = tubeGeometry; + } else { + this.issPathMesh.geometry.dispose(); + this.issPathMesh.geometry = new THREE.BufferGeometry(); + } + }; + + private countdownRenderer = ({ + days, + hours, + minutes, + seconds, + completed, + }: { + days: number; + hours: number; + minutes: number; + seconds: number; + completed: boolean; + }) => { + if (completed) { + return ( + + + + Axiom Mission 4 has launched! + + + ); + } + + return ( + + + Countdown to Axiom Mission 4 Launch (IST) + + + {days} Days + {hours} Hours + {minutes} Minutes + {seconds} Seconds + + + ); + }; + + + render() { + return ( + + + +
+ + + {!this.state.globeInitialized && !this.state.globeError && ( + + )} + {this.state.globeError && There was an error loading the globe.} +
+ +
+ + +
+ ); + } +} + +export default TrackerHome; \ No newline at end of file diff --git a/frontend/tsconfig.app.tsbuildinfo b/frontend/tsconfig.app.tsbuildinfo index 3864cb7..1eef274 100644 --- a/frontend/tsconfig.app.tsbuildinfo +++ b/frontend/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/App.tsx","./src/ErrorBoundary.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/assets/discordIcon.tsx","./src/components/AX4Home.tsx","./src/components/About.tsx","./src/components/HackathonDemos.tsx","./src/components/Home.tsx","./src/components/ISSHome.tsx","./src/components/LLMTechHome.tsx","./src/components/Maps.tsx","./src/components/NavBar.tsx","./src/components/NoMatch.tsx","./src/components/Space.tsx","./src/components/TaxHome.tsx","./src/components/chatbot/chatApp.tsx","./src/components/dashboard/Dashboard.tsx","./src/components/demos/DataTable.tsx","./src/components/demos/FoodGuardian.tsx","./src/components/demos/IndeterminateProgressBar.tsx","./src/components/demos/Recipes.tsx","./src/components/demos/SpeechASR.tsx","./src/components/demos/SpeechLLM.tsx","./src/components/demos/ollamaservice.ts","./src/components/demos/ontogpt/OntogptDemo.tsx","./src/components/demos/space/AX4Demo.tsx","./src/components/demos/space/ISSDemo.tsx","./src/components/demos/taxtech/TaxDemo.tsx","./src/components/demos/taxtech/TaxDemoTaxData.tsx","./src/components/indic_llm/IndicDemo.tsx","./src/components/login/Login.tsx","./src/components/login/LoginActions.tsx","./src/components/login/LoginReducer.tsx","./src/components/login/LoginTypes.tsx","./src/components/news/News.tsx","./src/components/signup/Signup.tsx","./src/components/signup/SignupActions.tsx","./src/components/signup/SignupReducer.tsx","./src/components/signup/SignupTypes.tsx","./src/components/speech/SpeechDemo.tsx","./src/components/text_llm/TextDemo.tsx","./src/components/theme/blog/Blog.tsx","./src/components/theme/blog/components/AppAppBar.tsx","./src/components/theme/blog/components/Footer.tsx","./src/components/theme/blog/components/Latest.tsx","./src/components/theme/blog/components/MainContent.tsx","./src/components/theme/blog/components/SitemarkIcon.tsx","./src/components/theme/checkout/Checkout.tsx","./src/components/theme/checkout/components/AddressForm.tsx","./src/components/theme/checkout/components/Info.tsx","./src/components/theme/checkout/components/InfoMobile.tsx","./src/components/theme/checkout/components/PaymentForm.tsx","./src/components/theme/checkout/components/Review.tsx","./src/components/theme/checkout/components/SitemarkIcon.tsx","./src/components/theme/dashboard/Dashboard.tsx","./src/components/theme/dashboard/components/AX4App.tsx","./src/components/theme/dashboard/components/AppNavbar.tsx","./src/components/theme/dashboard/components/CardAlert.tsx","./src/components/theme/dashboard/components/ChartUserByCountry.tsx","./src/components/theme/dashboard/components/CustomDatePicker.tsx","./src/components/theme/dashboard/components/CustomizedDataGrid.tsx","./src/components/theme/dashboard/components/CustomizedTreeView.tsx","./src/components/theme/dashboard/components/Header.tsx","./src/components/theme/dashboard/components/HighlightedCard.tsx","./src/components/theme/dashboard/components/ISSApp.tsx","./src/components/theme/dashboard/components/LLMTechDemoApp.tsx","./src/components/theme/dashboard/components/MainGrid.tsx","./src/components/theme/dashboard/components/MenuButton.tsx","./src/components/theme/dashboard/components/MenuContent.tsx","./src/components/theme/dashboard/components/MetricsApp.tsx","./src/components/theme/dashboard/components/NavbarBreadcrumbs.tsx","./src/components/theme/dashboard/components/OptionsMenu.tsx","./src/components/theme/dashboard/components/PageViewsBarChart.tsx","./src/components/theme/dashboard/components/Search.tsx","./src/components/theme/dashboard/components/SelectContent.tsx","./src/components/theme/dashboard/components/SessionsChart.tsx","./src/components/theme/dashboard/components/SideMenu.tsx","./src/components/theme/dashboard/components/SideMenuMobile.tsx","./src/components/theme/dashboard/components/StatCard.tsx","./src/components/theme/dashboard/components/TaxTechDemoApp.tsx","./src/components/theme/dashboard/internals/components/Copyright.tsx","./src/components/theme/dashboard/internals/components/CustomIcons.tsx","./src/components/theme/dashboard/internals/data/gridData.tsx","./src/components/theme/dashboard/theme/customizations/charts.ts","./src/components/theme/dashboard/theme/customizations/dataGrid.ts","./src/components/theme/dashboard/theme/customizations/datePickers.ts","./src/components/theme/dashboard/theme/customizations/index.ts","./src/components/theme/dashboard/theme/customizations/treeView.ts","./src/components/theme/shared-theme/AppTheme.tsx","./src/components/theme/shared-theme/ColorModeIconDropdown.tsx","./src/components/theme/shared-theme/ColorModeSelect.tsx","./src/components/theme/shared-theme/themePrimitives.ts","./src/components/theme/shared-theme/customizations/dataDisplay.tsx","./src/components/theme/shared-theme/customizations/feedback.tsx","./src/components/theme/shared-theme/customizations/inputs.tsx","./src/components/theme/shared-theme/customizations/navigation.tsx","./src/components/theme/shared-theme/customizations/surfaces.ts","./src/components/theme/sign-in/CustomIcons.tsx","./src/components/theme/sign-in/ForgotPassword.tsx","./src/components/theme/sign-in/SignIn.tsx","./src/components/theme/sign-up/CustomIcons.tsx","./src/components/theme/sign-up/SignUp.tsx","./src/components/translate/TranslateDemo.tsx","./src/components/vision/VisionDemo.tsx","./src/reducers/Reducer.tsx","./src/reducers/Root.tsx","./src/reducers/store.tsx","./src/reducers/space/AX4DashboardActions.ts","./src/reducers/space/AX4DashboardAddDataReducer.tsx","./src/reducers/space/AX4DashboardDataReducer.tsx","./src/reducers/tax/TaxDashboardActions.ts","./src/reducers/tax/TaxDashboardDataReducer.tsx","./src/reducers/tax/TaxDashboardTaxAddDataReducer.tsx","./src/types/three.d.ts","./src/utils/Utils.tsx"],"version":"5.8.3"} \ No newline at end of file +{"root":["./src/App.tsx","./src/ErrorBoundary.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/assets/discordIcon.tsx","./src/components/AX4Home.tsx","./src/components/About.tsx","./src/components/HackathonDemos.tsx","./src/components/Home.tsx","./src/components/ISSHome.tsx","./src/components/LLMTechHome.tsx","./src/components/LaunchTimers.tsx","./src/components/Maps.tsx","./src/components/NavBar.tsx","./src/components/NoMatch.tsx","./src/components/Space.tsx","./src/components/TaxHome.tsx","./src/components/TrackerHome.tsx","./src/components/chatbot/chatApp.tsx","./src/components/dashboard/Dashboard.tsx","./src/components/demos/DataTable.tsx","./src/components/demos/FoodGuardian.tsx","./src/components/demos/IndeterminateProgressBar.tsx","./src/components/demos/Recipes.tsx","./src/components/demos/SpeechASR.tsx","./src/components/demos/SpeechLLM.tsx","./src/components/demos/ollamaservice.ts","./src/components/demos/ontogpt/OntogptDemo.tsx","./src/components/demos/space/AX4Demo.tsx","./src/components/demos/space/ISSDemo.tsx","./src/components/demos/taxtech/TaxDemo.tsx","./src/components/demos/taxtech/TaxDemoTaxData.tsx","./src/components/indic_llm/IndicDemo.tsx","./src/components/login/Login.tsx","./src/components/login/LoginActions.tsx","./src/components/login/LoginReducer.tsx","./src/components/login/LoginTypes.tsx","./src/components/news/News.tsx","./src/components/signup/Signup.tsx","./src/components/signup/SignupActions.tsx","./src/components/signup/SignupReducer.tsx","./src/components/signup/SignupTypes.tsx","./src/components/speech/SpeechDemo.tsx","./src/components/text_llm/TextDemo.tsx","./src/components/theme/blog/Blog.tsx","./src/components/theme/blog/components/AppAppBar.tsx","./src/components/theme/blog/components/Footer.tsx","./src/components/theme/blog/components/Latest.tsx","./src/components/theme/blog/components/MainContent.tsx","./src/components/theme/blog/components/SitemarkIcon.tsx","./src/components/theme/checkout/Checkout.tsx","./src/components/theme/checkout/components/AddressForm.tsx","./src/components/theme/checkout/components/Info.tsx","./src/components/theme/checkout/components/InfoMobile.tsx","./src/components/theme/checkout/components/PaymentForm.tsx","./src/components/theme/checkout/components/Review.tsx","./src/components/theme/checkout/components/SitemarkIcon.tsx","./src/components/theme/dashboard/Dashboard.tsx","./src/components/theme/dashboard/components/AX4App.tsx","./src/components/theme/dashboard/components/AppNavbar.tsx","./src/components/theme/dashboard/components/CardAlert.tsx","./src/components/theme/dashboard/components/ChartUserByCountry.tsx","./src/components/theme/dashboard/components/CustomDatePicker.tsx","./src/components/theme/dashboard/components/CustomizedDataGrid.tsx","./src/components/theme/dashboard/components/CustomizedTreeView.tsx","./src/components/theme/dashboard/components/Header.tsx","./src/components/theme/dashboard/components/HighlightedCard.tsx","./src/components/theme/dashboard/components/ISSApp.tsx","./src/components/theme/dashboard/components/LLMTechDemoApp.tsx","./src/components/theme/dashboard/components/MainGrid.tsx","./src/components/theme/dashboard/components/MenuButton.tsx","./src/components/theme/dashboard/components/MenuContent.tsx","./src/components/theme/dashboard/components/MetricsApp.tsx","./src/components/theme/dashboard/components/NavbarBreadcrumbs.tsx","./src/components/theme/dashboard/components/OptionsMenu.tsx","./src/components/theme/dashboard/components/PageViewsBarChart.tsx","./src/components/theme/dashboard/components/Search.tsx","./src/components/theme/dashboard/components/SelectContent.tsx","./src/components/theme/dashboard/components/SessionsChart.tsx","./src/components/theme/dashboard/components/SideMenu.tsx","./src/components/theme/dashboard/components/SideMenuMobile.tsx","./src/components/theme/dashboard/components/StatCard.tsx","./src/components/theme/dashboard/components/TaxTechDemoApp.tsx","./src/components/theme/dashboard/internals/components/Copyright.tsx","./src/components/theme/dashboard/internals/components/CustomIcons.tsx","./src/components/theme/dashboard/internals/data/gridData.tsx","./src/components/theme/dashboard/theme/customizations/charts.ts","./src/components/theme/dashboard/theme/customizations/dataGrid.ts","./src/components/theme/dashboard/theme/customizations/datePickers.ts","./src/components/theme/dashboard/theme/customizations/index.ts","./src/components/theme/dashboard/theme/customizations/treeView.ts","./src/components/theme/shared-theme/AppTheme.tsx","./src/components/theme/shared-theme/ColorModeIconDropdown.tsx","./src/components/theme/shared-theme/ColorModeSelect.tsx","./src/components/theme/shared-theme/themePrimitives.ts","./src/components/theme/shared-theme/customizations/dataDisplay.tsx","./src/components/theme/shared-theme/customizations/feedback.tsx","./src/components/theme/shared-theme/customizations/inputs.tsx","./src/components/theme/shared-theme/customizations/navigation.tsx","./src/components/theme/shared-theme/customizations/surfaces.ts","./src/components/theme/sign-in/CustomIcons.tsx","./src/components/theme/sign-in/ForgotPassword.tsx","./src/components/theme/sign-in/SignIn.tsx","./src/components/theme/sign-up/CustomIcons.tsx","./src/components/theme/sign-up/SignUp.tsx","./src/components/translate/TranslateDemo.tsx","./src/components/vision/VisionDemo.tsx","./src/reducers/Reducer.tsx","./src/reducers/Root.tsx","./src/reducers/store.tsx","./src/reducers/space/AX4DashboardActions.ts","./src/reducers/space/AX4DashboardAddDataReducer.tsx","./src/reducers/space/AX4DashboardDataReducer.tsx","./src/reducers/tax/TaxDashboardActions.ts","./src/reducers/tax/TaxDashboardDataReducer.tsx","./src/reducers/tax/TaxDashboardTaxAddDataReducer.tsx","./src/types/three.d.ts","./src/utils/Utils.tsx"],"version":"5.8.3"} \ No newline at end of file