diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5c9433a8..4e3ff977 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,23 +1,22 @@ import { MapLayout } from './components/map/MapLayout'; import { PlayerDashboardFullPage } from './components/PlayerDashboardFullPage'; +import { MidsummerProvider } from './hooks/useMidsummer'; import { useLiveData } from './hooks/useLiveData'; import './styles/map-layout.css'; +import './styles/midsummer.css'; /** * Single SPA entry. Branches on `?view=` query param: * /?view=dashboard → fullscreen PlayerDashboardFullPage (new-tab target) * / → default map + sidebar layout - * - * We don't pull in react-router for one extra view — when a third view - * appears, swap this for proper routing. */ export default function App() { const view = new URLSearchParams(window.location.search).get('view'); - if (view === 'dashboard') { - return ; - } - // Default: full app with map + sidebar. - return ; + return ( + + {view === 'dashboard' ? : } + + ); } /** Default map-and-sidebar layout. Split out so the dashboard tab doesn't diff --git a/frontend/src/hooks/useMidsummer.tsx b/frontend/src/hooks/useMidsummer.tsx new file mode 100644 index 00000000..2847de50 --- /dev/null +++ b/frontend/src/hooks/useMidsummer.tsx @@ -0,0 +1,45 @@ +import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; + +const KEY = 'mo-midsummer'; +const SOUND_KEY = 'mo-midsummer-sound'; + +interface MidsummerCtx { + enabled: boolean; + toggle: () => void; + soundOn: boolean; + toggleSound: () => void; +} + +const Ctx = createContext(null); + +export const MidsummerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + // Default ON: only the literal "off" disables it. + const [enabled, setEnabled] = useState(() => localStorage.getItem(KEY) !== 'off'); + const [soundOn, setSoundOn] = useState(() => localStorage.getItem(SOUND_KEY) !== 'off'); + + useEffect(() => { + const el = document.documentElement; + if (enabled) el.setAttribute('data-midsummer', ''); + else el.removeAttribute('data-midsummer'); + localStorage.setItem(KEY, enabled ? 'on' : 'off'); + }, [enabled]); + + useEffect(() => { + localStorage.setItem(SOUND_KEY, soundOn ? 'on' : 'off'); + }, [soundOn]); + + const toggle = useCallback(() => setEnabled(e => !e), []); + const toggleSound = useCallback(() => setSoundOn(s => !s), []); + + return ( + + {children} + + ); +}; + +export function useMidsummer(): MidsummerCtx { + const c = useContext(Ctx); + if (!c) throw new Error('useMidsummer must be used within MidsummerProvider'); + return c; +} diff --git a/frontend/src/styles/midsummer.css b/frontend/src/styles/midsummer.css new file mode 100644 index 00000000..434133ec --- /dev/null +++ b/frontend/src/styles/midsummer.css @@ -0,0 +1,2 @@ +/* Midsummer "Små grodorna" theme overlay. All rules scoped under + :root[data-midsummer] so they only apply when the theme is on. */