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

@ -0,0 +1,54 @@
import React, { useCallback } from 'react';
import { MapTransformProvider } from '../../contexts/MapTransformContext';
import { MapView } from './MapView';
import { Sidebar } from './Sidebar';
import { usePlayerColors } from '../../hooks/usePlayerColors';
import type { DashboardState } from '../../hooks/useLiveData';
interface Props {
data: DashboardState;
onViewToggle: () => void;
}
export const MapLayout: React.FC<Props> = ({ data, onViewToggle }) => {
const getColor = usePlayerColors();
// Build flat players array from characters map
const players = Array.from(data.characters.values())
.filter(c => c.telemetry)
.map(c => c.telemetry!);
// Build vitals map
const vitalsMap = new Map(
Array.from(data.characters.values())
.filter(c => c.vitals)
.map(c => [c.name, c.vitals!])
);
const handleSelectPlayer = useCallback((name: string) => {
// TODO: zoom map to player position
console.log('Select player:', name);
}, []);
return (
<MapTransformProvider>
<div className="ml-layout">
<Sidebar
players={players}
vitals={vitalsMap}
serverHealth={data.serverHealth}
totalRares={data.totalRares}
totalKills={data.totalKills}
getColor={getColor}
onSelectPlayer={handleSelectPlayer}
onViewToggle={onViewToggle}
/>
<MapView
players={players}
getColor={getColor}
onSelectPlayer={handleSelectPlayer}
/>
</div>
</MapTransformProvider>
);
};