diff --git a/app/api/gameplay/inventory/route.ts b/app/api/gameplay/inventory/route.ts index 37bcd3af..a0e21e7a 100644 --- a/app/api/gameplay/inventory/route.ts +++ b/app/api/gameplay/inventory/route.ts @@ -59,7 +59,7 @@ const inventoryItems: InventoryItem[] = [ // Structures { - id: 3102, name: 'Automaton station', description: 'View, control and upgrade all your automatons and rovers here', cost: 1, icon_url: '/assets/Items/AutoController.png', ItemCategory: 'Structure', parentItem: 30, itemLevel: 1, locationType: 'Surface' + id: 3102, name: 'Automaton station', description: 'Control and upgrade your automatons here', cost: 1, icon_url: '/assets/Items/AutoController.png', ItemCategory: 'Structure', parentItem: 30, itemLevel: 1, locationType: 'Surface' }, { id: 3103, name: 'Telescope', description: 'Space-based observations & classifications', icon_url: '/assets/Items/Telescope.png', ItemCategory: 'Structure', locationType: 'Surface' @@ -83,33 +83,33 @@ const inventoryItems: InventoryItem[] = [ ItemCategory: "Structure", locationType: 'Surface', }, - { - id: 3107, - name: "Launchpad", - description: "Launch rockets and satellites", - icon_url: "/assets/Items/Launchpad.jpg", - ItemCategory: "Structure", - locationType: 'Surface', - }, - { - id: 3108, - name: "First rocket", - description: "Travel the solar-system", - icon_url: "/assets/Items/Rocket.png", - ItemCategory: "Structure", - locationType: 'Atmosphere', - }, + // { + // id: 3107, + // name: "Launchpad", + // description: "Launch rockets and satellites", + // icon_url: "/assets/Items/Launchpad.jpg", + // ItemCategory: "Structure", + // locationType: 'Surface', + // }, + // { + // id: 3108, + // name: "First rocket", + // description: "Travel the solar-system", + // icon_url: "/assets/Items/Rocket.png", + // ItemCategory: "Structure", + // locationType: 'Atmosphere', + // }, // Classification structures introduced in C2 - { - id: 31010, - name: "Physics Lab", - description: "Catalogue results from different particle experiments across the universe", - icon_url: "/assets/Items/PhysicsLab.png", - ItemCategory: "Structure", - locationType: 'Surface', - }, + // { + // id: 31010, + // name: "Physics Lab", + // description: "Catalogue results from different particle experiments across the universe", + // icon_url: "/assets/Items/PhysicsLab.png", + // ItemCategory: "Structure", + // locationType: 'Surface', + // }, // Community stations { diff --git a/app/auth/register/page.tsx b/app/auth/register/page.tsx index 74ae6ef2..dde0c78d 100644 --- a/app/auth/register/page.tsx +++ b/app/auth/register/page.tsx @@ -13,7 +13,7 @@ const Register = () => { useEffect(() => { if (session) { router.push('/'); - } + }; }, [session, router]); return ( diff --git a/app/layout.tsx b/app/layout.tsx index 216843a3..41593354 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -18,16 +18,20 @@ interface RootLayoutProps { children: ReactNode; }; -export default function RootLayout({ children }: RootLayoutProps) { - const [supabaseClient] = useState(() => createPagesBrowserClient()); - +function LayoutContent({ children }: { children: ReactNode }) { const { activePlanet } = useActivePlanet(); - if (activePlanet) { - useEffect(() => { + useEffect(() => { + if (activePlanet) { console.log("Active Planet: ", activePlanet); - }, [activePlanet]); - } + } + }, [activePlanet]); + + return <>{children}; +}; + +export default function RootLayout({ children }: RootLayoutProps) { + const [supabaseClient] = useState(() => createPagesBrowserClient()); return ( @@ -76,7 +80,8 @@ export default function RootLayout({ children }: RootLayoutProps) { {/* */} -
+ +
@@ -103,6 +108,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
{children}
+ diff --git a/app/page.tsx b/app/page.tsx index 14764631..f03b1a5c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { useSession, useSupabaseClient } from "@supabase/auth-helpers-react"; import { useEffect, useState } from "react"; @@ -20,70 +20,45 @@ import AllSatellitesOnActivePlanet from "@/components/Structures/Auto/AllSatelli export default function Home() { const session = useSession(); - + const supabase = useSupabaseClient(); const { activePlanet } = useActivePlanet(); + const [hasRequiredItems, setHasRequiredItems] = useState(null); useEffect(() => { - console.log(session?.user.id); - }); + if (!session) return; - const planetViews: Record = { - // 30: - // } - // middleSection={ - // - // } - // middleSectionTwo={ - // - // } - // />, - 30: -
- -
-
-
- {/* */} -
-
- -
-
-
-
-
-
- - -
-
-
-
-
- - -
-
- {/*
*/} -
-
- - // 60: , - // 62: , - // 61: , - // 70: , - // 71: , - // 80: , - // 81: , - // 90: , - // , - }; + const checkInventory = async () => { + try { + const { data, error } = await supabase + .from("inventory") + .select("id, quantity") + .eq("owner", session.user.id) + .in("item", [3105, 3104, 3103]) + .gt("quantity", 0); + + if (error) throw error; + + setHasRequiredItems(data.length > 0); + } catch (error: any) { + console.error("Error checking inventory:", error.message); + setHasRequiredItems(false); + } + }; + + checkInventory(); + }, [session, supabase]); if (!session) { return ; - }; + } + + if (hasRequiredItems === null) { + return
Loading...
; + } + + if (!hasRequiredItems) { + return ; + } return ( @@ -113,9 +88,7 @@ export default function Home() {
- {/*
*/} -
-
+
- ) + ); }; \ No newline at end of file diff --git a/app/planets/[id]/page.tsx b/app/planets/[id]/page.tsx index 6febd1f7..49d08957 100644 --- a/app/planets/[id]/page.tsx +++ b/app/planets/[id]/page.tsx @@ -5,6 +5,7 @@ import { useSession, useSupabaseClient } from "@supabase/auth-helpers-react"; import SimplePlanetGenerator from "@/components/Data/Generator/Astronomers/PlanetHunters/SimplePlanetGenerator"; import Navbar from "@/components/Layout/Navbar"; import { PostCardSingleWithGenerator } from "@/content/Posts/PostWithGen"; +import ClassificationComments from "@/content/Classifications/ClassificationStats"; interface Classification { id: number; @@ -21,7 +22,7 @@ interface Classification { tags?: string[]; images?: string[]; relatedClassifications?: Classification[]; -} +}; type Anomaly = { id: number; @@ -129,7 +130,7 @@ export default function ClassificationDetail({ params }: { params: { id: string classificationType={classification.classificationtype || "Unknown"} /> )} - {anomaly && ( + {/* {anomaly && (

Related Planet

{anomaly.content || `Anomaly #${anomaly.id}`}

@@ -149,6 +150,9 @@ export default function ClassificationDetail({ params }: { params: { id: string /> )}
+ )} */} + {classification && ( + )} {classification.relatedClassifications && classification.relatedClassifications.length > 0 && (
diff --git a/app/scenes/desert/page.tsx b/app/scenes/desert/page.tsx index b2f88512..218c49be 100644 --- a/app/scenes/desert/page.tsx +++ b/app/scenes/desert/page.tsx @@ -1,12 +1,147 @@ 'use client'; -import React, { useEffect, useState } from "react"; -import { useSupabaseClient, useSession } from "@supabase/auth-helpers-react"; +import React, { useState, useEffect } from "react"; +import { Bird, PawPrint } from "lucide-react"; +import { BurrowingOwl } from "@/components/Projects/Zoodex/burrowingOwls"; +import { ZoodexIguanas } from "@/components/Projects/Zoodex/iguanasFromAbove"; +import { BiomePattern } from "@/components/Structures/Missions/Biologists/BiomePattern"; +import { iconMap } from "@/components/Structures/Missions/Biologists/StationCard"; +import Navbar from "@/components/Layout/Navbar"; -// Show all stations that are marked as "Desert" with the similar styling (for now) as the regular `EarthViewLayout` +interface Project { + id: number; + station: number; + biome: string; + title: string; + icon: React.ElementType; + completedCount: number; + internalComponent?: React.ElementType; + color: string; +} -export default function DesertBaseEarthScene() { - return ( - <> - ); -}; \ No newline at end of file +interface Animal { + name: string; + icon: string; +} + +const fetchProjects = (): Project[] => { + return [ + { + id: 1, + station: 3104001, + biome: "Desert", + title: "Burrowing Owls", + icon: Bird, + completedCount: 0, + internalComponent: () => , + color: "text-green-400", + }, + { + id: 2, + station: 3104001, + biome: "Desert", + title: "Iguanas from Above", + icon: PawPrint, + completedCount: 5, + internalComponent: () => , + color: "text-green-700", + }, + ]; +}; + +const DesertBaseEarthScene = () => { + const [activeStation] = useState({ + id: 3104001, + name: "Desert Station", + description: "A station located in the desert biome.", + biome: "Desert", + animals: [ + { name: "Burrowing Owl", icon: "Bird" }, + { name: "Iguana", icon: "PawPrint" }, + ], + location: { coordinates: "23.6345° N, 46.8475° E", depth: "30m", altitude: "500m" }, + }); + const [projects, setProjects] = useState([]); + + useEffect(() => { + if (activeStation) { + const filteredProjects = fetchProjects().filter( + (p) => p.station === Number(activeStation.id) + ); + setProjects(filteredProjects); + } + }, [activeStation]); + + return ( +
+ Earth Background + +
+ {/*

Desert Station

*/} + +
+

{activeStation.name} | {activeStation.id}

+

{activeStation.description}

+ +
+

Biome

+
+ + {activeStation.biome} +
+
+ +
+

Animals available for study

+
+ {activeStation.animals.map((animal: Animal) => { + const AnimalIcon = iconMap[animal.icon as keyof typeof iconMap]; + return ( +
+ {AnimalIcon && } + {animal.name} +
+ ); + })} +
+
+ + {/*
+

Location

+

{activeStation.location.coordinates}

+ {activeStation.location.depth && ( +

{activeStation.location.depth}

+ )} + {activeStation.location.altitude && ( +

{activeStation.location.altitude}

+ )} +
*/} + +
+

Projects

+ {projects.length > 0 ? ( + projects.map((project) => ( +
+

{project.title}

+

Completed: {project.completedCount}

+
+

{project.title}

+ {project.internalComponent && } +
+
+ )) + ) : ( +

No projects available

+ )} +
+
+
+
+ ); +}; + +export default DesertBaseEarthScene; \ No newline at end of file diff --git a/app/scenes/ocean/page.tsx b/app/scenes/ocean/page.tsx new file mode 100644 index 00000000..ab169bb4 --- /dev/null +++ b/app/scenes/ocean/page.tsx @@ -0,0 +1,124 @@ +'use client'; + +import React, { useState, useEffect } from "react"; +import { Fish } from "lucide-react"; +import { PlanktonPortalFrame } from "@/components/Projects/Zoodex/planktonPortal"; +import { BiomePattern } from "@/components/Structures/Missions/Biologists/BiomePattern"; +import { iconMap } from "@/components/Structures/Missions/Biologists/StationCard"; +import Navbar from "@/components/Layout/Navbar"; + +interface Project { + id: number; + station: number; + biome: string; + title: string; + icon: React.ElementType; + completedCount: number; + internalComponent?: React.ElementType; + color: string; +} + +interface Animal { + name: string; + icon: string; +} + +const fetchProjects = (): Project[] => { + return [ + { + id: 1, + station: 3104002, + biome: "Ocean", + title: "Plankton Portal", + icon: Fish, + completedCount: 0, + internalComponent: () => , + color: "text-blue-400", + }, + ]; +}; + +const OceanBaseEarthScene = () => { + const [activeStation] = useState({ + id: 3104002, + name: "Ocean Station", + description: "A station located in the ocean biome focused on plankton research.", + biome: "Ocean", + animals: [ + { name: "Plankton", icon: "Fish" }, + ], + location: { coordinates: "3.123° N, 51.567° W", depth: "200m" }, + }); + const [projects, setProjects] = useState([]); + + useEffect(() => { + if (activeStation) { + const filteredProjects = fetchProjects().filter( + (p) => p.station === Number(activeStation.id) + ); + setProjects(filteredProjects); + } + }, [activeStation]); + + return ( +
+ Ocean Background + +
+

Ocean Station

+ +
+

{activeStation.name} | {activeStation.id}

+

{activeStation.description}

+ +
+

Biome

+
+ + {activeStation.biome} +
+
+ +
+

Animals available for study

+
+ {activeStation.animals.map((animal: Animal) => { + const AnimalIcon = iconMap[animal.icon as keyof typeof iconMap]; + return ( +
+ {AnimalIcon && } + {animal.name} +
+ ); + })} +
+
+ +
+

Projects

+ {projects.length > 0 ? ( + projects.map((project) => ( +
+

{project.title}

+

Completed: {project.completedCount}

+
+

{project.title}

+ {project.internalComponent && } +
+
+ )) + ) : ( +

No projects available

+ )} +
+
+
+
+ ); +}; + +export default OceanBaseEarthScene; \ No newline at end of file diff --git a/app/scenes/onboarding/page.tsx b/app/scenes/onboarding/page.tsx index 7a0f8ba5..0e65b4c5 100644 --- a/app/scenes/onboarding/page.tsx +++ b/app/scenes/onboarding/page.tsx @@ -1,17 +1,23 @@ +'use client'; + import React from "react"; import { EarthScene } from "@/app/scenes/earth/scene"; import InventoryPage from "@/components/Inventory/Grid/Grid"; import EnhancedWeatherEvents from '@/components/(scenes)/mining/enhanced-weather-events'; import MissionSelector from "@/components/Missions/mission-selector"; import VerticalToolbar from "@/components/Layout/Toolbar"; +import Navbar from "@/components/Layout/Navbar"; export default function Onboarding () { return ( - } - middleSection={} - // toolbar={} - // bottomSection={} - /> + <> + + } + middleSection={} + // toolbar={} + // bottomSection={} + /> + ); }; \ No newline at end of file diff --git a/app/tests/page.tsx b/app/tests/page.tsx index 031daa69..87c068a0 100644 --- a/app/tests/page.tsx +++ b/app/tests/page.tsx @@ -1,11 +1,14 @@ "use client"; +import PlanetGenerator2 from "@/components/Data/Generator/Astronomers/PlanetHunters/V2/PlanetGenerator2"; +import MySettlementsLocations from "@/content/Classifications/UserLocations"; + export default function TestPage() { return ( // <> - + // {/* */} ); diff --git a/app/tests/singleMissionGuide.tsx b/app/tests/singleMissionGuide.tsx index c0d6c38d..be82376c 100644 --- a/app/tests/singleMissionGuide.tsx +++ b/app/tests/singleMissionGuide.tsx @@ -5,17 +5,17 @@ import { CloudDrizzleIcon, LightbulbIcon, Telescope, ArrowUpIcon, ArrowDownIcon import { motion, AnimatePresence } from "framer-motion"; import { useSupabaseClient, useSession } from "@supabase/auth-helpers-react"; import JournalPage from "@/components/Structures/Missions/Stardust/Journal"; +import MilestoneCard from "@/components/Structures/Missions/Milestones/MilestoneCard"; -// Define Mission and other types export interface Mission { id: number; name: string; description: string; - additionalDescription: string | React.ReactNode; // New property for additional context + additionalDescription: string | React.ReactNode; icon: React.ElementType; color: string; identifier: string; -} +}; const projects: Record = { "Refracting Telescope": [ @@ -93,97 +93,23 @@ const defaultMission: Mission = { id: 0, name: "Welcome to Star Sailors", description: "You've been given some basic structures to start your journey. Click on their icons to classify the data they've collected for you. New data & projects are being added weekly.", - additionalDescription: , - // additionalDescription: "You'll get to explore various missions, starting with basic tasks such as classifying objects in the sky and on Earth. Each task you complete unlocks new insights. Next week (22nd December) you'll be able to upload your own discoveries and clips", + // additionalDescription: "You'll get to explore various missions, starting with basic tasks such as classifying objects in the sky and on Earth. Each task you complete unlocks new insights.", // + additionalDescription: , icon: LightbulbIcon, color: "text-blue-400", identifier: "default-starting-mission", }; -const structureTitles: Record = { - "Refracting Telescope": "Astronomer Missions", - Biodome: "Ecology Missions", - WeatherBalloon: "Meteorology Missions", -}; - const SimpleeMissionGuide = () => { - const supabase = useSupabaseClient(); - const session = useSession(); - - const [completedIdentifiers, setCompletedIdentifiers] = useState([]); - const [activeStructure, setActiveStructure] = useState("Refracting Telescope"); const [isMinimized, setIsMinimized] = useState(false); - - // Modal state const [isModalOpen, setIsModalOpen] = useState(false); - const [selectedMission, setSelectedMission] = useState(null); - - useEffect(() => { - const fetchClassifications = async () => { - if (!session?.user) return; - - try { - const { data, error } = await supabase - .from("classifications") - .select("classificationtype, author") - .eq("author", session.user.id); - - if (error) throw error; - - const identifiers = data.map((item: any) => item.classificationtype).filter(Boolean); - setCompletedIdentifiers(identifiers); - } catch (err) { - console.error("Error fetching classifications:", err); - } - }; - - fetchClassifications(); - }, [session?.user, supabase]); - - const allStructures = Object.keys(projects); - const currentMissions = projects[activeStructure] || []; - const handlePrevStructure = () => { - const currentIndex = allStructures.indexOf(activeStructure); - const prevIndex = (currentIndex - 1 + allStructures.length) % allStructures.length; - setActiveStructure(allStructures[prevIndex]); - }; - - const handleNextStructure = () => { - const currentIndex = allStructures.indexOf(activeStructure); - const nextIndex = (currentIndex + 1) % allStructures.length; - setActiveStructure(allStructures[nextIndex]); - }; - - const renderMission = (mission: Mission) => { - const isCompleted = completedIdentifiers.includes(mission.identifier); - - return ( - - - -
-

{mission.name}

-

{mission.description}

- -
-
-
- ); + const openModal = () => { + setIsModalOpen(true); }; const closeModal = () => { setIsModalOpen(false); - setSelectedMission(null); }; return ( @@ -193,22 +119,6 @@ const SimpleeMissionGuide = () => { animate={{ height: isMinimized ? "50px" : "auto" }} transition={{ duration: 0.5 }} > -
- {!isMinimized && } -
-

{structureTitles[activeStructure]}

- -
- {!isMinimized && } -
- {!isMinimized && ( { exit={{ opacity: 0, y: -10 }} transition={{ duration: 0.3 }} > - {renderMission(defaultMission)} - {currentMissions.map((mission) => renderMission(mission))} + + + +
+

{defaultMission.name}

+

{defaultMission.description}

+ +
+
+
)}
- {isModalOpen && selectedMission && ( + {/* Modal for additional description */} + {isModalOpen && (
-

{selectedMission.name}

-

{selectedMission.additionalDescription}

{/* Display additional description */} +

{defaultMission.name}

+

{defaultMission.additionalDescription}

diff --git a/components.json b/components.json index c6b106ab..d9ef0ae5 100644 --- a/components.json +++ b/components.json @@ -1,16 +1,21 @@ { "$schema": "https://ui.shadcn.com/schema.json", "style": "default", - "rsc": false, + "rsc": true, "tsx": true, "tailwind": { "config": "tailwind.config.ts", - "css": "styles/globals.css", - "baseColor": "gray", - "cssVariables": true + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" }, "aliases": { + "components": "@/components", "utils": "@/lib/utils", - "components": "@/components" - } + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" } \ No newline at end of file diff --git a/components/Data/Generator/Astronomers/PlanetHunters/V2/AtmosphereControls.tsx b/components/Data/Generator/Astronomers/PlanetHunters/V2/AtmosphereControls.tsx new file mode 100644 index 00000000..c3f62e48 --- /dev/null +++ b/components/Data/Generator/Astronomers/PlanetHunters/V2/AtmosphereControls.tsx @@ -0,0 +1,92 @@ +"use client" + +import { Slider } from "@/components/ui/slider"; +import { Label } from "@/components/ui/label"; +import { Card, CardContent } from "@/components/ui/card"; +import { Switch } from "@/components/ui/switch"; +import { Button } from "@/components/ui/button"; + +interface AtmosphereControlsProps { + atmosphereOpacity: number + showAtmosphere: boolean + atmosphereOffset: number + splitMeshes: boolean + storms: number + onAtmosphereOpacityChange: (value: number) => void + onShowAtmosphereChange: (value: boolean) => void + onAtmosphereOffsetChange: (value: number) => void + onSplitMeshesChange: (value: boolean) => void + onStormsChange: (value: number) => void +} + +export function AtmosphereControls({ + atmosphereOpacity, + showAtmosphere, + atmosphereOffset, + splitMeshes, + storms, + onAtmosphereOpacityChange, + onShowAtmosphereChange, + onAtmosphereOffsetChange, + onSplitMeshesChange, + onStormsChange, +}: AtmosphereControlsProps) { + return ( + + +
+ + +
+ + {showAtmosphere && ( + <> +
+ + onAtmosphereOpacityChange(value)} + /> +
+ {typeof atmosphereOpacity === "number" ? atmosphereOpacity.toFixed(2) : "N/A"} +
+
+ +
+ + onAtmosphereOffsetChange(value)} + /> +
+ {typeof atmosphereOffset === "number" ? atmosphereOffset.toFixed(3) : "N/A"} +
+
+ +
+ + onStormsChange(Math.round(value))} + /> +
{storms}
+
+ + + + )} +
+
+ ); +}; \ No newline at end of file diff --git a/components/Data/Generator/Astronomers/PlanetHunters/V2/PlanetGenerator2.tsx b/components/Data/Generator/Astronomers/PlanetHunters/V2/PlanetGenerator2.tsx new file mode 100644 index 00000000..c5d9508e --- /dev/null +++ b/components/Data/Generator/Astronomers/PlanetHunters/V2/PlanetGenerator2.tsx @@ -0,0 +1,119 @@ +"use client" + +import { useState } from "react" +import { PlanetScene } from "./planet-scene" +import { PlanetControls } from "./planet-controls" +import { PlanetImportExport } from "./planet-import-export" +import { calculatePlanetStats } from "@/utils/planet-physics" +import type { PlanetStats } from "@/utils/planet-physics" +import { AtmosphereControls } from "./AtmosphereControls" + +const TERRESTRIAL_THRESHOLD = 7.5 // Earth masses +const GASEOUS_THRESHOLD = 2.0 // Earth radii + +export default function PlanetGenerator2() { + const [mass, setMass] = useState(1) + const [radius, setRadius] = useState(1) + const [temperature, setTemperature] = useState(288) // Earth average temperature in Kelvin + const [orbitalPeriod, setOrbitalPeriod] = useState(365) + const [typeOverride, setTypeOverride] = useState<"terrestrial" | "gaseous" | null>(null) + const [atmosphereOpacity, setAtmosphereOpacity] = useState(0.3) + const [showAtmosphere, setShowAtmosphere] = useState(true) + const [showLiquid, setShowLiquid] = useState(true) + const [atmosphereOffset, setAtmosphereOffset] = useState(0.01) + const [splitMeshes, setSplitMeshes] = useState(false) + const [storms, setStorms] = useState(0) + + const stats = calculatePlanetStats(mass, radius, temperature, orbitalPeriod, typeOverride) + + const handleMassChange = (newMass: number) => { + if (typeOverride === "terrestrial" && newMass > TERRESTRIAL_THRESHOLD) { + setMass(TERRESTRIAL_THRESHOLD) + } else if (typeOverride === "gaseous" && newMass <= TERRESTRIAL_THRESHOLD) { + setMass(TERRESTRIAL_THRESHOLD + 0.1) + } else { + setMass(newMass) + } + } + + const handleRadiusChange = (newRadius: number) => { + if (typeOverride === "terrestrial" && newRadius > GASEOUS_THRESHOLD) { + setRadius(GASEOUS_THRESHOLD) + } else if (typeOverride === "gaseous" && newRadius <= GASEOUS_THRESHOLD) { + setRadius(GASEOUS_THRESHOLD + 0.1) + } else { + setRadius(newRadius) + } + } + + const handleTypeOverride = (type: "terrestrial" | "gaseous" | null) => { + setTypeOverride(type) + if (type === "terrestrial") { + if (mass > TERRESTRIAL_THRESHOLD) setMass(TERRESTRIAL_THRESHOLD) + if (radius > GASEOUS_THRESHOLD) setRadius(GASEOUS_THRESHOLD) + } else if (type === "gaseous") { + if (mass <= TERRESTRIAL_THRESHOLD) setMass(TERRESTRIAL_THRESHOLD + 0.1) + if (radius <= GASEOUS_THRESHOLD) setRadius(GASEOUS_THRESHOLD + 0.1) + } + } + + const handleImport = (importedStats: Partial) => { + if (importedStats.mass !== undefined) { + setMass(importedStats.mass) + } + if (importedStats.radius !== undefined) { + setRadius(importedStats.radius) + } + if (importedStats.temperature !== undefined) { + setTemperature(importedStats.temperature) + } + if (importedStats.orbitalPeriod !== undefined) { + setOrbitalPeriod(importedStats.orbitalPeriod) + } + setTypeOverride(null) + } + + return ( +
+
+

Procedural Planet Generator

+
+ +
+ + + +
+
+
+
+ ) +}; \ No newline at end of file diff --git a/components/Data/Generator/Astronomers/PlanetHunters/V2/planet-controls.tsx b/components/Data/Generator/Astronomers/PlanetHunters/V2/planet-controls.tsx new file mode 100644 index 00000000..1f54508b --- /dev/null +++ b/components/Data/Generator/Astronomers/PlanetHunters/V2/planet-controls.tsx @@ -0,0 +1,131 @@ +"use client"; + +import { Slider } from "@/components/ui/slider"; +import { Label } from "@/components/ui/label"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { Switch } from "@/components/ui/switch"; +import { determineLiquidType } from "@/utils/planet-physics"; +import type { PlanetStats } from "@/utils/planet-physics"; + +interface PlanetControlsProps { + stats: PlanetStats + showLiquid: boolean + onMassChange: (value: number) => void + onRadiusChange: (value: number) => void + onTemperatureChange: (value: number) => void + onOrbitalPeriodChange: (value: number) => void + onTypeOverride: (type: "terrestrial" | "gaseous" | null) => void + onShowLiquidChange: (value: boolean) => void +}; + +function calculateBiomeTemperatures(stats: PlanetStats) { + const baseTemp = stats.temperature ?? 300 // Default to 300K if undefined + const massEffect = (stats.mass - 1) * 10 + + return { + ocean: Math.round(baseTemp - 5), + beach: Math.round(baseTemp), + ground: Math.round(baseTemp + 5), + mountain: Math.round(baseTemp - 15), + }; +}; + +export function PlanetControls({ + stats, + showLiquid, + onMassChange, + onRadiusChange, + onTemperatureChange, + onOrbitalPeriodChange, + onTypeOverride, + onShowLiquidChange, +}: PlanetControlsProps) { + const biomeTemperatures = calculateBiomeTemperatures(stats) + const liquidInfo = determineLiquidType(stats.temperature ?? 300); + + return ( + + +
+ + onMassChange(value)} /> +
{stats.mass.toFixed(1)} M⊕
+
+ +
+ + onRadiusChange(value)} + /> +
{stats.radius.toFixed(1)} R⊕
+
+ +
+ + onTemperatureChange(value)} + /> +
{stats.temperature}K
+
+ +
+ + onOrbitalPeriodChange(value)} + /> +
{stats.orbitalPeriod} days
+
+ +
+
Density: {stats.density.toFixed(2)} g/cm³
+
Type: {stats.type}
+
+ +
+ + +
+ + {showLiquid && ( +
+
Liquid Solvent: {liquidInfo.type}
+
Temperature Range: {liquidInfo.temperatureRange}
+
+ )} + + {/*
+ +
Ocean: {biomeTemperatures.ocean}K
+
Beach: {biomeTemperatures.beach}K
+
Ground: {biomeTemperatures.ground}K
+
Mountain: {biomeTemperatures.mountain}K
+
*/} + +
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/components/Data/Generator/Astronomers/PlanetHunters/V2/planet-import-export.tsx b/components/Data/Generator/Astronomers/PlanetHunters/V2/planet-import-export.tsx new file mode 100644 index 00000000..f3b85d55 --- /dev/null +++ b/components/Data/Generator/Astronomers/PlanetHunters/V2/planet-import-export.tsx @@ -0,0 +1,58 @@ +'use client'; + +import { useState } from 'react'; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import type { PlanetStats } from '@/utils/planet-physics'; + +interface PlanetImportExportProps { + stats: PlanetStats + onImport: (importedStats: Partial) => void +} + +export function PlanetImportExport({ stats, onImport }: PlanetImportExportProps) { + const [importExportText, setImportExportText] = useState('') + + const handleExport = () => { + const exportText = `radius: ${stats.radius.toFixed(2)} +mass: ${stats.mass.toFixed(2)}` + setImportExportText(exportText) + } + + const handleImport = () => { + const lines = importExportText.split('\n') + const importedStats: Partial = {} + + lines.forEach(line => { + const [key, value] = line.split(':').map(part => part.trim()) + if (key === 'radius' || key === 'mass') { + importedStats[key] = parseFloat(value) + } + }) + + if (Object.keys(importedStats).length > 0) { + onImport(importedStats) + } + } + + return ( + + + Import/Export Settings + + +