diff --git a/src/App.tsx b/src/App.tsx index 18daf2e..9ea9163 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,11 @@ + import { Toaster } from "@/components/ui/toaster"; import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import Index from "./pages/Index"; +import RegionPage from "./pages/RegionPage"; import NotFound from "./pages/NotFound"; const queryClient = new QueryClient(); @@ -16,6 +18,7 @@ const App = () => ( } /> + } /> {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> diff --git a/src/components/Connection.tsx b/src/components/Connection.tsx new file mode 100644 index 0000000..a194023 --- /dev/null +++ b/src/components/Connection.tsx @@ -0,0 +1,37 @@ + +import React from 'react'; + +interface ConnectionProps { + from: { x: number; y: number }; + to: { x: number; y: number }; +} + +export const Connection: React.FC = ({ from, to }) => { + return ( + + {/* Glow effect */} + + + {/* Main line */} + + + ); +}; diff --git a/src/components/GalaxyMap.tsx b/src/components/GalaxyMap.tsx new file mode 100644 index 0000000..c6deef2 --- /dev/null +++ b/src/components/GalaxyMap.tsx @@ -0,0 +1,100 @@ + +import React, { useState, useRef, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { MapNode } from './MapNode'; +import { Connection } from './Connection'; +import { galaxyData } from '../data/galaxyData'; + +interface Position { + x: number; + y: number; +} + +export const GalaxyMap = () => { + const navigate = useNavigate(); + const [draggedNode, setDraggedNode] = useState(null); + const [nodePositions, setNodePositions] = useState>(galaxyData.nodePositions); + const svgRef = useRef(null); + + const handleNodeClick = (regionId: string) => { + navigate(`/regions/${regionId}`); + }; + + const handleMouseDown = useCallback((nodeId: string) => { + setDraggedNode(nodeId); + }, []); + + const handleMouseMove = useCallback((e: React.MouseEvent) => { + if (!draggedNode || !svgRef.current) return; + + const rect = svgRef.current.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + setNodePositions(prev => ({ + ...prev, + [draggedNode]: { x, y } + })); + }, [draggedNode]); + + const handleMouseUp = useCallback(() => { + setDraggedNode(null); + }, []); + + return ( +
+
+ +
+

Galaxy Map

+

Navigate the known regions of space

+ +
+ + + + + + + + + + + + {/* Render connections first (behind nodes) */} + {galaxyData.connections.map((connection, index) => ( + + ))} + + {/* Render nodes */} + {galaxyData.regions.map((region) => ( + handleNodeClick(region.id)} + onMouseDown={() => handleMouseDown(region.id)} + isDragging={draggedNode === region.id} + type="region" + /> + ))} + +
+
+
+ ); +}; diff --git a/src/components/MapNode.tsx b/src/components/MapNode.tsx new file mode 100644 index 0000000..5b0284a --- /dev/null +++ b/src/components/MapNode.tsx @@ -0,0 +1,95 @@ + +import React, { useState } from 'react'; + +interface MapNodeProps { + id: string; + name: string; + position: { x: number; y: number }; + onClick: () => void; + onMouseDown: () => void; + isDragging: boolean; + type: 'region' | 'system'; +} + +export const MapNode: React.FC = ({ + id, + name, + position, + onClick, + onMouseDown, + isDragging, + type +}) => { + const [isHovered, setIsHovered] = useState(false); + + const nodeSize = type === 'region' ? 12 : 8; + const textOffset = type === 'region' ? 20 : 15; + + const nodeColor = type === 'region' + ? (isHovered ? '#8b5cf6' : '#a855f7') + : (isHovered ? '#06b6d4' : '#0891b2'); + + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onMouseDown={(e) => { + e.preventDefault(); + onMouseDown(); + }} + onClick={(e) => { + e.stopPropagation(); + if (!isDragging) { + onClick(); + } + }} + > + {/* Node glow effect */} + + + {/* Main node */} + + + {/* Inner core */} + + + {/* Node label */} + + {name} + + + ); +}; diff --git a/src/components/RegionMap.tsx b/src/components/RegionMap.tsx new file mode 100644 index 0000000..8b9b2ba --- /dev/null +++ b/src/components/RegionMap.tsx @@ -0,0 +1,122 @@ + +import React, { useState, useRef, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { MapNode } from './MapNode'; +import { Connection } from './Connection'; +import { Button } from '@/components/ui/button'; +import { ArrowLeft } from 'lucide-react'; + +interface Position { + x: number; + y: number; +} + +interface RegionMapProps { + regionData: { + name: string; + systems: Array<{ id: string; name: string }>; + nodePositions: Record; + connections: Array<{ from: string; to: string }>; + }; +} + +export const RegionMap: React.FC = ({ regionData }) => { + const navigate = useNavigate(); + const [draggedNode, setDraggedNode] = useState(null); + const [nodePositions, setNodePositions] = useState>(regionData.nodePositions); + const svgRef = useRef(null); + + const handleSystemClick = (systemId: string) => { + navigate(`/systems/${systemId}`); + }; + + const handleMouseDown = useCallback((nodeId: string) => { + setDraggedNode(nodeId); + }, []); + + const handleMouseMove = useCallback((e: React.MouseEvent) => { + if (!draggedNode || !svgRef.current) return; + + const rect = svgRef.current.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + setNodePositions(prev => ({ + ...prev, + [draggedNode]: { x, y } + })); + }, [draggedNode]); + + const handleMouseUp = useCallback(() => { + setDraggedNode(null); + }, []); + + return ( +
+
+ +
+
+ +
+

{regionData.name} Region

+

Explore the systems within this region

+
+
+ +
+ + + + + + + + + + + + {/* Render connections first (behind nodes) */} + {regionData.connections.map((connection, index) => ( + + ))} + + {/* Render systems */} + {regionData.systems.map((system) => ( + handleSystemClick(system.id)} + onMouseDown={() => handleMouseDown(system.id)} + isDragging={draggedNode === system.id} + type="system" + /> + ))} + +
+
+
+ ); +}; diff --git a/src/data/galaxyData.ts b/src/data/galaxyData.ts new file mode 100644 index 0000000..9e971d4 --- /dev/null +++ b/src/data/galaxyData.ts @@ -0,0 +1,125 @@ + +export const galaxyData = { + regions: [ + { id: 'outer-ring', name: 'Outer Ring' }, + { id: 'syndicate', name: 'Syndicate' }, + { id: 'fountain', name: 'Fountain' }, + { id: 'cloud-ring', name: 'Cloud Ring' }, + { id: 'fade', name: 'Fade' }, + { id: 'pure-blind', name: 'Pure Blind' }, + { id: 'deklein', name: 'Deklein' }, + { id: 'branch', name: 'Branch' }, + { id: 'tenal', name: 'Tenal' }, + { id: 'venal', name: 'Venal' }, + { id: 'tribute', name: 'Tribute' }, + { id: 'vale-of-silent', name: 'Vale of the Silent' }, + { id: 'geminate', name: 'Geminate' }, + { id: 'the-citadel', name: 'The Citadel' }, + { id: 'the-forge', name: 'The Forge' }, + { id: 'lonetrek', name: 'Lonetrek' } + ], + nodePositions: { + 'outer-ring': { x: 150, y: 200 }, + 'syndicate': { x: 250, y: 350 }, + 'fountain': { x: 120, y: 400 }, + 'cloud-ring': { x: 320, y: 220 }, + 'fade': { x: 450, y: 180 }, + 'pure-blind': { x: 480, y: 250 }, + 'deklein': { x: 450, y: 130 }, + 'branch': { x: 550, y: 100 }, + 'tenal': { x: 650, y: 120 }, + 'venal': { x: 580, y: 170 }, + 'tribute': { x: 580, y: 220 }, + 'vale-of-silent': { x: 680, y: 260 }, + 'geminate': { x: 720, y: 320 }, + 'the-citadel': { x: 560, y: 370 }, + 'the-forge': { x: 660, y: 370 }, + 'lonetrek': { x: 580, y: 290 } + }, + connections: [ + { from: 'outer-ring', to: 'syndicate' }, + { from: 'outer-ring', to: 'fountain' }, + { from: 'syndicate', to: 'cloud-ring' }, + { from: 'cloud-ring', to: 'fade' }, + { from: 'cloud-ring', to: 'pure-blind' }, + { from: 'fade', to: 'pure-blind' }, + { from: 'fade', to: 'deklein' }, + { from: 'deklein', to: 'branch' }, + { from: 'branch', to: 'tenal' }, + { from: 'tenal', to: 'venal' }, + { from: 'venal', to: 'tribute' }, + { from: 'pure-blind', to: 'tribute' }, + { from: 'tribute', to: 'vale-of-silent' }, + { from: 'vale-of-silent', to: 'geminate' }, + { from: 'tribute', to: 'lonetrek' }, + { from: 'lonetrek', to: 'the-citadel' }, + { from: 'lonetrek', to: 'the-forge' }, + { from: 'the-citadel', to: 'the-forge' }, + { from: 'geminate', to: 'the-forge' } + ] +}; + +export const regionData: Record = { + 'the-forge': { + name: 'The Forge', + systems: [ + { id: 'jita', name: 'Jita' }, + { id: 'perimeter', name: 'Perimeter' }, + { id: 'sobaseki', name: 'Sobaseki' }, + { id: 'urlen', name: 'Urlen' }, + { id: 'maurasi', name: 'Maurasi' }, + { id: 'kimotoro', name: 'Kimotoro' }, + { id: 'new-caldari', name: 'New Caldari' }, + { id: 'outuni', name: 'Outuni' } + ], + nodePositions: { + 'jita': { x: 600, y: 400 }, + 'perimeter': { x: 550, y: 350 }, + 'sobaseki': { x: 650, y: 350 }, + 'urlen': { x: 500, y: 400 }, + 'maurasi': { x: 700, y: 400 }, + 'kimotoro': { x: 600, y: 300 }, + 'new-caldari': { x: 600, y: 500 }, + 'outuni': { x: 550, y: 450 } + }, + connections: [ + { from: 'jita', to: 'perimeter' }, + { from: 'jita', to: 'sobaseki' }, + { from: 'jita', to: 'urlen' }, + { from: 'jita', to: 'maurasi' }, + { from: 'jita', to: 'kimotoro' }, + { from: 'jita', to: 'new-caldari' }, + { from: 'perimeter', to: 'kimotoro' }, + { from: 'perimeter', to: 'outuni' }, + { from: 'sobaseki', to: 'maurasi' }, + { from: 'new-caldari', to: 'outuni' } + ] + }, + 'syndicate': { + name: 'Syndicate', + systems: [ + { id: 'poitot', name: 'Poitot' }, + { id: '6-cz49', name: '6-CZ49' }, + { id: 'x-7omm', name: 'X-7OMM' }, + { id: 'pc9-ay', name: 'PC9-AY' }, + { id: 'mj-5f9', name: 'MJ-5F9' }, + { id: 'f-88pk', name: 'F-88PK' } + ], + nodePositions: { + 'poitot': { x: 600, y: 400 }, + '6-cz49': { x: 500, y: 350 }, + 'x-7omm': { x: 700, y: 350 }, + 'pc9-ay': { x: 550, y: 300 }, + 'mj-5f9': { x: 650, y: 300 }, + 'f-88pk': { x: 600, y: 250 } + }, + connections: [ + { from: 'poitot', to: '6-cz49' }, + { from: 'poitot', to: 'x-7omm' }, + { from: '6-cz49', to: 'pc9-ay' }, + { from: 'x-7omm', to: 'mj-5f9' }, + { from: 'pc9-ay', to: 'f-88pk' }, + { from: 'mj-5f9', to: 'f-88pk' } + ] + } +}; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 99637ff..bf893bf 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1,14 +1,8 @@ -// Update this page (the content is just a fallback if you fail to update the page) + +import { GalaxyMap } from '../components/GalaxyMap'; const Index = () => { - return ( -
-
-

Welcome to Your Blank App

-

Start building your amazing project here!

-
-
- ); + return ; }; export default Index; diff --git a/src/pages/RegionPage.tsx b/src/pages/RegionPage.tsx new file mode 100644 index 0000000..3bc6905 --- /dev/null +++ b/src/pages/RegionPage.tsx @@ -0,0 +1,34 @@ + +import { useParams } from 'react-router-dom'; +import { RegionMap } from '../components/RegionMap'; +import { regionData } from '../data/galaxyData'; +import { Button } from '@/components/ui/button'; +import { ArrowLeft } from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; + +const RegionPage = () => { + const { region } = useParams<{ region: string }>(); + const navigate = useNavigate(); + + if (!region || !regionData[region]) { + return ( +
+
+

Region Not Found

+

The requested region does not exist in our database.

+ +
+
+ ); + } + + return ; +}; + +export default RegionPage;