MosswartOverlord/frontend/src/components/map/MapLayout.tsx
Erik d86bc48862 feat(midsummer): rain of flowers/frogs/Swedish flags, dots become frogs, drop jingle
Per request: remove the WebAudio jingle (+ its 🔊 toggle and sound state);
replace the one-shot confetti with a continuous rain of 🌼🌸🐸🇸🇪🌿 over the
screen (MidsummerRain, gated by the theme, reduced-motion aware, leak-free);
and replace player-dot markers with frogs themselves (override the inline
dot color/border) instead of a flower-crown on top. Still toggled by the
🐸 Midsommar switch. Includes rebuilt static bundle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-19 09:47:39 +02:00

81 lines
3.1 KiB
TypeScript

import React, { useCallback, useState, useMemo, useEffect } from 'react';
import { apiFetch } from '../../api/client';
import { WindowManagerProvider, useWindowManager } from '../../contexts/WindowManagerContext';
import { MapView } from './MapView';
import { Sidebar } from './Sidebar';
import { WindowRenderer } from '../windows/WindowRenderer';
import { RareNotification } from '../effects/RareNotification';
import { DeathNotification } from '../effects/DeathNotification';
import { usePlayerColors } from '../../hooks/usePlayerColors';
import { MidsummerBanner } from '../midsummer/MidsummerBanner';
import { MidsummerRain } from '../midsummer/MidsummerRain';
import type { DashboardState } from '../../hooks/useLiveData';
interface Props {
data: DashboardState;
}
export const MapLayout: React.FC<Props> = ({ data }) => {
const getColor = usePlayerColors();
const [showHeatmap, setShowHeatmap] = useState(false);
const [showPortals, setShowPortals] = useState(false);
const [selectedPlayer, setSelectedPlayer] = useState<string | null>(null);
const players = useMemo(() =>
Array.from(data.characters.values()).filter(c => c.telemetry).map(c => c.telemetry!),
[data.characters]);
const vitalsMap = useMemo(() =>
new Map(Array.from(data.characters.values()).filter(c => c.vitals).map(c => [c.name, c.vitals!])),
[data.characters]);
const [version, setVersion] = useState('');
useEffect(() => {
// /api-version is the actual route — apiFetch adds /api prefix, so use raw fetch
fetch('/api/api-version', { credentials: 'include' }).then(r => r.json()).then(d => setVersion(d.version ?? '')).catch(() => {});
}, []);
const handleSelectPlayer = useCallback((name: string) => {
setSelectedPlayer(prev => prev === name ? null : name);
}, []);
return (
<WindowManagerProvider>
<div className="ml-layout">
<MidsummerBanner />
<MidsummerRain />
<Sidebar
players={players}
vitals={vitalsMap}
serverHealth={data.serverHealth}
totalRares={data.totalRares}
totalKills={data.totalKills}
getColor={getColor}
onSelectPlayer={handleSelectPlayer}
showHeatmap={showHeatmap}
showPortals={showPortals}
onToggleHeatmap={setShowHeatmap}
onTogglePortals={setShowPortals}
version={version}
selectedPlayer={selectedPlayer}
/>
<MapView
players={players}
getColor={getColor}
onSelectPlayer={handleSelectPlayer}
showHeatmap={showHeatmap}
showPortals={showPortals}
selectedPlayer={selectedPlayer}
/>
<WindowRenderer characters={data.characters} chatMessages={data.chatMessages}
nearbyObjects={data.nearbyObjects} inventoryVersions={data.inventoryVersions}
equipmentCantrips={data.equipmentCantrips} characterStats={data.characterStats}
socket={data.socketRef.current} />
<RareNotification recentRares={data.recentRares} />
<DeathNotification deathAlerts={data.deathAlerts} />
</div>
</WindowManagerProvider>
);
};