Fix visual issues and add features
- Remove white boxes around system nodes. - Reverse zoom direction. - Reduce region node size. - Implement URL encoding for region names. - Color connections based on node security.
This commit is contained in:
		@@ -4,18 +4,30 @@ import React from 'react';
 | 
			
		||||
interface ConnectionProps {
 | 
			
		||||
  from: { x: number; y: number };
 | 
			
		||||
  to: { x: number; y: number };
 | 
			
		||||
  fromColor?: string;
 | 
			
		||||
  toColor?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const Connection: React.FC<ConnectionProps> = ({ from, to }) => {
 | 
			
		||||
export const Connection: React.FC<ConnectionProps> = ({ from, to, fromColor = '#a855f7', toColor = '#a855f7' }) => {
 | 
			
		||||
  // Create gradient ID based on colors to ensure uniqueness
 | 
			
		||||
  const gradientId = `gradient-${fromColor.replace('#', '')}-${toColor.replace('#', '')}`;
 | 
			
		||||
  
 | 
			
		||||
  return (
 | 
			
		||||
    <g>
 | 
			
		||||
      <defs>
 | 
			
		||||
        <linearGradient id={gradientId} x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
          <stop offset="0%" stopColor={fromColor} stopOpacity="0.7" />
 | 
			
		||||
          <stop offset="100%" stopColor={toColor} stopOpacity="0.7" />
 | 
			
		||||
        </linearGradient>
 | 
			
		||||
      </defs>
 | 
			
		||||
      
 | 
			
		||||
      {/* Glow effect */}
 | 
			
		||||
      <line
 | 
			
		||||
        x1={from.x}
 | 
			
		||||
        y1={from.y}
 | 
			
		||||
        x2={to.x}
 | 
			
		||||
        y2={to.y}
 | 
			
		||||
        stroke="#8b5cf6"
 | 
			
		||||
        stroke={`url(#${gradientId})`}
 | 
			
		||||
        strokeWidth="3"
 | 
			
		||||
        opacity="0.3"
 | 
			
		||||
        filter="url(#glow)"
 | 
			
		||||
@@ -27,7 +39,7 @@ export const Connection: React.FC<ConnectionProps> = ({ from, to }) => {
 | 
			
		||||
        y1={from.y}
 | 
			
		||||
        x2={to.x}
 | 
			
		||||
        y2={to.y}
 | 
			
		||||
        stroke="#a855f7"
 | 
			
		||||
        stroke={`url(#${gradientId})`}
 | 
			
		||||
        strokeWidth="1"
 | 
			
		||||
        opacity="0.7"
 | 
			
		||||
        className="transition-all duration-300"
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom';
 | 
			
		||||
import { MapNode } from './MapNode';
 | 
			
		||||
import { Connection } from './Connection';
 | 
			
		||||
import { useQuery } from '@tanstack/react-query';
 | 
			
		||||
import { getSecurityColor } from '../utils/securityColors';
 | 
			
		||||
 | 
			
		||||
interface Region {
 | 
			
		||||
  regionName: string;
 | 
			
		||||
@@ -102,6 +103,7 @@ export const GalaxyMap = () => {
 | 
			
		||||
    const mouseX = e.clientX - rect.left;
 | 
			
		||||
    const mouseY = e.clientY - rect.top;
 | 
			
		||||
 | 
			
		||||
    // Reverse scroll direction: positive deltaY zooms out, negative zooms in
 | 
			
		||||
    const scale = e.deltaY > 0 ? 1.1 : 0.9;
 | 
			
		||||
    const newWidth = viewBox.width * scale;
 | 
			
		||||
    const newHeight = viewBox.height * scale;
 | 
			
		||||
@@ -174,11 +176,18 @@ export const GalaxyMap = () => {
 | 
			
		||||
                const toPos = nodePositions[connectedRegion];
 | 
			
		||||
                if (!fromPos || !toPos) return null;
 | 
			
		||||
                
 | 
			
		||||
                // Get colors for both regions
 | 
			
		||||
                const fromColor = getSecurityColor(region.security);
 | 
			
		||||
                const toRegion = regions.find(r => r.regionName === connectedRegion);
 | 
			
		||||
                const toColor = toRegion ? getSecurityColor(toRegion.security) : fromColor;
 | 
			
		||||
                
 | 
			
		||||
                return (
 | 
			
		||||
                  <Connection
 | 
			
		||||
                    key={`${region.regionName}-${connectedRegion}`}
 | 
			
		||||
                    from={fromPos}
 | 
			
		||||
                    to={toPos}
 | 
			
		||||
                    fromColor={fromColor}
 | 
			
		||||
                    toColor={toColor}
 | 
			
		||||
                  />
 | 
			
		||||
                );
 | 
			
		||||
              })
 | 
			
		||||
 
 | 
			
		||||
@@ -27,9 +27,9 @@ export const MapNode: React.FC<MapNodeProps> = ({
 | 
			
		||||
    : '#a855f7'; // fallback purple color
 | 
			
		||||
 | 
			
		||||
  if (type === 'region') {
 | 
			
		||||
    // Render region as a pill/rounded rectangle
 | 
			
		||||
    const pillWidth = Math.max(name.length * 12, 80);
 | 
			
		||||
    const pillHeight = 32;
 | 
			
		||||
    // Reduce region size to prevent overlap
 | 
			
		||||
    const pillWidth = Math.max(name.length * 8, 60); // Reduced from 12 to 8, min from 80 to 60
 | 
			
		||||
    const pillHeight = 24; // Reduced from 32 to 24
 | 
			
		||||
    
 | 
			
		||||
    return (
 | 
			
		||||
      <g
 | 
			
		||||
@@ -44,11 +44,11 @@ export const MapNode: React.FC<MapNodeProps> = ({
 | 
			
		||||
      >
 | 
			
		||||
        {/* Glow effect */}
 | 
			
		||||
        <rect
 | 
			
		||||
          x={-pillWidth/2 - 4}
 | 
			
		||||
          y={-pillHeight/2 - 4}
 | 
			
		||||
          width={pillWidth + 8}
 | 
			
		||||
          height={pillHeight + 8}
 | 
			
		||||
          rx={(pillHeight + 8) / 2}
 | 
			
		||||
          x={-pillWidth/2 - 3}
 | 
			
		||||
          y={-pillHeight/2 - 3}
 | 
			
		||||
          width={pillWidth + 6}
 | 
			
		||||
          height={pillHeight + 6}
 | 
			
		||||
          rx={(pillHeight + 6) / 2}
 | 
			
		||||
          fill={nodeColor}
 | 
			
		||||
          opacity={isHovered ? 0.3 : 0.1}
 | 
			
		||||
          filter="url(#glow)"
 | 
			
		||||
@@ -64,7 +64,7 @@ export const MapNode: React.FC<MapNodeProps> = ({
 | 
			
		||||
          rx={pillHeight / 2}
 | 
			
		||||
          fill={nodeColor}
 | 
			
		||||
          stroke="#ffffff"
 | 
			
		||||
          strokeWidth="2"
 | 
			
		||||
          strokeWidth="1.5"
 | 
			
		||||
          filter="url(#glow)"
 | 
			
		||||
          className={`transition-all duration-300 ${
 | 
			
		||||
            isHovered ? 'drop-shadow-lg' : ''
 | 
			
		||||
@@ -74,10 +74,10 @@ export const MapNode: React.FC<MapNodeProps> = ({
 | 
			
		||||
        {/* Text inside pill */}
 | 
			
		||||
        <text
 | 
			
		||||
          x="0"
 | 
			
		||||
          y="5"
 | 
			
		||||
          y="4"
 | 
			
		||||
          textAnchor="middle"
 | 
			
		||||
          fill="#ffffff"
 | 
			
		||||
          fontSize="14"
 | 
			
		||||
          fontSize="11"
 | 
			
		||||
          fontWeight="bold"
 | 
			
		||||
          className="transition-all duration-300 pointer-events-none select-none"
 | 
			
		||||
          style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.8)' }}
 | 
			
		||||
@@ -88,8 +88,8 @@ export const MapNode: React.FC<MapNodeProps> = ({
 | 
			
		||||
    );
 | 
			
		||||
  } else {
 | 
			
		||||
    // Render system as a dot with external label
 | 
			
		||||
    const nodeSize = 8;
 | 
			
		||||
    const textOffset = 20; // Increased from 15 to move text further down
 | 
			
		||||
    const nodeSize = 6;
 | 
			
		||||
    const textOffset = 18; // Position text below the dot
 | 
			
		||||
    
 | 
			
		||||
    return (
 | 
			
		||||
      <g
 | 
			
		||||
@@ -104,19 +104,17 @@ export const MapNode: React.FC<MapNodeProps> = ({
 | 
			
		||||
      >
 | 
			
		||||
        {/* Node glow effect */}
 | 
			
		||||
        <circle
 | 
			
		||||
          r={nodeSize + 6}
 | 
			
		||||
          r={nodeSize + 4}
 | 
			
		||||
          fill={nodeColor}
 | 
			
		||||
          opacity={isHovered ? 0.3 : 0.1}
 | 
			
		||||
          filter="url(#glow)"
 | 
			
		||||
          className="transition-all duration-300"
 | 
			
		||||
        />
 | 
			
		||||
        
 | 
			
		||||
        {/* Main node */}
 | 
			
		||||
        {/* Main node - removed stroke to eliminate white border */}
 | 
			
		||||
        <circle
 | 
			
		||||
          r={nodeSize}
 | 
			
		||||
          fill={nodeColor}
 | 
			
		||||
          stroke="#ffffff"
 | 
			
		||||
          strokeWidth="2"
 | 
			
		||||
          filter="url(#glow)"
 | 
			
		||||
          className={`transition-all duration-300 ${
 | 
			
		||||
            isHovered ? 'drop-shadow-lg' : ''
 | 
			
		||||
@@ -125,7 +123,7 @@ export const MapNode: React.FC<MapNodeProps> = ({
 | 
			
		||||
        
 | 
			
		||||
        {/* Inner core */}
 | 
			
		||||
        <circle
 | 
			
		||||
          r={nodeSize - 4}
 | 
			
		||||
          r={nodeSize - 3}
 | 
			
		||||
          fill={isHovered ? '#ffffff' : nodeColor}
 | 
			
		||||
          opacity={0.8}
 | 
			
		||||
          className="transition-all duration-300"
 | 
			
		||||
@@ -137,7 +135,7 @@ export const MapNode: React.FC<MapNodeProps> = ({
 | 
			
		||||
          y={textOffset}
 | 
			
		||||
          textAnchor="middle"
 | 
			
		||||
          fill="#ffffff"
 | 
			
		||||
          fontSize="12"
 | 
			
		||||
          fontSize="10"
 | 
			
		||||
          fontWeight="bold"
 | 
			
		||||
          className={`transition-all duration-300 ${
 | 
			
		||||
            isHovered ? 'fill-purple-200' : 'fill-white'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,14 @@
 | 
			
		||||
 | 
			
		||||
import React, { useState, useRef, useCallback, useEffect } from 'react';
 | 
			
		||||
import { useNavigate, useParams } from 'react-router-dom';
 | 
			
		||||
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';
 | 
			
		||||
import { useQuery } from '@tanstack/react-query';
 | 
			
		||||
import { getSecurityColor } from '../utils/securityColors';
 | 
			
		||||
 | 
			
		||||
interface System {
 | 
			
		||||
interface SolarSystem {
 | 
			
		||||
  solarSystemName: string;
 | 
			
		||||
  x: string;
 | 
			
		||||
  y: string;
 | 
			
		||||
@@ -20,21 +21,15 @@ interface Position {
 | 
			
		||||
  y: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface RegionMapProps {
 | 
			
		||||
  regionName: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fetchRegionData = async (regionName: string): Promise<System[]> => {
 | 
			
		||||
  // Decode the region name to handle spaces and special characters
 | 
			
		||||
  const decodedRegionName = decodeURIComponent(regionName);
 | 
			
		||||
  const response = await fetch(`/${decodedRegionName}.json`);
 | 
			
		||||
const fetchRegionData = async (regionName: string): Promise<SolarSystem[]> => {
 | 
			
		||||
  const response = await fetch(`/${regionName}.json`);
 | 
			
		||||
  if (!response.ok) {
 | 
			
		||||
    throw new Error(`Failed to fetch ${decodedRegionName} data`);
 | 
			
		||||
    throw new Error('Failed to fetch region data');
 | 
			
		||||
  }
 | 
			
		||||
  return response.json();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const RegionMap: React.FC<RegionMapProps> = ({ regionName }) => {
 | 
			
		||||
export const RegionMap: React.FC<{ regionName: string }> = ({ regionName }) => {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const [viewBox, setViewBox] = useState({ x: 0, y: 0, width: 1200, height: 800 });
 | 
			
		||||
  const [isPanning, setIsPanning] = useState(false);
 | 
			
		||||
@@ -42,8 +37,6 @@ export const RegionMap: React.FC<RegionMapProps> = ({ regionName }) => {
 | 
			
		||||
  const [nodePositions, setNodePositions] = useState<Record<string, Position>>({});
 | 
			
		||||
  const svgRef = useRef<SVGSVGElement>(null);
 | 
			
		||||
 | 
			
		||||
  const decodedRegionName = decodeURIComponent(regionName);
 | 
			
		||||
 | 
			
		||||
  const { data: systems, isLoading, error } = useQuery({
 | 
			
		||||
    queryKey: ['region', regionName],
 | 
			
		||||
    queryFn: () => fetchRegionData(regionName),
 | 
			
		||||
@@ -64,13 +57,8 @@ export const RegionMap: React.FC<RegionMapProps> = ({ regionName }) => {
 | 
			
		||||
  }, [systems]);
 | 
			
		||||
 | 
			
		||||
  const handleSystemClick = (systemName: string) => {
 | 
			
		||||
    const encodedSystem = encodeURIComponent(systemName);
 | 
			
		||||
    navigate(`/systems/${encodedSystem}`);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleRegionClick = (regionName: string) => {
 | 
			
		||||
    const encodedRegion = encodeURIComponent(regionName);
 | 
			
		||||
    navigate(`/regions/${encodedRegion}`);
 | 
			
		||||
    console.log(`Clicked system: ${systemName}`);
 | 
			
		||||
    // Future: Navigate to system details
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleMouseDown = useCallback((e: React.MouseEvent) => {
 | 
			
		||||
@@ -116,6 +104,7 @@ export const RegionMap: React.FC<RegionMapProps> = ({ regionName }) => {
 | 
			
		||||
    const mouseX = e.clientX - rect.left;
 | 
			
		||||
    const mouseY = e.clientY - rect.top;
 | 
			
		||||
 | 
			
		||||
    // Reverse scroll direction: positive deltaY zooms out, negative zooms in
 | 
			
		||||
    const scale = e.deltaY > 0 ? 1.1 : 0.9;
 | 
			
		||||
    const newWidth = viewBox.width * scale;
 | 
			
		||||
    const newHeight = viewBox.height * scale;
 | 
			
		||||
@@ -124,7 +113,7 @@ export const RegionMap: React.FC<RegionMapProps> = ({ regionName }) => {
 | 
			
		||||
    const mouseYInSVG = viewBox.y + (mouseY / rect.height) * viewBox.height;
 | 
			
		||||
 | 
			
		||||
    const newX = mouseXInSVG - (mouseX / rect.width) * newWidth;
 | 
			
		||||
    const newY = mouseYInSVG - (mouseY / rect.height) * newHeight;
 | 
			
		||||
    const newY = mouseYInSVG - (mouseY / rect.height) * newWidth;
 | 
			
		||||
 | 
			
		||||
    setViewBox({
 | 
			
		||||
      x: newX,
 | 
			
		||||
@@ -134,58 +123,52 @@ export const RegionMap: React.FC<RegionMapProps> = ({ regionName }) => {
 | 
			
		||||
    });
 | 
			
		||||
  }, [viewBox]);
 | 
			
		||||
 | 
			
		||||
  // Get system names that exist in current region
 | 
			
		||||
  const systemNamesInRegion = new Set(systems?.map(s => s.solarSystemName) || []);
 | 
			
		||||
 | 
			
		||||
  // Find cross-region connections and extract region names
 | 
			
		||||
  const crossRegionConnections = new Set<string>();
 | 
			
		||||
  systems?.forEach(system => {
 | 
			
		||||
    system.connectedSystems?.forEach(connectedSystem => {
 | 
			
		||||
      if (!systemNamesInRegion.has(connectedSystem)) {
 | 
			
		||||
        // This is a cross-region connection - we'll need to determine the region
 | 
			
		||||
        // For now, we'll just store the system name and handle it in rendering
 | 
			
		||||
        crossRegionConnections.add(connectedSystem);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (isLoading) {
 | 
			
		||||
    return (
 | 
			
		||||
      <div className="w-full h-screen bg-gradient-to-br from-slate-900 via-cyan-900 to-slate-900 flex items-center justify-center">
 | 
			
		||||
        <div className="text-white text-xl">Loading {decodedRegionName} data...</div>
 | 
			
		||||
      <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-white text-xl">Loading {regionName} data...</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (error) {
 | 
			
		||||
    return (
 | 
			
		||||
      <div className="w-full h-screen bg-gradient-to-br from-slate-900 via-cyan-900 to-slate-900 flex items-center justify-center">
 | 
			
		||||
        <div className="text-red-400 text-xl">Error loading {decodedRegionName} data</div>
 | 
			
		||||
      <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">Error Loading Region</h1>
 | 
			
		||||
          <p className="text-red-400 mb-6">Failed to load data for {regionName}</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 (
 | 
			
		||||
    <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="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">
 | 
			
		||||
        <div className="flex items-center gap-4 mb-6">
 | 
			
		||||
        <div className="flex items-center justify-between mb-8">
 | 
			
		||||
          <div>
 | 
			
		||||
            <h1 className="text-4xl font-bold text-white mb-2">{regionName}</h1>
 | 
			
		||||
            <p className="text-purple-200">Solar systems in this region</p>
 | 
			
		||||
          </div>
 | 
			
		||||
          <Button 
 | 
			
		||||
            variant="outline" 
 | 
			
		||||
            onClick={() => navigate('/')}
 | 
			
		||||
            className="bg-black/20 border-cyan-500/30 text-cyan-200 hover:bg-cyan-500/20"
 | 
			
		||||
            className="bg-purple-600 hover:bg-purple-700"
 | 
			
		||||
          >
 | 
			
		||||
            <ArrowLeft className="w-4 h-4 mr-2" />
 | 
			
		||||
            Back to Galaxy
 | 
			
		||||
          </Button>
 | 
			
		||||
          <div>
 | 
			
		||||
            <h1 className="text-4xl font-bold text-white">{decodedRegionName} 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">
 | 
			
		||||
        <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%"
 | 
			
		||||
@@ -212,15 +195,23 @@ export const RegionMap: React.FC<RegionMapProps> = ({ regionName }) => {
 | 
			
		||||
            {systems?.map((system) =>
 | 
			
		||||
              system.connectedSystems?.map((connectedSystem) => {
 | 
			
		||||
                const fromPos = nodePositions[system.solarSystemName];
 | 
			
		||||
                // Only render connection if target system is in current region
 | 
			
		||||
                const toPos = nodePositions[connectedSystem];
 | 
			
		||||
                
 | 
			
		||||
                // If connected system is not in current region, skip the connection
 | 
			
		||||
                if (!fromPos || !toPos) return null;
 | 
			
		||||
                
 | 
			
		||||
                // Get colors for both systems
 | 
			
		||||
                const fromColor = getSecurityColor(system.security);
 | 
			
		||||
                const toSystem = systems.find(s => s.solarSystemName === connectedSystem);
 | 
			
		||||
                const toColor = toSystem ? getSecurityColor(toSystem.security) : fromColor;
 | 
			
		||||
                
 | 
			
		||||
                return (
 | 
			
		||||
                  <Connection
 | 
			
		||||
                    key={`${system.solarSystemName}-${connectedSystem}`}
 | 
			
		||||
                    from={fromPos}
 | 
			
		||||
                    to={toPos}
 | 
			
		||||
                    fromColor={fromColor}
 | 
			
		||||
                    toColor={toColor}
 | 
			
		||||
                  />
 | 
			
		||||
                );
 | 
			
		||||
              })
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user