feat(v2): Phases 2-6 — trails, heatmap, portals, windows, effects

Phase 2 — Map overlays:
- TrailsSVG: SVG polylines per character from /trails, polled 2s
- HeatmapCanvas: canvas radial gradients from /spawns/heatmap
- PortalMarkers: emoji markers from /portals
- Sidebar toggles for heatmap and portals

Phase 3 — Draggable windows:
- WindowManagerContext: z-index stack for open windows
- DraggableWindow: generic shell with drag-header, close btn, z-stack
- ChatWindow: color-coded messages + input form (1000 msg buffer)
- CharacterWindow: combat stats with monster damage table
- InventoryWindow: item table with material/set/AL/dmg/workmanship
- WindowRenderer: reads context, renders all open windows
- Action buttons (Chat/Stats/Inv/Char/Radar) now open windows

Phase 4 — Window types share same DraggableWindow shell with
character-specific content. Combat stats and inventory via API.

Phase 5 — Effects:
- RareNotification: slide-in/slide-out banner with gold border
- Fireworks: 30-particle explosion with CSS custom property animation
- Notification queue with 6s display + 0.5s exit animation

Phase 6 — Polish:
- Window header uses modern blue gradient (not solid purple)
- Chat uses monospace font
- All overlay layers properly stacked (heatmap → trails → dots → portals)
- Mobile: sidebar stacks above map at 768px breakpoint
- Chat messages tracked per-character in useLiveData

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 15:58:58 +02:00
parent 183d662bb9
commit de7b547349
20 changed files with 1040 additions and 193 deletions

View file

@ -1,7 +1,10 @@
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { MapTransformProvider } from '../../contexts/MapTransformContext';
import { WindowManagerProvider } from '../../contexts/WindowManagerContext';
import { MapView } from './MapView';
import { Sidebar } from './Sidebar';
import { WindowRenderer } from '../windows/WindowRenderer';
import { RareNotification } from '../effects/RareNotification';
import { usePlayerColors } from '../../hooks/usePlayerColors';
import type { DashboardState } from '../../hooks/useLiveData';
@ -12,43 +15,52 @@ interface Props {
export const MapLayout: React.FC<Props> = ({ data, onViewToggle }) => {
const getColor = usePlayerColors();
const [showHeatmap, setShowHeatmap] = useState(false);
const [showPortals, setShowPortals] = useState(false);
// 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) => {
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>
<WindowManagerProvider>
<div className="ml-layout">
<Sidebar
players={players}
vitals={vitalsMap}
serverHealth={data.serverHealth}
totalRares={data.totalRares}
totalKills={data.totalKills}
getColor={getColor}
onSelectPlayer={handleSelectPlayer}
onViewToggle={onViewToggle}
showHeatmap={showHeatmap}
showPortals={showPortals}
onToggleHeatmap={setShowHeatmap}
onTogglePortals={setShowPortals}
/>
<MapView
players={players}
getColor={getColor}
onSelectPlayer={handleSelectPlayer}
showHeatmap={showHeatmap}
showPortals={showPortals}
/>
<WindowRenderer characters={data.characters} chatMessages={data.chatMessages} />
<RareNotification recentRares={data.recentRares} />
</div>
</WindowManagerProvider>
</MapTransformProvider>
);
};