diff --git a/.DS_Store b/.DS_Store index 9e9981d7..94e4b3bd 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/STAR_SAILORS_USER_FLOWS.md b/STAR_SAILORS_USER_FLOWS.md index d5b0e440..f027402a 100644 --- a/STAR_SAILORS_USER_FLOWS.md +++ b/STAR_SAILORS_USER_FLOWS.md @@ -19,6 +19,23 @@ Star Sailors is a Next.js 14 gamified citizen science platform where users deploy virtual astronomical structures, classify real scientific data, and contribute to research. The application transforms data analysis into an exploration game with progression mechanics, resource discovery, and community collaboration. +### Recent Additions & Review Summary + +This document was reviewed and updated to explicitly call out recently added projects, features, and system improvements. Below is a concise summary of notable additions and changes covered in this review: + +- **Mineral Deposit System (expanded):** Full orchestration clarified including research checks, planetary compatibility checks, deposit-roll logic, mineral selection functions, and database insertion flow. Extraction prerequisites and deposit metadata handling are documented. +- **Extraction Minigames:** Extraction flow (rover and satellite) and yield calculation added, with state changes for deposit extraction and UI behavior described. +- **Fast Deploy & Onboarding:** Fast deploy mechanics reaffirmed for anonymous users and effects on unlock timings documented. +- **Research Tree Enhancements:** New and emphasized techs: `spectroscopy`, `findMinerals`, `p4Minerals`, `ngtsAccess`, `roverExtraction`, and `satelliteExtraction` with costs and deployment effects. +- **Rover / AI4M Improvements:** Rover waypoint assignment, mineral waypoint logic, route storage schema, and AI4M terrain mineral selection and probability clarified. +- **Satellite Unlock Schedule & Modes:** Weather, Planetary Survey, and Wind Survey modes clarified along with unlock timing and satellite-specific unlock mechanics. +- **Sunspot & Continuous Participation:** Sunspot participation mechanics and unlock/expiration windows documented. +- **Inventory & UI Updates:** Mineral deposit cards, filters, extraction gating, and inventory interactions described. +- **Social Features & Community Bonuses:** Activity feed, comment/vote-based bonus deploys, leaderboards, and community recognition flows emphasized. +- **Annotation & Media Export:** Annotation tool improvements and PNG export behavior covered. + +If you'd like, I can (a) expand any of the bullets into a new detailed subsection, (b) run a link/anchor consistency pass across the file, or (c) create a changelog section listing exact commit references for each feature. + ### Core Mechanics - **Classification**: Analyze real astronomical/planetary data to earn points - **Deployment**: Deploy telescopes, satellites, and rovers to unlock new data diff --git a/app/activity/deploy/roover/page.tsx b/app/activity/deploy/roover/page.tsx index 168632de..e76a680c 100644 --- a/app/activity/deploy/roover/page.tsx +++ b/app/activity/deploy/roover/page.tsx @@ -91,7 +91,7 @@ export default function DeployRoverPage() { .eq("author", session.user.id) .eq("automaton", "Rover"); if (existingDeployments && existingDeployments.length > 0) { - window.location.href = "/viewports/roover"; + window.location.href = "/game?view=rover"; } } checkExistingRoverDeployment(); @@ -231,7 +231,7 @@ export default function DeployRoverPage() { setDeployMessage("Rover deployed successfully!"); setTimeout(() => { - window.location.href = "/viewports/roover"; + window.location.href = "/game?view=rover"; }, 2000); }; diff --git a/app/game/page.tsx b/app/game/page.tsx index 7ffc871b..f0f06645 100644 --- a/app/game/page.tsx +++ b/app/game/page.tsx @@ -1,147 +1,127 @@ "use client"; -import { useState, useEffect, useMemo, useRef, useCallback } from "react"; -import { useRouter } from "next/navigation"; -import dynamic from 'next/dynamic'; +import { Suspense, useState, useEffect, useMemo } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import dynamic from "next/dynamic"; import { useSession, useSessionContext } from "@supabase/auth-helpers-react"; -import { usePostHog } from 'posthog-js/react'; - -// Dynamic heavy components (Three.js scene, popup, banners, forms) -const TelescopeBackground = dynamic(() => import('@/src/components/classification/telescope/telescope-background').then(m => m.TelescopeBackground), { ssr: false, loading: () =>
}); -const NPSPopup = dynamic(() => import('@/src/components/ui/helpers/nps-popup'), { loading: () => null }); -const WeeklyBanner = dynamic(() => import('@/src/components/ui/update-banner'), { loading: () => null }); -const CompleteProfileForm = dynamic(() => import('@/src/components/profile/setup/FinishProfile'), { loading: () =>
Loading profile form…
}); -const PWAPrompt = dynamic(() => import('@/src/components/pwa/PWAPrompt'), { loading: () => null }); +import { usePostHog } from "posthog-js/react"; + +// Dynamic heavy components +const TelescopeBackground = dynamic( + () => + import("@/src/components/classification/telescope/telescope-background").then( + (m) => m.TelescopeBackground + ), + { + ssr: false, + loading: () => ( +
+ ), + } +); +const NPSPopup = dynamic(() => import("@/src/components/ui/helpers/nps-popup"), { + loading: () => null, +}); +const CompleteProfileForm = dynamic( + () => import("@/src/components/profile/setup/FinishProfile"), + { + loading: () => ( +
Loading profile form…
+ ), + } +); +const PWAPrompt = dynamic(() => import("@/src/components/pwa/PWAPrompt"), { + loading: () => null, +}); + +// Dynamic tab content components +const TelescopeTab = dynamic(() => import("@/src/components/tabs/TelescopeTab"), { + loading: () => ( +
Loading telescope…
+ ), +}); +const SatelliteTab = dynamic(() => import("@/src/components/tabs/SatelliteTab"), { + loading: () => ( +
Loading satellite…
+ ), +}); +const RoverTab = dynamic(() => import("@/src/components/tabs/RoverTab"), { + loading: () =>
Loading rover…
, +}); +const SolarTab = dynamic(() => import("@/src/components/tabs/SolarTab"), { + loading: () =>
Loading solar…
, +}); +const InventoryTab = dynamic(() => import("@/src/components/tabs/InventoryTab"), { + loading: () => ( +
Loading inventory…
+ ), +}); + +// UI Components import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/src/components/ui/dialog"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/src/components/ui/tabs"; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, +} from "@/src/components/ui/sheet"; import AnonymousUserPrompt from "@/src/components/profile/auth/AnonymousUserPrompt"; -import MainHeader from "@/src/components/layout/Header/MainHeader"; -const ActivityHeaderSection = dynamic(() => import('@/src/components/social/activity/ActivityHeaderSection'), { loading: () => null }); -const ProfileSetupRequired = dynamic(() => import('@/src/components/profile/setup/ProfileSetupRequired'), { loading: () => null }); -// Remove unused NotificationSubscribeButton (not rendered) +// New redesigned components +import GameHeader from "@/src/components/game/GameHeader"; +import PlanetHeroSection from "@/src/components/game/PlanetHeroSection"; +import MissionControlCard from "@/src/components/game/MissionControlCard"; +import StructureCard from "@/src/components/game/StructureCard"; +import BottomNavigation from "@/src/components/game/BottomNavigation"; +import RecentActivity from "@/src/components/social/activity/RecentActivity"; -// Import custom hooks +// Hooks import { usePageData } from "@/hooks/usePageData"; import { useNPSManagement } from "@/hooks/useNPSManagement"; -import { useTabsPersistence, TabConfig } from "@/hooks/useTabsPersistence"; +import { useUserPreferences } from "@/src/hooks/useUserPreferences"; import UseDarkMode from "@/src/shared/hooks/useDarkMode"; -// Import tab components -const TelescopeTab = dynamic(() => import('@/src/components/tabs/TelescopeTab'), { loading: () =>
Loading telescope…
}); -const SatelliteTab = dynamic(() => import('@/src/components/tabs/SatelliteTab'), { loading: () =>
Loading satellite…
}); -const RoverTab = dynamic(() => import('@/src/components/tabs/RoverTab'), { loading: () =>
Loading rover…
}); -const SolarTab = dynamic(() => import('@/src/components/tabs/SolarTab'), { loading: () =>
Loading solar…
}); -const InventoryTab = dynamic(() => import('@/src/components/tabs/InventoryTab'), { loading: () =>
Loading inventory…
}); -const UpdatesTab = dynamic(() => import('@/src/components/tabs/UpdatesTab'), { loading: () =>
Loading updates…
}); -const OnboardingTab = dynamic(() => import('@/src/components/tabs/OnboardingTab'), { loading: () =>
Loading onboarding…
}); - -// Import icons -import { Telescope, Satellite, Car, Package, Bell, Sun, ArrowLeft, ArrowRight } from "lucide-react"; - -// Simple Tab Trigger with reorder buttons -interface SimpleTabTriggerProps { - id: string; - icon: React.ReactNode; - label: string; - onMoveLeft?: () => void; - onMoveRight?: () => void; - canMoveLeft: boolean; - canMoveRight: boolean; - onTabClick: (id: string) => void; -}; - -function SimpleTabTrigger({ - id, - icon, - label, - onMoveLeft, - onMoveRight, - canMoveLeft, - canMoveRight, - onTabClick -}: SimpleTabTriggerProps) { - return ( -
- {canMoveLeft && ( - - )} - - { - e.preventDefault(); - onTabClick(id); - }} - > - {icon} - {label} - - - {canMoveRight && ( - - )} -
- ); -} - -type TabId = "updates" | "solar" | "telescope" | "satellite" | "rover" | "inventory"; +// Onboarding components +import ProjectPreferencesModal from "@/src/components/onboarding/ProjectPreferencesModal"; +import { ProjectType } from "@/src/hooks/useUserPreferences"; -export default function GamePage() { +// Icons +import { + Telescope, + Satellite, + Car, + Package, + Sun, + FlaskConical, + AlertCircle, + CheckCircle, + Clock, + ChevronLeft, +} from "lucide-react"; + +type ViewMode = "base" | "telescope" | "satellite" | "rover" | "solar" | "inventory"; + +// Create a separate component for the search params logic +function GamePageContent() { const session = useSession(); const { isLoading: isAuthLoading } = useSessionContext(); const posthog = usePostHog(); const router = useRouter(); + const searchParams = useSearchParams(); + const initialView = (searchParams.get("view") as ViewMode) || "base"; + // State + const [activeView, setActiveView] = useState(initialView); const [notificationsOpen, setNotificationsOpen] = useState(false); - const [landmarksExpanded, setLandmarksExpanded] = useState(false); const [showProfileModal, setShowProfileModal] = useState(false); - const [tabContentExpanded, setTabContentExpanded] = useState(false); - const [showReorderHint, setShowReorderHint] = useState(true); - const [isFullScreen, setIsFullScreen] = useState(false); - const [canScrollLeft, setCanScrollLeft] = useState(false); - const [canScrollRight, setCanScrollRight] = useState(false); - const tabListContainerRef = useRef(null); - - const updateScrollState = useCallback(() => { - const container = tabListContainerRef.current; - if (!container) { - setCanScrollLeft(false); - setCanScrollRight(false); - return; - } - - const { scrollLeft, scrollWidth, clientWidth } = container; - const epsilon = 1; - setCanScrollLeft(scrollLeft > epsilon); - setCanScrollRight(scrollWidth - clientWidth - scrollLeft > epsilon); - }, []); + const [showPreferencesModal, setShowPreferencesModal] = useState(false); - // Custom hooks for data management + // Hooks const { linkedAnomalies, activityFeed, @@ -152,482 +132,484 @@ export default function GamePage() { } = usePageData(); const { showNpsModal, handleCloseNps } = useNPSManagement(); - - // Use the global theme hook - const { isDark, toggleDarkMode } = UseDarkMode(); - - // Tab persistence hook + const { isDark } = UseDarkMode(); const { - activeTab, - setActiveTab, - initializeTabOrder, - reorderTabs, - getOrderedTabs, - isInitialized, - hasSavedOrder, - } = useTabsPersistence('updates'); - - // Handle tab click - toggle full screen if clicking active tab, otherwise switch tabs - const handleTabClick = (tabId: string) => { - if (tabId === activeTab) { - // Toggle full screen when clicking active tab - setIsFullScreen(prev => !prev); - posthog?.capture('tab_fullscreen_toggled', { - tab_id: tabId, - is_fullscreen: !isFullScreen, - }); - } else { - // Switch to new tab, keep full screen state - setActiveTab(tabId); - posthog?.capture('tab_switched', { - from_tab: activeTab, - to_tab: tabId, - classification_count: classifications.length, - }); - } - }; + preferences, + isLoading: preferencesLoading, + needsPreferencesPrompt, + setProjectInterests, + } = useUserPreferences(); - // Track page view and user state + // Show preferences modal on first visit or new device useEffect(() => { - if (session?.user) { - posthog?.capture('game_page_viewed', { - user_id: session.user.id, - has_classifications: classifications.length > 0, - classification_count: classifications.length, - has_discoveries: linkedAnomalies.length > 0, - discovery_count: linkedAnomalies.length, - needs_profile_setup: needsProfileSetup, - }); + if (!preferencesLoading && needsPreferencesPrompt && session) { + setShowPreferencesModal(true); } - }, [session, posthog]); - - // Check if user has seen the reorder hint - useEffect(() => { - if (typeof window !== 'undefined') { - const hasSeenHint = localStorage.getItem('tab-reorder-hint-seen'); - if (hasSeenHint) { - setShowReorderHint(false); - } - } - }, []); - - const dismissReorderHint = () => { - setShowReorderHint(false); - if (typeof window !== 'undefined') { - localStorage.setItem('tab-reorder-hint-seen', 'true'); - } - posthog?.capture('reorder_hint_dismissed'); - }; - - const tabPriorityCounts = useMemo(() => { - const counts: Record = { - updates: 0, - solar: 0, - telescope: 0, - satellite: 0, - rover: 0, - inventory: 0, - }; - - linkedAnomalies.forEach((linked) => { - const automaton = (linked.automaton || '').toLowerCase(); - const anomalyType = (linked.anomaly?.anomalytype || '').toLowerCase(); - const anomalySet = (linked.anomaly?.anomalySet || '').toLowerCase(); - - if (anomalyType.includes('sunspot') || anomalyType.includes('solar') || anomalySet.includes('sunspot')) { - counts.solar += 1; - } - - if ( - automaton.includes('telescope') || - anomalyType.includes('planet') || - anomalyType.includes('asteroid') || - anomalyType.includes('variable') || - anomalyType.includes('disk') || - anomalySet.includes('telescope') || - anomalySet.includes('planet') || - anomalySet.includes('asteroid') || - anomalySet.includes('variable') || - anomalySet.includes('disk') - ) { - counts.telescope += 1; - } - - if ( - automaton.includes('satellite') || - automaton.includes('weather') || - anomalyType.includes('cloud') || - anomalyType.includes('balloon') || - anomalyType.includes('lidar') || - anomalySet.includes('cloud') || - anomalySet.includes('balloon') || - anomalySet.includes('lidar') - ) { - counts.satellite += 1; - } - - if ( - automaton.includes('rover') || - anomalyType.includes('rover') || - anomalyType.includes('automaton') || - anomalySet.includes('rover') || - anomalySet.includes('automaton') - ) { - counts.rover += 1; - } - }); - - return counts; - }, [linkedAnomalies]); - - const baseTabs: TabConfig[] = useMemo(() => [ - { - id: 'updates', - label: 'Updates', - icon: , - }, - { - id: 'solar', - label: 'Solar', - icon: , - }, - { - id: 'telescope', - label: 'Telescope', - icon: , - }, - { - id: 'satellite', - label: 'Satellite', - icon: , - }, - { - id: 'rover', - label: 'Rover', - icon: , - }, - { - id: 'inventory', - label: 'Inventory', - icon: , - }, - ], []); - - const prioritizedTabs = useMemo(() => { - if (hasSavedOrder) { - return baseTabs; - } - - const baseOrder: Record = { - updates: 0, - solar: 1, - telescope: 2, - satellite: 3, - rover: 4, - inventory: 5, - }; - - return [...baseTabs].sort((a, b) => { - const aId = a.id as TabId; - const bId = b.id as TabId; - const aBoost = tabPriorityCounts[aId] > 0 ? 10 : 0; - const bBoost = tabPriorityCounts[bId] > 0 ? 10 : 0; - const aScore = baseOrder[aId] - aBoost - tabPriorityCounts[aId] * 0.01; - const bScore = baseOrder[bId] - bBoost - tabPriorityCounts[bId] * 0.01; - return aScore - bScore; - }); - }, [baseTabs, tabPriorityCounts, hasSavedOrder]); - - const orderedTabs = useMemo(() => getOrderedTabs(prioritizedTabs), [prioritizedTabs, getOrderedTabs]); - - useEffect(() => { - if (isInitialized) { - initializeTabOrder(prioritizedTabs); - } - }, [prioritizedTabs, initializeTabOrder, isInitialized]); - - useEffect(() => { - const container = tabListContainerRef.current; - updateScrollState(); - - if (!container) { - return; - } - - const handleScroll = () => updateScrollState(); - const rafId = requestAnimationFrame(updateScrollState); - - container.addEventListener('scroll', handleScroll); - - let resizeObserver: ResizeObserver | null = null; - if (typeof ResizeObserver !== 'undefined') { - resizeObserver = new ResizeObserver(() => updateScrollState()); - resizeObserver.observe(container); - } - - window.addEventListener('resize', updateScrollState); - - return () => { - cancelAnimationFrame(rafId); - container.removeEventListener('scroll', handleScroll); - window.removeEventListener('resize', updateScrollState); - resizeObserver?.disconnect(); - }; - }, [orderedTabs, updateScrollState]); - - // Move tab left or right - const moveTab = (tabId: string, direction: 'left' | 'right') => { - const tabIds = orderedTabs.map(tab => tab.id); - const currentIndex = tabIds.indexOf(tabId); - - if (direction === 'left' && currentIndex > 0) { - const newOrder = [...tabIds]; - [newOrder[currentIndex - 1], newOrder[currentIndex]] = [newOrder[currentIndex], newOrder[currentIndex - 1]]; - reorderTabs(newOrder); - posthog?.capture('tab_reordered', { - tab_id: tabId, - direction: 'left', - new_index: currentIndex - 1, - }); - } else if (direction === 'right' && currentIndex < tabIds.length - 1) { - const newOrder = [...tabIds]; - [newOrder[currentIndex], newOrder[currentIndex + 1]] = [newOrder[currentIndex + 1], newOrder[currentIndex]]; - reorderTabs(newOrder); - posthog?.capture('tab_reordered', { - tab_id: tabId, - direction: 'right', - new_index: currentIndex + 1, - }); - } - }; + }, [preferencesLoading, needsPreferencesPrompt, session]); + + // Determine which structures to show based on preferences + const shouldShowStructure = (structureType: "telescope" | "satellite" | "rover" | "solar") => { + // If no preferences set, show all + if (preferences.projectInterests.length === 0) return true; + + // Map structure types to project interests + const structureToProjects: Record = { + telescope: ["planet-hunting", "asteroid-hunting"], + satellite: ["cloud-tracking", "ice-tracking"], + rover: ["rover-training"], + solar: ["solar-monitoring"], + }; + + const relatedProjects = structureToProjects[structureType] || []; + return relatedProjects.some(project => preferences.projectInterests.includes(project)); + }; + + // Handle structure click - navigate to dedicated setup pages + const handleStructureClick = ( + structureType: "telescope" | "satellite" | "rover" | "solar", + viewMode: ViewMode + ) => { + console.log("[DEBUG] handleStructureClick - navigating to setup page:", structureType); + + // Navigate to dedicated setup pages with onboarding + const setupRoutes = { + telescope: "/setup/telescope", + satellite: "/setup/satellite", + rover: "/setup/rover", + solar: "/setup/solar" + }; + + router.push(setupRoutes[structureType]); + }; + + const handlePreferencesSave = (interests: ProjectType[]) => { + setProjectInterests(interests); + setShowPreferencesModal(false); + }; + + // Calculate mission control stats + const missionStats = useMemo(() => { + const unclassifiedCount = linkedAnomalies.filter((la) => { + // Check if this anomaly has been classified + return !classifications.some((c) => c.anomaly?.content === la.anomaly?.content); + }).length; + + const recentClassifications = classifications.filter((c) => { + const classDate = new Date(c.created_at); + const now = new Date(); + const diffHours = (now.getTime() - classDate.getTime()) / (1000 * 60 * 60); + return diffHours < 24; + }); - const needsProfileSetup = !profile?.username || !profile?.full_name; + // Check for upcoming unlocks + const lockedAnomalies = linkedAnomalies.filter( + (la) => la.unlocked === false || la.unlocked === null + ); + const nextUnlock = lockedAnomalies.length > 0 ? lockedAnomalies[0] : null; + + return { + awaiting: unclassifiedCount, + recentCount: recentClassifications.length, + lastClassification: recentClassifications[0], + nextUnlock, + hasUnlockingSoon: lockedAnomalies.length > 0, + }; + }, [linkedAnomalies, classifications]); + + // Structure deployment status + const structureStatus = useMemo(() => { + const telescopeAnomalies = linkedAnomalies.filter( + (la) => + la.automaton?.includes("telescope") || + la.anomaly?.anomalySet?.includes("telescope") || + la.anomaly?.anomalytype?.includes("planet") + ); + const satelliteAnomalies = linkedAnomalies.filter( + (la) => + la.automaton?.includes("Satellite") || + la.automaton?.includes("Weather") || + la.anomaly?.anomalySet?.includes("cloud") + ); + const roverAnomalies = linkedAnomalies.filter( + (la) => + la.automaton?.includes("rover") || + la.anomaly?.anomalySet?.includes("automaton") + ); + const solarAnomalies = linkedAnomalies.filter( + (la) => la.anomaly?.anomalySet?.includes("sunspot") + ); + + return { + telescope: { + deployed: telescopeAnomalies.length > 0, + count: telescopeAnomalies.length, + status: telescopeAnomalies.length > 0 ? `${telescopeAnomalies.length} targets` : "Deploy now", + }, + satellite: { + deployed: satelliteAnomalies.length > 0, + count: satelliteAnomalies.length, + status: satelliteAnomalies.length > 0 ? `${satelliteAnomalies.length} clouds` : "Unlocking...", + }, + rover: { + deployed: roverAnomalies.length > 0, + count: roverAnomalies.length, + status: roverAnomalies.length > 0 ? `${roverAnomalies.length} waypoints` : "Deploy now", + }, + solar: { + deployed: solarAnomalies.length > 0, + count: solarAnomalies.length, + status: solarAnomalies.length > 0 ? "Active" : "Join mission", + }, + }; + }, [linkedAnomalies]); + + // Track page view + useEffect(() => { + if (session?.user) { + posthog?.capture("game_page_viewed", { + user_id: session.user.id, + classification_count: classifications.length, + discovery_count: linkedAnomalies.length, + }); + } + }, [session, posthog, classifications.length, linkedAnomalies.length]); + + // Redirect unauthenticated users + useEffect(() => { + if (!isAuthLoading && !session) { + router.push("/"); + } + }, [isAuthLoading, session, router]); + + // Handle view navigation + const handleViewChange = (view: ViewMode) => { + console.log("[DEBUG] handleViewChange called with:", view); + console.log("[DEBUG] current activeView:", activeView); + setActiveView(view); + posthog?.capture("viewport_opened", { viewport: view }); + }; + + // Handle classify now action + const handleClassifyNow = () => { + // Find the first unclassified anomaly and navigate to it + if (structureStatus.telescope.count > 0) { + handleViewChange("telescope"); + } else if (structureStatus.satellite.count > 0) { + handleViewChange("satellite"); + } else if (structureStatus.rover.count > 0) { + handleViewChange("rover"); + } + }; + + const needsProfileSetup = !profile?.username || !profile?.full_name; + + // Loading state + if (!session) { + return ( +
+ Redirecting… +
+ ); + } + + if (loading) { + return ( +
+
+
+
+
+
+
+
+
+
+
+ ); + } + + // If in a viewport (not base), show full-screen viewport content + if (activeView !== "base") { + return ( +
+ {/* Background */} +
+ {}} + /> +
- // Redirect unauthenticated users to `/` (middleware may handle this server-side; this is a client fallback) - useEffect(() => { - if (!isAuthLoading && !session) { - router.push('/'); - } - }, [isAuthLoading, session, router]); - - // If not logged in, show minimal gate while redirect happens. - if (!session) { - return ( -
- Redirecting… -
- ); - } + {/* Viewport Header */} +
+
+ +

{activeView}

+
+
+ + {/* Viewport Content */} +
+
+ {activeView === "telescope" && } + {activeView === "satellite" && } + {activeView === "rover" && } + {activeView === "solar" && } + {activeView === "inventory" && } +
+
- // Lightweight skeleton while data loads to avoid blocking on dynamic chunk hydration. - // Important: do not gate the entire page on having data, or new users will see the - // skeleton forever. - if (loading) { - return ( -
-
-
-
-
-
-
-
-
-
- ); - } + {/* NPS Popup */} + {showNpsModal && session && ( + + )} - return ( -
- {/* Telescope Background - Full screen behind everything */} -
- console.log("Clicked anomaly:", anomaly)} - /> -
- - {/* Main Header */} - { - toggleDarkMode(); - posthog?.capture('theme_toggled', { new_theme: !isDark ? 'dark' : 'light' }); - }} - notificationsOpen={notificationsOpen} - onToggleNotifications={() => { - setNotificationsOpen((open) => { - posthog?.capture('notifications_toggled', { is_open: !open }); - return !open; - }); - }} - activityFeed={activityFeed} - otherClassifications={otherClassifications} - /> - -
- {/* Anonymous User Upgrade Prompt */} - - - {/* Activity Header - User profile and deployment status - Hidden when full screen */} - {!tabContentExpanded && !isFullScreen && ( - { - setLandmarksExpanded((prev) => { - posthog?.capture('landmarks_toggled', { is_expanded: !prev }); - return !prev; - }); - }} - /> - )} - - {/* Profile Setup Required */} - {needsProfileSetup && ( - { - posthog?.capture('profile_setup_modal_opened'); - setShowProfileModal(true); - }} - /> - )} - - {/* Main Tabbed Interface */} -
- - {/* Tab Navigation */} -
-
- {canScrollLeft && ( -
- )} -
- - {orderedTabs.map((tab, index) => ( - moveTab(tab.id, 'left')} - onMoveRight={() => moveTab(tab.id, 'right')} - canMoveLeft={index > 0} - canMoveRight={index < orderedTabs.length - 1} - onTabClick={handleTabClick} - /> - ))} - -
- {canScrollRight && ( -
-
- -
-
- )} + +
+ ); + } + + // Base view - main dashboard + return ( + Loading...
}> +
+ {/* Background */} +
+ {}} + />
- {/* Reorder hint - shows on first visit */} - {showReorderHint && ( -
-
-

- - - - New! Hover over tabs to see arrows and reorder them. Your layout is saved automatically. - -

- -
+

+ Complete your profile to unlock all features +

+
+ )} + + {/* Mission Control Section */} +
+

+ Mission Control +

+
+ {/* Anomalies Awaiting */} + {missionStats.awaiting > 0 && ( + } + title={`${missionStats.awaiting} Anomalies Awaiting`} + subtitle="Telescope targets ready to classify" + variant="action" + actionLabel="Classify Now" + onAction={handleClassifyNow} + /> + )} + + {/* Recent Classification */} + {missionStats.lastClassification && ( + } + title="Classification Confirmed" + subtitle={`${missionStats.lastClassification.classificationtype || "Discovery"} • ${ + new Date(missionStats.lastClassification.created_at).toLocaleDateString() + }`} + variant="status" + /> + )} + + {/* Unlock Progress */} + {missionStats.hasUnlockingSoon && ( + } + title="Satellite Unlocks Soon" + subtitle="Cloud formations awaiting analysis" + variant="progress" + progress={65} + /> + )} +
+
+ + {/* Your Structures Section */} +
+
+

+ Your Structures +

+ {preferences.projectInterests.length > 0 && ( + + )} +
+
+ {shouldShowStructure("telescope") && ( + } + name="Telescope" + status={structureStatus.telescope.status} + statusColor={structureStatus.telescope.deployed ? "green" : "muted"} + hasNotification={structureStatus.telescope.count > 0} + onClick={() => handleStructureClick("telescope", "telescope")} + data-structure="telescope" + /> + )} + {shouldShowStructure("satellite") && ( + } + name="Satellite" + status={structureStatus.satellite.status} + statusColor={structureStatus.satellite.deployed ? "blue" : "muted"} + onClick={() => handleStructureClick("satellite", "satellite")} + data-structure="satellite" + /> + )} + {shouldShowStructure("rover") && ( + } + name="Rover" + status={structureStatus.rover.status} + statusColor={structureStatus.rover.deployed ? "green" : "muted"} + onClick={() => handleStructureClick("rover", "rover")} + data-structure="rover" + /> + )} + {shouldShowStructure("solar") && ( + } + name="Solar" + status={structureStatus.solar.status} + statusColor={structureStatus.solar.deployed ? "amber" : "muted"} + onClick={() => handleStructureClick("solar", "solar")} + data-structure="solar" + /> + )} + } + name="Research" + status="Upgrades available" + statusColor="amber" + onClick={() => router.push("/research")} + /> + } + name="Inventory" + status="View minerals" + statusColor="muted" + onClick={() => handleViewChange("inventory")} + /> +
+
+ + + {/* Bottom Navigation (Mobile) */} + handleViewChange(item as ViewMode)} + telescopeNotification={structureStatus.telescope.count > 0} + satelliteNotification={structureStatus.satellite.count > 0} + roverNotification={structureStatus.rover.count > 0} + solarNotification={structureStatus.solar.count > 0} + /> + + {/* Notifications Sheet */} + + + + Activity + +
+ +
+
+
+ + {/* Profile Modal */} + + + + Complete Your Profile + + setShowProfileModal(false)} /> + + + + {/* NPS Popup */} + {showNpsModal && session && ( + )} -
- {/* Tab Content - Expands to full height when full screen is active */} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - + {/* Project Preferences Modal */} + setShowPreferencesModal(false)} + onSave={handlePreferencesSave} + initialInterests={preferences.projectInterests} + /> + +
-
-
- - {/* Notification Subscription */} -
- - - - - - Complete Your Profile - - - setShowProfileModal(false)} /> - - - - {showNpsModal && session && ( - - )} - - - - {/* PWA Install Prompt - Only for authenticated users */} - {session && } -
+ + ); +} + +export default function GamePage() { + return ( + Loading...
}> + + ); -}; +} diff --git a/app/setup/rover/page.tsx b/app/setup/rover/page.tsx new file mode 100644 index 00000000..55e42b20 --- /dev/null +++ b/app/setup/rover/page.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import GameNavbar from "@/src/components/layout/Tes"; +import RoverSection from "@/src/components/scenes/deploy/Rover/RoverSection"; +import TutorialWrapper, { ROVER_INTRO_STEPS } from "@/src/components/onboarding/TutorialWrapper"; +import UseDarkMode from "@/src/shared/hooks/useDarkMode"; + +export default function RoverSetupPage() { + const router = useRouter(); + const { isDark } = UseDarkMode(); + + return ( +
+
+ +
+ +
+ { + console.log("Rover tutorial completed"); + }} + > + + +
+
+ ); +} \ No newline at end of file diff --git a/app/setup/satellite/page.tsx b/app/setup/satellite/page.tsx new file mode 100644 index 00000000..b3ffd88f --- /dev/null +++ b/app/setup/satellite/page.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import GameNavbar from "@/src/components/layout/Tes"; +import DeploySatelliteViewport from "@/src/components/scenes/deploy/satellite/DeploySatellite"; +import TutorialWrapper, { SATELLITE_INTRO_STEPS } from "@/src/components/onboarding/TutorialWrapper"; +import UseDarkMode from "@/src/shared/hooks/useDarkMode"; + +export default function SatelliteSetupPage() { + const router = useRouter(); + const { isDark } = UseDarkMode(); + + return ( +
+
+ +
+ +
+ { + console.log("Satellite tutorial completed"); + }} + > + + +
+
+ ); +} \ No newline at end of file diff --git a/app/setup/solar/page.tsx b/app/setup/solar/page.tsx new file mode 100644 index 00000000..0dd029b2 --- /dev/null +++ b/app/setup/solar/page.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import GameNavbar from "@/src/components/layout/Tes"; +import SolarHealth from "@/src/components/scenes/deploy/solar/SolarHealth"; +import TutorialWrapper, { SOLAR_INTRO_STEPS } from "@/src/components/onboarding/TutorialWrapper"; +import UseDarkMode from "@/src/shared/hooks/useDarkMode"; + +export default function SolarSetupPage() { + const router = useRouter(); + const { isDark } = UseDarkMode(); + + return ( +
+
+ +
+ +
+ { + console.log("Solar observatory tutorial completed"); + }} + > + + +
+
+ ); +} \ No newline at end of file diff --git a/app/setup/telescope/page.tsx b/app/setup/telescope/page.tsx new file mode 100644 index 00000000..f7446d19 --- /dev/null +++ b/app/setup/telescope/page.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import GameNavbar from "@/src/components/layout/Tes"; +import TelescopeSection from "@/src/components/scenes/deploy/Telescope/TelescopeSection"; +import TutorialWrapper, { TELESCOPE_INTRO_STEPS } from "@/src/components/onboarding/TutorialWrapper"; +import UseDarkMode from "@/src/shared/hooks/useDarkMode"; + +export default function TelescopeSetupPage() { + const router = useRouter(); + const { isDark } = UseDarkMode(); + + return ( +
+
+ +
+ +
+ { + console.log("Telescope tutorial completed"); + }} + > + + +
+
+ ); +} \ No newline at end of file diff --git a/app/viewports/roover/page.tsx b/app/viewports/roover/page.tsx index c1b59804..cecf5578 100644 --- a/app/viewports/roover/page.tsx +++ b/app/viewports/roover/page.tsx @@ -9,33 +9,13 @@ export default function RoverViewportExpandedPage() { const router = useRouter(); return ( -
- Earth Background - -
+
+
-
- { - if (!open) router.push("/"); - }} - > - -
- -
-
-
+
+
); diff --git a/app/viewports/satellite/page.tsx b/app/viewports/satellite/page.tsx index 5fc4f953..2c835770 100644 --- a/app/viewports/satellite/page.tsx +++ b/app/viewports/satellite/page.tsx @@ -63,36 +63,16 @@ export default function SatelliteViewportExpandedPage() { })(); return ( -
- Earth Background - -
+
+
-
- { - if (!open) router.push("/"); - }} - > - -
- -
-
-
+
+
); diff --git a/app/viewports/solar/page.tsx b/app/viewports/solar/page.tsx index 265a6644..84709670 100644 --- a/app/viewports/solar/page.tsx +++ b/app/viewports/solar/page.tsx @@ -9,32 +9,14 @@ export default function SolarHealthViewportExpandedPage() { const router = useRouter(); return ( -
- Earth Background Image - -
+
+
- { - if (!open) router.push("/") - }} - > - -
- -
-
-
+
+ +
) } \ No newline at end of file diff --git a/public/fallback-GO8Z5TWONYn8u-ZQ8w2np.js b/public/fallback-6h6WhEmi6GVbw-DS42T1t.js similarity index 100% rename from public/fallback-GO8Z5TWONYn8u-ZQ8w2np.js rename to public/fallback-6h6WhEmi6GVbw-DS42T1t.js diff --git a/public/sw.js b/public/sw.js index 3b3089ff..d03f194a 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1 +1 @@ -if(!self.define){let e,s={};const a=(a,n)=>(a=new URL(a+".js",n).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(n,i)=>{const c=e||("document"in self?document.currentScript.src:"")||location.href;if(s[c])return;let r={};const t=e=>a(e,c),o={module:{uri:c},exports:r,require:t};s[c]=Promise.all(n.map(e=>o[e]||t(e))).then(e=>(i(...e),r))}}define(["./workbox-92923e46"],function(e){"use strict";importScripts("fallback-GO8Z5TWONYn8u-ZQ8w2np.js"),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/Campbells.jpg",revision:"11385cbf8fbc65ddbe1ee9cc4f61599c"},{url:"/Eric.jpg",revision:"57cd76a7a6645463439016cea20d348c"},{url:"/Fred.webp",revision:"20a4d0dd0bb457f26136b4cb3299bbbf"},{url:"/Planet.png",revision:"dadf5193009bd2284bff301dfb69a2d6"},{url:"/_next/app-build-manifest.json",revision:"f418883a59e8b1824b1fa01ad46a49ec"},{url:"/_next/static/GO8Z5TWONYn8u-ZQ8w2np/_buildManifest.js",revision:"b362bc6587ac0d49f4c8883e0cfffd0b"},{url:"/_next/static/GO8Z5TWONYn8u-ZQ8w2np/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/1151-b7d68f1322e7e7c3.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/1176-5f324c6d652a71dd.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/1216.cddc9b1bdc338fea.js",revision:"cddc9b1bdc338fea"},{url:"/_next/static/chunks/1363.9139665fccd9c7a0.js",revision:"9139665fccd9c7a0"},{url:"/_next/static/chunks/1402-db70e87dd0aaa75a.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/1567.c6634902c5a70b82.js",revision:"c6634902c5a70b82"},{url:"/_next/static/chunks/1673-247ff55ef7832fe3.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/1714-69c6268587164c61.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2038-67b6bf0710306f48.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2117-3f08714425d7b4a3.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/216-8e0dbe85dc469b3c.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2234.60d9ee493bfa1716.js",revision:"60d9ee493bfa1716"},{url:"/_next/static/chunks/2252-16175c36ab0b36cb.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2268-cbdad168331f2555.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2370-88cf2fb867efbba9.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2417-6a7500aa90d5775f.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2507-cee6d9689411d046.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2509-fc7017b5dc896aed.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2561.8ceb795e1a58925f.js",revision:"8ceb795e1a58925f"},{url:"/_next/static/chunks/2571-3dffe60ba6dc164a.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2729-2021e8d50f3f36f1.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2764-70e2b3fc5da3d7c9.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2852.ac25a49f39a6345f.js",revision:"ac25a49f39a6345f"},{url:"/_next/static/chunks/29.1c32b08d9067edce.js",revision:"1c32b08d9067edce"},{url:"/_next/static/chunks/2955-cc4260c441819f04.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/2963.f8ecad3ef10569f1.js",revision:"f8ecad3ef10569f1"},{url:"/_next/static/chunks/3188.d5da9771bc685cbe.js",revision:"d5da9771bc685cbe"},{url:"/_next/static/chunks/3256.5ff9735db57c5e31.js",revision:"5ff9735db57c5e31"},{url:"/_next/static/chunks/3292-c65d11cf5eb7ea50.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/3519-30ef23394d16a428.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/3553-5a3f66ed7d110bbc.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/376-8fb2227601d36e71.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/3892-655ac5977004f25c.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/4058-58cf3c0f227a6502.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/4257-2b1c0c5ee7d831c6.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/4314-78e5aff737494d7e.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/4419-0704c9619d7dd836.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/4565-bd4ecddb3c74fc6f.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/4617-7b4bfb3af308cbf3.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/4752-ead1d9a00ff0799c.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/4793.d52b74fcc1b22924.js",revision:"d52b74fcc1b22924"},{url:"/_next/static/chunks/5479-24bb95af51728a2b.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/5569-9bf5ebeb9c966fec.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/5583-2da3fdce8a49a823.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/5687-0d6dfcb7142de33b.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/5754.9fe54a30958cb897.js",revision:"9fe54a30958cb897"},{url:"/_next/static/chunks/5786.858a278f3619e9af.js",revision:"858a278f3619e9af"},{url:"/_next/static/chunks/5798-07fcea046cd8c7df.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/6187.e5c3fce0bd2c01f2.js",revision:"e5c3fce0bd2c01f2"},{url:"/_next/static/chunks/6489-5ee34b16822daa42.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/6592-6e84ca872d2b03be.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/6781.305558d259e85644.js",revision:"305558d259e85644"},{url:"/_next/static/chunks/6834-4b8b72b63c953eb8.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/6873-12de5600f07d687a.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/6943-c8c8e8cc58399f18.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/7091-093b2d29bec59b24.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/7194-7558f5bebe3e5d5d.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/7231-4e548080f78aed0d.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/7441-1fc10da04bdfcee6.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/7452-6679dcc0b14334a4.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/7647.356a6e60de5e7acd.js",revision:"356a6e60de5e7acd"},{url:"/_next/static/chunks/7790-4104f398bd998ed9.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/7834-8103bd721711041d.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/8091-212cbd034d81322a.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/8155.0e681ee25da7a4b3.js",revision:"0e681ee25da7a4b3"},{url:"/_next/static/chunks/8184.3feb12a2805f25fc.js",revision:"3feb12a2805f25fc"},{url:"/_next/static/chunks/8280-4b558f2983782f8e.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/8302-0411d723b2092b7f.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/8346-efda2ccb39e438dc.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/8627-ece0cf0d92a2cf80.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/8642-806efa449c5defa6.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/8703-8c8720bd5176ac94.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/884-6e87a71d04e80b25.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/8917-e506b0696998c2e7.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/8953-8aa3871a96d78b98.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/9106.054c34baea7e1e2b.js",revision:"054c34baea7e1e2b"},{url:"/_next/static/chunks/9208-00530379d3472d72.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/9299-b52d19e591de95b2.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/9387-779bc1719f3ac753.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/9592-f5dcf85286aefa12.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/9828.8f0a5e098250b33b.js",revision:"8f0a5e098250b33b"},{url:"/_next/static/chunks/9854-9e508be7190fa9b0.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/9882.4761fa205905a733.js",revision:"4761fa205905a733"},{url:"/_next/static/chunks/9da6db1e-e4043ef83035bbcf.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/ad2866b8-d9a081783e4ea5b3.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/_not-found/page-98b09e372b5d8f95.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/account/page-a227a01cec8c0396.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/activity/deploy/page-d1acc66c5524b8f6.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/activity/deploy/roover/page-87cadb4c81a08849.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/apt/page-21fb3cf1f72b4dcd.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/auth/page-4a5e6f4b7ed80998.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/auth/register/page-ed79460e4cb255fc.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/extraction/%5Bid%5D/page-40a03d329de381b9.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/game/page-817d78c55f04db70.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/inventory/classifications/page-2b0310feb23f58fe.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/inventory/page-b6c34fa70e746938.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/layout-00eba830eb7100c3.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/layouts/activity/layout-b8481d06d04e4f01.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/layouts/activity/page-29e302e2917279b1.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/leaderboards/sunspots/page-7391120d8ec7a4ce.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/next/%5Bid%5D/page-ea722af97c8e241e.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/offline/page-eed6593f1d73de96.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/page-89bfd2cf1ab83421.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/planets/%5Bid%5D/page-b2e1f8fc4708809c.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/planets/clouds/%5Bid%5D/page-e8667a9dd80c269a.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/planets/edit/%5Bid%5D/page-3ae54dacc24b24fc.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/planets/paint/%5Bid%5D/page-f76ac959822cf8b0.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/posts/%5Bid%5D/page-975ff21562255a0b.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/posts/surveyor/%5Bid%5D/page-1706b93f0b2d108c.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/privacy/page-600810fa6978abd6.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/research/page-b2f70ce45b0341bc.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/scenes/uploads/page-0330f935cdd49d82.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/structures/balloon/%5Bproject%5D/%5Bid%5D/%5Bmission%5D/page-9285b153040e216a.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/structures/balloon/%5Bproject%5D/page-d5defee487e6306f.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/structures/balloon/page-d53d74df120ff86e.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/structures/cameras/page-6e1bed15f9fa232d.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/structures/seiscam/%5Bproject%5D/%5Bid%5D/%5Bmission%5D/page-930938cb93de1cb9.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/structures/seiscam/page-b2307e4979d058b0.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/structures/telescope/%5Bproject%5D/%5Bid%5D/%5Bmission%5D/page-095648fbad53ca3f.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/structures/telescope/%5Bproject%5D/page-b3529f7167d21399.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/structures/telescope/page-5ef194bfeae78fc3.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/terms/page-9152b1804afe8f78.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/tests/page-9e0915aeaeb9d54a.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/viewports/roover/page-fed5f0db0b11d335.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/viewports/satellite/deploy/page-feaeccc092b6a313.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/viewports/satellite/page-79cef5943cfbdd53.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/app/viewports/solar/page-8c104a6ed4703716.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/b536a0f1-ee91348fe72fbac9.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/bd904a5c-4ebb2c5169124366.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/c16f53c3-42f37dfb9f90c107.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/fd9d1056-79b6cfb6f9ff6563.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/framework-56dfd39ab9a08705.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/main-2e6b6772a23b103a.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/main-app-ec79de62d0df1dfd.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/pages/_app-3c9ca398d360b709.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/pages/_error-cf5ca766ac8f493f.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-5eb15f7fe9d72029.js",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/_next/static/css/5de508b88cbafd37.css",revision:"5de508b88cbafd37"},{url:"/apple-touch-icon.png",revision:"84dfa551865290b103d7a506ce294058"},{url:"/assets/Archive/Inventory/Items/AeroCameraLevel1.png",revision:"b0095b88572678b14b751164bdafec8a"},{url:"/assets/Archive/Inventory/Items/AeroCameraLevel1NoBg.png",revision:"d92db950b319ff898b4b3b028998ee23"},{url:"/assets/Archive/Inventory/Items/Coal.png",revision:"f2c12cb628c5f1befb00934ef590eed9"},{url:"/assets/Archive/Inventory/Items/Fuel.png",revision:"aa39fc0ee9e5310fbd8df9c9c9228caf"},{url:"/assets/Archive/Inventory/Items/GoldenTelescopeLevel1Original.jpg",revision:"4ed335ca5ad7579441fcae7d4c311435"},{url:"/assets/Archive/Inventory/Items/GoldenTelescopeLevel1noBg.png",revision:"fe61acf446aa92820168b915e1d64e3b"},{url:"/assets/Archive/Inventory/Items/Silicates1.png",revision:"57aeda36fe8e660f9e7e388aa9cae035"},{url:"/assets/Archive/Inventory/Planets/Dump/65Binned.png",revision:"384b67f48a661347251312232e9ebf85"},{url:"/assets/Archive/Inventory/Planets/Dump/65Cover.png",revision:"fa14b14d9ffb751661fc22adfc3d8b7c"},{url:"/assets/Archive/Inventory/Planets/Dump/65Phase.png",revision:"f66e7998c7e7b89ca58a8073de29bb95"},{url:"/assets/Archive/Inventory/Planets/Dump/66Cover.png",revision:"9b53b2ac283656684d5ea5a00a8d94f5"},{url:"/assets/Archive/Inventory/Planets/Europa.png",revision:"5e01baea533129113d9d34f6de5c9a35"},{url:"/assets/Archive/Inventory/Planets/Mars.png",revision:"56b8fe5594110efe61945113cd019476"},{url:"/assets/Archive/Inventory/Planets/Planet59.png",revision:"428c91489994b502f47f529fbbe1747c"},{url:"/assets/Archive/Inventory/Planets/Planet63.png",revision:"d05e92b091ce34dd2a405f3c403b7698"},{url:"/assets/Archive/Inventory/Planets/Planet64.png",revision:"bc43db3cae98d2907e439596807805fa"},{url:"/assets/Archive/Inventory/Planets/PlanetBg.png",revision:"aa73eaf3e0304e7be31de35fe4a86d3a"},{url:"/assets/Archive/Inventory/Planets/SectorBg.png",revision:"6278cb9d4a7080eaacf2b2ffb250c567"},{url:"/assets/Archive/Inventory/Planets/rover.png",revision:"28e46f4d9a871a1afec830c482e726fb"},{url:"/assets/Archive/Inventory/Structures/Telescope.png",revision:"d2142a6fb24edb6ed6f40638e895068f"},{url:"/assets/Archive/Inventory/Structures/Telescope2.png",revision:"94b0a790fff053144e2bb63c1143b173"},{url:"/assets/Archive/Inventory/Structures/TelescopeReceiver.png",revision:"adbdc303fd7b86dd08761d23b607882e"},{url:"/assets/Archive/Inventory/Structures/TelescopeReceiverStruct.png",revision:"6cf3fb4e2f292ccfc356d28b05dccff0"},{url:"/assets/Archive/Onboarding/Bg.png",revision:"3d1d9e5ede244694750492b0bfc94be6"},{url:"/assets/Archive/Onboarding/Missions/Crucible/CrucibleGif.webp",revision:"e86425b7abb107c6a8934b77d5130631"},{url:"/assets/Archive/Onboarding/Missions/Crucible/CrucibleImage1.png",revision:"0000a3fca273b34662387215e2b9038b"},{url:"/assets/Archive/Onboarding/Missions/Crucible/CrucibleImage2.png",revision:"2fdeaf5d0cbd2aabe96a9735ca76272a"},{url:"/assets/Archive/Onboarding/Missions/Crucible/CrucibleImage3.png",revision:"dcd55aab5f70f29b13f65a5005bad2a1"},{url:"/assets/Archive/Onboarding/Missions/Emergence/EmergenceImage1.png",revision:"05738c37105a229f6dc67e3f95e9076d"},{url:"/assets/Archive/Onboarding/Missions/Emergence/EmergenceImage2.png",revision:"1687d7ef021d977dba7c13dde60a40d9"},{url:"/assets/Archive/Onboarding/Missions/Emergence/EmergenceImage3.png",revision:"bcce9d5b4cd6cc5505d1490e3305db65"},{url:"/assets/Archive/Onboarding/Missions/Emergence/EmergenceImage4.png",revision:"e05fd92c25df01e070cfc22b3bf89e9c"},{url:"/assets/Archive/Onboarding/Missions/Emergence/EmergenceImage5.png",revision:"35d9a1b812a520dfc243680c8ae09fad"},{url:"/assets/Archive/Onboarding/Missions/Emergence/TALONOVA (5)_clipdrop-enhance.png",revision:"b59102a0a7655c275cdc689bfb71f476"},{url:"/assets/Archive/Onboarding/Missions/Emergence/cartographer.png",revision:"bc0bd32c00672651498e4ac27f6ec911"},{url:"/assets/Archive/Onboarding/Missions/Emergence/cartographer.svg",revision:"67a1cbec05d00c226cd86b0a981f8dac"},{url:"/assets/Archive/Onboarding/Missions/Emergence/guardian.png",revision:"c4cb1a5d06f4ecf36d16ceb5c92f87d8"},{url:"/assets/Archive/Onboarding/Missions/Emergence/guardian.svg",revision:"b4759ba8ab3a66362c97209aa84b013e"},{url:"/assets/Archive/Onboarding/Missions/Emergence/navigator.png",revision:"43ef8636fca24a9469c7c5f22296d6af"},{url:"/assets/Archive/Onboarding/Missions/Emergence/navigator.svg",revision:"3f6467f410d7818933af8bb513a38fe3"},{url:"/assets/Archive/Onboarding/Missions/Navigate/NavigateImage1.png",revision:"9b53b2ac283656684d5ea5a00a8d94f5"},{url:"/assets/Archive/Onboarding/Missions/Navigate/NavigateImage2.png",revision:"0beb47aeb676517fd8ba8ba594b08656"},{url:"/assets/Archive/Onboarding/Missions/Navigate/NavigateImage3.png",revision:"5ac7701589b2c66636ad0b539a195bb6"},{url:"/assets/Archive/Onboarding/Missions/Navigate/ab936_kepler_view_of_a_ringed_planet_saturncore_in_outer_space__bb9ae32e-743a-4893-bebf-31f89ed21559.png",revision:"449a146738f989296326fcb4c8a0871f"},{url:"/assets/Archive/Onboarding/Missions/Silfur/GameItem1.png",revision:"5bd91fa3d1247aca7dc3cb29282a2fc9"},{url:"/assets/Archive/Onboarding/Missions/Silfur/GameItem2.png",revision:"9132028c444969591293e9ff14000252"},{url:"/assets/Archive/Onboarding/Missions/Silfur/GameItem3.png",revision:"d9d7495c85a1a4968ecc0fef2ba9ab64"},{url:"/assets/Archive/Onboarding/Missions/Silfur/SilfurImage1.png",revision:"449a146738f989296326fcb4c8a0871f"},{url:"/assets/Archive/Onboarding/Missions/Silfur/SolarShip1.png",revision:"972e5ba346bcfdc104ecfb4565435c0d"},{url:"/assets/Archive/Onboarding/Missions/Silfur/SolarShip2.png",revision:"00fa05cae65591850cd47f8df207a5f6"},{url:"/assets/Archive/Onboarding/Missions/Silfur/SolarShip3.png",revision:"74bf7d8b62b8b8ce829c45e353e819cb"},{url:"/assets/Archive/Surface.mp4",revision:"ebe3e35e7b78bc3b581ed95fcbd2d78d"},{url:"/assets/Archive/audio/Instrumental 02; Inventory.mp3",revision:"12e840bd86f49a17ad237e77cff0b980"},{url:"/assets/Archive/audio/Instrumental 04; Search and ecounter aliens-01.mp3",revision:"b4eec1a6d61d18387afce77193373941"},{url:"/assets/Archive/audio/Instrumental 05 v.6; boss fight.mp3",revision:"8db9a25951f264a4fbe96a696705f804"},{url:"/assets/Archive/audio/Instrumental 07; Ice Planet.mp3",revision:"271df8ef17f2dc2583f213595d9e3d81"},{url:"/assets/Archive/audio/Instrumental 08; Barren Planet.mp3",revision:"eb7f1c4896252caed2663da7cf40110b"},{url:"/assets/Archive/audio/WakeUp.mp3",revision:"4a2c4aa351cea6e2e62bddbe725794a2"},{url:"/assets/Archive/r2d2.png",revision:"0beaf06406d6265cb92aa50baea8c578"},{url:"/assets/Archive/ui/planet.svg",revision:"c268d8db01b8175ed7a1355739ca3754"},{url:"/assets/Automatons/ExploreRover1.png",revision:"50da0c3cd9e4b905941f771a382df800"},{url:"/assets/Automatons/Sat.png",revision:"b0b80fb493eff78ceeacd3e471c87fdb"},{url:"/assets/Backdrops/BioStations/Desert.jpg",revision:"4d30a152ec16dc975201a0f54fdb6a91"},{url:"/assets/Backdrops/Earth.png",revision:"1beae52a03baaa96f589519a325e5d27"},{url:"/assets/Backdrops/Mercury.png",revision:"49ddca4b14fbb0853b26c00c9cd186ca"},{url:"/assets/Backdrops/Negotiations.jpg",revision:"be23b513dd06d2ae40ab255357eeb536"},{url:"/assets/Backdrops/Venus.png",revision:"313b0a6b0aed2fe62d82edb31d0216d3"},{url:"/assets/Backdrops/View.jpeg",revision:"667d4e6811b0953b705e0b905c988948"},{url:"/assets/Backdrops/View.mp4",revision:"f001f7cbedb0aa8351700e194c3457cb"},{url:"/assets/Backdrops/background1.jpg",revision:"5c77cfacc630046769718c3ef38865b2"},{url:"/assets/Backdrops/background2.jpeg",revision:"f530c13582f2ebe7e7cdbba67efa6105"},{url:"/assets/Backdrops/cons.jpg",revision:"5f469d57a188d458313346319a7a6c23"},{url:"/assets/Backdrops/garden.png",revision:"893bd51dee46b01967c65b28a6e9560c"},{url:"/assets/Backdrops/gardens.png",revision:"8edcc3f921543cdf7d41bf75e33a9072"},{url:"/assets/Backdrops/gasgiant.jpeg",revision:"02ab340dd21ebd3494cfb9816e0bb473"},{url:"/assets/Backdrops/image.jpeg",revision:"65e83f2cf3a4ca02c1e006db1c802164"},{url:"/assets/Backdrops/satellite.jpg",revision:"ea79001d9cfc83c4eb5dbf0000fc3e77"},{url:"/assets/Bodies/sun-texture.png",revision:"b829dfb4a0a036c4727c5d61104c197b"},{url:"/assets/Captn.jpg",revision:"84dfa551865290b103d7a506ce294058"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step1.jpeg",revision:"4ce724c32f04466cb09c78f4591ecf6b"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step2.png",revision:"c8a926b536d4165efd3fec0ea8a75159"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step3.png",revision:"f130eb4200a4aeb8fecaabc69fef9b93"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step4.png",revision:"417c0e61d96e2ea6e1ce24f795512d61"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step5.png",revision:"62b7e6849c9e3a3b0383c2493965653b"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step6.png",revision:"1b4382df0d9eeb73e9fbd03b8273e3d3"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step7.jpeg",revision:"4ce724c32f04466cb09c78f4591ecf6b"},{url:"/assets/Docs/Curves/Step1.png",revision:"b36d119c989dcfa50a51ab039886990d"},{url:"/assets/Docs/Curves/Step2.png",revision:"15c9ea1ad29f1778c2ea597c281bd65f"},{url:"/assets/Docs/Curves/Step3.png",revision:"2b8841b66eda2f24223c5f49a8e86c0e"},{url:"/assets/Docs/Curves/Step4.png",revision:"d3fdaa57e975083d0e5e4f1c4caa2c1c"},{url:"/assets/Docs/Guide/AvailableMissions.png",revision:"a55e93d58a491bdb0555da49ebb52372"},{url:"/assets/Docs/Guide/EditMode.png",revision:"4e90cc507cceb5efefee2e1d148ceb3a"},{url:"/assets/Docs/Guide/FirstClassification.png",revision:"7b919dad4b454cd7649864414965d549"},{url:"/assets/Docs/Guide/OnEarth.png",revision:"34d2fd1d740215688653db9df06c9c8a"},{url:"/assets/Docs/Guide/ResearchMissions.png",revision:"b7cd1e08307784a993f95ba484f0f9fa"},{url:"/assets/Docs/Guide/ResearchModal.png",revision:"05b6622b931c0004e1927e4b22deb8ff"},{url:"/assets/Docs/Guide/StructureModal.png",revision:"d6564905be3985e89e32605069b9d65d"},{url:"/assets/Docs/Guide/TravelInfo.png",revision:"56f24df7bcc02b10625a66ed6921087b"},{url:"/assets/Docs/Guide/TutorialExample.png",revision:"62a7f43e5c37660d7bbe4123b70c0e6a"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step1.jpeg",revision:"7c6325133dd48f4c8b82e9f7ad4e3980"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step2.png",revision:"c66d776d990119e59ca0671fe1ca445b"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step3.jpeg",revision:"d57d7e20d708adaff94032b1c82678b2"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step4.png",revision:"4787699232a87527248fbea7bd3cf468"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step5.png",revision:"a2b1e8a1938cce3d75ed30a5e83c0edf"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step6.jpeg",revision:"1d335e4736b9a3a2382898ee7aae6081"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step7.png",revision:"eb706f8365606dfb111d425bcac6d7ff"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step8.png",revision:"1e5fe9c1ae3fcaab5d693b993470334b"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step9.jpeg",revision:"53c5c82ec397f772d33f2c882583ad50"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/crater.png",revision:"84ffc72703cdec2a7b441826e8ebdeb6"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/disk.png",revision:"fb4017b0db388f52e150a32635c824b7"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/dotted.png",revision:"81600b12cb517932d75e70d749dd2adf"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/gravitywave.png",revision:"9ae251aa1154678a3f709e50451b2203"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/other-general.png",revision:"866d16a12862c63f13fe3ac9b06faf6d"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/ozone.png",revision:"cc469f63fb5195a3eb557f6a1eda2b80"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/streak.png",revision:"6b15aba938944735d03e5692bf42b9e3"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/twilight.png",revision:"9645bc077029d14a538f4dbb4533eb2d"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/vortex.png",revision:"6dbfe2e209d7c1270a38ff67c8a505b2"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step1.png",revision:"6f64fe0a408aac02ce873674f434f181"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step2.png",revision:"f45ca57f448de583f78e9a75ea33fad9"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step3.png",revision:"f818e8e0c32dd94b638687195a8bfa0d"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step4.png",revision:"935dc3518abb08d87578938b920a52d9"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step5.png",revision:"ac98f087e6ea0c548d079df05b0aa137"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step6.png",revision:"d731a38125b393ccfb3b6dc25221f704"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step7.png",revision:"6cd3bec383dfd6fa4f6d68cf4e0c6a9b"},{url:"/assets/Docs/Satellites/Planet-Four/Step1.jpeg",revision:"b3e5f0774b0345e9fd332f44048e374d"},{url:"/assets/Docs/Satellites/Planet-Four/Step2.jpeg",revision:"fe7050776b47c2089308129b138575e0"},{url:"/assets/Docs/Satellites/Planet-Four/Step3.jpeg",revision:"4b1298adb19b51f9e2298a6a28f8c8f4"},{url:"/assets/Docs/Satellites/Planet-Four/Step4.gif",revision:"f824b0b7958b1d1553e7e904b0379327"},{url:"/assets/Docs/Satellites/Planet-Four/Step5.gif",revision:"7b347c35e21d132143aa1e39d567243b"},{url:"/assets/Docs/Satellites/Planet-Four/Step6.jpeg",revision:"e2fd14f8fab251bbe1b8ba629ed491e4"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step0.jpeg",revision:"b7a2a99748c14b7de66cfd547e9ac5ed"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step1.png",revision:"4b83160b7c815fc45e485e702e4c8143"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step2.gif",revision:"84ac95a7be981b61672edb3d2f696db5"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step3.gif",revision:"bb37732c200620e0655c02e03c3e623e"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step4.png",revision:"6bfde27fabf4faed90da2d79e49c4f64"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step5.png",revision:"b9f977c7c87ab60d0d86a3430ec824f0"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/Step1.png",revision:"ea45bef432497c0692f4eb37ece44dec"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/Step2.png",revision:"07af3996151ce251a1fc5a5d863ed8b1"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/Step3.png",revision:"9743622ff48e5763ea856f9497c23256"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/Step4.png",revision:"3927be877440eb87ba7cca394f3e3464"},{url:"/assets/Docs/Telescopes/DiskDetector/Step1.png",revision:"922c8aa86d86f212d15bbdf51f401e2d"},{url:"/assets/Docs/Telescopes/DiskDetector/Step2.png",revision:"fa164673ae38a1768dbadf1391009153"},{url:"/assets/Docs/Telescopes/DiskDetector/Step3.png",revision:"51d413923e5d490c104a0109a1866e16"},{url:"/assets/Docs/Telescopes/DiskDetector/Step4.png",revision:"e5a14f2e8a50da4767b498b7794eafa8"},{url:"/assets/Docs/Telescopes/Sunspots/Step1.png",revision:"8fe0b00b92288b74862e7da82482a436"},{url:"/assets/Docs/Telescopes/Sunspots/Step2.png",revision:"a2fc4469a6172c691ebb2c7a411c5eae"},{url:"/assets/Docs/Telescopes/Sunspots/Step3.png",revision:"5f890b7d285a3a917b9bc14beb65ee20"},{url:"/assets/Docs/Telescopes/Sunspots/Step4.png",revision:"78a1bbd2bc27dfa78a31ebf14c645e66"},{url:"/assets/Docs/Telescopes/SuperWASP/1.gif",revision:"e7b5d9712baffde7aa1ee28bb9d77663"},{url:"/assets/Docs/Telescopes/SuperWASP/2.gif",revision:"e16a5836f1c7b4c085469c6fcaa0ecc9"},{url:"/assets/Docs/Telescopes/SuperWASP/3.gif",revision:"8782fcec46ccd9cfde7ea2881f6efa9b"},{url:"/assets/Docs/Telescopes/SuperWASP/4.gif",revision:"e1d08e4f688599c19177dc49a9de5e90"},{url:"/assets/Docs/Telescopes/SuperWASP/5.gif",revision:"efb001734bd5a3bbb2fb8c26557b0bc9"},{url:"/assets/Docs/Zoodex/zoodex-IguanasFromAbove/Step1.jpeg",revision:"0f8d074c5eb03331e0ad80320133b1a0"},{url:"/assets/Docs/Zoodex/zoodex-IguanasFromAbove/Step2.jpg",revision:"2427f36af822ee6ac99ab2b841d7d7e7"},{url:"/assets/Docs/Zoodex/zoodex-IguanasFromAbove/Step3.jpg",revision:"6ea8d338f543905d8ec4991f92cd88fe"},{url:"/assets/Docs/Zoodex/zoodex-IguanasFromAbove/Step4.jpg",revision:"d94a66ef8803352c9b3febb5866e7c9a"},{url:"/assets/Docs/Zoodex/zoodex-IguanasFromAbove/Step5.jpg",revision:"15c51a9bb682ab75dc3418e7e2f89ab7"},{url:"/assets/Docs/Zoodex/zoodex-PenguinWatch/Step1.jpeg",revision:"5bc4c6b5c384409446bcfacff9d343e4"},{url:"/assets/Docs/Zoodex/zoodex-PenguinWatch/Step2.jpeg",revision:"f65c9b2ab621f447435eb8f80d696c39"},{url:"/assets/Docs/Zoodex/zoodex-PenguinWatch/Step3.jpeg",revision:"3636a781f06b0cedf0f77890e7cb2b39"},{url:"/assets/Docs/Zoodex/zoodex-PenguinWatch/Step4.jpeg",revision:"632e1d7643b1826f4e62ac34a8d7e944"},{url:"/assets/Docs/Zoodex/zoodex-PlanktonPortal/Step1.png",revision:"ececc0ef084a126840e926f2476a4c5d"},{url:"/assets/Docs/Zoodex/zoodex-PlanktonPortal/Step2.png",revision:"17c017e3c1995aef55fb07138b654640"},{url:"/assets/Docs/Zoodex/zoodex-PlanktonPortal/Step3.png",revision:"fea9931a6f258b66eb9cfa856cb26ceb"},{url:"/assets/Docs/Zoodex/zoodex-PlanktonPortal/Step4.png",revision:"fe09f67239daf3db60c449b455334ac6"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step1.jpeg",revision:"6625e4a0f0aa7a23fe11860f53d474a6"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step2.png",revision:"1abf2dd1128700126c680993d60b522b"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step3.png",revision:"c478d6469168910e1ae49e6cd0d696f8"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step4.jpeg",revision:"6c3c551ee26c2934b696efd4946643c0"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step5.jpeg",revision:"d444b87070bf268e585fc387475aa4f0"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step6.png",revision:"ff7430f42f1fa38b113bcb3abc86df3d"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step7.jpeg",revision:"2969154c39321c95df5dad26319c49fb"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Antipathes-Atlantica.jpeg",revision:"80bbfa0fd91738832664e429d17e2c27"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Antipathes-Furcata.jpeg",revision:"5f1d6dbf427592495eebdffe02bb86dc"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Bebryce-Sp.jpg",revision:"81be15d42949c3118106bc733d8957b2"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Ellisellidae.jpeg",revision:"22d5fbe1e8f65d46095bcbf2368f01e2"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Fish.jpeg",revision:"ac4263e0478bf21d1d6507ee22b6db26"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Madracis-sp..jpg",revision:"8a5d8bb7183d4a5e09a23bab582445cb"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Madrepora-Sp.jpeg",revision:"5cb6bd705a4136856f51bd50c4baba6f"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Muricea-Pendula.jpg",revision:"13577b7e19c3575500efce98c3299918"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Paramuriciade.jpg",revision:"5b7eac3ddcf4627d508df6de4d4d5d29"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Spoonge.jpeg",revision:"9644a6de3ad0e3fea08534746cba4167"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Stichopathes.jpg",revision:"351a22ac8cb2839b2f0fe6767e2859f6"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Swiftia-Exserta.jpeg",revision:"c81564435c0522b8f90b14155cf77e08"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Thesea-Nivea.jpeg",revision:"9283e0faf204d65afe9fc06b088f3b37"},{url:"/assets/Docs/Zoodex/zoodex-nestQuestGo/Step1.png",revision:"9ac89ae431688bbc59f0172e427567ab"},{url:"/assets/Docs/Zoodex/zoodex-nestQuestGo/Step2.png",revision:"484e0de8ee3614b8b5eadcad47c0577d"},{url:"/assets/Docs/Zoodex/zoodex-nestQuestGo/Step3.png",revision:"484e0de8ee3614b8b5eadcad47c0577d"},{url:"/assets/Docs/Zoodex/zoodex-nestQuestGo/Step4.png",revision:"52095cabc0f3f15fcb539dfd19c08df2"},{url:"/assets/Docs/Zoodex/zoodex-nestQuestGo/Step5.png",revision:"013eef5ab240240bdd2c3d61c45ed704"},{url:"/assets/Images/capacity-1.png",revision:"b11baa8002d956d83035dc56d8b196bd"},{url:"/assets/Images/capacity-2.png",revision:"3aa28af2d9970405993a674875ccc05c"},{url:"/assets/Images/capacity-3.png",revision:"fb2edead7a418ac1ccc3b2b8658582b1"},{url:"/assets/Images/landing1.jpg",revision:"d614027097dc6c4b482b1ebe6dd4ca06"},{url:"/assets/Images/landing2.jpg",revision:"37295f0d482d11f6fec67a8e91f025e5"},{url:"/assets/Images/landing3.jpg",revision:"1fcab1a9ab528d50b39491d5945ae996"},{url:"/assets/Images/landing4.jpg",revision:"824d43b5f6c0b7cdbe308e839e55c2ab"},{url:"/assets/Images/landing5.jpg",revision:"4661f0b795c93f4fd452f7c11064bd18"},{url:"/assets/Images/landing6.jpg",revision:"0f961272ab9e266be515eb843400c95c"},{url:"/assets/Images/power-1.png",revision:"6dc4d6fc65a6a4a4626dad5b223f0b15"},{url:"/assets/Images/power-2.png",revision:"02e4c35f2cc98683d8446af714467aed"},{url:"/assets/Images/power-3.png",revision:"e85e80fc437cbdd37ea281ebad6861ca"},{url:"/assets/Images/speed-1.png",revision:"c2c11563476fa94cd79c529cae68cc2f"},{url:"/assets/Images/speed-2.png",revision:"14f619efe74218e9e0ca37e28a6d8ac8"},{url:"/assets/Images/speed-3.png",revision:"83b2fa733f09488123b88e404c7db635"},{url:"/assets/Items/22.png",revision:"aaf676880015ae1fca6670026e9edab5"},{url:"/assets/Items/28.png",revision:"58a70597ee0a85b0e929049c1518496a"},{url:"/assets/Items/Alloy.png",revision:"1ab072f72ce2d014a9d176b2780230ec"},{url:"/assets/Items/AutoController.png",revision:"65aa5761bcfdc2d9c1086afbde0433f7"},{url:"/assets/Items/AutomatonController.png",revision:"9bec20ac77c7a10c9e0863903094b147"},{url:"/assets/Items/CameraReceiver.png",revision:"e3d4b72f37cfe9b03a8a4231c10a1735"},{url:"/assets/Items/Chromite.png",revision:"48459b3c96b2eb0e03fa0303ba8dbac6"},{url:"/assets/Items/Coal.png",revision:"a20225d254d9c7c0b04bf01a89b8ac58"},{url:"/assets/Items/Copper.png",revision:"01eae5b57bb8475f67be87c38ebe52c0"},{url:"/assets/Items/Greenhouse.jpg",revision:"89ce4d9952c5fcca5246af132bc33cee"},{url:"/assets/Items/Greenhouse.png",revision:"dbf4ec7c99a5ac515de500e7f4bde2aa"},{url:"/assets/Items/Helicopter.png",revision:"8fabf68dab697ac41b1ddb5d23952bdb"},{url:"/assets/Items/Ice.png",revision:"655fc37ae85f470103f864a39e8ac847"},{url:"/assets/Items/Iron.png",revision:"7a673727c900b1a253cc2fe9b96c2ac9"},{url:"/assets/Items/Launchpad.jpg",revision:"76324cf59091e8e07f0f76565bf0918c"},{url:"/assets/Items/Lidar.png",revision:"50a112cd26a8e868e64f6db7c5dc0ea4"},{url:"/assets/Items/MiningStructure.png",revision:"96640003bac02c57d277f011c2e7d1d7"},{url:"/assets/Items/Nickel.png",revision:"47473b610a62cd8c4a2ec26c86666e60"},{url:"/assets/Items/Pokedex.png",revision:"7c5bdbbdb6942471f6dee26f35f57464"},{url:"/assets/Items/Research.png",revision:"05e5a3b0f4398c4b7dd5293822295d94"},{url:"/assets/Items/Rocket.png",revision:"7e7eece84a6324977c3e5077b8446aa7"},{url:"/assets/Items/Roover.gif",revision:"ba881bf19d84414d2b61853ae2c262a1"},{url:"/assets/Items/Scoper.png",revision:"7637fbddf3ea93f9fafd012a4ddcea6f"},{url:"/assets/Items/Silicon.png",revision:"01eae5b57bb8475f67be87c38ebe52c0"},{url:"/assets/Items/Telescope.jpg",revision:"23071871f6142f8dd0c7c3a3f7b07788"},{url:"/assets/Items/Telescope.png",revision:"2289bb2e88c120250b9c70eb2fe6cc75"},{url:"/assets/Items/TransitingTelescope.png",revision:"c57789844332d12cfbd36e2a46435130"},{url:"/assets/Items/Vector.png",revision:"1930faa9e00cffd9cefb4764c5cecb11"},{url:"/assets/Items/WeatherBalloon.png",revision:"7e7c0065a9be091098d5cd5fe2199dd6"},{url:"/assets/Items/Zoodex.png",revision:"57d29ff98723319efa6f03093fafec3f"},{url:"/assets/Items/camerars.png",revision:"74b20c14a9c01178533eed11bb93becc"},{url:"/assets/Items/miningstation.png",revision:"60f6398da814012af4fd64c94af65729"},{url:"/assets/Items/roover.png",revision:"eacaaf0fab67591794313b11bfd166a7"},{url:"/assets/Items/rover.svg",revision:"61143d0da3adec08b3325962b6198ee4"},{url:"/assets/Items/sw.js",revision:"aa14caf35328d6ef2b804009fce82acc"},{url:"/assets/Planets/Earth.png",revision:"0fa6e6f81ec8207ee4ae82f6a37ce617"},{url:"/assets/Planets/Mars.png",revision:"3f4ed19d54bed733b121f73d6a7e8ec9"},{url:"/assets/Planets/Mercury.png",revision:"15dd11dcd44ddf6cb2dd46525c45e1b7"},{url:"/assets/Planets/Moon.png",revision:"c9a970a4c0cfe6cd816ce0229069384b"},{url:"/assets/Planets/Venus.png",revision:"f4f6739d5f289093699d8a8557d8aaf8"},{url:"/assets/Template.png",revision:"d12bd71d3d676bc16e638dc0434c2f81"},{url:"/assets/Viewports/Satellite/Satellite_Tile1.png",revision:"238c6328351a9fd56b71a02dc2738a4e"},{url:"/assets/Viewports/Satellite/Satellite_Tile2.png",revision:"4743b76f39f6c16a9e8e3195c7617032"},{url:"/assets/Viewports/Satellite/Satellite_Tile3.png",revision:"8c8639aed6eadeabdeeac8a4c0004b98"},{url:"/assets/audio/notifs/r2d2.wav",revision:"6dc4c91a7c1daf63929b65047ceebd38"},{url:"/assets/audio/notifs/r2d21.wav",revision:"e981d8366a5dc119bb1573912e2e4bda"},{url:"/bg.png",revision:"c5ba6d9e151811b7d47e5e51d3b65e70"},{url:"/d.jpg",revision:"a7ac3ec6ee8bc8f70d3ccea891c910d2"},{url:"/favicon.ico",revision:"2483625e322b353626b172cc06960f01"},{url:"/floating_pinnie.png",revision:"a2c6520d64f813b77a91e7bc7ba22893"},{url:"/hero.png",revision:"cf4830d9cf114680b0ac6bea06186d17"},{url:"/icon-192.png",revision:"84dfa551865290b103d7a506ce294058"},{url:"/icon-512.png",revision:"84dfa551865290b103d7a506ce294058"},{url:"/logo.png",revision:"8b6947f0a54d1c2cb88e6a8b7be60081"},{url:"/manifest.json",revision:"1fadd711614177976c354739be470524"},{url:"/noise-2.svg",revision:"c84d42cb9ba68c2fc5e28d985b36c42f"},{url:"/offline",revision:"GO8Z5TWONYn8u-ZQ8w2np"},{url:"/orc.webp",revision:"10fb3a698407e30a6bfe44043b8a7892"},{url:"/out0.png",revision:"4b24a573657e70d0799e743d86439756"},{url:"/pinnie.png",revision:"8b1f06202c16b2b4d8870aeb07a0504e"},{url:"/placeholder-user.jpg",revision:"7ee6562646feae6d6d77e2c72e204591"},{url:"/placeholder.svg",revision:"446325a7666c39d7f840f674836a5454"},{url:"/planet.svg",revision:"e1e69e2676b03d06e2e34c2ecd9eac93"},{url:"/rocket.png",revision:"e2972433825395d77752a6df5e8a1039"},{url:"/satellite.svg",revision:"a48e3337b7aa672558c7bce79c41e171"},{url:"/satellite_new.png",revision:"52e659a29549f87929846dd495514d27"},{url:"/scuba_pinnie.png",revision:"cdd794024c17069a8bfe95e7b9e915c2"},{url:"/service-worker.js",revision:"6a808abb5ba06c5bd72f6205e1c7324d"},{url:"/thirdweb.svg",revision:"075df596cf30f53ea22bbb1cddfbba73"},{url:"/ufo.png",revision:"5b5dd617dc864c18fa2f90b299275e0f"},{url:"/vercel.svg",revision:"61c6b19abff40ea7acd577be818f3976"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:a,state:n})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s},{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/^https?.*/,new e.NetworkFirst({cacheName:"offlineCache",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:200,maxAgeSeconds:2592e3}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET")}); +if(!self.define){let e,s={};const a=(a,i)=>(a=new URL(a+".js",i).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(i,c)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let r={};const n=e=>a(e,t),o={module:{uri:t},exports:r,require:n};s[t]=Promise.all(i.map(e=>o[e]||n(e))).then(e=>(c(...e),r))}}define(["./workbox-92923e46"],function(e){"use strict";importScripts("fallback-6h6WhEmi6GVbw-DS42T1t.js"),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/Campbells.jpg",revision:"11385cbf8fbc65ddbe1ee9cc4f61599c"},{url:"/Eric.jpg",revision:"57cd76a7a6645463439016cea20d348c"},{url:"/Fred.webp",revision:"20a4d0dd0bb457f26136b4cb3299bbbf"},{url:"/Planet.png",revision:"dadf5193009bd2284bff301dfb69a2d6"},{url:"/_next/app-build-manifest.json",revision:"15ff0065e4618056cfe189bdcbd9812a"},{url:"/_next/static/6h6WhEmi6GVbw-DS42T1t/_buildManifest.js",revision:"b362bc6587ac0d49f4c8883e0cfffd0b"},{url:"/_next/static/6h6WhEmi6GVbw-DS42T1t/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/1124-14bc1f09e2ac278b.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/1151-b7d68f1322e7e7c3.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/1176-5f324c6d652a71dd.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/134-f9acad4f4607c218.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/1363.9139665fccd9c7a0.js",revision:"9139665fccd9c7a0"},{url:"/_next/static/chunks/1389-4651072de2ee59b6.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/1402-db70e87dd0aaa75a.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/160-05c9b156da9b8e0d.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/1630-3e2e4845958a5172.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/1714-69c6268587164c61.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/2117-3f08714425d7b4a3.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/2157-df6cc974b26701a0.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/216-8e0dbe85dc469b3c.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/2234.60d9ee493bfa1716.js",revision:"60d9ee493bfa1716"},{url:"/_next/static/chunks/2252-16175c36ab0b36cb.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/2268-cbdad168331f2555.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/2417-6a7500aa90d5775f.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/2571-c82d07672c163353.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/2764-5ea57478a9745b43.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/2852.ac25a49f39a6345f.js",revision:"ac25a49f39a6345f"},{url:"/_next/static/chunks/29.1c32b08d9067edce.js",revision:"1c32b08d9067edce"},{url:"/_next/static/chunks/2902.d0ed83999d38c5d6.js",revision:"d0ed83999d38c5d6"},{url:"/_next/static/chunks/2955-cc4260c441819f04.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/3021-7932ac817f54e9ec.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/3188.d5da9771bc685cbe.js",revision:"d5da9771bc685cbe"},{url:"/_next/static/chunks/3292-c65d11cf5eb7ea50.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/3519-30ef23394d16a428.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/3553-5a3f66ed7d110bbc.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/359-2bf64bad9cdf3143.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/376-8fb2227601d36e71.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/4023-b251514b785be7d0.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/4253-429e14250f0eca87.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/4419-00d437f93f6a09b4.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/459-9cfa94027e67f1e0.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/4752-ead1d9a00ff0799c.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/4938-e2bcdd4984dcd09d.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/4956-543b19a86b2c7f84.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/4965-e22e741e7203ead1.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/5117-aed0577c81b96431.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/5179-6cea212e5e9b5c45.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/5472-1fa25d1393bca9bb.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/5559-185de098d304fb85.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/5569-9bf5ebeb9c966fec.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/5754.9fe54a30958cb897.js",revision:"9fe54a30958cb897"},{url:"/_next/static/chunks/5786.e2a1f7304f59ca43.js",revision:"e2a1f7304f59ca43"},{url:"/_next/static/chunks/5794-ecfb65921fefbce7.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/5949-8c18702d6886cd9a.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/6275.2eed9dbb872eb1ca.js",revision:"2eed9dbb872eb1ca"},{url:"/_next/static/chunks/6489-5ee34b16822daa42.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/6592-24fccc72e2b5923a.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/6637.83233efb7ab7a299.js",revision:"83233efb7ab7a299"},{url:"/_next/static/chunks/6781.305558d259e85644.js",revision:"305558d259e85644"},{url:"/_next/static/chunks/6834-4f7005d572130663.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/6873-12de5600f07d687a.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/6974-df7ef91174118378.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/7000-64f06cb5733e932c.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/7091-634cf23798735c2a.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/740-ca6fcf79c231d02b.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/7441-80a026ee40961569.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/7452-6679dcc0b14334a4.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/7656-901d6dfd8fb17730.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/766-2036b130a6ad4821.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/785-90d5a5c5fe8025bb.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/8014-c5c8207436baad12.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/8155.0e681ee25da7a4b3.js",revision:"0e681ee25da7a4b3"},{url:"/_next/static/chunks/8280-4b558f2983782f8e.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/8302-bc1512bf5eb8bb12.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/8642-7f7c53e98eda5711.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/884-e90cfe3e769b26b3.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/8846-9b5f87b5a913439e.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/8953-ab0de8aee107c001.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/9208-00530379d3472d72.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/9452.03bc05918fd850a9.js",revision:"03bc05918fd850a9"},{url:"/_next/static/chunks/954-ce190c8740a673ed.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/956-397b516d897c8537.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/9592-f5dcf85286aefa12.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/9678-04f54ea3bb402ce7.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/9784-71b3e6212b5deb31.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/9828.8f0a5e098250b33b.js",revision:"8f0a5e098250b33b"},{url:"/_next/static/chunks/9854-9e508be7190fa9b0.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/9874.a6ba60664ca4966b.js",revision:"a6ba60664ca4966b"},{url:"/_next/static/chunks/9da6db1e-e4043ef83035bbcf.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/ad2866b8-d9a081783e4ea5b3.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/_not-found/page-98b09e372b5d8f95.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/account/page-3659327570709cb6.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/activity/deploy/page-d23550ea5dbfdb43.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/activity/deploy/roover/page-6dabce3034fcea2d.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/apt/page-3ce7cb848a171ceb.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/auth/page-43dee1043a8b498f.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/auth/register/page-8afcb9372c213a07.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/extraction/%5Bid%5D/page-b8cbeda07108677f.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/game/page-abb272691595e811.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/inventory/classifications/page-e82ae779e5178823.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/inventory/page-ea7c59aeb186a2f4.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/layout-06c96591de2e0a5c.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/layouts/activity/layout-45c221308cf2b175.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/layouts/activity/page-e0014248c1d3140f.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/leaderboards/sunspots/page-03cea3b35cb7ccfc.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/next/%5Bid%5D/page-17c28e35b6ae6be0.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/offline/page-b73c17cbc8b2e45a.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/page-cbac64e512e78c3e.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/planets/%5Bid%5D/page-1e85d834fb250d5d.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/planets/clouds/%5Bid%5D/page-7ed13e93bdeb848e.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/planets/edit/%5Bid%5D/page-aa33b0c036245744.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/planets/paint/%5Bid%5D/page-3b0b44da74c2b7db.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/posts/%5Bid%5D/page-17087a88fc14aba9.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/posts/surveyor/%5Bid%5D/page-75e15447a49801e7.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/privacy/page-600810fa6978abd6.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/research/page-2fb688d278d7b81b.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/scenes/uploads/page-9ffc9ea4cec58c17.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/setup/rover/page-fab4d5bb0be96b89.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/setup/satellite/page-c15ff49c14e06a5f.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/setup/solar/page-fe3a12bd03fdca9f.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/setup/telescope/page-b9bd577670b44857.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/structures/balloon/%5Bproject%5D/%5Bid%5D/%5Bmission%5D/page-b4009bb0ddabc079.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/structures/balloon/%5Bproject%5D/page-97ac3354b1cc10b9.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/structures/balloon/page-e2f83d0184a1f683.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/structures/cameras/page-0e50ba58ca8d08ce.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/structures/seiscam/%5Bproject%5D/%5Bid%5D/%5Bmission%5D/page-01d8add67dc428ac.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/structures/seiscam/page-049dc472dd30ace9.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/structures/telescope/%5Bproject%5D/%5Bid%5D/%5Bmission%5D/page-04c8db782e8f6196.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/structures/telescope/%5Bproject%5D/page-5b704ab83bd6466b.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/structures/telescope/page-6bd008674b950087.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/terms/page-9152b1804afe8f78.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/tests/page-9e0915aeaeb9d54a.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/viewports/roover/page-7ebe4343d0c7bc61.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/viewports/satellite/deploy/page-cb1af96801808566.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/viewports/satellite/page-4e4ddfbdfa88105b.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/app/viewports/solar/page-1cecc4b822ea7bb4.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/b536a0f1-ee91348fe72fbac9.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/bd904a5c-4ebb2c5169124366.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/c16f53c3-42f37dfb9f90c107.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/fd9d1056-79b6cfb6f9ff6563.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/framework-56dfd39ab9a08705.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/main-app-ec79de62d0df1dfd.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/main-dcb92ab713cde97d.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/pages/_app-3c9ca398d360b709.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/pages/_error-cf5ca766ac8f493f.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-9bf66aabc154b11c.js",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/_next/static/css/d51c0ddd98e45088.css",revision:"d51c0ddd98e45088"},{url:"/apple-touch-icon.png",revision:"84dfa551865290b103d7a506ce294058"},{url:"/assets/Archive/Inventory/Items/AeroCameraLevel1.png",revision:"b0095b88572678b14b751164bdafec8a"},{url:"/assets/Archive/Inventory/Items/AeroCameraLevel1NoBg.png",revision:"d92db950b319ff898b4b3b028998ee23"},{url:"/assets/Archive/Inventory/Items/Coal.png",revision:"f2c12cb628c5f1befb00934ef590eed9"},{url:"/assets/Archive/Inventory/Items/Fuel.png",revision:"aa39fc0ee9e5310fbd8df9c9c9228caf"},{url:"/assets/Archive/Inventory/Items/GoldenTelescopeLevel1Original.jpg",revision:"4ed335ca5ad7579441fcae7d4c311435"},{url:"/assets/Archive/Inventory/Items/GoldenTelescopeLevel1noBg.png",revision:"fe61acf446aa92820168b915e1d64e3b"},{url:"/assets/Archive/Inventory/Items/Silicates1.png",revision:"57aeda36fe8e660f9e7e388aa9cae035"},{url:"/assets/Archive/Inventory/Planets/Dump/65Binned.png",revision:"384b67f48a661347251312232e9ebf85"},{url:"/assets/Archive/Inventory/Planets/Dump/65Cover.png",revision:"fa14b14d9ffb751661fc22adfc3d8b7c"},{url:"/assets/Archive/Inventory/Planets/Dump/65Phase.png",revision:"f66e7998c7e7b89ca58a8073de29bb95"},{url:"/assets/Archive/Inventory/Planets/Dump/66Cover.png",revision:"9b53b2ac283656684d5ea5a00a8d94f5"},{url:"/assets/Archive/Inventory/Planets/Europa.png",revision:"5e01baea533129113d9d34f6de5c9a35"},{url:"/assets/Archive/Inventory/Planets/Mars.png",revision:"56b8fe5594110efe61945113cd019476"},{url:"/assets/Archive/Inventory/Planets/Planet59.png",revision:"428c91489994b502f47f529fbbe1747c"},{url:"/assets/Archive/Inventory/Planets/Planet63.png",revision:"d05e92b091ce34dd2a405f3c403b7698"},{url:"/assets/Archive/Inventory/Planets/Planet64.png",revision:"bc43db3cae98d2907e439596807805fa"},{url:"/assets/Archive/Inventory/Planets/PlanetBg.png",revision:"aa73eaf3e0304e7be31de35fe4a86d3a"},{url:"/assets/Archive/Inventory/Planets/SectorBg.png",revision:"6278cb9d4a7080eaacf2b2ffb250c567"},{url:"/assets/Archive/Inventory/Planets/rover.png",revision:"28e46f4d9a871a1afec830c482e726fb"},{url:"/assets/Archive/Inventory/Structures/Telescope.png",revision:"d2142a6fb24edb6ed6f40638e895068f"},{url:"/assets/Archive/Inventory/Structures/Telescope2.png",revision:"94b0a790fff053144e2bb63c1143b173"},{url:"/assets/Archive/Inventory/Structures/TelescopeReceiver.png",revision:"adbdc303fd7b86dd08761d23b607882e"},{url:"/assets/Archive/Inventory/Structures/TelescopeReceiverStruct.png",revision:"6cf3fb4e2f292ccfc356d28b05dccff0"},{url:"/assets/Archive/Onboarding/Bg.png",revision:"3d1d9e5ede244694750492b0bfc94be6"},{url:"/assets/Archive/Onboarding/Missions/Crucible/CrucibleGif.webp",revision:"e86425b7abb107c6a8934b77d5130631"},{url:"/assets/Archive/Onboarding/Missions/Crucible/CrucibleImage1.png",revision:"0000a3fca273b34662387215e2b9038b"},{url:"/assets/Archive/Onboarding/Missions/Crucible/CrucibleImage2.png",revision:"2fdeaf5d0cbd2aabe96a9735ca76272a"},{url:"/assets/Archive/Onboarding/Missions/Crucible/CrucibleImage3.png",revision:"dcd55aab5f70f29b13f65a5005bad2a1"},{url:"/assets/Archive/Onboarding/Missions/Emergence/EmergenceImage1.png",revision:"05738c37105a229f6dc67e3f95e9076d"},{url:"/assets/Archive/Onboarding/Missions/Emergence/EmergenceImage2.png",revision:"1687d7ef021d977dba7c13dde60a40d9"},{url:"/assets/Archive/Onboarding/Missions/Emergence/EmergenceImage3.png",revision:"bcce9d5b4cd6cc5505d1490e3305db65"},{url:"/assets/Archive/Onboarding/Missions/Emergence/EmergenceImage4.png",revision:"e05fd92c25df01e070cfc22b3bf89e9c"},{url:"/assets/Archive/Onboarding/Missions/Emergence/EmergenceImage5.png",revision:"35d9a1b812a520dfc243680c8ae09fad"},{url:"/assets/Archive/Onboarding/Missions/Emergence/TALONOVA (5)_clipdrop-enhance.png",revision:"b59102a0a7655c275cdc689bfb71f476"},{url:"/assets/Archive/Onboarding/Missions/Emergence/cartographer.png",revision:"bc0bd32c00672651498e4ac27f6ec911"},{url:"/assets/Archive/Onboarding/Missions/Emergence/cartographer.svg",revision:"67a1cbec05d00c226cd86b0a981f8dac"},{url:"/assets/Archive/Onboarding/Missions/Emergence/guardian.png",revision:"c4cb1a5d06f4ecf36d16ceb5c92f87d8"},{url:"/assets/Archive/Onboarding/Missions/Emergence/guardian.svg",revision:"b4759ba8ab3a66362c97209aa84b013e"},{url:"/assets/Archive/Onboarding/Missions/Emergence/navigator.png",revision:"43ef8636fca24a9469c7c5f22296d6af"},{url:"/assets/Archive/Onboarding/Missions/Emergence/navigator.svg",revision:"3f6467f410d7818933af8bb513a38fe3"},{url:"/assets/Archive/Onboarding/Missions/Navigate/NavigateImage1.png",revision:"9b53b2ac283656684d5ea5a00a8d94f5"},{url:"/assets/Archive/Onboarding/Missions/Navigate/NavigateImage2.png",revision:"0beb47aeb676517fd8ba8ba594b08656"},{url:"/assets/Archive/Onboarding/Missions/Navigate/NavigateImage3.png",revision:"5ac7701589b2c66636ad0b539a195bb6"},{url:"/assets/Archive/Onboarding/Missions/Navigate/ab936_kepler_view_of_a_ringed_planet_saturncore_in_outer_space__bb9ae32e-743a-4893-bebf-31f89ed21559.png",revision:"449a146738f989296326fcb4c8a0871f"},{url:"/assets/Archive/Onboarding/Missions/Silfur/GameItem1.png",revision:"5bd91fa3d1247aca7dc3cb29282a2fc9"},{url:"/assets/Archive/Onboarding/Missions/Silfur/GameItem2.png",revision:"9132028c444969591293e9ff14000252"},{url:"/assets/Archive/Onboarding/Missions/Silfur/GameItem3.png",revision:"d9d7495c85a1a4968ecc0fef2ba9ab64"},{url:"/assets/Archive/Onboarding/Missions/Silfur/SilfurImage1.png",revision:"449a146738f989296326fcb4c8a0871f"},{url:"/assets/Archive/Onboarding/Missions/Silfur/SolarShip1.png",revision:"972e5ba346bcfdc104ecfb4565435c0d"},{url:"/assets/Archive/Onboarding/Missions/Silfur/SolarShip2.png",revision:"00fa05cae65591850cd47f8df207a5f6"},{url:"/assets/Archive/Onboarding/Missions/Silfur/SolarShip3.png",revision:"74bf7d8b62b8b8ce829c45e353e819cb"},{url:"/assets/Archive/Surface.mp4",revision:"ebe3e35e7b78bc3b581ed95fcbd2d78d"},{url:"/assets/Archive/audio/Instrumental 02; Inventory.mp3",revision:"12e840bd86f49a17ad237e77cff0b980"},{url:"/assets/Archive/audio/Instrumental 04; Search and ecounter aliens-01.mp3",revision:"b4eec1a6d61d18387afce77193373941"},{url:"/assets/Archive/audio/Instrumental 05 v.6; boss fight.mp3",revision:"8db9a25951f264a4fbe96a696705f804"},{url:"/assets/Archive/audio/Instrumental 07; Ice Planet.mp3",revision:"271df8ef17f2dc2583f213595d9e3d81"},{url:"/assets/Archive/audio/Instrumental 08; Barren Planet.mp3",revision:"eb7f1c4896252caed2663da7cf40110b"},{url:"/assets/Archive/audio/WakeUp.mp3",revision:"4a2c4aa351cea6e2e62bddbe725794a2"},{url:"/assets/Archive/r2d2.png",revision:"0beaf06406d6265cb92aa50baea8c578"},{url:"/assets/Archive/ui/planet.svg",revision:"c268d8db01b8175ed7a1355739ca3754"},{url:"/assets/Automatons/ExploreRover1.png",revision:"50da0c3cd9e4b905941f771a382df800"},{url:"/assets/Automatons/Sat.png",revision:"b0b80fb493eff78ceeacd3e471c87fdb"},{url:"/assets/Backdrops/BioStations/Desert.jpg",revision:"4d30a152ec16dc975201a0f54fdb6a91"},{url:"/assets/Backdrops/Earth.png",revision:"1beae52a03baaa96f589519a325e5d27"},{url:"/assets/Backdrops/Mercury.png",revision:"49ddca4b14fbb0853b26c00c9cd186ca"},{url:"/assets/Backdrops/Negotiations.jpg",revision:"be23b513dd06d2ae40ab255357eeb536"},{url:"/assets/Backdrops/Venus.png",revision:"313b0a6b0aed2fe62d82edb31d0216d3"},{url:"/assets/Backdrops/View.jpeg",revision:"667d4e6811b0953b705e0b905c988948"},{url:"/assets/Backdrops/View.mp4",revision:"f001f7cbedb0aa8351700e194c3457cb"},{url:"/assets/Backdrops/background1.jpg",revision:"5c77cfacc630046769718c3ef38865b2"},{url:"/assets/Backdrops/background2.jpeg",revision:"f530c13582f2ebe7e7cdbba67efa6105"},{url:"/assets/Backdrops/cons.jpg",revision:"5f469d57a188d458313346319a7a6c23"},{url:"/assets/Backdrops/garden.png",revision:"893bd51dee46b01967c65b28a6e9560c"},{url:"/assets/Backdrops/gardens.png",revision:"8edcc3f921543cdf7d41bf75e33a9072"},{url:"/assets/Backdrops/gasgiant.jpeg",revision:"02ab340dd21ebd3494cfb9816e0bb473"},{url:"/assets/Backdrops/image.jpeg",revision:"65e83f2cf3a4ca02c1e006db1c802164"},{url:"/assets/Backdrops/satellite.jpg",revision:"ea79001d9cfc83c4eb5dbf0000fc3e77"},{url:"/assets/Bodies/sun-texture.png",revision:"b829dfb4a0a036c4727c5d61104c197b"},{url:"/assets/Captn.jpg",revision:"84dfa551865290b103d7a506ce294058"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step1.jpeg",revision:"4ce724c32f04466cb09c78f4591ecf6b"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step2.png",revision:"c8a926b536d4165efd3fec0ea8a75159"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step3.png",revision:"f130eb4200a4aeb8fecaabc69fef9b93"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step4.png",revision:"417c0e61d96e2ea6e1ce24f795512d61"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step5.png",revision:"62b7e6849c9e3a3b0383c2493965653b"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step6.png",revision:"1b4382df0d9eeb73e9fbd03b8273e3d3"},{url:"/assets/Docs/Automatons/automatons-ai4Mars/Step7.jpeg",revision:"4ce724c32f04466cb09c78f4591ecf6b"},{url:"/assets/Docs/Curves/Step1.png",revision:"b36d119c989dcfa50a51ab039886990d"},{url:"/assets/Docs/Curves/Step2.png",revision:"15c9ea1ad29f1778c2ea597c281bd65f"},{url:"/assets/Docs/Curves/Step3.png",revision:"2b8841b66eda2f24223c5f49a8e86c0e"},{url:"/assets/Docs/Curves/Step4.png",revision:"d3fdaa57e975083d0e5e4f1c4caa2c1c"},{url:"/assets/Docs/Guide/AvailableMissions.png",revision:"a55e93d58a491bdb0555da49ebb52372"},{url:"/assets/Docs/Guide/EditMode.png",revision:"4e90cc507cceb5efefee2e1d148ceb3a"},{url:"/assets/Docs/Guide/FirstClassification.png",revision:"7b919dad4b454cd7649864414965d549"},{url:"/assets/Docs/Guide/OnEarth.png",revision:"34d2fd1d740215688653db9df06c9c8a"},{url:"/assets/Docs/Guide/ResearchMissions.png",revision:"b7cd1e08307784a993f95ba484f0f9fa"},{url:"/assets/Docs/Guide/ResearchModal.png",revision:"05b6622b931c0004e1927e4b22deb8ff"},{url:"/assets/Docs/Guide/StructureModal.png",revision:"d6564905be3985e89e32605069b9d65d"},{url:"/assets/Docs/Guide/TravelInfo.png",revision:"56f24df7bcc02b10625a66ed6921087b"},{url:"/assets/Docs/Guide/TutorialExample.png",revision:"62a7f43e5c37660d7bbe4123b70c0e6a"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step1.jpeg",revision:"7c6325133dd48f4c8b82e9f7ad4e3980"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step2.png",revision:"c66d776d990119e59ca0671fe1ca445b"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step3.jpeg",revision:"d57d7e20d708adaff94032b1c82678b2"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step4.png",revision:"4787699232a87527248fbea7bd3cf468"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step5.png",revision:"a2b1e8a1938cce3d75ed30a5e83c0edf"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step6.jpeg",revision:"1d335e4736b9a3a2382898ee7aae6081"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step7.png",revision:"eb706f8365606dfb111d425bcac6d7ff"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step8.png",revision:"1e5fe9c1ae3fcaab5d693b993470334b"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/Step9.jpeg",revision:"53c5c82ec397f772d33f2c882583ad50"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/crater.png",revision:"84ffc72703cdec2a7b441826e8ebdeb6"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/disk.png",revision:"fb4017b0db388f52e150a32635c824b7"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/dotted.png",revision:"81600b12cb517932d75e70d749dd2adf"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/gravitywave.png",revision:"9ae251aa1154678a3f709e50451b2203"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/other-general.png",revision:"866d16a12862c63f13fe3ac9b06faf6d"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/ozone.png",revision:"cc469f63fb5195a3eb557f6a1eda2b80"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/streak.png",revision:"6b15aba938944735d03e5692bf42b9e3"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/twilight.png",revision:"9645bc077029d14a538f4dbb4533eb2d"},{url:"/assets/Docs/LIDAR/lidar-martianClouds/vortex.png",revision:"6dbfe2e209d7c1270a38ff67c8a505b2"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step1.png",revision:"6f64fe0a408aac02ce873674f434f181"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step2.png",revision:"f45ca57f448de583f78e9a75ea33fad9"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step3.png",revision:"f818e8e0c32dd94b638687195a8bfa0d"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step4.png",revision:"935dc3518abb08d87578938b920a52d9"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step5.png",revision:"ac98f087e6ea0c548d079df05b0aa137"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step6.png",revision:"d731a38125b393ccfb3b6dc25221f704"},{url:"/assets/Docs/Satellites/JovianVortexHunter/Step7.png",revision:"6cd3bec383dfd6fa4f6d68cf4e0c6a9b"},{url:"/assets/Docs/Satellites/Planet-Four/Step1.jpeg",revision:"b3e5f0774b0345e9fd332f44048e374d"},{url:"/assets/Docs/Satellites/Planet-Four/Step2.jpeg",revision:"fe7050776b47c2089308129b138575e0"},{url:"/assets/Docs/Satellites/Planet-Four/Step3.jpeg",revision:"4b1298adb19b51f9e2298a6a28f8c8f4"},{url:"/assets/Docs/Satellites/Planet-Four/Step4.gif",revision:"f824b0b7958b1d1553e7e904b0379327"},{url:"/assets/Docs/Satellites/Planet-Four/Step5.gif",revision:"7b347c35e21d132143aa1e39d567243b"},{url:"/assets/Docs/Satellites/Planet-Four/Step6.jpeg",revision:"e2fd14f8fab251bbe1b8ba629ed491e4"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step0.jpeg",revision:"b7a2a99748c14b7de66cfd547e9ac5ed"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step1.png",revision:"4b83160b7c815fc45e485e702e4c8143"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step2.gif",revision:"84ac95a7be981b61672edb3d2f696db5"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step3.gif",revision:"bb37732c200620e0655c02e03c3e623e"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step4.png",revision:"6bfde27fabf4faed90da2d79e49c4f64"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/ActiveAsteroids/Step5.png",revision:"b9f977c7c87ab60d0d86a3430ec824f0"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/Step1.png",revision:"ea45bef432497c0692f4eb37ece44dec"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/Step2.png",revision:"07af3996151ce251a1fc5a5d863ed8b1"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/Step3.png",revision:"9743622ff48e5763ea856f9497c23256"},{url:"/assets/Docs/Telescopes/DailyMinorPlanet/Step4.png",revision:"3927be877440eb87ba7cca394f3e3464"},{url:"/assets/Docs/Telescopes/DiskDetector/Step1.png",revision:"922c8aa86d86f212d15bbdf51f401e2d"},{url:"/assets/Docs/Telescopes/DiskDetector/Step2.png",revision:"fa164673ae38a1768dbadf1391009153"},{url:"/assets/Docs/Telescopes/DiskDetector/Step3.png",revision:"51d413923e5d490c104a0109a1866e16"},{url:"/assets/Docs/Telescopes/DiskDetector/Step4.png",revision:"e5a14f2e8a50da4767b498b7794eafa8"},{url:"/assets/Docs/Telescopes/Sunspots/Step1.png",revision:"8fe0b00b92288b74862e7da82482a436"},{url:"/assets/Docs/Telescopes/Sunspots/Step2.png",revision:"a2fc4469a6172c691ebb2c7a411c5eae"},{url:"/assets/Docs/Telescopes/Sunspots/Step3.png",revision:"5f890b7d285a3a917b9bc14beb65ee20"},{url:"/assets/Docs/Telescopes/Sunspots/Step4.png",revision:"78a1bbd2bc27dfa78a31ebf14c645e66"},{url:"/assets/Docs/Telescopes/SuperWASP/1.gif",revision:"e7b5d9712baffde7aa1ee28bb9d77663"},{url:"/assets/Docs/Telescopes/SuperWASP/2.gif",revision:"e16a5836f1c7b4c085469c6fcaa0ecc9"},{url:"/assets/Docs/Telescopes/SuperWASP/3.gif",revision:"8782fcec46ccd9cfde7ea2881f6efa9b"},{url:"/assets/Docs/Telescopes/SuperWASP/4.gif",revision:"e1d08e4f688599c19177dc49a9de5e90"},{url:"/assets/Docs/Telescopes/SuperWASP/5.gif",revision:"efb001734bd5a3bbb2fb8c26557b0bc9"},{url:"/assets/Docs/Zoodex/zoodex-IguanasFromAbove/Step1.jpeg",revision:"0f8d074c5eb03331e0ad80320133b1a0"},{url:"/assets/Docs/Zoodex/zoodex-IguanasFromAbove/Step2.jpg",revision:"2427f36af822ee6ac99ab2b841d7d7e7"},{url:"/assets/Docs/Zoodex/zoodex-IguanasFromAbove/Step3.jpg",revision:"6ea8d338f543905d8ec4991f92cd88fe"},{url:"/assets/Docs/Zoodex/zoodex-IguanasFromAbove/Step4.jpg",revision:"d94a66ef8803352c9b3febb5866e7c9a"},{url:"/assets/Docs/Zoodex/zoodex-IguanasFromAbove/Step5.jpg",revision:"15c51a9bb682ab75dc3418e7e2f89ab7"},{url:"/assets/Docs/Zoodex/zoodex-PenguinWatch/Step1.jpeg",revision:"5bc4c6b5c384409446bcfacff9d343e4"},{url:"/assets/Docs/Zoodex/zoodex-PenguinWatch/Step2.jpeg",revision:"f65c9b2ab621f447435eb8f80d696c39"},{url:"/assets/Docs/Zoodex/zoodex-PenguinWatch/Step3.jpeg",revision:"3636a781f06b0cedf0f77890e7cb2b39"},{url:"/assets/Docs/Zoodex/zoodex-PenguinWatch/Step4.jpeg",revision:"632e1d7643b1826f4e62ac34a8d7e944"},{url:"/assets/Docs/Zoodex/zoodex-PlanktonPortal/Step1.png",revision:"ececc0ef084a126840e926f2476a4c5d"},{url:"/assets/Docs/Zoodex/zoodex-PlanktonPortal/Step2.png",revision:"17c017e3c1995aef55fb07138b654640"},{url:"/assets/Docs/Zoodex/zoodex-PlanktonPortal/Step3.png",revision:"fea9931a6f258b66eb9cfa856cb26ceb"},{url:"/assets/Docs/Zoodex/zoodex-PlanktonPortal/Step4.png",revision:"fe09f67239daf3db60c449b455334ac6"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step1.jpeg",revision:"6625e4a0f0aa7a23fe11860f53d474a6"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step2.png",revision:"1abf2dd1128700126c680993d60b522b"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step3.png",revision:"c478d6469168910e1ae49e6cd0d696f8"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step4.jpeg",revision:"6c3c551ee26c2934b696efd4946643c0"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step5.jpeg",revision:"d444b87070bf268e585fc387475aa4f0"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step6.png",revision:"ff7430f42f1fa38b113bcb3abc86df3d"},{url:"/assets/Docs/Zoodex/zoodex-burrowingOwl/Step7.jpeg",revision:"2969154c39321c95df5dad26319c49fb"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Antipathes-Atlantica.jpeg",revision:"80bbfa0fd91738832664e429d17e2c27"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Antipathes-Furcata.jpeg",revision:"5f1d6dbf427592495eebdffe02bb86dc"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Bebryce-Sp.jpg",revision:"81be15d42949c3118106bc733d8957b2"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Ellisellidae.jpeg",revision:"22d5fbe1e8f65d46095bcbf2368f01e2"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Fish.jpeg",revision:"ac4263e0478bf21d1d6507ee22b6db26"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Madracis-sp..jpg",revision:"8a5d8bb7183d4a5e09a23bab582445cb"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Madrepora-Sp.jpeg",revision:"5cb6bd705a4136856f51bd50c4baba6f"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Muricea-Pendula.jpg",revision:"13577b7e19c3575500efce98c3299918"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Paramuriciade.jpg",revision:"5b7eac3ddcf4627d508df6de4d4d5d29"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Spoonge.jpeg",revision:"9644a6de3ad0e3fea08534746cba4167"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Stichopathes.jpg",revision:"351a22ac8cb2839b2f0fe6767e2859f6"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Swiftia-Exserta.jpeg",revision:"c81564435c0522b8f90b14155cf77e08"},{url:"/assets/Docs/Zoodex/zoodex-clickACoral/Thesea-Nivea.jpeg",revision:"9283e0faf204d65afe9fc06b088f3b37"},{url:"/assets/Docs/Zoodex/zoodex-nestQuestGo/Step1.png",revision:"9ac89ae431688bbc59f0172e427567ab"},{url:"/assets/Docs/Zoodex/zoodex-nestQuestGo/Step2.png",revision:"484e0de8ee3614b8b5eadcad47c0577d"},{url:"/assets/Docs/Zoodex/zoodex-nestQuestGo/Step3.png",revision:"484e0de8ee3614b8b5eadcad47c0577d"},{url:"/assets/Docs/Zoodex/zoodex-nestQuestGo/Step4.png",revision:"52095cabc0f3f15fcb539dfd19c08df2"},{url:"/assets/Docs/Zoodex/zoodex-nestQuestGo/Step5.png",revision:"013eef5ab240240bdd2c3d61c45ed704"},{url:"/assets/Images/capacity-1.png",revision:"b11baa8002d956d83035dc56d8b196bd"},{url:"/assets/Images/capacity-2.png",revision:"3aa28af2d9970405993a674875ccc05c"},{url:"/assets/Images/capacity-3.png",revision:"fb2edead7a418ac1ccc3b2b8658582b1"},{url:"/assets/Images/landing1.jpg",revision:"d614027097dc6c4b482b1ebe6dd4ca06"},{url:"/assets/Images/landing2.jpg",revision:"37295f0d482d11f6fec67a8e91f025e5"},{url:"/assets/Images/landing3.jpg",revision:"1fcab1a9ab528d50b39491d5945ae996"},{url:"/assets/Images/landing4.jpg",revision:"824d43b5f6c0b7cdbe308e839e55c2ab"},{url:"/assets/Images/landing5.jpg",revision:"4661f0b795c93f4fd452f7c11064bd18"},{url:"/assets/Images/landing6.jpg",revision:"0f961272ab9e266be515eb843400c95c"},{url:"/assets/Images/power-1.png",revision:"6dc4d6fc65a6a4a4626dad5b223f0b15"},{url:"/assets/Images/power-2.png",revision:"02e4c35f2cc98683d8446af714467aed"},{url:"/assets/Images/power-3.png",revision:"e85e80fc437cbdd37ea281ebad6861ca"},{url:"/assets/Images/speed-1.png",revision:"c2c11563476fa94cd79c529cae68cc2f"},{url:"/assets/Images/speed-2.png",revision:"14f619efe74218e9e0ca37e28a6d8ac8"},{url:"/assets/Images/speed-3.png",revision:"83b2fa733f09488123b88e404c7db635"},{url:"/assets/Items/22.png",revision:"aaf676880015ae1fca6670026e9edab5"},{url:"/assets/Items/28.png",revision:"58a70597ee0a85b0e929049c1518496a"},{url:"/assets/Items/Alloy.png",revision:"1ab072f72ce2d014a9d176b2780230ec"},{url:"/assets/Items/AutoController.png",revision:"65aa5761bcfdc2d9c1086afbde0433f7"},{url:"/assets/Items/AutomatonController.png",revision:"9bec20ac77c7a10c9e0863903094b147"},{url:"/assets/Items/CameraReceiver.png",revision:"e3d4b72f37cfe9b03a8a4231c10a1735"},{url:"/assets/Items/Chromite.png",revision:"48459b3c96b2eb0e03fa0303ba8dbac6"},{url:"/assets/Items/Coal.png",revision:"a20225d254d9c7c0b04bf01a89b8ac58"},{url:"/assets/Items/Copper.png",revision:"01eae5b57bb8475f67be87c38ebe52c0"},{url:"/assets/Items/Greenhouse.jpg",revision:"89ce4d9952c5fcca5246af132bc33cee"},{url:"/assets/Items/Greenhouse.png",revision:"dbf4ec7c99a5ac515de500e7f4bde2aa"},{url:"/assets/Items/Helicopter.png",revision:"8fabf68dab697ac41b1ddb5d23952bdb"},{url:"/assets/Items/Ice.png",revision:"655fc37ae85f470103f864a39e8ac847"},{url:"/assets/Items/Iron.png",revision:"7a673727c900b1a253cc2fe9b96c2ac9"},{url:"/assets/Items/Launchpad.jpg",revision:"76324cf59091e8e07f0f76565bf0918c"},{url:"/assets/Items/Lidar.png",revision:"50a112cd26a8e868e64f6db7c5dc0ea4"},{url:"/assets/Items/MiningStructure.png",revision:"96640003bac02c57d277f011c2e7d1d7"},{url:"/assets/Items/Nickel.png",revision:"47473b610a62cd8c4a2ec26c86666e60"},{url:"/assets/Items/Pokedex.png",revision:"7c5bdbbdb6942471f6dee26f35f57464"},{url:"/assets/Items/Research.png",revision:"05e5a3b0f4398c4b7dd5293822295d94"},{url:"/assets/Items/Rocket.png",revision:"7e7eece84a6324977c3e5077b8446aa7"},{url:"/assets/Items/Roover.gif",revision:"ba881bf19d84414d2b61853ae2c262a1"},{url:"/assets/Items/Scoper.png",revision:"7637fbddf3ea93f9fafd012a4ddcea6f"},{url:"/assets/Items/Silicon.png",revision:"01eae5b57bb8475f67be87c38ebe52c0"},{url:"/assets/Items/Telescope.jpg",revision:"23071871f6142f8dd0c7c3a3f7b07788"},{url:"/assets/Items/Telescope.png",revision:"2289bb2e88c120250b9c70eb2fe6cc75"},{url:"/assets/Items/TransitingTelescope.png",revision:"c57789844332d12cfbd36e2a46435130"},{url:"/assets/Items/Vector.png",revision:"1930faa9e00cffd9cefb4764c5cecb11"},{url:"/assets/Items/WeatherBalloon.png",revision:"7e7c0065a9be091098d5cd5fe2199dd6"},{url:"/assets/Items/Zoodex.png",revision:"57d29ff98723319efa6f03093fafec3f"},{url:"/assets/Items/camerars.png",revision:"74b20c14a9c01178533eed11bb93becc"},{url:"/assets/Items/miningstation.png",revision:"60f6398da814012af4fd64c94af65729"},{url:"/assets/Items/roover.png",revision:"eacaaf0fab67591794313b11bfd166a7"},{url:"/assets/Items/rover.svg",revision:"61143d0da3adec08b3325962b6198ee4"},{url:"/assets/Items/sw.js",revision:"aa14caf35328d6ef2b804009fce82acc"},{url:"/assets/Planets/Earth.png",revision:"0fa6e6f81ec8207ee4ae82f6a37ce617"},{url:"/assets/Planets/Mars.png",revision:"3f4ed19d54bed733b121f73d6a7e8ec9"},{url:"/assets/Planets/Mercury.png",revision:"15dd11dcd44ddf6cb2dd46525c45e1b7"},{url:"/assets/Planets/Moon.png",revision:"c9a970a4c0cfe6cd816ce0229069384b"},{url:"/assets/Planets/Venus.png",revision:"f4f6739d5f289093699d8a8557d8aaf8"},{url:"/assets/Template.png",revision:"d12bd71d3d676bc16e638dc0434c2f81"},{url:"/assets/Viewports/Satellite/Satellite_Tile1.png",revision:"238c6328351a9fd56b71a02dc2738a4e"},{url:"/assets/Viewports/Satellite/Satellite_Tile2.png",revision:"4743b76f39f6c16a9e8e3195c7617032"},{url:"/assets/Viewports/Satellite/Satellite_Tile3.png",revision:"8c8639aed6eadeabdeeac8a4c0004b98"},{url:"/assets/audio/notifs/r2d2.wav",revision:"6dc4c91a7c1daf63929b65047ceebd38"},{url:"/assets/audio/notifs/r2d21.wav",revision:"e981d8366a5dc119bb1573912e2e4bda"},{url:"/bg.png",revision:"c5ba6d9e151811b7d47e5e51d3b65e70"},{url:"/d.jpg",revision:"a7ac3ec6ee8bc8f70d3ccea891c910d2"},{url:"/favicon.ico",revision:"2483625e322b353626b172cc06960f01"},{url:"/floating_pinnie.png",revision:"a2c6520d64f813b77a91e7bc7ba22893"},{url:"/hero.png",revision:"cf4830d9cf114680b0ac6bea06186d17"},{url:"/icon-192.png",revision:"84dfa551865290b103d7a506ce294058"},{url:"/icon-512.png",revision:"84dfa551865290b103d7a506ce294058"},{url:"/logo.png",revision:"8b6947f0a54d1c2cb88e6a8b7be60081"},{url:"/manifest.json",revision:"1fadd711614177976c354739be470524"},{url:"/noise-2.svg",revision:"c84d42cb9ba68c2fc5e28d985b36c42f"},{url:"/offline",revision:"6h6WhEmi6GVbw-DS42T1t"},{url:"/orc.webp",revision:"10fb3a698407e30a6bfe44043b8a7892"},{url:"/out0.png",revision:"4b24a573657e70d0799e743d86439756"},{url:"/pinnie.png",revision:"8b1f06202c16b2b4d8870aeb07a0504e"},{url:"/placeholder-user.jpg",revision:"7ee6562646feae6d6d77e2c72e204591"},{url:"/placeholder.svg",revision:"446325a7666c39d7f840f674836a5454"},{url:"/planet.svg",revision:"e1e69e2676b03d06e2e34c2ecd9eac93"},{url:"/rocket.png",revision:"e2972433825395d77752a6df5e8a1039"},{url:"/satellite.svg",revision:"a48e3337b7aa672558c7bce79c41e171"},{url:"/satellite_new.png",revision:"52e659a29549f87929846dd495514d27"},{url:"/scuba_pinnie.png",revision:"cdd794024c17069a8bfe95e7b9e915c2"},{url:"/service-worker.js",revision:"6a808abb5ba06c5bd72f6205e1c7324d"},{url:"/thirdweb.svg",revision:"075df596cf30f53ea22bbb1cddfbba73"},{url:"/ufo.png",revision:"5b5dd617dc864c18fa2f90b299275e0f"},{url:"/vercel.svg",revision:"61c6b19abff40ea7acd577be818f3976"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:a,state:i})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s},{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/^https?.*/,new e.NetworkFirst({cacheName:"offlineCache",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:200,maxAgeSeconds:2592e3}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET")}); diff --git a/src/components/classification/tools/image-classifier.tsx b/src/components/classification/tools/image-classifier.tsx index 211186d7..77950d96 100644 --- a/src/components/classification/tools/image-classifier.tsx +++ b/src/components/classification/tools/image-classifier.tsx @@ -121,7 +121,9 @@ const uploadImageToSupabase = async (file: File) => { console.error("Entry error:", entryError.message); } else { console.log("Upload entry:", uploadEntry); - window.location.reload(); + if (typeof window !== "undefined") { + window.location.reload(); + } // saveLifeEntity(); } } @@ -170,7 +172,9 @@ const sendImage = async () => { console.log("Full API Response:", res.data); // Log full response if (res.data.refresh) { - window.location.reload(); + if (typeof window !== "undefined") { + window.location.reload(); + } return; } diff --git a/src/components/deployment/extraction/ex-scene.tsx b/src/components/deployment/extraction/ex-scene.tsx index 1a649e83..3d7bfddc 100644 --- a/src/components/deployment/extraction/ex-scene.tsx +++ b/src/components/deployment/extraction/ex-scene.tsx @@ -388,7 +388,9 @@ export function ExtractionScene({

{mineralType}

-

{location}

+

+ {typeof window !== "undefined" && location} +

diff --git a/src/components/deployment/extraction/mineral-extraction.tsx b/src/components/deployment/extraction/mineral-extraction.tsx index 6448ffe4..1624b116 100644 --- a/src/components/deployment/extraction/mineral-extraction.tsx +++ b/src/components/deployment/extraction/mineral-extraction.tsx @@ -165,7 +165,7 @@ export function MineralExtraction({ {projectType}
- {location && ( + {typeof window !== "undefined" && location && ( {location} diff --git a/src/components/deployment/missions/structures/Astronomers/PlanetHunters/PHVote.tsx b/src/components/deployment/missions/structures/Astronomers/PlanetHunters/PHVote.tsx index 3dff6b6a..f58b6f1a 100644 --- a/src/components/deployment/missions/structures/Astronomers/PlanetHunters/PHVote.tsx +++ b/src/components/deployment/missions/structures/Astronomers/PlanetHunters/PHVote.tsx @@ -100,7 +100,9 @@ export default function VotePlanetClassifications({ classificationId }: VotePlan } catch (error) { console.error("PHVote: Router.push error:", error); // Fallback to window.location - window.location.href = '/'; + if (typeof window !== "undefined") { + window.location.href = '/'; + } } }, 3000); }; @@ -167,7 +169,9 @@ export default function VotePlanetClassifications({ classificationId }: VotePlan router.push('/'); } catch (error) { console.error('PHVote: Manual redirect error:', error); - window.location.href = '/'; + if (typeof window !== "undefined") { + window.location.href = '/'; + } } }} className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 mb-4" diff --git a/src/components/deployment/structures/Structures.tsx b/src/components/deployment/structures/Structures.tsx index ddea042e..1cd9ea95 100644 --- a/src/components/deployment/structures/Structures.tsx +++ b/src/components/deployment/structures/Structures.tsx @@ -69,10 +69,12 @@ export default function Structures() { const itemDetail = itemMap.get(structure.item); if (itemDetail && !uniqueStructureMap.has(structure.item)) { uniqueStructureMap.set(structure.item, structure); - const location = itemDetail.locationType || 'Surface'; - if (location in groups) { - groups[location as keyof typeof groups].push(structure); - }; + if (typeof window !== "undefined") { + const location = itemDetail.locationType || 'Surface'; + if (location in groups) { + groups[location as keyof typeof groups].push(structure); + }; + } }; }; diff --git a/src/components/deployment/structures/mineral-card.tsx b/src/components/deployment/structures/mineral-card.tsx index bf37864c..6686636f 100644 --- a/src/components/deployment/structures/mineral-card.tsx +++ b/src/components/deployment/structures/mineral-card.tsx @@ -43,7 +43,9 @@ export function MineralCard({

{getMineralDisplayName(mineral.mineralType)}

-

📍 {location}

+ {typeof window !== "undefined" && ( +

📍 {location}

+ )}
{mineral.estimatedQuantity}
diff --git a/src/components/game/BottomNavigation.tsx b/src/components/game/BottomNavigation.tsx new file mode 100644 index 00000000..e4249d98 --- /dev/null +++ b/src/components/game/BottomNavigation.tsx @@ -0,0 +1,147 @@ +"use client"; + +import { ReactNode } from "react"; +import { cn } from "@/src/shared/utils"; +import { Telescope, Satellite, Home, Car, Sun } from "lucide-react"; + +type NavItem = "telescope" | "satellite" | "base" | "rover" | "solar"; + +interface BottomNavigationProps { + activeItem: NavItem; + onItemClick: (item: NavItem) => void; + telescopeNotification?: boolean; + satelliteNotification?: boolean; + roverNotification?: boolean; + solarNotification?: boolean; + className?: string; +} + +interface NavButtonProps { + icon: ReactNode; + label: string; + isActive: boolean; + isCenter?: boolean; + hasNotification?: boolean; + onClick: () => void; +} + +function NavButton({ + icon, + label, + isActive, + isCenter = false, + hasNotification = false, + onClick, +}: NavButtonProps) { + if (isCenter) { + return ( + + ); + } + + return ( + + ); +} + +export default function BottomNavigation({ + activeItem, + onItemClick, + telescopeNotification = false, + satelliteNotification = false, + roverNotification = false, + solarNotification = false, + className, +}: BottomNavigationProps) { + return ( + + ); +} diff --git a/src/components/game/GameHeader.tsx b/src/components/game/GameHeader.tsx new file mode 100644 index 00000000..dce6ffc6 --- /dev/null +++ b/src/components/game/GameHeader.tsx @@ -0,0 +1,186 @@ +"use client"; + +import { useState } from "react"; +import { useSession, useSupabaseClient } from "@supabase/auth-helpers-react"; +import { Bell, User, UserX, Zap, Sparkles, LogOut } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/src/components/ui/dropdown-menu"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/src/components/ui/dialog"; +import { Badge } from "@/src/components/ui/badge"; +import ConvertAnonymousAccount from "@/src/components/profile/auth/ConvertAnonymousAccount"; +import ProjectPreferencesModal from "@/src/components/onboarding/ProjectPreferencesModal"; +import { useUserPreferences, ProjectType } from "@/src/hooks/useUserPreferences"; +import Link from "next/link"; +import { cn } from "@/src/shared/utils"; + +interface GameHeaderProps { + stardust?: number; + hasNotifications?: boolean; + onNotificationsClick?: () => void; + className?: string; +} + +export default function GameHeader({ + stardust = 0, + hasNotifications = false, + onNotificationsClick, + className, +}: GameHeaderProps) { + const session = useSession(); + const supabase = useSupabaseClient(); + const [showUpgradeModal, setShowUpgradeModal] = useState(false); + const [projectPreferencesOpen, setProjectPreferencesOpen] = useState(false); + const { preferences, savePreferences } = useUserPreferences(); + + const isAnonymousUser = session?.user?.is_anonymous; + + const handleProjectPreferencesSave = (interests: ProjectType[]) => { + savePreferences({ projectInterests: interests }); + }; + + const handleLogout = async () => { + // Clear all browser storage before signing out + if (typeof window !== 'undefined') { + window.localStorage.clear(); + window.sessionStorage.clear(); + // Remove all cookies + document.cookie.split(';').forEach(function(c) { + document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date(0).toUTCString() + ';path=/'); + }); + } + const { error } = await supabase.auth.signOut(); + if (error) { + console.error("Error signing out:", error.message); + } else { + if (typeof window !== "undefined") { + window.location.href = "/"; + } + } + }; + + return ( + <> +
+
+ {/* Logo */} + +
+ +
+ + Star Sailors + + + + {/* Center - Stardust count */} +
+ + + {stardust.toLocaleString()} + +
+ + {/* Right - Actions */} +
+ {/* Notifications */} + + + {/* Profile dropdown */} + + + + + + + {isAnonymousUser ? ( +
+ + + Guest + +
+ ) : ( + {session?.user?.email || "User"} + )} +
+ + + {isAnonymousUser && ( + setShowUpgradeModal(true)}> + + Upgrade Account + + )} + + + Research + + + Leaderboards + + setProjectPreferencesOpen(true)}> + + Project Preferences + + + + + + Sign out + +
+
+
+
+
+ + {/* Upgrade modal for anonymous users */} + + + + Upgrade Your Account + + setShowUpgradeModal(false)} /> + + + + setProjectPreferencesOpen(false)} + onSave={handleProjectPreferencesSave} + initialInterests={preferences?.projectInterests} + /> + + ); +} diff --git a/src/components/game/MissionControlCard.tsx b/src/components/game/MissionControlCard.tsx new file mode 100644 index 00000000..add93be6 --- /dev/null +++ b/src/components/game/MissionControlCard.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { ReactNode } from "react"; +import { cn } from "@/src/shared/utils"; + +type CardVariant = "action" | "status" | "progress"; + +interface MissionControlCardProps { + icon: ReactNode; + iconBgColor?: string; + title: string; + subtitle: string; + variant?: CardVariant; + actionLabel?: string; + onAction?: () => void; + progress?: number; // 0-100 for progress variant + className?: string; +} + +export default function MissionControlCard({ + icon, + iconBgColor = "bg-amber-500/20", + title, + subtitle, + variant = "status", + actionLabel, + onAction, + progress, + className, +}: MissionControlCardProps) { + const getIconStyle = () => { + switch (variant) { + case "action": + return "text-amber-400"; + case "status": + return "text-green-400"; + case "progress": + return "text-cyan-400"; + default: + return "text-muted-foreground"; + } + }; + + const getIconBg = () => { + switch (variant) { + case "action": + return "bg-amber-500/20 border-amber-500/30"; + case "status": + return "bg-green-500/20 border-green-500/30"; + case "progress": + return "bg-cyan-500/20 border-cyan-500/30"; + default: + return iconBgColor; + } + }; + + return ( +
+ {/* Icon */} +
+ {icon} +
+ + {/* Content */} +
+

{title}

+

{subtitle}

+ + {/* Progress bar for progress variant */} + {variant === "progress" && typeof progress === "number" && ( +
+
+
+ )} +
+ + {/* Action button for action variant */} + {variant === "action" && actionLabel && onAction && ( + + )} +
+ ); +} diff --git a/src/components/game/PlanetHeroSection.tsx b/src/components/game/PlanetHeroSection.tsx new file mode 100644 index 00000000..c3578ce3 --- /dev/null +++ b/src/components/game/PlanetHeroSection.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { cn } from "@/src/shared/utils"; + +interface PlanetHeroSectionProps { + planetName: string; + sectorName?: string; + backgroundImage?: string; + stardust?: number; + rank?: number; + className?: string; +} + +export default function PlanetHeroSection({ + planetName, + sectorName = "Sector 7G", + backgroundImage = "/assets/Backdrops/Earth.png", + stardust, + rank, + className, +}: PlanetHeroSectionProps) { + return ( +
+ {/* Background image with gradient overlay */} +
+ {planetName} +
+ + {/* Decorative dots/stars */} +
+ {[...Array(12)].map((_, i) => ( +
+ ))} +
+
+ + {/* Content */} +
+
+ {/* Planet info */} +
+

+ {planetName} +

+

{sectorName}

+
+ + {/* Stats (desktop only) */} + {(stardust !== undefined || rank !== undefined) && ( +
+ {stardust !== undefined && ( +
+

+ Stardust +

+

+ {stardust.toLocaleString()} +

+
+ )} + {rank !== undefined && ( +
+

+ Rank +

+

#{rank}

+
+ )} +
+ )} +
+
+
+ ); +} diff --git a/src/components/game/StructureCard.tsx b/src/components/game/StructureCard.tsx new file mode 100644 index 00000000..de6ad742 --- /dev/null +++ b/src/components/game/StructureCard.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { ReactNode } from "react"; +import { cn } from "@/src/shared/utils"; + +interface StructureCardProps { + icon: ReactNode; + name: string; + status?: string; + statusColor?: "green" | "blue" | "amber" | "red" | "muted"; + onClick?: () => void; + hasNotification?: boolean; + disabled?: boolean; + className?: string; +} + +export default function StructureCard({ + icon, + name, + status, + statusColor = "muted", + onClick, + hasNotification = false, + disabled = false, + className, +}: StructureCardProps) { + const getStatusColorClass = () => { + switch (statusColor) { + case "green": + return "text-green-400"; + case "blue": + return "text-cyan-400"; + case "amber": + return "text-amber-400"; + case "red": + return "text-red-400"; + default: + return "text-muted-foreground"; + } + }; + + return ( + + ); +} diff --git a/src/components/game/index.ts b/src/components/game/index.ts new file mode 100644 index 00000000..dd3afa18 --- /dev/null +++ b/src/components/game/index.ts @@ -0,0 +1,5 @@ +export { default as GameHeader } from "./GameHeader"; +export { default as PlanetHeroSection } from "./PlanetHeroSection"; +export { default as MissionControlCard } from "./MissionControlCard"; +export { default as StructureCard } from "./StructureCard"; +export { default as BottomNavigation } from "./BottomNavigation"; diff --git a/src/components/layout/Header/MainHeader.tsx b/src/components/layout/Header/MainHeader.tsx index 111a5ad3..0c08ded0 100644 --- a/src/components/layout/Header/MainHeader.tsx +++ b/src/components/layout/Header/MainHeader.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { useSession, useSupabaseClient } from "@supabase/auth-helpers-react"; -import { Bell, Sun, Moon, User, UserPlus, UserX, Zap, LogOut } from "lucide-react"; +import { Bell, Sun, Moon, User, UserPlus, UserX, Zap, LogOut, Sparkles } from "lucide-react"; import { DropdownMenu, DropdownMenuContent, @@ -21,6 +21,8 @@ import { Switch } from "@/src/components/ui/switch"; import { Badge } from "@/src/components/ui/badge"; import RecentActivity from "@/src/components/social/activity/RecentActivity"; import ConvertAnonymousAccount from "@/src/components/profile/auth/ConvertAnonymousAccount"; +import ProjectPreferencesModal from "@/src/components/onboarding/ProjectPreferencesModal"; +import { useUserPreferences, ProjectType } from "@/src/hooks/useUserPreferences"; import Link from "next/link"; interface CommentVote { @@ -59,6 +61,8 @@ export default function MainHeader({ const session = useSession(); const supabase = useSupabaseClient(); const [showUpgradeModal, setShowUpgradeModal] = useState(false); + const [projectPreferencesOpen, setProjectPreferencesOpen] = useState(false); + const { preferences, savePreferences } = useUserPreferences(); const isAnonymousUser = session?.user?.is_anonymous; @@ -70,13 +74,28 @@ export default function MainHeader({ setShowUpgradeModal(false); }; + const handleProjectPreferencesSave = (interests: ProjectType[]) => { + savePreferences({ projectInterests: interests }); + }; + const handleLogout = async () => { + // Clear all browser storage before signing out + if (typeof window !== 'undefined') { + window.localStorage.clear(); + window.sessionStorage.clear(); + // Remove all cookies + document.cookie.split(';').forEach(function(c) { + document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date(0).toUTCString() + ';path=/'); + }); + } const { error } = await supabase.auth.signOut(); if (error) { console.error("Error signing out:", error.message); } else { // Signed out - window.location.href = '/'; + if (typeof window !== "undefined") { + window.location.href = '/'; + } } }; @@ -174,6 +193,13 @@ export default function MainHeader({ )} Settings + setProjectPreferencesOpen(true)} + className="cursor-pointer" + > + + Project Preferences + Log out @@ -206,6 +232,13 @@ export default function MainHeader({ /> + + setProjectPreferencesOpen(false)} + onSave={handleProjectPreferencesSave} + initialInterests={preferences?.projectInterests} + />
); } diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index 1fa1e885..be92d48a 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -33,6 +33,15 @@ export default function Navbar() { }, []); const signOut = async () => { + // Clear all browser storage before signing out + if (typeof window !== 'undefined') { + window.localStorage.clear(); + window.sessionStorage.clear(); + // Remove all cookies + document.cookie.split(';').forEach(function(c) { + document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date(0).toUTCString() + ';path=/'); + }); + } const { error } = await supabase.auth.signOut(); if (error) { console.error("Error signing out:", error.message); diff --git a/src/components/layout/Tes.tsx b/src/components/layout/Tes.tsx index ad67dc0f..58c2833e 100644 --- a/src/components/layout/Tes.tsx +++ b/src/components/layout/Tes.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from "react" import Link from "next/link" import { useSupabaseClient, useSession } from "@supabase/auth-helpers-react" -import { Bell, ChevronDown, HammerIcon, LogOut, Settings, Star, Trophy, User, X, Zap } from "lucide-react" +import { Bell, ChevronDown, HammerIcon, LogOut, Settings, Star, Trophy, User, X, Zap, Sparkles } from "lucide-react" import { formatDistanceToNow, startOfDay, addDays } from "date-fns" import { Avatar, AvatarGenerator } from "../profile/setup/Avatar"; import { Moon, Sun } from "lucide-react" @@ -29,6 +29,8 @@ import { LocationsDropdown } from "./Navigation/LocationsDropdown" import TechnologyPopover, { TechnologySection } from "./Navigation/TechTreeDropdown" import { useRouter } from "next/navigation" import ResponsiveAlerts from "./Navigation/AlertsDropdown" +import ProjectPreferencesModal from "@/src/components/onboarding/ProjectPreferencesModal" +import { useUserPreferences, ProjectType } from "@/src/hooks/useUserPreferences" // Sample data - replace with actual data in your implementation const techTree = [ @@ -45,6 +47,7 @@ export default function GameNavbar() { const router = useRouter(); const { isDark, toggleDarkMode } = UseDarkMode(); + const { preferences, savePreferences } = useUserPreferences(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [alertMessage, setAlertMessage] = useState(""); @@ -55,8 +58,18 @@ export default function GameNavbar() { const [userProgress, setUserProgress] = useState<{ [key: string]: number }>({}); const [hasNewAlert, setHasNewAlert] = useState(false); const [newNotificationsCount, setNewNotificationsCount] = useState(0); + const [projectPreferencesOpen, setProjectPreferencesOpen] = useState(false); const signOut = async () => { + // Clear all local storage, session storage, and cookies before signing out + if (typeof window !== 'undefined') { + window.localStorage.clear(); + window.sessionStorage.clear(); + // Remove all cookies for current domain + document.cookie.split(';').forEach(function(c) { + document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date(0).toUTCString() + ';path=/'); + }); + } const { error } = await supabase.auth.signOut(); if (error) { console.error("Error signing out:", error.message); @@ -66,6 +79,10 @@ export default function GameNavbar() { } }; + const handleProjectPreferencesSave = (interests: ProjectType[]) => { + savePreferences({ projectInterests: interests }); + }; + // Calculate time remaining useEffect(() => { const calculateTimeRemaining = () => { @@ -133,8 +150,9 @@ export default function GameNavbar() { }, []); return ( + <> + + setProjectPreferencesOpen(false)} + onSave={handleProjectPreferencesSave} + initialInterests={preferences?.projectInterests} + /> + ); }; \ No newline at end of file diff --git a/src/components/onboarding/InteractiveTutorial.tsx b/src/components/onboarding/InteractiveTutorial.tsx new file mode 100644 index 00000000..38e486df --- /dev/null +++ b/src/components/onboarding/InteractiveTutorial.tsx @@ -0,0 +1,446 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { cn } from "@/src/shared/utils"; +import { Button } from "@/src/components/ui/button"; +import { + ChevronRight, + ChevronLeft, + X, + Target, + Sparkles, + CheckCircle2, + Circle, + Lightbulb, + MousePointer2, +} from "lucide-react"; + +export interface TutorialStep { + id: string; + title: string; + description: string; + /** Optional element selector to highlight */ + highlightSelector?: string; + /** Optional action the user needs to take */ + action?: { + type: "click" | "wait" | "any"; + targetSelector?: string; + waitMs?: number; + }; + /** Tip shown after the main content */ + tip?: string; + /** Image or icon to display */ + visual?: React.ReactNode; + /** Position of the tooltip relative to highlighted element */ + position?: "top" | "bottom" | "left" | "right" | "center"; +} + +interface InteractiveTutorialProps { + steps: TutorialStep[]; + onComplete: () => void; + onSkip?: () => void; + /** Called when moving to a step (for analytics) */ + onStepChange?: (stepIndex: number, stepId: string) => void; + /** Starting step index */ + startStep?: number; + /** Allow skipping individual steps */ + allowSkipSteps?: boolean; + /** Tutorial title */ + title?: string; +} + +export default function InteractiveTutorial({ + steps, + onComplete, + onSkip, + onStepChange, + startStep = 0, + allowSkipSteps = true, + title = "Getting Started", +}: InteractiveTutorialProps) { + const [currentStep, setCurrentStep] = useState(startStep); + const [completedSteps, setCompletedSteps] = useState>(new Set()); + const [isWaiting, setIsWaiting] = useState(false); + const [highlightRect, setHighlightRect] = useState(null); + + const step = steps[currentStep]; + const isLastStep = currentStep === steps.length - 1; + const isFirstStep = currentStep === 0; + const progress = ((currentStep + 1) / steps.length) * 100; + + // Update highlight position when step changes + useEffect(() => { + if (step?.highlightSelector) { + const element = document.querySelector(step.highlightSelector); + if (element) { + const rect = element.getBoundingClientRect(); + setHighlightRect(rect); + + // Scroll element into view if needed + element.scrollIntoView({ behavior: "smooth", block: "center" }); + } else { + setHighlightRect(null); + } + } else { + setHighlightRect(null); + } + }, [step]); + + // Handle action completion + useEffect(() => { + if (!step?.action) return; + + if (step.action.type === "wait" && step.action.waitMs) { + setIsWaiting(true); + const timer = setTimeout(() => { + setIsWaiting(false); + markStepComplete(step.id); + }, step.action.waitMs); + return () => clearTimeout(timer); + } + + if (step.action.type === "click" && step.action.targetSelector) { + const handler = (e: Event) => { + const target = e.target as HTMLElement; + if (target.matches(step.action!.targetSelector!)) { + markStepComplete(step.id); + goNext(); + } + }; + document.addEventListener("click", handler, true); + return () => document.removeEventListener("click", handler, true); + } + }, [step]); + + const markStepComplete = useCallback((stepId: string) => { + setCompletedSteps((prev) => new Set([...prev, stepId])); + }, []); + + const goNext = useCallback(() => { + markStepComplete(step.id); + + if (isLastStep) { + onComplete(); + } else { + const nextIndex = currentStep + 1; + setCurrentStep(nextIndex); + onStepChange?.(nextIndex, steps[nextIndex].id); + } + }, [currentStep, isLastStep, onComplete, onStepChange, step, steps, markStepComplete]); + + const goPrev = useCallback(() => { + if (!isFirstStep) { + const prevIndex = currentStep - 1; + setCurrentStep(prevIndex); + onStepChange?.(prevIndex, steps[prevIndex].id); + } + }, [currentStep, isFirstStep, onStepChange, steps]); + + const goToStep = useCallback( + (index: number) => { + if (allowSkipSteps || index <= currentStep || completedSteps.has(steps[index - 1]?.id)) { + setCurrentStep(index); + onStepChange?.(index, steps[index].id); + } + }, + [allowSkipSteps, currentStep, completedSteps, onStepChange, steps] + ); + + // Handle keyboard shortcuts + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" && onSkip) { + onSkip(); + } else if (e.key === "ArrowRight") { + goNext(); + } else if (e.key === "ArrowLeft") { + goPrev(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [goNext, goPrev, onSkip]); + + // Tooltip position calculation + const getTooltipStyle = (): React.CSSProperties => { + if (!highlightRect || step?.position === "center") { + return { + position: "fixed", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + }; + } + + const padding = 16; + const tooltipWidth = 400; + const tooltipHeight = 300; + + switch (step?.position) { + case "top": + return { + position: "fixed", + bottom: window.innerHeight - highlightRect.top + padding, + left: Math.max(padding, highlightRect.left + highlightRect.width / 2 - tooltipWidth / 2), + }; + case "bottom": + return { + position: "fixed", + top: highlightRect.bottom + padding, + left: Math.max(padding, highlightRect.left + highlightRect.width / 2 - tooltipWidth / 2), + }; + case "left": + return { + position: "fixed", + top: Math.max(padding, highlightRect.top + highlightRect.height / 2 - tooltipHeight / 2), + right: window.innerWidth - highlightRect.left + padding, + }; + case "right": + return { + position: "fixed", + top: Math.max(padding, highlightRect.top + highlightRect.height / 2 - tooltipHeight / 2), + left: highlightRect.right + padding, + }; + default: + return { + position: "fixed", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + }; + } + }; + + return ( + <> + {/* Overlay */} +
+ + {/* Highlight cutout */} + {highlightRect && ( +
+ {/* Pulsing indicator */} +
+
+
+ +
+
+
+ )} + + {/* Tutorial card */} +
+ {/* Header */} +
+
+
+ + {title} +
+ {onSkip && ( + + )} +
+ + {/* Progress bar */} +
+
+
+ + {/* Step indicators */} +
+ {steps.map((s, index) => { + const isComplete = completedSteps.has(s.id); + const isCurrent = index === currentStep; + return ( + + ); + })} +
+
+ + {/* Content */} +
+ {/* Visual */} + {step.visual && ( +
{step.visual}
+ )} + + {/* Step title */} +

{step.title}

+ + {/* Step description */} +

{step.description}

+ + {/* Waiting indicator */} + {isWaiting && ( +
+
+ Please wait... +
+ )} + + {/* Tip */} + {step.tip && ( +
+ +

{step.tip}

+
+ )} +
+ + {/* Actions */} +
+ + + + {currentStep + 1} / {steps.length} + + + +
+
+ + ); +} + +// Preset tutorial flows +export const ONBOARDING_STEPS: TutorialStep[] = [ + { + id: "welcome", + title: "Welcome to Star Sailors! 🚀", + description: + "You're about to become a citizen scientist! Your discoveries will contribute to real space research and help us understand our universe.", + tip: "Everything you do here uses real data from NASA and other space agencies.", + position: "center", + }, + { + id: "structures-overview", + title: "Your Space Station", + description: + "You have four main structures: a Telescope for observing distant stars, a Satellite for orbiting planets, a Rover for surface exploration, and a Solar Observatory for monitoring the Sun.", + tip: "Each structure unlocks different types of discoveries.", + position: "center", + }, + { + id: "telescope-intro", + title: "Start with Your Telescope", + description: + "The telescope is the heart of your station. Use it to hunt for exoplanets by analyzing light curves - you're looking for tiny dips in brightness when a planet passes in front of its star.", + highlightSelector: "[data-structure='telescope']", + position: "bottom", + }, + { + id: "deploy-structure", + title: "Deploy Your Structure", + description: + "Click on any structure to deploy it. Once deployed, you'll see missions you can complete. Each mission teaches you something new while contributing to real science!", + tip: "Start with the beginner missions - they'll walk you through everything.", + position: "center", + }, + { + id: "earn-rewards", + title: "Earn Stardust & Discoveries", + description: + "Complete missions to earn Stardust (our currency) and make real discoveries. Your classifications are reviewed by scientists and can appear in actual research papers!", + position: "center", + }, + { + id: "ready-to-explore", + title: "You're Ready to Explore!", + description: + "That's all you need to get started. Deploy your telescope and make your first discovery. The universe is waiting for you!", + tip: "Don't worry about making mistakes - learning is part of the journey.", + position: "center", + }, +]; + +export const DEPLOYMENT_STEPS: TutorialStep[] = [ + { + id: "select-location", + title: "Choose a Location", + description: + "First, select where you want to deploy this structure. Different locations offer different types of discoveries.", + position: "center", + }, + { + id: "view-missions", + title: "Browse Available Missions", + description: + "Each location has unique missions. Look for ones marked 'Beginner' if you're just starting out.", + position: "center", + }, +]; + +// Ensure location-related logic is wrapped in a client-side check +if (typeof window !== "undefined") { + console.log("Location is available on the client-side."); +} diff --git a/src/components/onboarding/ProjectPreferencesModal.tsx b/src/components/onboarding/ProjectPreferencesModal.tsx new file mode 100644 index 00000000..d1dd0411 --- /dev/null +++ b/src/components/onboarding/ProjectPreferencesModal.tsx @@ -0,0 +1,270 @@ +"use client"; + +import { useState } from "react"; +import { cn } from "@/src/shared/utils"; +import { Button } from "@/src/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/src/components/ui/dialog"; +import { + Telescope, + Globe, + Cloud, + Car, + Snowflake, + Sun, + Check, + Sparkles, + ArrowRight, +} from "lucide-react"; +import { ProjectType } from "@/src/hooks/useUserPreferences"; + +interface Project { + id: ProjectType; + title: string; + shortTitle: string; + description: string; + detailedDescription: string; + icon: React.ReactNode; + color: string; + bgColor: string; + structure: "telescope" | "satellite" | "rover" | "solar"; +} + +const PROJECTS: Project[] = [ + { + id: "planet-hunting", + title: "Discover Exoplanets", + shortTitle: "Planets", + description: "Find planets orbiting distant stars", + detailedDescription: + "Use your telescope to analyze light curves from NASA's TESS mission. When a planet passes in front of its star, it causes a tiny dip in brightness. By identifying these patterns, you help discover new worlds that could harbor life!", + icon: , + color: "text-blue-400", + bgColor: "bg-blue-500/20 border-blue-500/30", + structure: "telescope", + }, + { + id: "asteroid-hunting", + title: "Hunt Asteroids", + shortTitle: "Asteroids", + description: "Track asteroids and minor planets", + detailedDescription: + "Compare sequences of telescope images to spot moving objects. Asteroids shift position between frames while stars stay fixed. Your discoveries help track potentially hazardous asteroids and find future exploration targets!", + icon: , + color: "text-orange-400", + bgColor: "bg-orange-500/20 border-orange-500/30", + structure: "telescope", + }, + { + id: "cloud-tracking", + title: "Track Clouds & Storms", + shortTitle: "Clouds", + description: "Study alien atmospheres", + detailedDescription: + "Deploy satellites to orbit planets you've discovered. Identify cloud formations on Mars and massive storms on gas giants like Jupiter. Your classifications help scientists understand climate patterns across the solar system!", + icon: , + color: "text-cyan-400", + bgColor: "bg-cyan-500/20 border-cyan-500/30", + structure: "satellite", + }, + { + id: "rover-training", + title: "Train AI Rovers", + shortTitle: "Rovers", + description: "Help rovers navigate terrain", + detailedDescription: + "Classify Martian terrain to train autonomous rovers. Identify sand, rocks, and soil in rover images. Your work directly helps NASA's rovers navigate safely and discover mineral deposits for future mining operations!", + icon: , + color: "text-green-400", + bgColor: "bg-green-500/20 border-green-500/30", + structure: "rover", + }, + { + id: "ice-tracking", + title: "Track Seasonal Ice", + shortTitle: "Ice", + description: "Monitor planetary ice behavior", + detailedDescription: + "Study how ice sublimates (turns directly to gas) on Mars. Identify 'fans' and 'blotches' - dark patterns that appear as CO₂ ice evaporates in spring. This helps us understand Martian seasons and find water resources!", + icon: , + color: "text-sky-300", + bgColor: "bg-sky-500/20 border-sky-500/30", + structure: "satellite", + }, + { + id: "solar-monitoring", + title: "Monitor Solar Activity", + shortTitle: "Solar", + description: "Track sunspots and solar flares", + detailedDescription: + "Count and classify sunspots to monitor our Sun's health. Solar activity can cause electrical outages on Earth and affects space weather throughout the solar system. Your observations help predict dangerous solar storms!", + icon: , + color: "text-yellow-400", + bgColor: "bg-yellow-500/20 border-yellow-500/30", + structure: "solar", + }, +]; + +interface ProjectPreferencesModalProps { + isOpen: boolean; + onClose: () => void; + onSave: (interests: ProjectType[]) => void; + initialInterests?: ProjectType[]; +} + +export default function ProjectPreferencesModal({ + isOpen, + onClose, + onSave, + initialInterests = [], +}: ProjectPreferencesModalProps) { + const [selectedProjects, setSelectedProjects] = useState(initialInterests); + const [expandedProject, setExpandedProject] = useState(null); + + const toggleProject = (projectId: ProjectType) => { + setSelectedProjects((prev) => + prev.includes(projectId) + ? prev.filter((p) => p !== projectId) + : [...prev, projectId] + ); + }; + + const handleSave = () => { + // If nothing selected, default to all projects + const interests = selectedProjects.length > 0 ? selectedProjects : PROJECTS.map((p) => p.id); + onSave(interests); + onClose(); + }; + + const selectAll = () => { + setSelectedProjects(PROJECTS.map((p) => p.id)); + }; + + return ( + !open && onClose()}> + + + + + What interests you most? + + + Choose the projects you'd like to focus on. We'll highlight these on your + dashboard and tailor your experience. You can change this anytime. + + + +
+ {PROJECTS.map((project) => { + const isSelected = selectedProjects.includes(project.id); + const isExpanded = expandedProject === project.id; + + return ( +
+ {/* Main row */} +
toggleProject(project.id)} + > + {/* Icon */} +
+ + {project.icon} + +
+ + {/* Content */} +
+

{project.title}

+

{project.description}

+
+ + {/* Checkbox */} +
+ {isSelected && } +
+
+ + {/* Learn more button */} + + + {/* Expanded content */} + {isExpanded && ( +
+ {project.detailedDescription} +
+ Uses: + + {project.structure.charAt(0).toUpperCase() + project.structure.slice(1)} + +
+
+ )} +
+ ); + })} +
+ + {/* Actions */} +
+ + +
+ + +
+
+ + {/* Selection summary */} + {selectedProjects.length > 0 && ( +

+ {selectedProjects.length} project{selectedProjects.length !== 1 ? "s" : ""} selected +

+ )} +
+
+ ); +} diff --git a/src/components/onboarding/README.md b/src/components/onboarding/README.md index a12ada83..fc56d354 100644 --- a/src/components/onboarding/README.md +++ b/src/components/onboarding/README.md @@ -1,6 +1,122 @@ # Onboarding Components -This directory contains components for new user onboarding and project selection. +This directory contains components for new user onboarding, project selection, and tutorials. + +## Overview + +The onboarding system consists of several interconnected components: + +1. **ProjectPreferencesModal** - Asks users what projects they're interested in +2. **StructureExplainer** - Explains what each structure does and how it connects to real science +3. **InteractiveTutorial** - Step-by-step guided tour of the app +4. **ProjectSelectionViewport** - Quick project selection for new users + +## ProjectPreferencesModal + +A dialog that asks users to select which projects interest them most. Selections are saved to localStorage and used to customize the dashboard. + +### Features + +- **6 Project Types** with detailed descriptions +- **Expandable cards** with "Learn more" options +- **Multi-select** with visual feedback +- **Persistent storage** via `useUserPreferences` hook + +### Usage + +```tsx +import ProjectPreferencesModal from '@/src/components/onboarding/ProjectPreferencesModal'; +import { ProjectType } from '@/src/hooks/useUserPreferences'; + + setShowModal(false)} + onSave={(interests: ProjectType[]) => { + // Handle save + }} + initialInterests={[]} +/> +``` + +## StructureExplainer + +Explains what each structure (Telescope, Satellite, Rover, Solar) does and why it matters for science. + +### Features + +- **Detailed explanations** of each structure +- **"What can you do here?"** list of activities +- **Fun facts** about real science +- **Mineral connection** explanation (for Rover) +- **Carousel mode** for viewing all structures + +### Usage + +```tsx +import StructureExplainer from '@/src/components/onboarding/StructureExplainer'; + +// Single structure + setShowExplainer(false)} +/> + +// All structures in carousel + markAsComplete()} +/> +``` + +## InteractiveTutorial + +A step-by-step guided tour overlay with progress tracking and optional element highlighting. + +### Features + +- **Step indicators** with progress bar +- **Element highlighting** (optional) +- **Quiz support** (check understanding) +- **Tips** for each step +- **Skip/back navigation** + +### Usage + +```tsx +import InteractiveTutorial, { + ONBOARDING_STEPS, + DEPLOYMENT_STEPS, + TutorialStep +} from '@/src/components/onboarding/InteractiveTutorial'; + + markTutorialComplete()} + onSkip={() => setShowTutorial(false)} + title="Welcome Tour" +/> +``` + +### Custom Steps + +```tsx +const customSteps: TutorialStep[] = [ + { + id: 'step-1', + title: 'Welcome!', + description: 'This is a custom step.', + tip: 'Optional helpful tip', + highlightSelector: '[data-tutorial="target"]', // Optional + position: 'bottom', // top | bottom | left | right | center + quiz: { // Optional + question: 'What did you learn?', + options: ['A', 'B', 'C'], + correctIndex: 0, + explanation: 'A is correct because...' + } + } +]; +``` ## ProjectSelectionViewport @@ -34,18 +150,73 @@ import ProjectSelectionViewport from '@/src/components/onboarding/ProjectSelecti showWelcomeMessage={true} onProjectSelect={(projectId) => { console.log('Selected:', projectId); - // Handle fast deployment logic }} /> ``` -### Integration +## useUserPreferences Hook -The component is integrated into the main activity page (`app/page.tsx`) and only displays for users who have made 0 classifications. Once they select a project and activate tools, they are redirected to the appropriate deployment page. +A hook for managing user preferences stored in localStorage. -### Future Enhancements +### Features + +- **Project interests** - Which projects the user wants to focus on +- **Onboarding state** - Track tutorial/guide completion +- **Device detection** - Prompt for preferences on new devices +- **Structure order** - Custom dashboard layout + +### Usage + +```tsx +import { useUserPreferences, ProjectType } from '@/src/hooks/useUserPreferences'; + +const { + preferences, + isLoading, + needsPreferencesPrompt, + setProjectInterests, + completeOnboarding, + markStructureGuideSeen, + resetPreferences, +} = useUserPreferences(); +``` -- Fast deployment backend integration -- Progress tracking -- Project recommendations based on user behavior -- Tutorial integration \ No newline at end of file +## Integration with TutorialContentBlock + +The existing `TutorialContentBlock` component (in `/src/components/projects/`) has been enhanced with: + +- **Tips** for each slide +- **Quiz questions** to check understanding +- **Scientific context** banner explaining real-world impact +- **Structure type** association + +```tsx +import TutorialContentBlock, { + createTutorialSlides, + SCIENTIFIC_CONTEXTS +} from '@/src/components/projects/TutorialContentBlock'; + +const slides = createTutorialSlides([ + { + title: 'Step 1', + text: 'Description', + image: '/path/to/image.png', + tip: 'Helpful tip!', + quiz: { + question: 'Question?', + options: ['A', 'B', 'C'], + correctIndex: 0, + explanation: 'Explanation' + } + } +]); + + {}} + title="Tutorial Title" + scientificContext={SCIENTIFIC_CONTEXTS['balloon-marsCloudShapes']} + structureType="satellite" +/> +``` \ No newline at end of file diff --git a/src/components/onboarding/StructureExplainer.tsx b/src/components/onboarding/StructureExplainer.tsx new file mode 100644 index 00000000..1a427864 --- /dev/null +++ b/src/components/onboarding/StructureExplainer.tsx @@ -0,0 +1,309 @@ +"use client"; + +import { useState } from "react"; +import { cn } from "@/src/shared/utils"; +import { Button } from "@/src/components/ui/button"; +import { + Telescope, + Satellite, + Car, + Sun, + ChevronRight, + ArrowLeft, + ArrowRight, + Pickaxe, + Sparkles, + X, +} from "lucide-react"; + +interface StructureInfo { + id: "telescope" | "satellite" | "rover" | "solar"; + name: string; + icon: React.ReactNode; + color: string; + bgColor: string; + whatIsIt: string; + whatCanYouDo: string[]; + funFact: string; + mineralConnection?: string; +} + +const STRUCTURES: StructureInfo[] = [ + { + id: "telescope", + name: "Telescope Observatory", + icon: , + color: "text-purple-400", + bgColor: "bg-purple-500/20 border-purple-500/30", + whatIsIt: + "Your telescope is connected to real space observatories like NASA's TESS (Transiting Exoplanet Survey Satellite). It lets you analyze actual data from space missions!", + whatCanYouDo: [ + "Hunt for exoplanets by spotting tiny dips in starlight when planets pass in front of their stars", + "Track asteroids and minor planets as they move across the sky", + "Study distant galaxies and help classify cosmic phenomena", + "Contribute to real scientific papers and discoveries", + ], + funFact: + "Citizen scientists have discovered over 100 new exoplanets using telescope data - including some in the habitable zone!", + }, + { + id: "satellite", + name: "Orbital Satellite", + icon: , + color: "text-cyan-400", + bgColor: "bg-cyan-500/20 border-cyan-500/30", + whatIsIt: + "Your satellites orbit planets you've discovered. They take high-resolution images of planetary surfaces and atmospheres using data from missions like Mars Reconnaissance Orbiter.", + whatCanYouDo: [ + "Track cloud formations and massive storms on planets", + "Study seasonal changes like ice melting on Mars", + "Identify interesting terrain features for rover exploration", + "Monitor atmospheric changes over time", + ], + funFact: + "The 'fans' and 'blotches' you identify on Mars are caused by CO₂ geysers erupting from beneath the ice as it sublimates in spring!", + }, + { + id: "rover", + name: "Surface Rover", + icon: , + color: "text-green-400", + bgColor: "bg-green-500/20 border-green-500/30", + whatIsIt: + "Your rovers explore planetary surfaces, just like NASA's Perseverance and Curiosity on Mars. They analyze rocks, take photos, and search for resources.", + whatCanYouDo: [ + "Train AI to help real rovers navigate autonomously", + "Classify terrain types: sand, rocks, soil, and bedrock", + "Discover mineral deposits valuable for future space mining", + "Help plan safe routes for actual Mars rovers", + ], + funFact: + "Real NASA rover drivers use terrain classifications like yours to plan each day's drive path - your work literally guides robots on Mars!", + mineralConnection: + "When you classify terrain, you might discover mineral deposits! Minerals come from analyzing rover images and satellite data to identify resource-rich areas. Future space mining operations will depend on discoveries like yours.", + }, + { + id: "solar", + name: "Solar Observatory", + icon: , + color: "text-yellow-400", + bgColor: "bg-yellow-500/20 border-yellow-500/30", + whatIsIt: + "Your solar observatory monitors our Sun using data from NASA's Solar Dynamics Observatory. The Sun's activity affects Earth and the entire solar system!", + whatCanYouDo: [ + "Count and classify sunspots on the solar surface", + "Track solar flares and predict space weather", + "Monitor the Sun's magnetic activity cycle", + "Help protect satellites and power grids from solar storms", + ], + funFact: + "A massive solar storm in 1859 (the Carrington Event) was so powerful that telegraph operators got electric shocks! Monitoring sunspots helps us prepare for the next big one.", + }, +]; + +interface StructureExplainerProps { + structureId?: "telescope" | "satellite" | "rover" | "solar"; + onClose?: () => void; + onComplete?: () => void; + showAll?: boolean; + compact?: boolean; +} + +export default function StructureExplainer({ + structureId, + onClose, + onComplete, + showAll = false, + compact = false, +}: StructureExplainerProps) { + const [currentIndex, setCurrentIndex] = useState( + structureId ? STRUCTURES.findIndex((s) => s.id === structureId) : 0 + ); + const [hasSeenAll, setHasSeenAll] = useState(false); + + const structures = showAll ? STRUCTURES : [STRUCTURES.find((s) => s.id === structureId)!]; + const structure = structures[currentIndex]; + + const goNext = () => { + if (currentIndex < structures.length - 1) { + setCurrentIndex((prev) => prev + 1); + if (currentIndex === structures.length - 2) { + setHasSeenAll(true); + } + } else if (onComplete) { + onComplete(); + } + }; + + const goPrev = () => { + if (currentIndex > 0) { + setCurrentIndex((prev) => prev - 1); + } + }; + + if (!structure) return null; + + if (compact) { + return ( +
+
+
+ {structure.icon} +
+
+

{structure.name}

+
+
+

{structure.whatIsIt}

+
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+
+ {structure.icon} +
+
+

{structure.name}

+ {showAll && ( +

+ {currentIndex + 1} of {structures.length} structures +

+ )} +
+
+ {onClose && ( + + )} +
+
+ + {/* Content */} +
+ {/* What is it */} +
+

+ + What is this? +

+

{structure.whatIsIt}

+
+ + {/* What can you do */} +
+

+ + What can you do here? +

+
    + {structure.whatCanYouDo.map((item, index) => ( +
  • +
    + {item} +
  • + ))} +
+
+ + {/* Mineral connection (if exists) */} + {structure.mineralConnection && ( +
+

+ + Where do minerals come from? +

+

+ {structure.mineralConnection} +

+
+ )} + + {/* Fun fact */} +
+

+ Fun fact: + {structure.funFact} +

+
+
+ + {/* Navigation (for showAll mode) */} + {showAll && ( +
+ + + {/* Dots indicator */} +
+ {structures.map((_, index) => ( +
+ + +
+ )} + + {/* Single structure close button */} + {!showAll && onClose && ( +
+ +
+ )} +
+ ); +} + +// Export structures for use in other components +export { STRUCTURES }; +export type { StructureInfo }; diff --git a/src/components/onboarding/TutorialWrapper.tsx b/src/components/onboarding/TutorialWrapper.tsx new file mode 100644 index 00000000..75a88302 --- /dev/null +++ b/src/components/onboarding/TutorialWrapper.tsx @@ -0,0 +1,323 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useUserPreferences, TutorialId } from "@/src/hooks/useUserPreferences"; +import InteractiveTutorial, { TutorialStep } from "./InteractiveTutorial"; +import { Button } from "@/src/components/ui/button"; +import { HelpCircle, RotateCcw } from "lucide-react"; + +interface TutorialWrapperProps { + /** Unique identifier for this tutorial */ + tutorialId: TutorialId; + /** The tutorial steps */ + steps: TutorialStep[]; + /** Title shown in the tutorial */ + title: string; + /** Content to render when tutorial is complete or skipped */ + children: React.ReactNode; + /** Optional: Force show tutorial even if completed */ + forceShow?: boolean; + /** Optional: Show replay button when tutorial is complete */ + showReplayButton?: boolean; + /** Optional: Position of replay button */ + replayButtonPosition?: "top-right" | "bottom-right" | "inline"; + /** Optional: Callback when tutorial completes */ + onComplete?: () => void; + /** Optional: Callback when tutorial is skipped */ + onSkip?: () => void; +} + +/** + * TutorialWrapper - Wraps content with an optional tutorial overlay + * + * Shows a tutorial to users who haven't completed it yet. + * Tutorial completion is tracked in localStorage via useUserPreferences. + */ +export default function TutorialWrapper({ + tutorialId, + steps, + title, + children, + forceShow = false, + showReplayButton = true, + replayButtonPosition = "top-right", + onComplete, + onSkip, +}: TutorialWrapperProps) { + const { + hasTutorialCompleted, + markTutorialComplete, + resetTutorial, + isLoading, + } = useUserPreferences(); + + const [showTutorial, setShowTutorial] = useState(false); + const [hasChecked, setHasChecked] = useState(false); + + // Check if tutorial should be shown + useEffect(() => { + if (isLoading) return; + + const completed = hasTutorialCompleted(tutorialId); + + if (forceShow || !completed) { + setShowTutorial(true); + } else { + setShowTutorial(false); + } + + setHasChecked(true); + }, [isLoading, tutorialId, forceShow, hasTutorialCompleted]); + + const handleComplete = () => { + markTutorialComplete(tutorialId); + setShowTutorial(false); + onComplete?.(); + }; + + const handleSkip = () => { + markTutorialComplete(tutorialId); + setShowTutorial(false); + onSkip?.(); + }; + + const handleReplay = () => { + resetTutorial(tutorialId); + setShowTutorial(true); + }; + + // Show loading state while checking preferences + if (!hasChecked || isLoading) { + return ( +
+ {children} +
+ ); + } + + // Show tutorial overlay + if (showTutorial) { + return ( + <> + + {/* Still render children behind the overlay so users see the context */} +
+ {children} +
+ + ); + } + + // Tutorial completed - show content with optional replay button + return ( +
+ {showReplayButton && ( + + )} + {children} +
+ ); +} + +interface ReplayButtonProps { + position: "top-right" | "bottom-right" | "inline"; + onReplay: () => void; + title: string; +} + +function ReplayButton({ position, onReplay, title }: ReplayButtonProps) { + if (position === "inline") { + return ( + + ); + } + + const positionClasses = position === "top-right" + ? "absolute top-2 right-2 z-40" + : "absolute bottom-2 right-2 z-40"; + + return ( + + ); +} + +// Pre-built tutorial step sets for common scenarios +// These tutorials guide users through actual interactions with the interface + +export const TELESCOPE_INTRO_STEPS: TutorialStep[] = [ + { + id: "telescope-welcome", + title: "Welcome to Your Telescope! 🔭", + description: + "Your telescope connects you to real space observatories like NASA's TESS satellite. Let's get you started with your first deployment!", + tip: "Citizen scientists have discovered over 100 exoplanets using telescope data just like this.", + position: "center", + }, + { + id: "telescope-look-around", + title: "Look Around", + description: + "Take a moment to explore this view. You can see the starfield background - this represents the sector of space your telescope can observe.", + action: { type: "wait", waitMs: 3000 }, + position: "center", + }, + { + id: "telescope-find-deploy", + title: "Find the Deploy Button", + description: + "Look for the 'Deploy Telescope' button below. This is how you'll point your telescope at a new sector of space each week.", + highlightSelector: "[data-tutorial='deploy-telescope']", + position: "bottom", + }, + { + id: "telescope-click-deploy", + title: "Click Deploy Telescope", + description: + "Go ahead and click the Deploy button to choose where to point your telescope!", + highlightSelector: "[data-tutorial='deploy-telescope']", + action: { type: "click", targetSelector: "[data-tutorial='deploy-telescope']" }, + position: "bottom", + }, +]; + +export const TELESCOPE_DEPLOY_STEPS: TutorialStep[] = [ + { + id: "deploy-intro", + title: "Choose Your Observation", + description: + "You're about to deploy your telescope! Different projects focus on different discoveries.", + position: "center", + }, + { + id: "deploy-select-project", + title: "Select a Project", + description: + "Click on one of the project options to choose what you'll be searching for this week.", + highlightSelector: "[data-tutorial='project-options']", + action: { type: "click", targetSelector: "[data-tutorial='project-options']" }, + position: "center", + }, + { + id: "deploy-confirm", + title: "Confirm Your Choice", + description: + "Great choice! Now click the Confirm button to start your telescope scanning the sky.", + highlightSelector: "[data-tutorial='confirm-deploy']", + action: { type: "click", targetSelector: "[data-tutorial='confirm-deploy']" }, + position: "center", + }, +]; + +export const SATELLITE_INTRO_STEPS: TutorialStep[] = [ + { + id: "satellite-welcome", + title: "Your Orbital Satellite 🛰️", + description: + "Satellites orbit planets to capture images of surfaces and atmospheres. Let's learn how to use yours!", + position: "center", + }, + { + id: "satellite-explore", + title: "Explore the View", + description: + "This view shows planets you can observe. Take a moment to look around - you'll see the planet details and navigation controls.", + action: { type: "wait", waitMs: 3000 }, + position: "center", + }, + { + id: "satellite-navigate", + title: "Navigate Between Planets", + description: + "Use the arrow buttons to browse through available planets. Each planet has different characteristics to study.", + highlightSelector: "[data-tutorial='planet-nav-next']", + action: { type: "click", targetSelector: "[data-tutorial='planet-nav-next']" }, + position: "center", + }, + { + id: "satellite-deploy", + title: "Deploy Satellite", + description: + "Click the Deploy Satellite button to launch your satellite into orbit around this planet.", + highlightSelector: "[data-tutorial='deploy-satellite']", + action: { type: "click", targetSelector: "[data-tutorial='deploy-satellite']" }, + position: "center", + }, +]; + +export const ROVER_INTRO_STEPS: TutorialStep[] = [ + { + id: "rover-welcome", + title: "Meet Your Rover 🚗", + description: + "Your rover explores Mars! You'll set waypoints and it will traverse the terrain, finding objects for you to classify.", + position: "center", + }, + { + id: "rover-look-surface", + title: "Survey the Surface", + description: + "Look at the Martian surface below. The terrain varies - sand, rocks, and geological features are all waiting to be explored.", + action: { type: "wait", waitMs: 3000 }, + position: "center", + }, + { + id: "rover-deploy-button", + title: "Deploy Your Rover", + description: + "Click the Deploy Rover button to set waypoints for your rover's journey across Mars.", + highlightSelector: "[data-tutorial='deploy-rover']", + action: { type: "click", targetSelector: "[data-tutorial='deploy-rover']" }, + position: "center", + }, +]; + +export const SOLAR_INTRO_STEPS: TutorialStep[] = [ + { + id: "solar-welcome", + title: "Solar Observatory ☀️", + description: + "Your solar observatory monitors our Sun! You'll count sunspots and track solar activity.", + position: "center", + }, + { + id: "solar-observe-sun", + title: "Observe the Sun", + description: + "Look at the 3D model of the Sun. Can you see any dark spots? Those are sunspots - areas of intense magnetic activity.", + action: { type: "wait", waitMs: 3000 }, + tip: "Try rotating the Sun to see it from different angles!", + position: "center", + }, + { + id: "solar-participate", + title: "Join the Mission", + description: + "Click the Participate button to join this week's sunspot counting mission.", + highlightSelector: "[data-tutorial='participate-solar']", + action: { type: "click", targetSelector: "[data-tutorial='participate-solar']" }, + position: "center", + }, +]; diff --git a/src/components/projects/Lidar/cloudspottingOnMars.tsx b/src/components/projects/Lidar/cloudspottingOnMars.tsx index 99d3161a..69f619fa 100644 --- a/src/components/projects/Lidar/cloudspottingOnMars.tsx +++ b/src/components/projects/Lidar/cloudspottingOnMars.tsx @@ -4,7 +4,7 @@ import React, { useState } from "react"; import { useSupabaseClient, useSession } from "@supabase/auth-helpers-react"; import { useActivePlanet } from "@/src/core/context/ActivePlanet"; import ClassificationForm from "../(classifications)/PostForm"; -import TutorialContentBlock, { createTutorialSlides } from "../TutorialContentBlock"; +import TutorialContentBlock, { createTutorialSlides, SCIENTIFIC_CONTEXTS } from "../TutorialContentBlock"; interface LidarProps { anomalyId: string; @@ -21,56 +21,84 @@ export const CloudspottingOnMarsTutorial: React.FC = ({ const imageUrl = `${supabaseUrl}/storage/v1/object/public/clouds/${anomalyId}.png`; const [showClassification, setShowClassification] = useState(false); - // Tutorial slides for Cloudspotting on Mars + // Enhanced tutorial slides with tips and quizzes const tutorialSlides = createTutorialSlides([ { - title: "Cloudspotting on Mars", - text: "Hello, and welcome to Cloudspotting on Mars!", + title: "Welcome to Cloudspotting on Mars! ☁️", + text: "You're about to help NASA scientists study the Martian atmosphere! The Mars Climate Sounder on the Mars Reconnaissance Orbiter has captured incredible data about clouds high above the red planet, and we need your keen eyes to identify them.", + tip: "Your classifications help scientists understand Martian weather patterns and track water and CO₂ in the atmosphere.", }, { - title: "Finding Mars Clouds", - text: "We need your help finding clouds in Mars's atmosphere. The Mars Climate Sounder (MCS) on NASA's Mars Reconnaissance Orbiter observes the atmosphere in the infrared to determine the temperature and ice and dust content. Its measurements point to water-ice clouds and carbon-dioxide clouds at very high altitudes.", - image: "/assets/Docs/LIDAR/lidar-martianClouds/Step1.jpeg" + title: "The Mars Climate Sounder", + text: "The Mars Climate Sounder (MCS) on NASA's Mars Reconnaissance Orbiter observes the atmosphere in the infrared to measure temperature, ice, and dust content. Its measurements reveal water-ice clouds and carbon-dioxide clouds at very high altitudes - sometimes over 80 km above the surface!", + image: "/assets/Docs/LIDAR/lidar-martianClouds/Step1.jpeg", + tip: "These clouds are similar to Earth's noctilucent clouds, but they form from CO₂ and water ice instead of just water.", }, { - title: "Citizen Science", - text: "Clouds have been observed at Mars by various instruments on several orbiters (and rovers!). But there are so many clouds, a single individual cannot find them all on their own. With help from you and other citizen scientists, we think tens of thousands of these clouds can be identified!", - image: "/assets/Docs/LIDAR/lidar-martianClouds/Step2.png" + title: "Why Citizen Science?", + text: "Clouds have been observed at Mars by various instruments on several orbiters and rovers. But there are SO many clouds that no single researcher could possibly find them all! With your help, tens of thousands of cloud arches can be identified, creating a complete picture of Martian atmospheric dynamics.", + image: "/assets/Docs/LIDAR/lidar-martianClouds/Step2.png", + quiz: { + question: "Why do we need citizen scientists to identify Mars clouds?", + options: [ + "The images are too blurry for computers", + "There are too many images for researchers to analyze alone", + "NASA doesn't have any scientists", + "The clouds are invisible to machines" + ], + correctIndex: 1, + explanation: "Correct! There are thousands of observations, and human pattern recognition combined with many volunteers helps identify features that might otherwise be missed.", + }, }, { - title: "Cloud Arches", - text: "As your spacecraft moves through its orbit, clouds appear to rise from behind the atmosphere to a higher altitude and then fall again. This leads to an arch-like shape in the data. The peak of the arch is the real location of the cloud.", - image: "/assets/Docs/LIDAR/lidar-martianClouds/Step3.jpeg" + title: "Understanding Cloud Arches", + text: "As the spacecraft moves through its orbit, clouds appear to rise from behind the atmosphere to a higher altitude and then fall again. This creates a distinctive arch-like shape in the data. The peak of the arch marks the real location of the cloud in space.", + image: "/assets/Docs/LIDAR/lidar-martianClouds/Step3.jpeg", + tip: "Think of it like driving past a mountain - it seems to 'rise' and 'fall' as you pass by, even though it's standing still.", }, { title: "Reading the Data", - text: "You'll be presented with images that span four hours worth of MCS observations. From left to right, time moves forward as the spacecraft moves through its orbit. The vertical axis represents altitude (from 0 to 100 km) -- you'll see the brighter lower atmosphere on the bottom fading out higher in the atmosphere above. But sometimes high-altitude clouds appear as arches! Your goal is to search for arches like this. To decide if something is an arch, look for two distinct legs and a peak. Tell us what shape you see, how many clouds, and then the rough location in the text box.", - image: "/assets/Docs/LIDAR/lidar-martianClouds/Step4.png" + text: "Each image spans about four hours of MCS observations. Time moves from left to right. The vertical axis shows altitude (0-100 km) - you'll see the brighter lower atmosphere at the bottom, fading out higher up. High-altitude clouds appear as arches! Your goal: search for arches with two distinct legs and a peak.", + image: "/assets/Docs/LIDAR/lidar-martianClouds/Step4.png", + tip: "Focus on the upper portions of the image (above ~40km) where clouds are most visible against the darker background.", }, { - title: "Identifying Arches", - text: "Remember, you're looking for an arch with two legs and a peak. There are several bright streaks in the zoomed-in image below, but only the one on the left has two legs with a clear gap in between and a peak at the top.", - image: "/assets/Docs/LIDAR/lidar-martianClouds/Step5.png" + title: "What Makes a Valid Arch?", + text: "A valid cloud arch has TWO LEGS and a clear PEAK at the top. Look carefully - there might be bright streaks that aren't arches! In this zoomed example, only the left feature has two distinct legs with a gap between them and a clear peak.", + image: "/assets/Docs/LIDAR/lidar-martianClouds/Step5.png", + quiz: { + question: "What are the key features of a cloud arch?", + options: [ + "Just one bright vertical line", + "Two legs with a peak connecting them", + "A horizontal bright band", + "Random scattered dots" + ], + correctIndex: 1, + explanation: "Correct! Look for two distinct 'legs' rising up and connecting at a peak - like an upside-down V or the letter Λ.", + }, }, { - title: "Few Arches Example", - text: "Most images have about 2-3 arches. Here's an example with just 1-2:", - image: "/assets/Docs/LIDAR/lidar-martianClouds/Step6.jpeg" + title: "Example: 1-2 Arches", + text: "Most images have 2-3 arches, but some have fewer. Here's an example with just 1-2 visible cloud arches. Don't worry if you only find a few - that's still valuable data!", + image: "/assets/Docs/LIDAR/lidar-martianClouds/Step6.jpeg", + tip: "Quality over quantity! It's better to identify one definite arch than to guess at several uncertain ones.", }, { - title: "Many Arches Example", - text: "Many have several arches throughout (sometimes more than 10):", - image: "/assets/Docs/LIDAR/lidar-martianClouds/Step7.png" + title: "Example: Many Arches", + text: "Some images are rich with cloud activity and contain many arches - sometimes more than 10! These are exciting finds that show lots of atmospheric activity.", + image: "/assets/Docs/LIDAR/lidar-martianClouds/Step7.png", }, { - title: "No Arches Example", - text: "Others have none at all:", - image: "/assets/Docs/LIDAR/lidar-martianClouds/Step8.png" + title: "Example: No Arches", + text: "And some images have no arches at all - that's perfectly normal and still useful information! Knowing where clouds AREN'T is just as valuable as knowing where they are.", + image: "/assets/Docs/LIDAR/lidar-martianClouds/Step8.png", + tip: "If you don't see any arches, that's a valid classification. Don't try to force patterns that aren't there.", }, { - title: "Ready to Begin!", - text: "You're ready to begin cloudspotting on Mars!", - image: "/assets/Docs/LIDAR/lidar-martianClouds/Step9.jpeg" + title: "You're Ready! 🚀", + text: "You're now equipped to spot clouds in the Martian atmosphere! Remember: look for arch shapes with two legs and a peak. Report what you see, even if it's 'no arches.' Every classification helps scientists understand Mars better. Happy cloudspotting!", + image: "/assets/Docs/LIDAR/lidar-martianClouds/Step9.jpeg", } ]); @@ -80,13 +108,15 @@ export const CloudspottingOnMarsTutorial: React.FC = ({ return (
- {/* Tutorial Component */} + {/* Tutorial Component with scientific context */} {/* Classification Interface - shown after tutorial or for returning users */} diff --git a/src/components/projects/TutorialContentBlock.tsx b/src/components/projects/TutorialContentBlock.tsx index 35fe41bd..4761f9f3 100644 --- a/src/components/projects/TutorialContentBlock.tsx +++ b/src/components/projects/TutorialContentBlock.tsx @@ -5,12 +5,34 @@ import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'; import Image from 'next/image'; import { Button } from '@/src/components/ui/button'; import { Card, CardContent } from '@/src/components/ui/card'; -import { ChevronLeft, ChevronRight, Play, X } from 'lucide-react'; +import { + ChevronLeft, + ChevronRight, + Play, + X, + Lightbulb, + HelpCircle, + CheckCircle2, + Info, + Sparkles +} from 'lucide-react'; +import { cn } from '@/src/shared/utils'; interface TutorialSlide { text: string; image?: string; title?: string; + /** Optional tip shown at the bottom */ + tip?: string; + /** Optional quiz question to check understanding */ + quiz?: { + question: string; + options: string[]; + correctIndex: number; + explanation: string; + }; + /** Optional highlight for specific UI element (for future interactive mode) */ + highlight?: string; } interface TutorialContentBlockProps { @@ -20,7 +42,11 @@ interface TutorialContentBlockProps { onSkip?: () => void; title?: string; description?: string; - forceShow?: boolean; // Add prop to force tutorial display + forceShow?: boolean; + /** Context about what this classification helps with */ + scientificContext?: string; + /** What structure this classification uses */ + structureType?: 'telescope' | 'satellite' | 'rover' | 'solar'; } export default function TutorialContentBlock({ @@ -30,7 +56,9 @@ export default function TutorialContentBlock({ onSkip, title = "Tutorial", description = "Learn how to complete this classification", - forceShow = false + forceShow = false, + scientificContext, + structureType }: TutorialContentBlockProps) { const supabase = useSupabaseClient(); const session = useSession(); @@ -39,6 +67,10 @@ export default function TutorialContentBlock({ const [showTutorial, setShowTutorial] = useState(false); const [isCheckingFirstTime, setIsCheckingFirstTime] = useState(true); const [loading, setLoading] = useState(true); + const [quizAnswer, setQuizAnswer] = useState(null); + const [showQuizFeedback, setShowQuizFeedback] = useState(false); + const [quizCorrect, setQuizCorrect] = useState(false); + const [showContextInfo, setShowContextInfo] = useState(false); // Check if user has completed this classification type before useEffect(() => { @@ -88,15 +120,34 @@ export default function TutorialContentBlock({ const nextSlide = () => { if (currentSlide < slides.length - 1) { setCurrentSlide(prev => prev + 1); + // Reset quiz state for new slide + setQuizAnswer(null); + setShowQuizFeedback(false); + setQuizCorrect(false); } }; const prevSlide = () => { if (currentSlide > 0) { setCurrentSlide(prev => prev - 1); + setQuizAnswer(null); + setShowQuizFeedback(false); + setQuizCorrect(false); } }; + const handleQuizAnswer = (index: number) => { + if (showQuizFeedback) return; // Prevent changing answer after submission + setQuizAnswer(index); + }; + + const submitQuiz = () => { + if (quizAnswer === null || !currentSlideData.quiz) return; + const isCorrect = quizAnswer === currentSlideData.quiz.correctIndex; + setQuizCorrect(isCorrect); + setShowQuizFeedback(true); + }; + const handleComplete = () => { setShowTutorial(false); onComplete(); @@ -151,19 +202,132 @@ export default function TutorialContentBlock({ {/* Main Content */} + {/* Scientific Context Banner */} + {scientificContext && showContextInfo && ( +
+
+ +
+

Why does this matter?

+

{scientificContext}

+
+ +
+
+ )} + + {/* Context toggle button */} + {scientificContext && !showContextInfo && ( + + )} + {/* Slide Content */}
{/* Text Content */} -
+
{currentSlideData.title && ( -

+

+ {currentSlideData.title}

)}

{currentSlideData.text}

+ + {/* Tip section */} + {currentSlideData.tip && ( +
+ +

{currentSlideData.tip}

+
+ )} + + {/* Quiz section */} + {currentSlideData.quiz && ( +
+

+ + Quick Check +

+

{currentSlideData.quiz.question}

+ +
+ {currentSlideData.quiz.options.map((option, index) => { + const isSelected = quizAnswer === index; + const isCorrectOption = index === currentSlideData.quiz!.correctIndex; + let optionClasses = "w-full p-3 rounded-lg text-left text-sm transition-all border "; + + if (showQuizFeedback) { + if (isCorrectOption) { + optionClasses += "bg-green-500/20 border-green-500/50 text-green-200"; + } else if (isSelected && !isCorrectOption) { + optionClasses += "bg-red-500/20 border-red-500/50 text-red-200"; + } else { + optionClasses += "bg-white/5 border-white/10 text-white/50"; + } + } else { + optionClasses += isSelected + ? "bg-purple-500/30 border-purple-500/50 text-white" + : "bg-white/5 border-white/10 text-white/80 hover:bg-white/10"; + } + + return ( + + ); + })} +
+ + {!showQuizFeedback && quizAnswer !== null && ( + + )} + + {showQuizFeedback && ( +
+

+ {quizCorrect ? "✓ Correct!" : "Not quite!"} +

+

{currentSlideData.quiz.explanation}

+
+ )} +
+ )}
@@ -264,13 +428,35 @@ export function createTutorialSlides(slidesData: Array<{ text: string; image?: string; title?: string; + tip?: string; + quiz?: { + question: string; + options: string[]; + correctIndex: number; + explanation: string; + }; }>): TutorialSlide[] { return slidesData.map((slide) => ({ text: slide.text, image: slide.image, title: slide.title, + tip: slide.tip, + quiz: slide.quiz, })); } -// Export hook for tutorial state management +// Pre-built scientific context strings for different classification types +export const SCIENTIFIC_CONTEXTS = { + 'planet': 'By analyzing light curves from TESS, you help discover worlds beyond our solar system. Your classifications contribute to real scientific research and have led to the discovery of new exoplanets!', + 'telescope-minorPlanet': 'Tracking asteroids helps us understand our solar system\'s history and identify potentially hazardous objects. Your observations directly contribute to planetary defense efforts.', + 'sunspot': 'Monitoring solar activity helps predict space weather that can affect satellites, power grids, and astronauts. Your sunspot counts continue a 400-year scientific tradition!', + 'balloon-marsCloudShapes': 'Understanding Martian clouds reveals how water moves through the atmosphere and where it might be found. This is crucial for future Mars missions and understanding climate patterns.', + 'satellite-planetFour': 'Seasonal fans and blotches on Mars reveal active CO₂ geysers and help us understand how volatiles behave on cold worlds. This informs our search for subsurface water.', + 'automaton-aiForMars': 'Training AI rovers helps real Mars missions. Your terrain classifications directly inform how Perseverance and Curiosity navigate, making future exploration safer and more efficient.', + 'lidar-jovianVortexHunter': 'Jupiter\'s storms can last centuries! Understanding their dynamics helps us understand atmospheric physics on gas giants throughout the universe.', +}; + +// Export the TutorialSlide type for use in other components +export type { TutorialSlide }; + // useTutorial hook removed (deleted per cleanup request) diff --git a/src/components/scenes/deploy/ActivityHeader.tsx b/src/components/scenes/deploy/ActivityHeader.tsx index ef4720a7..ae99c69f 100644 --- a/src/components/scenes/deploy/ActivityHeader.tsx +++ b/src/components/scenes/deploy/ActivityHeader.tsx @@ -169,7 +169,9 @@ export default function ActivityHeader({ // Refresh deployment status setTimeout(() => { - window.location.reload(); + if (typeof window !== "undefined") { + window.location.reload(); + } }, 2000); } catch (error) { @@ -213,7 +215,7 @@ export default function ActivityHeader({ 'galaxy': "/assets/Backdrops/Galaxy.png", }; - return locationBackgrounds[location.toLowerCase()] || "/assets/Backdrops/Earth.png"; + return locationBackgrounds[location?.toLowerCase() || 'earth'] || "/assets/Backdrops/Earth.png"; }; return ( diff --git a/src/components/scenes/deploy/Rover/RoverSection.tsx b/src/components/scenes/deploy/Rover/RoverSection.tsx index fa861ba0..eec1cbc7 100644 --- a/src/components/scenes/deploy/Rover/RoverSection.tsx +++ b/src/components/scenes/deploy/Rover/RoverSection.tsx @@ -1,6 +1,7 @@ "use client" import Section from "@/src/components/sections/Section"; +import TutorialWrapper, { ROVER_INTRO_STEPS } from "@/src/components/onboarding/TutorialWrapper"; import { Anomaly } from "@/types/Structures/telescope"; import { useSession, useSupabaseClient } from "@supabase/auth-helpers-react"; import { useEffect, useState } from "react"; @@ -13,6 +14,7 @@ export default function RoverViewportSection() { const supabase = useSupabaseClient(); const session = useSession(); + const [isLoading, setIsLoading] = useState(true); const [hasRoverDeployed, setHasRoverDeployed] = useState(false); const [linkedAnomalies, setLinkedAnomalies] = useState([]); @@ -78,6 +80,7 @@ export default function RoverViewportSection() { async function fetchLinkedAnomaliesAndWaypoints() { const now = new Date(); if (!session) { + setIsLoading(false); return; } @@ -131,6 +134,7 @@ export default function RoverViewportSection() { if (!config || !config.anomalies || !config.waypoints) { setWaypoints([]); + setIsLoading(false); return; } setWaypoints(routes); @@ -324,6 +328,7 @@ export default function RoverViewportSection() { } else { setWaypoints([]); } + setIsLoading(false); } fetchLinkedAnomaliesAndWaypoints(); }, [session, supabase, isFastDeployEnabled]); @@ -348,7 +353,9 @@ export default function RoverViewportSection() { // Deploy handler const handleDeployRover = async () => { - window.location.href="/activity/deploy/roover/"; + if (typeof window !== "undefined") { + window.location.href="/activity/deploy/roover/"; + } }; // Return home handler - removes routes and linked_anomalies for this user's rover @@ -409,9 +416,17 @@ export default function RoverViewportSection() { "View and control your surface rovers to find and discover surface anomalies" } expandLink={"/viewports/roover"} + className="h-full" > -
- {!hasRoverDeployed ? ( +
+ {isLoading ? ( +
+
+
+

Loading rover data...

+
+
+ ) : !hasRoverDeployed ? (

Ready to Train Your Rover?

@@ -419,6 +434,7 @@ export default function RoverViewportSection() {
diff --git a/src/components/scenes/deploy/Telescope/TypeSelection.tsx b/src/components/scenes/deploy/Telescope/TypeSelection.tsx index cc5368b3..cc549b1a 100644 --- a/src/components/scenes/deploy/Telescope/TypeSelection.tsx +++ b/src/components/scenes/deploy/Telescope/TypeSelection.tsx @@ -50,9 +50,10 @@ export default function TypeSelection({ -
+
onChooseType("stellar")}> + onClick={() => onChooseType("stellar")} + data-tutorial="project-stellar">
@@ -97,7 +98,8 @@ export default function TypeSelection({ onChooseType("planetary")}> + onClick={() => onChooseType("planetary")} + data-tutorial="project-planetary">
diff --git a/src/components/scenes/deploy/TelescopeViewportRange.tsx b/src/components/scenes/deploy/TelescopeViewportRange.tsx index 7be5647b..bfd22ccd 100644 --- a/src/components/scenes/deploy/TelescopeViewportRange.tsx +++ b/src/components/scenes/deploy/TelescopeViewportRange.tsx @@ -10,13 +10,13 @@ import { Button } from "@/src/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/src/components/ui/card" import { Badge } from "@/src/components/ui/badge" import { CheckCircle, Telescope, X, Target, Info, Sun, ArrowLeft } from "lucide-react" -import TypeSelection from "./Telescope/TypeSelection" import DeploymentConfirmation from "./Telescope/DeploymentConfirmation" import TelescopeDeploySidebar from "./Telescope/TelescopeDeploySidebar" import { seededRandom1, generateAnomalyFromDBFactory, DatabaseAnomaly } from "./Telescope/TelescopeUtils" import { fetchAnomalies as fetchAnomaliesAction, checkDeployment as checkDeploymentAction, loadSector as loadSectorAction, fetchSkillProgress as fetchSkillProgressAction, handleDeployAction } from "./Telescope/TelescopeActions" import UseDarkMode from "@/src/shared/hooks/useDarkMode" import { TelescopeBackground } from "@/src/components/classification/telescope/telescope-background" +import { useUserPreferences, TelescopeFocusType } from "@/src/hooks/useUserPreferences" export type DeploymentType = "stellar" | "planetary"; @@ -25,10 +25,26 @@ export default function DeployTelescopeViewport() { const session = useSession(); const router = useRouter() + + // Get stored telescope focus preference + const { preferences, setTelescopeFocus, isLoading: prefsLoading } = useUserPreferences(); - // New deployment type selection state + // Deployment type - defaults to "planetary" (most popular), can be changed via sidebar const [deploymentType, setDeploymentType] = useState(null) - const [showTypeSelection, setShowTypeSelection] = useState(true) + + // Initialize from stored preferences or default to planetary + useEffect(() => { + if (!prefsLoading) { + const storedFocus = preferences?.telescopeFocus as DeploymentType | null; + const focusType = storedFocus || "planetary"; // Default to planetary + setDeploymentType(focusType); + + // Save default preference if none was set + if (!storedFocus) { + setTelescopeFocus("planetary"); + } + } + }, [prefsLoading, preferences?.telescopeFocus, setTelescopeFocus]); const [currentSector, setCurrentSector] = useState({ x: 0, y: 0 }) const [tessAnomalies, setTessAnomalies] = useState([]) @@ -102,12 +118,14 @@ export default function DeployTelescopeViewport() { }); } - const handleBackToTypeSelection = () => { - setDeploymentType(null) - setShowTypeSelection(true) - setTessAnomalies([]) - setSectorAnomalies([]) - setSelectedSector(null) + // Toggle between stellar and planetary focus + const handleToggleMissionType = () => { + const newType: DeploymentType = deploymentType === "stellar" ? "planetary" : "stellar"; + setDeploymentType(newType); + setTelescopeFocus(newType as TelescopeFocusType); + setTessAnomalies([]); + setSectorAnomalies([]); + setSelectedSector(null); } const handleConfirmationClose = () => { @@ -155,16 +173,6 @@ export default function DeployTelescopeViewport() { ) } - if (showTypeSelection && !deploymentType) { - return ( - { setDeploymentType(t); setShowTypeSelection(false) }} - onBack={() => router.push('/')} - session={session} - /> - ) - } - return (
@@ -228,7 +236,7 @@ export default function DeployTelescopeViewport() { isDeploying={deploying} alreadyDeployed={alreadyDeployed} deploymentMessage={deploymentMessage} - onBackToTypeSelection={handleBackToTypeSelection} + onBackToTypeSelection={handleToggleMissionType} isMobile isDarkMode={isDark} tessAnomaliesCount={tessAnomalies.length} diff --git a/src/components/scenes/deploy/satellite/DeploySatellite.tsx b/src/components/scenes/deploy/satellite/DeploySatellite.tsx index 062fa108..a23b5056 100644 --- a/src/components/scenes/deploy/satellite/DeploySatellite.tsx +++ b/src/components/scenes/deploy/satellite/DeploySatellite.tsx @@ -11,6 +11,7 @@ import { useState as useReactState } from "react"; import PlanetFocusView from './PlanetFocusView'; import DeploySidebar from './DeploySidebar'; import { TelescopeBackground } from "@/src/components/classification/telescope/telescope-background"; +import TutorialWrapper, { SATELLITE_INTRO_STEPS } from "@/src/components/onboarding/TutorialWrapper"; import UseDarkMode from "@/src/shared/hooks/useDarkMode"; type LocalAnomaly = Anomaly & { dbData: DatabaseAnomaly }; @@ -894,11 +895,17 @@ export default function DeploySatelliteViewport() { }; return ( -
+ +
{/* Top bar: controls and stats - HIDDEN to fix layout */} {/*
*/}
+
); }; \ No newline at end of file diff --git a/src/components/scenes/deploy/satellite/DeploySidebar.tsx b/src/components/scenes/deploy/satellite/DeploySidebar.tsx index 66e21b7a..27c88662 100644 --- a/src/components/scenes/deploy/satellite/DeploySidebar.tsx +++ b/src/components/scenes/deploy/satellite/DeploySidebar.tsx @@ -78,7 +78,8 @@ const DeploySidebar: React.FC = ({ {!isDeployDisabled && (
)} -
+
{children}
{/* Info area at bottom, responsive */} diff --git a/src/components/social/comments/CommentForm.tsx b/src/components/social/comments/CommentForm.tsx index 85e7fdb6..caea52fa 100644 --- a/src/components/social/comments/CommentForm.tsx +++ b/src/components/social/comments/CommentForm.tsx @@ -93,7 +93,9 @@ export function CommentForm({ } catch (error) { console.error('CommentForm: Router.push error:', error); // Fallback to window.location - window.location.href = '/'; + if (typeof window !== "undefined") { + window.location.href = '/'; + } } }, 3000); }; @@ -146,7 +148,9 @@ export function CommentForm({ router.push('/'); } catch (error) { console.error('Manual redirect error:', error); - window.location.href = '/'; + if (typeof window !== "undefined") { + window.location.href = '/'; + } } }} className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 mb-4" diff --git a/src/components/social/posts/Surveyor/SurveyorPostCard.tsx b/src/components/social/posts/Surveyor/SurveyorPostCard.tsx index 6fda7ab2..e170d4fb 100644 --- a/src/components/social/posts/Surveyor/SurveyorPostCard.tsx +++ b/src/components/social/posts/Surveyor/SurveyorPostCard.tsx @@ -119,7 +119,9 @@ export function SurveyorComments({ } catch (error) { console.error('SurveyorComments: Router.push error:', error); // Fallback to window.location - window.location.href = '/'; + if (typeof window !== "undefined") { + window.location.href = '/'; + } } }, 3000); @@ -168,7 +170,9 @@ export function SurveyorComments({ } catch (error) { console.error('SurveyorComments: Router.push error:', error); // Fallback to window.location - window.location.href = '/'; + if (typeof window !== "undefined") { + window.location.href = '/'; + } } }, 3000); @@ -217,7 +221,9 @@ export function SurveyorComments({ } catch (error) { console.error('SurveyorComments: Router.push error:', error); // Fallback to window.location - window.location.href = '/'; + if (typeof window !== "undefined") { + window.location.href = '/'; + } } }, 3000); @@ -379,7 +385,9 @@ export function SurveyorComments({ router.push('/'); } catch (error) { console.error('Manual redirect error:', error); - window.location.href = '/'; + if (typeof window !== "undefined") { + window.location.href = '/'; + } } }} className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 mb-4" diff --git a/src/components/tabs/OnboardingTab.tsx b/src/components/tabs/OnboardingTab.tsx index 3ee775bc..aade81ba 100644 --- a/src/components/tabs/OnboardingTab.tsx +++ b/src/components/tabs/OnboardingTab.tsx @@ -1,20 +1,397 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useSession } from "@supabase/auth-helpers-react"; import { usePageData } from "@/hooks/usePageData"; +import { useUserPreferences } from "@/src/hooks/useUserPreferences"; +import ProjectPreferencesModal from "@/src/components/onboarding/ProjectPreferencesModal"; +import StructureExplainer from "@/src/components/onboarding/StructureExplainer"; +import InteractiveTutorial, { + ONBOARDING_STEPS, +} from "@/src/components/onboarding/InteractiveTutorial"; +import { Button } from "@/src/components/ui/button"; +import { Card, CardContent } from "@/src/components/ui/card"; +import { + Sparkles, + Target, + Telescope, + Satellite, + Car, + Sun, + HelpCircle, + CheckCircle2, + ArrowRight, + RotateCcw, + Settings2, + Rocket, +} from "lucide-react"; +import { cn } from "@/src/shared/utils"; +import { ProjectType } from "@/src/hooks/useUserPreferences"; + +interface OnboardingStep { + id: string; + title: string; + description: string; + isComplete: boolean; + action?: () => void; + actionLabel?: string; +} export default function OnboardingTab() { + const session = useSession(); const { classifications } = usePageData(); + const { + preferences, + isLoading: preferencesLoading, + needsPreferencesPrompt, + setProjectInterests, + completeOnboarding, + markStructureGuideSeen, + resetPreferences, + } = useUserPreferences(); + + const [showPreferencesModal, setShowPreferencesModal] = useState(false); + const [showTutorial, setShowTutorial] = useState(false); + const [showStructureGuide, setShowStructureGuide] = useState(false); + const [selectedStructure, setSelectedStructure] = useState< + "telescope" | "satellite" | "rover" | "solar" | null + >(null); + + // Check if user needs preferences prompt on mount + useEffect(() => { + if (!preferencesLoading && needsPreferencesPrompt) { + setShowPreferencesModal(true); + } + }, [preferencesLoading, needsPreferencesPrompt]); + + // Calculate onboarding progress + const hasClassifications = classifications.length > 0; + const hasSetPreferences = preferences.projectInterests.length > 0; + const hasCompletedTutorial = preferences.hasCompletedOnboarding; + const hasSeenStructures = preferences.hasSeenStructureGuide; + + const onboardingSteps: OnboardingStep[] = [ + { + id: "preferences", + title: "Choose Your Interests", + description: "Tell us what excites you most - planets, asteroids, rovers, or solar activity.", + isComplete: hasSetPreferences, + action: () => setShowPreferencesModal(true), + actionLabel: hasSetPreferences ? "Update Preferences" : "Get Started", + }, + { + id: "tutorial", + title: "Learn the Basics", + description: "A quick interactive tour of your space station and how to make discoveries.", + isComplete: hasCompletedTutorial, + action: () => setShowTutorial(true), + actionLabel: hasCompletedTutorial ? "Replay Tutorial" : "Start Tour", + }, + { + id: "structures", + title: "Explore Your Structures", + description: "Learn what each structure does and how they connect to real science.", + isComplete: hasSeenStructures, + action: () => setShowStructureGuide(true), + actionLabel: "Explore Structures", + }, + { + id: "first-classification", + title: "Make Your First Discovery", + description: "Deploy a structure and complete your first classification to join the mission!", + isComplete: hasClassifications, + actionLabel: "Go to Structures", + }, + ]; + + const completedCount = onboardingSteps.filter((s) => s.isComplete).length; + const progress = (completedCount / onboardingSteps.length) * 100; + + // Handle preference save + const handlePreferencesSave = (interests: ProjectType[]) => { + setProjectInterests(interests); + setShowPreferencesModal(false); + }; + + // Handle tutorial complete + const handleTutorialComplete = () => { + completeOnboarding(); + setShowTutorial(false); + }; + + // Structure quick access cards + const structureCards = [ + { + id: "telescope" as const, + name: "Telescope", + icon: , + color: "text-purple-400", + bgColor: "bg-purple-500/10 hover:bg-purple-500/20", + }, + { + id: "satellite" as const, + name: "Satellite", + icon: , + color: "text-cyan-400", + bgColor: "bg-cyan-500/10 hover:bg-cyan-500/20", + }, + { + id: "rover" as const, + name: "Rover", + icon: , + color: "text-green-400", + bgColor: "bg-green-500/10 hover:bg-green-500/20", + }, + { + id: "solar" as const, + name: "Solar Observatory", + icon: , + color: "text-yellow-400", + bgColor: "bg-yellow-500/10 hover:bg-yellow-500/20", + }, + ]; + + if (preferencesLoading) { + return ( +
+
+
+ ); + } return ( -
-

Welcome to Star Sailors!

-

- This tab is for onboarding content. Check the Updates tab to get started with your first mission, - view your progress, and track your achievements! -

- {classifications.length === 0 && ( -

- 👉 Click on the Updates tab to begin your journey -

+ <> + {/* Preferences Modal */} + setShowPreferencesModal(false)} + onSave={handlePreferencesSave} + initialInterests={preferences.projectInterests} + /> + + {/* Interactive Tutorial */} + {showTutorial && ( + setShowTutorial(false)} + title="Welcome Tour" + /> )} -
+ + {/* Structure Guide Modal */} + {showStructureGuide && ( +
+
+ {selectedStructure ? ( + { + markStructureGuideSeen(); + setSelectedStructure(null); + }} + /> + ) : ( + setShowStructureGuide(false)} + onComplete={() => { + markStructureGuideSeen(); + setShowStructureGuide(false); + }} + /> + )} +
+
+ )} + + {/* Main Content */} +
+ {/* Welcome Section */} +
+
+ +

+ Welcome{session?.user?.email ? `, ${session.user.email.split("@")[0]}` : ""}! +

+
+

+ You're about to become a citizen scientist. Your discoveries will contribute + to real space research! +

+
+ + {/* Progress Card */} + + +
+
+ + Getting Started Progress +
+ + {completedCount} of {onboardingSteps.length} complete + +
+ + {/* Progress bar */} +
+
+
+ + {/* Steps */} +
+ {onboardingSteps.map((step, index) => ( +
+ {/* Step number / check */} +
+ {step.isComplete ? ( + + ) : ( + {index + 1} + )} +
+ + {/* Content */} +
+

+ {step.title} +

+

{step.description}

+
+ + {/* Action */} + {step.action && ( + + )} +
+ ))} +
+ + {/* All complete message */} + {completedCount === onboardingSteps.length && ( +
+ +

You're all set!

+

+ Start making discoveries by deploying your structures. +

+
+ )} + + + + {/* Quick Structure Access */} +
+
+

+ + Learn About Structures +

+
+ +
+ {structureCards.map((structure) => ( + + ))} +
+
+ + {/* User Preferences Summary */} + {hasSetPreferences && ( + + +
+

+ + Your Interests +

+ +
+
+ {preferences.projectInterests.map((interest) => ( + + {interest + .split("-") + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(" ")} + + ))} +
+
+
+ )} + + {/* Reset Option */} +
+ +
+
+ ); } diff --git a/src/core/context/ActivePlanet.tsx b/src/core/context/ActivePlanet.tsx index bf7b3c2d..b12963a8 100644 --- a/src/core/context/ActivePlanet.tsx +++ b/src/core/context/ActivePlanet.tsx @@ -41,7 +41,7 @@ export const ActivePlanetProvider: React.FC<{ children: ReactNode }> = ({ childr if (profileError) throw profileError; - let location = profile?.location ?? 30; + let location = typeof window !== "undefined" ? profile?.location ?? 30 : 30; // If the location is null, update it to 30 if (profile?.location === null) { diff --git a/src/hooks/useUserPreferences.ts b/src/hooks/useUserPreferences.ts new file mode 100644 index 00000000..69b5eca8 --- /dev/null +++ b/src/hooks/useUserPreferences.ts @@ -0,0 +1,314 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; + +// Project types available in Star Sailors +export type ProjectType = + | "planet-hunting" // TESS/NGTS exoplanet discovery + | "asteroid-hunting" // Daily Minor Planet + | "cloud-tracking" // Cloudspotting on Mars, JVH + | "rover-training" // AI for Mars terrain classification + | "ice-tracking" // Planet Four seasonal changes + | "solar-monitoring"; // Sunspot classification + +// Structure types +export type StructureType = "telescope" | "satellite" | "rover" | "solar"; + +// Telescope focus types +export type TelescopeFocusType = "stellar" | "planetary"; + +// Tutorial identifiers - comprehensive list of all tutorials in the app +export type TutorialId = + // Overall app tutorials + | "welcome-tour" // First-time welcome tour + | "game-overview" // Overall game mechanics + // Structure tutorials + | "telescope-intro" // What is the telescope? + | "satellite-intro" // What is the satellite? + | "rover-intro" // What is the rover? + | "solar-intro" // What is the solar observatory? + // Deployment tutorials + | "telescope-deploy" // How to deploy telescope + | "satellite-deploy" // How to deploy satellite + | "rover-deploy" // How to deploy rover + | "solar-deploy" // Solar observatory deployment tutorial + // Project-specific tutorials + | "planet-hunting" // TESS planet hunting + | "asteroid-hunting" // Minor planet/asteroid detection + | "cloud-tracking" // Mars cloud spotting + | "jovian-vortex" // Jupiter storm hunting + | "ice-tracking" // Planet Four ice/fans + | "solar-monitoring" // Sunspot classification + | "rover-terrain" // AI for Mars + // Page tutorials + | "research-page" // Research page intro + | "inventory-page" // Inventory/minerals page + | "leaderboard-page" // Leaderboard explanation + // Feature tutorials + | "mineral-guide" // Where do minerals come from + | "stardust-guide"; // What is stardust + +// Track which tutorials have been completed +export interface TutorialCompletion { + [key: string]: boolean; +} + +export interface UserPreferences { + // Project interests (what the user wants to focus on) + projectInterests: ProjectType[]; + + // Has the user completed the initial onboarding? + hasCompletedOnboarding: boolean; + + // Has the user seen the structure explanations? + hasSeenStructureGuide: boolean; + + // Has the user seen the deployment tutorial? + hasSeenDeploymentTutorial: boolean; + + // Has the user seen the mineral explanation? + hasSeenMineralGuide: boolean; + + // Track individual tutorial completions + completedTutorials: TutorialCompletion; + + // Preferred structure order on dashboard + structureOrder: StructureType[]; + + // Telescope focus preference (stellar vs planetary) + telescopeFocus: TelescopeFocusType | null; + + // Last time preferences were asked (to avoid asking too often) + lastPreferencesAsked: string | null; + + // Device ID for cross-device detection + deviceId: string; +} + +const STORAGE_KEY = "star-sailors-preferences"; +const DEVICE_ID_KEY = "star-sailors-device-id"; + +// Generate a random device ID +function generateDeviceId(): string { + return `device-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; +} + +// Get or create device ID +function getDeviceId(): string { + if (typeof window === "undefined") return ""; + + let deviceId = localStorage.getItem(DEVICE_ID_KEY); + if (!deviceId) { + deviceId = generateDeviceId(); + localStorage.setItem(DEVICE_ID_KEY, deviceId); + } + return deviceId; +} + +const defaultPreferences: UserPreferences = { + projectInterests: [], + hasCompletedOnboarding: false, + hasSeenStructureGuide: false, + hasSeenDeploymentTutorial: false, + hasSeenMineralGuide: false, + completedTutorials: {}, + structureOrder: ["telescope", "satellite", "rover", "solar"], + telescopeFocus: null, + lastPreferencesAsked: null, + deviceId: "", +}; + +export function useUserPreferences() { + const [preferences, setPreferences] = useState(defaultPreferences); + const [isLoading, setIsLoading] = useState(true); + const [needsPreferencesPrompt, setNeedsPreferencesPrompt] = useState(false); + + // Load preferences from localStorage + useEffect(() => { + if (typeof window === "undefined") return; + + try { + const stored = localStorage.getItem(STORAGE_KEY); + const currentDeviceId = getDeviceId(); + + if (stored) { + const parsed = JSON.parse(stored) as UserPreferences; + + // Check if this is a new device (different device ID) + if (parsed.deviceId && parsed.deviceId !== currentDeviceId) { + // New device - need to ask preferences again + setNeedsPreferencesPrompt(true); + setPreferences({ + ...parsed, + deviceId: currentDeviceId, + }); + } else if (!parsed.projectInterests || parsed.projectInterests.length === 0) { + // No preferences set yet + setNeedsPreferencesPrompt(true); + setPreferences({ + ...parsed, + deviceId: currentDeviceId, + }); + } else { + setPreferences({ + ...parsed, + deviceId: currentDeviceId, + }); + } + } else { + // First time - need to ask preferences + setNeedsPreferencesPrompt(true); + setPreferences({ + ...defaultPreferences, + deviceId: currentDeviceId, + }); + } + } catch (error) { + console.error("Error loading preferences:", error); + setPreferences({ + ...defaultPreferences, + deviceId: getDeviceId(), + }); + setNeedsPreferencesPrompt(true); + } finally { + setIsLoading(false); + } + }, []); + + // Save preferences to localStorage + const savePreferences = useCallback((newPreferences: Partial) => { + setPreferences((prev) => { + const updated = { ...prev, ...newPreferences }; + + if (typeof window !== "undefined") { + localStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); + } + + return updated; + }); + }, []); + + // Set project interests + const setProjectInterests = useCallback((interests: ProjectType[]) => { + savePreferences({ + projectInterests: interests, + lastPreferencesAsked: new Date().toISOString(), + }); + setNeedsPreferencesPrompt(false); + }, [savePreferences]); + + // Mark onboarding as complete + const completeOnboarding = useCallback(() => { + savePreferences({ hasCompletedOnboarding: true }); + }, [savePreferences]); + + // Mark structure guide as seen + const markStructureGuideSeen = useCallback(() => { + savePreferences({ hasSeenStructureGuide: true }); + }, [savePreferences]); + + // Mark deployment tutorial as seen + const markDeploymentTutorialSeen = useCallback(() => { + savePreferences({ hasSeenDeploymentTutorial: true }); + }, [savePreferences]); + + // Mark mineral guide as seen + const markMineralGuideSeen = useCallback(() => { + savePreferences({ hasSeenMineralGuide: true }); + }, [savePreferences]); + + // Update structure order + const setStructureOrder = useCallback((order: StructureType[]) => { + savePreferences({ structureOrder: order }); + }, [savePreferences]); + + // Set telescope focus preference + const setTelescopeFocus = useCallback((focus: TelescopeFocusType | null) => { + savePreferences({ telescopeFocus: focus }); + }, [savePreferences]); + + // Dismiss the preferences prompt without setting preferences + const dismissPreferencesPrompt = useCallback(() => { + setNeedsPreferencesPrompt(false); + }, []); + + // Force show preferences prompt + const showPreferencesPrompt = useCallback(() => { + setNeedsPreferencesPrompt(true); + }, []); + + // Check if a specific project is in user's interests + const isProjectInterested = useCallback((project: ProjectType) => { + // If no preferences set, show all projects + if (preferences.projectInterests.length === 0) return true; + return preferences.projectInterests.includes(project); + }, [preferences.projectInterests]); + + // Mark a specific tutorial as completed + const markTutorialComplete = useCallback((tutorialId: TutorialId) => { + setPreferences((prev) => { + const updated = { + ...prev, + completedTutorials: { + ...prev.completedTutorials, + [tutorialId]: true, + }, + }; + if (typeof window !== "undefined") { + localStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); + } + return updated; + }); + }, []); + + // Check if a specific tutorial has been completed + const hasTutorialCompleted = useCallback((tutorialId: TutorialId): boolean => { + return preferences.completedTutorials?.[tutorialId] === true; + }, [preferences.completedTutorials]); + + // Reset a specific tutorial (allow user to replay) + const resetTutorial = useCallback((tutorialId: TutorialId) => { + setPreferences((prev) => { + const updated = { ...prev.completedTutorials }; + delete updated[tutorialId]; + const newPrefs = { ...prev, completedTutorials: updated }; + if (typeof window !== "undefined") { + localStorage.setItem(STORAGE_KEY, JSON.stringify(newPrefs)); + } + return newPrefs; + }); + }, []); + + // Reset all preferences (for testing) + const resetPreferences = useCallback(() => { + if (typeof window !== "undefined") { + localStorage.removeItem(STORAGE_KEY); + } + setPreferences({ + ...defaultPreferences, + deviceId: getDeviceId(), + }); + setNeedsPreferencesPrompt(true); + }, []); + + return { + preferences, + isLoading, + needsPreferencesPrompt, + setProjectInterests, + completeOnboarding, + markStructureGuideSeen, + markDeploymentTutorialSeen, + markMineralGuideSeen, + markTutorialComplete, + hasTutorialCompleted, + resetTutorial, + setStructureOrder, + setTelescopeFocus, + dismissPreferencesPrompt, + showPreferencesPrompt, + isProjectInterested, + resetPreferences, + savePreferences, + }; +} diff --git a/src/utils/mineralDepositCreation.ts b/src/utils/mineralDepositCreation.ts index 1b9ca669..e82b40a1 100644 --- a/src/utils/mineralDepositCreation.ts +++ b/src/utils/mineralDepositCreation.ts @@ -179,7 +179,7 @@ export async function createMineralDeposit({ anomaly: anomalyId, discovery: classificationId, mineralconfiguration: mineralConfig, - location: location || null, + location: typeof window !== "undefined" ? location || null : null, created_at: new Date().toISOString() }) .select("id") diff --git a/supabase/config.toml b/supabase/config.toml index 05214acc..467c9855 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -162,7 +162,7 @@ refresh_token_reuse_interval = 10 # Allow/disallow new user signups to your project. enable_signup = true # Allow/disallow anonymous sign-ins to your project. -enable_anonymous_sign_ins = false +enable_anonymous_sign_ins = true # Allow/disallow testing manual linking of accounts enable_manual_linking = false # Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.