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:
parent
183d662bb9
commit
de7b547349
20 changed files with 1040 additions and 193 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { PlayerList } from '../sidebar/PlayerList';
|
||||
import { SortButtons, type SortKey } from '../sidebar/SortButtons';
|
||||
import type { TelemetrySnapshot, VitalsMessage, ServerHealth } from '../../types';
|
||||
|
|
@ -12,10 +12,15 @@ interface Props {
|
|||
getColor: (name: string) => string;
|
||||
onSelectPlayer: (name: string) => void;
|
||||
onViewToggle: () => void;
|
||||
showHeatmap: boolean;
|
||||
showPortals: boolean;
|
||||
onToggleHeatmap: (v: boolean) => void;
|
||||
onTogglePortals: (v: boolean) => void;
|
||||
}
|
||||
|
||||
export const Sidebar: React.FC<Props> = ({
|
||||
players, vitals, serverHealth, totalRares, totalKills, getColor, onSelectPlayer, onViewToggle,
|
||||
showHeatmap, showPortals, onToggleHeatmap, onTogglePortals,
|
||||
}) => {
|
||||
const [sortKey, setSortKey] = useState<SortKey>('name');
|
||||
const [filter, setFilter] = useState('');
|
||||
|
|
@ -46,27 +51,35 @@ export const Sidebar: React.FC<Props> = ({
|
|||
|
||||
return (
|
||||
<div className="ml-sidebar">
|
||||
{/* Header */}
|
||||
<div className="ml-sidebar-header">
|
||||
<span className="ml-sidebar-title">Active Mosswart Enjoyers ({players.length})</span>
|
||||
<button className="ml-view-toggle" onClick={onViewToggle}>Dashboard</button>
|
||||
</div>
|
||||
|
||||
{/* Server status */}
|
||||
<div className="ml-server-status">
|
||||
<span className={`ml-status-dot ${isOnline ? 'online' : 'offline'}`} />
|
||||
<span className="ml-status-text">Coldeve</span>
|
||||
{serverHealth?.latency_ms != null && <span className="ml-status-latency">{serverHealth.latency_ms}ms</span>}
|
||||
</div>
|
||||
|
||||
{/* Aggregate counters */}
|
||||
<div className="ml-counters">
|
||||
<div className="ml-counter rares"><span className="ml-counter-val">{totalRares}</span><span className="ml-counter-lbl">Rares</span></div>
|
||||
<div className={`ml-counter kph ${serverKph > 5000 ? 'ultra' : ''}`}><span className="ml-counter-val">{serverKph.toLocaleString()}</span><span className="ml-counter-lbl">Server KPH</span></div>
|
||||
<div className="ml-counter kills"><span className="ml-counter-val">{totalKills.toLocaleString()}</span><span className="ml-counter-lbl">Kills</span></div>
|
||||
</div>
|
||||
|
||||
{/* Sort + filter */}
|
||||
{/* Map toggles */}
|
||||
<div className="ml-toggles">
|
||||
<label className="ml-toggle-label">
|
||||
<input type="checkbox" checked={showHeatmap} onChange={e => onToggleHeatmap(e.target.checked)} />
|
||||
<span>Spawn Heatmap</span>
|
||||
</label>
|
||||
<label className="ml-toggle-label">
|
||||
<input type="checkbox" checked={showPortals} onChange={e => onTogglePortals(e.target.checked)} />
|
||||
<span>Portals</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<SortButtons value={sortKey} onChange={setSortKey} />
|
||||
<input
|
||||
className="ml-filter"
|
||||
|
|
@ -76,7 +89,6 @@ export const Sidebar: React.FC<Props> = ({
|
|||
onChange={e => setFilter(e.target.value)}
|
||||
/>
|
||||
|
||||
{/* Player list */}
|
||||
<PlayerList
|
||||
players={sorted}
|
||||
vitals={vitals}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue