feat: Implement interactive map views
Create interactive map views using SVG for nodes and connections. Implement navigation to region and system pages on click.
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
|
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import { Toaster as Sonner } from "@/components/ui/sonner";
|
import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||||
import Index from "./pages/Index";
|
import Index from "./pages/Index";
|
||||||
|
import RegionPage from "./pages/RegionPage";
|
||||||
import NotFound from "./pages/NotFound";
|
import NotFound from "./pages/NotFound";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
@@ -16,6 +18,7 @@ const App = () => (
|
|||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Index />} />
|
<Route path="/" element={<Index />} />
|
||||||
|
<Route path="/regions/:region" element={<RegionPage />} />
|
||||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
37
src/components/Connection.tsx
Normal file
37
src/components/Connection.tsx
Normal file
@@ -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<ConnectionProps> = ({ from, to }) => {
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
{/* Glow effect */}
|
||||||
|
<line
|
||||||
|
x1={from.x}
|
||||||
|
y1={from.y}
|
||||||
|
x2={to.x}
|
||||||
|
y2={to.y}
|
||||||
|
stroke="#8b5cf6"
|
||||||
|
strokeWidth="3"
|
||||||
|
opacity="0.3"
|
||||||
|
filter="url(#glow)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Main line */}
|
||||||
|
<line
|
||||||
|
x1={from.x}
|
||||||
|
y1={from.y}
|
||||||
|
x2={to.x}
|
||||||
|
y2={to.y}
|
||||||
|
stroke="#a855f7"
|
||||||
|
strokeWidth="1"
|
||||||
|
opacity="0.7"
|
||||||
|
className="transition-all duration-300"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
100
src/components/GalaxyMap.tsx
Normal file
100
src/components/GalaxyMap.tsx
Normal file
@@ -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<string | null>(null);
|
||||||
|
const [nodePositions, setNodePositions] = useState<Record<string, Position>>(galaxyData.nodePositions);
|
||||||
|
const svgRef = useRef<SVGSVGElement>(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 (
|
||||||
|
<div className="w-full h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 overflow-hidden relative">
|
||||||
|
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-purple-900/20 via-slate-900/40 to-black"></div>
|
||||||
|
|
||||||
|
<div className="relative z-10 p-8">
|
||||||
|
<h1 className="text-4xl font-bold text-white mb-2 text-center">Galaxy Map</h1>
|
||||||
|
<p className="text-purple-200 text-center mb-8">Navigate the known regions of space</p>
|
||||||
|
|
||||||
|
<div className="w-full h-[calc(100vh-200px)] border border-purple-500/30 rounded-lg overflow-hidden bg-black/20 backdrop-blur-sm">
|
||||||
|
<svg
|
||||||
|
ref={svgRef}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
viewBox="0 0 1200 800"
|
||||||
|
className="cursor-crosshair"
|
||||||
|
onMouseMove={handleMouseMove}
|
||||||
|
onMouseUp={handleMouseUp}
|
||||||
|
onMouseLeave={handleMouseUp}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<filter id="glow">
|
||||||
|
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="coloredBlur"/>
|
||||||
|
<feMergeNode in="SourceGraphic"/>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
{/* Render connections first (behind nodes) */}
|
||||||
|
{galaxyData.connections.map((connection, index) => (
|
||||||
|
<Connection
|
||||||
|
key={index}
|
||||||
|
from={nodePositions[connection.from]}
|
||||||
|
to={nodePositions[connection.to]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Render nodes */}
|
||||||
|
{galaxyData.regions.map((region) => (
|
||||||
|
<MapNode
|
||||||
|
key={region.id}
|
||||||
|
id={region.id}
|
||||||
|
name={region.name}
|
||||||
|
position={nodePositions[region.id]}
|
||||||
|
onClick={() => handleNodeClick(region.id)}
|
||||||
|
onMouseDown={() => handleMouseDown(region.id)}
|
||||||
|
isDragging={draggedNode === region.id}
|
||||||
|
type="region"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
95
src/components/MapNode.tsx
Normal file
95
src/components/MapNode.tsx
Normal file
@@ -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<MapNodeProps> = ({
|
||||||
|
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 (
|
||||||
|
<g
|
||||||
|
transform={`translate(${position.x}, ${position.y})`}
|
||||||
|
className="cursor-pointer select-none"
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onMouseDown();
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!isDragging) {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Node glow effect */}
|
||||||
|
<circle
|
||||||
|
r={nodeSize + 6}
|
||||||
|
fill={nodeColor}
|
||||||
|
opacity={isHovered ? 0.3 : 0.1}
|
||||||
|
filter="url(#glow)"
|
||||||
|
className="transition-all duration-300"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Main node */}
|
||||||
|
<circle
|
||||||
|
r={nodeSize}
|
||||||
|
fill={nodeColor}
|
||||||
|
stroke="#ffffff"
|
||||||
|
strokeWidth="2"
|
||||||
|
filter="url(#glow)"
|
||||||
|
className={`transition-all duration-300 ${
|
||||||
|
isHovered ? 'drop-shadow-lg' : ''
|
||||||
|
} ${isDragging ? 'opacity-80' : ''}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Inner core */}
|
||||||
|
<circle
|
||||||
|
r={nodeSize - 4}
|
||||||
|
fill={isHovered ? '#ffffff' : nodeColor}
|
||||||
|
opacity={0.8}
|
||||||
|
className="transition-all duration-300"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Node label */}
|
||||||
|
<text
|
||||||
|
x="0"
|
||||||
|
y={textOffset}
|
||||||
|
textAnchor="middle"
|
||||||
|
fill="#ffffff"
|
||||||
|
fontSize={type === 'region' ? '14' : '12'}
|
||||||
|
fontWeight="bold"
|
||||||
|
className={`transition-all duration-300 ${
|
||||||
|
isHovered ? 'fill-purple-200' : 'fill-white'
|
||||||
|
} pointer-events-none select-none`}
|
||||||
|
style={{ textShadow: '2px 2px 4px rgba(0,0,0,0.8)' }}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
122
src/components/RegionMap.tsx
Normal file
122
src/components/RegionMap.tsx
Normal file
@@ -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<string, Position>;
|
||||||
|
connections: Array<{ from: string; to: string }>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegionMap: React.FC<RegionMapProps> = ({ regionData }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [draggedNode, setDraggedNode] = useState<string | null>(null);
|
||||||
|
const [nodePositions, setNodePositions] = useState<Record<string, Position>>(regionData.nodePositions);
|
||||||
|
const svgRef = useRef<SVGSVGElement>(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 (
|
||||||
|
<div className="w-full h-screen bg-gradient-to-br from-slate-900 via-cyan-900 to-slate-900 overflow-hidden relative">
|
||||||
|
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-cyan-900/20 via-slate-900/40 to-black"></div>
|
||||||
|
|
||||||
|
<div className="relative z-10 p-8">
|
||||||
|
<div className="flex items-center gap-4 mb-6">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
className="bg-black/20 border-cyan-500/30 text-cyan-200 hover:bg-cyan-500/20"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
|
Back to Galaxy
|
||||||
|
</Button>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-4xl font-bold text-white">{regionData.name} Region</h1>
|
||||||
|
<p className="text-cyan-200">Explore the systems within this region</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full h-[calc(100vh-200px)] border border-cyan-500/30 rounded-lg overflow-hidden bg-black/20 backdrop-blur-sm">
|
||||||
|
<svg
|
||||||
|
ref={svgRef}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
viewBox="0 0 1200 800"
|
||||||
|
className="cursor-crosshair"
|
||||||
|
onMouseMove={handleMouseMove}
|
||||||
|
onMouseUp={handleMouseUp}
|
||||||
|
onMouseLeave={handleMouseUp}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<filter id="glow">
|
||||||
|
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="coloredBlur"/>
|
||||||
|
<feMergeNode in="SourceGraphic"/>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
{/* Render connections first (behind nodes) */}
|
||||||
|
{regionData.connections.map((connection, index) => (
|
||||||
|
<Connection
|
||||||
|
key={index}
|
||||||
|
from={nodePositions[connection.from]}
|
||||||
|
to={nodePositions[connection.to]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Render systems */}
|
||||||
|
{regionData.systems.map((system) => (
|
||||||
|
<MapNode
|
||||||
|
key={system.id}
|
||||||
|
id={system.id}
|
||||||
|
name={system.name}
|
||||||
|
position={nodePositions[system.id]}
|
||||||
|
onClick={() => handleSystemClick(system.id)}
|
||||||
|
onMouseDown={() => handleMouseDown(system.id)}
|
||||||
|
isDragging={draggedNode === system.id}
|
||||||
|
type="system"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
125
src/data/galaxyData.ts
Normal file
125
src/data/galaxyData.ts
Normal file
@@ -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<string, any> = {
|
||||||
|
'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' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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 = () => {
|
const Index = () => {
|
||||||
return (
|
return <GalaxyMap />;
|
||||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
||||||
<div className="text-center">
|
|
||||||
<h1 className="text-4xl font-bold mb-4">Welcome to Your Blank App</h1>
|
|
||||||
<p className="text-xl text-muted-foreground">Start building your amazing project here!</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Index;
|
export default Index;
|
||||||
|
|||||||
34
src/pages/RegionPage.tsx
Normal file
34
src/pages/RegionPage.tsx
Normal file
@@ -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 (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-4xl font-bold text-white mb-4">Region Not Found</h1>
|
||||||
|
<p className="text-purple-200 mb-6">The requested region does not exist in our database.</p>
|
||||||
|
<Button
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
className="bg-purple-600 hover:bg-purple-700"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
|
Return to Galaxy Map
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <RegionMap regionData={regionData[region]} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RegionPage;
|
||||||
Reference in New Issue
Block a user