diff options
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/DailyPage.tsx | 2 | ||||
| -rw-r--r-- | src/pages/LandingPage.tsx | 125 | ||||
| -rw-r--r-- | src/pages/MVPage.tsx | 76 | ||||
| -rw-r--r-- | src/pages/UnlimitedPage.tsx | 2 |
4 files changed, 172 insertions, 33 deletions
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 ( <main> <Header openInfoPopUp={openInfoPopUp} /> - {isInfoPopUpOpen && <InfoPopUp onClose={closeInfoPopUp} />} + {isInfoPopUpOpen && <InfoPopUp onClose={closeInfoPopUp} gameMode="daily"/>} <Styled.Container> <Game guesses={guesses} diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index e1d29cd..40e818a 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -1,92 +1,155 @@ -import React from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import { appName } from "../constants"; +const getColor = (variant?: "green" | "purple" | "cyan") => { + 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 ( <Container> <Title>{appName}</Title> - <Subtitle>Choose a game mode</Subtitle> - <ButtonGroup> - <ModeButton onClick={() => navigate("/daily")}> - Daily - <ModeDescription>One song per day</ModeDescription> - </ModeButton> - <ModeButton variant="purple" onClick={() => navigate("/unlimited")}> - Unlimited - <ModeDescription>Endless songs, no limits</ModeDescription> - </ModeButton> - </ButtonGroup> + <Subtitle>a kpop music guessing game</Subtitle> + <ModeGroups> + <div> + <GroupLabel>Song Guessing</GroupLabel> + <ButtonGroup> + <ModeButton onClick={() => navigate("/daily")}> + Daily + <ModeDescription>One song per day</ModeDescription> + </ModeButton> + <ModeButton variant="purple" onClick={() => navigate("/unlimited")}> + Unlimited + <ModeDescription>Endless songs, no limits</ModeDescription> + </ModeButton> + </ButtonGroup> + </div> + <div> + <GroupLabel>Music Video Guessing</GroupLabel> + <ButtonGroup> + <ModeButton variant="cyan" onClick={() => navigate("/mv")}> + Daily MV + <ModeDescription>Guess the MV from frames</ModeDescription> + </ModeButton> + </ButtonGroup> + </div> + </ModeGroups> </Container> ); } 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<DailySolution | null>(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<boolean>(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 ( + <main> + <Header openInfoPopUp={openInfoPopUp} /> + {isInfoPopUpOpen && <InfoPopUp onClose={closeInfoPopUp} gameMode="dailyMV" />} + <Styled.Container> + <Game + guesses={guesses} + didGuess={didGuess} + todaysSolution={todaysSolution.song} + dailyDate={todaysSolution.date} + currentTry={currentTry} + setSelectedSong={setSelectedSong} + skip={skip} + guess={guess} + mode="dailyMV" + isSubmitting={isSubmitting} + /> + </Styled.Container> + <Footer /> + </main> + ); +} 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 ( <main> <Header openInfoPopUp={openInfoPopUp} /> - {isInfoPopUpOpen && <InfoPopUp onClose={closeInfoPopUp} />} + {isInfoPopUpOpen && <InfoPopUp onClose={closeInfoPopUp} gameMode="unlimited" />} <Styled.Container> <Game guesses={guesses} |
