diff --git a/src/components/frontend/eod/EodArena.tsx b/src/components/frontend/eod/EodArena.tsx index 25a93e6..fc6d026 100644 --- a/src/components/frontend/eod/EodArena.tsx +++ b/src/components/frontend/eod/EodArena.tsx @@ -4,8 +4,10 @@ import { useCallback, useEffect, useState } from "react"; import Link from "next/link"; import { Bomb } from "lucide-react"; import type { EodDifficulty, EodStats } from "@/app/(frontend)/eod/actions"; +import { isEodUnlocked } from "@/lib/custom-minigames/eod"; import { useRealtimePresence } from "@/hooks/useRealtimePresence"; import { useSidebar } from "@/components/ui/sidebar"; +import { UnlockNotice } from "@/components/frontend/minigames/UnlockNotice"; import { EodLeaderboard } from "./EodLeaderboard"; import { MinesweeperBoard } from "./MinesweeperBoard"; @@ -25,6 +27,17 @@ export function EodArena({ setPlaying(phase === "playing"); }, []); + const customUnlocked = isEodUnlocked({ + bestTrainingTime: initialStats.bestTrainingTime, + bestPerimeterTime: initialStats.bestPerimeterTime, + bestSupplyRoadTime: initialStats.bestSupplyRoadTime, + }); + const clearedSectors = [ + initialStats.bestTrainingTime, + initialStats.bestPerimeterTime, + initialStats.bestSupplyRoadTime, + ].filter((time) => time > 0).length; + useEffect(() => { if (playing && isMobile) setOpenMobile(false); }, [isMobile, playing, setOpenMobile]); @@ -38,6 +51,13 @@ export function EodArena({ Custom minefields + {!customUnlocked && ( + + )} setRefreshKey((key) => key + 1)} diff --git a/src/components/frontend/minigames/UnlockNotice.tsx b/src/components/frontend/minigames/UnlockNotice.tsx new file mode 100644 index 0000000..32baaa1 --- /dev/null +++ b/src/components/frontend/minigames/UnlockNotice.tsx @@ -0,0 +1,26 @@ +import { Lock } from "lucide-react"; + +/** + * Locked-state hint shown beside a minigame's custom-content link: tells the + * player exactly which milestones unlock authoring user-generated content and + * how close they are. Unlocked players see nothing (the link speaks for itself). + */ +export function UnlockNotice({ + title, + requirement, + progress, +}: { + title: string; + requirement: string; + progress: string; +}) { + return ( +

+ + + Author your own {title} once {requirement}{" "} + ({progress}) + +

+ ); +} diff --git a/src/components/frontend/radio/RadioArena.tsx b/src/components/frontend/radio/RadioArena.tsx index fa8ca0e..22110da 100644 --- a/src/components/frontend/radio/RadioArena.tsx +++ b/src/components/frontend/radio/RadioArena.tsx @@ -4,7 +4,13 @@ import { useCallback, useEffect, useState } from "react"; import Link from "next/link"; import { Radio } from "lucide-react"; import type { RadioStats } from "@/app/(frontend)/radio/actions"; +import { + isRadioUnlocked, + SPRINT_ACCURACY_REQUIREMENT, + WATCH_ACCURACY_REQUIREMENT, +} from "@/lib/custom-minigames/radio"; import { useRealtimePresence } from "@/hooks/useRealtimePresence"; +import { UnlockNotice } from "@/components/frontend/minigames/UnlockNotice"; import { RadioLeaderboard } from "./RadioLeaderboard"; import { RadioTraffic } from "./RadioTraffic"; @@ -24,6 +30,13 @@ export function RadioArena({ setPlaying(phase === "playing"); }, []); + const customUnlocked = isRadioUnlocked({ + sprintBestAccuracy: initialStats.sprintBestAccuracy, + watchBestAccuracy: initialStats.watchBestAccuracy, + }); + const sprintPercent = Math.round(initialStats.sprintBestAccuracy * 100); + const watchPercent = Math.round(initialStats.watchBestAccuracy * 100); + useEffect(() => { const savedMode = window.localStorage.getItem("radio-traffic-mode"); if (savedMode === "sprint" || savedMode === "watch") setMode(savedMode); @@ -43,6 +56,13 @@ export function RadioArena({ Custom radio tests + {!customUnlocked && ( + + )}