From 00d476a0e51629f06407aed035fbc001d3f6b114 Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Fri, 26 Jun 2026 21:55:46 -0700 Subject: initial guess MV mode --- src/pages/DailyPage.tsx | 2 +- src/pages/LandingPage.tsx | 125 +++++++++++++++++++++++++++++++++----------- src/pages/MVPage.tsx | 76 +++++++++++++++++++++++++++ src/pages/UnlimitedPage.tsx | 2 +- 4 files changed, 172 insertions(+), 33 deletions(-) create mode 100644 src/pages/MVPage.tsx (limited to 'src/pages') diff --git a/src/pages/DailyPage.tsx b/src/pages/DailyPage.tsx index 17efaeb..5b39701 100644 --- a/src/pages/DailyPage.tsx +++ b/src/pages/DailyPage.tsx @@ -99,7 +99,7 @@ export function DailyPage() { return (
- {isInfoPopUpOpen && } + {isInfoPopUpOpen && } { + switch (variant) { + case "purple": + return "var(--cl-purple, #a855f7)"; + case "cyan": + return "var(--cl-cyan-6)"; + default: + return "var(--cl-green-6)"; + } +}; + const Container = styled.div` display: flex; flex-direction: column; align-items: center; justify-content: center; - min-height: 60vh; - gap: 24px; + min-height: 70vh; + padding: 24px; + gap: 14px; `; const Title = styled.h1` font-family: "Roboto Mono", monospace; - font-size: 2rem; + font-size: clamp(1.5rem, 3vw, 2rem); font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--cl-white); + text-align: center; + margin: 0; `; const Subtitle = styled.p` font-family: "Roboto Mono", monospace; font-size: 0.9rem; color: var(--cl-gray-6); - margin: 0; + margin: 4px 0 0; + text-align: center; +`; + +const ModeGroups = styled.div` + display: flex; + flex-direction: column; + gap: 22px; + width: 100%; + max-width: 600px; `; const ButtonGroup = styled.div` display: flex; gap: 16px; - margin-top: 16px; + justify-content: center; + flex-wrap: wrap; @media (max-width: 480px) { flex-direction: column; - width: 100%; - padding: 0 24px; + align-items: stretch; } `; -const ModeButton = styled.button<{ variant?: "green" | "purple" }>` +const GroupLabel = styled.span` + display: block; + font-family: "Roboto Mono", monospace; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--cl-gray-6); + text-align: center; + margin-bottom: 10px; +`; + +const ModeButton = styled.button<{ variant?: "green" | "purple" | "cyan" }>` font-family: "Roboto Mono", monospace; font-size: 1rem; font-weight: 600; - padding: 16px 32px; - border: 2px solid - ${({ variant }) => - variant === "purple" ? "var(--cl-purple, #a855f7)" : "var(--cl-green-6)"}; + padding: 16px 28px; + min-width: 180px; + + border: 2px solid ${({ variant }) => getColor(variant)}; background: transparent; - color: ${({ variant }) => - variant === "purple" ? "var(--cl-purple, #a855f7)" : "var(--cl-green-6)"}; + color: ${({ variant }) => getColor(variant)}; + cursor: pointer; transition: all 0.15s ease; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + &:hover { - background: ${({ variant }) => - variant === "purple" ? "var(--cl-purple, #a855f7)" : "var(--cl-green-6)"}; + background: ${({ variant }) => getColor(variant)}; color: var(--cl-black, #000); } + + &:focus-visible { + outline: 3px solid ${({ variant }) => getColor(variant)}; + outline-offset: 3px; + } + + @media (max-width: 480px) { + width: 100%; + min-width: unset; + } `; const ModeDescription = styled.span` - display: block; - font-size: 0.7rem; + font-size: 0.72rem; font-weight: 400; color: var(--cl-gray-6); - margin-top: 4px; + margin-top: 6px; `; + export function LandingPage() { const navigate = useNavigate(); return ( {appName} - Choose a game mode - - navigate("/daily")}> - Daily - One song per day - - navigate("/unlimited")}> - Unlimited - Endless songs, no limits - - + a kpop music guessing game + +
+ Song Guessing + + navigate("/daily")}> + Daily + One song per day + + navigate("/unlimited")}> + Unlimited + Endless songs, no limits + + +
+
+ Music Video Guessing + + navigate("/mv")}> + Daily MV + Guess the MV from frames + + +
+
); } diff --git a/src/pages/MVPage.tsx b/src/pages/MVPage.tsx new file mode 100644 index 0000000..c135918 --- /dev/null +++ b/src/pages/MVPage.tsx @@ -0,0 +1,76 @@ +import React from "react"; + +import { DailySolution, getDailyMVSolution } from "../helpers/fetchSolution"; +import { useGameState } from "../hooks/useGameState"; + +import { Header, InfoPopUp, Game, Footer } from "../components"; + +import * as Styled from "../app.styled"; + +export function MVPage() { + const [todaysSolution, setTodaysSolution] = + React.useState(null); + + const firstRun = localStorage.getItem("firstRun") === null; + + React.useEffect(() => { + getDailyMVSolution().then((solution) => setTodaysSolution(solution)); + }, []); + + const { + guesses, + currentTry, + setSelectedSong, + didGuess, + skip, + guess, + isSubmitting, + } = useGameState({ + solution: todaysSolution?.song ?? null, + persist: true, + sessionDate: todaysSolution?.date, + sessionToken: todaysSolution?.sessionToken, + initialSig: todaysSolution?.initialSig, + mode: "dailyMV", + }); + + const [isInfoPopUpOpen, setIsInfoPopUpOpen] = + React.useState(firstRun); + + const openInfoPopUp = React.useCallback(() => { + setIsInfoPopUpOpen(true); + }, []); + + const closeInfoPopUp = React.useCallback(() => { + if (firstRun) { + localStorage.setItem("firstRun", "false"); + } + setIsInfoPopUpOpen(false); + }, [localStorage.getItem("firstRun")]); + + if (todaysSolution === null) { + return null; + } + + return ( +
+
+ {isInfoPopUpOpen && } + + + +
+
+ ); +} diff --git a/src/pages/UnlimitedPage.tsx b/src/pages/UnlimitedPage.tsx index 761d2b9..23a4daf 100644 --- a/src/pages/UnlimitedPage.tsx +++ b/src/pages/UnlimitedPage.tsx @@ -58,7 +58,7 @@ export function UnlimitedPage() { return (
- {isInfoPopUpOpen && } + {isInfoPopUpOpen && }