aboutsummaryrefslogtreecommitdiffstats
path: root/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/DailyPage.tsx2
-rw-r--r--src/pages/LandingPage.tsx125
-rw-r--r--src/pages/MVPage.tsx76
-rw-r--r--src/pages/UnlimitedPage.tsx2
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}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage