feat(v2): Phase 1 — map-first layout matching v1

Rebuilds the v1 map-centric experience in React:

Layout:
- 400px sidebar on left, interactive map on right (flex, 100vh)
- Exact same proportions and dark theme as v1

Sidebar (top→bottom):
- Header with active player count + Dashboard toggle button
- Server status dot (Coldeve online/offline with pulse)
- Aggregate counters: Rares (gold), Server KPH (blue glow), Kills (red)
- 6 sort buttons (Name, KPH, S.Kills, S.Rares, T.Kills, KPR)
- Player name filter
- Scrollable player list with per-row:
  - Name + coordinates
  - HP/Stamina/Mana vital bars (red/orange/blue gradients)
  - Session kills, total kills, KPH
  - Session rares, total rares, VTank meta state pill
  - Online time, deaths, prismatic tapers
  - Color-coded left border per player

Map:
- dereth.png with CSS transform pan (drag) + zoom (wheel, 1.1x factor, max 20x)
- Player dots (6px circles, color-matched to sidebar)
- Hover tooltip (name, coords, kph, kills)
- World coordinate display at cursor position
- Fit-to-window on first load

View toggle: Map View ↔ Dashboard with localStorage persistence.
All v1 CSS ported under ml-* prefix, scoped via map-layout.css.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 15:38:14 +02:00
parent 3791c01bf3
commit 2c4b8d3afb
16 changed files with 995 additions and 151 deletions

View file

@ -1,3 +1,4 @@
import { useState } from 'react';
import { Layout } from './components/Layout';
import { GlobalStats } from './components/GlobalStats';
import { CharacterGrid } from './components/CharacterGrid';
@ -6,44 +7,43 @@ import { CombatTab } from './components/tabs/CombatTab';
import { RaresTab } from './components/tabs/RaresTab';
import { MapTab } from './components/tabs/MapTab';
import { InventoryTab } from './components/tabs/InventoryTab';
import { MapLayout } from './components/map/MapLayout';
import { useLiveData } from './hooks/useLiveData';
import './styles/global.css';
import './styles/map-layout.css';
type ViewMode = 'map' | 'dashboard';
export default function App() {
const { characters, serverHealth, totalRares, totalKills, recentRares } = useLiveData();
const [viewMode, setViewMode] = useState<ViewMode>(
() => (localStorage.getItem('v2-view') as ViewMode) || 'map'
);
const data = useLiveData();
const toggleView = () => {
const next = viewMode === 'map' ? 'dashboard' : 'map';
setViewMode(next);
localStorage.setItem('v2-view', next);
};
if (viewMode === 'map') {
return <MapLayout data={data} onViewToggle={toggleView} />;
}
const tabs = [
{
id: 'combat',
label: 'Combat',
content: <CombatTab characters={characters} />,
},
{
id: 'rares',
label: 'Rares',
content: <RaresTab characters={characters} totalRares={totalRares} totalKills={totalKills} recentRares={recentRares} />,
},
{
id: 'map',
label: 'Map',
content: <MapTab characters={characters} />,
},
{
id: 'inventory',
label: 'Inventory',
content: <InventoryTab />,
},
{ id: 'combat', label: 'Combat', content: <CombatTab characters={data.characters} /> },
{ id: 'rares', label: 'Rares', content: <RaresTab characters={data.characters} totalRares={data.totalRares} totalKills={data.totalKills} recentRares={data.recentRares} /> },
{ id: 'map', label: 'Map', content: <MapTab characters={data.characters} /> },
{ id: 'inventory', label: 'Inventory', content: <InventoryTab /> },
];
return (
<Layout>
<GlobalStats
activeChars={characters.size}
totalKills={totalKills}
totalRares={totalRares}
serverHealth={serverHealth}
/>
<CharacterGrid characters={characters} />
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 8 }}>
<button onClick={toggleView} className="tab-btn">Map View</button>
</div>
<GlobalStats activeChars={data.characters.size} totalKills={data.totalKills} totalRares={data.totalRares} serverHealth={data.serverHealth} />
<CharacterGrid characters={data.characters} />
<TabContainer tabs={tabs} />
</Layout>
);