aboutsummaryrefslogtreecommitdiffstats
path: root/src/app/game
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2026-06-02 02:12:57 -0700
committerPinapelz <yukais@pinapelz.com>2026-06-02 02:13:10 -0700
commit0335b0ad81169232a3dbb1be1341fdcfce548645 (patch)
tree910593fa5e072ea77f594b6f10ddd96e49452446 /src/app/game
parent0d35e75edbc75f186e4a1ed52fbc3549ee9f5cd6 (diff)
migrate to pocketbase backend + auth/login
Diffstat (limited to 'src/app/game')
-rw-r--r--src/app/game/[slug]/game.stat.ts (renamed from src/app/game/game.stat.ts)0
-rw-r--r--src/app/game/[slug]/game.utils.ts (renamed from src/app/game/game.utils.ts)0
-rw-r--r--src/app/game/[slug]/page.styles.ts (renamed from src/app/game/page.styles.ts)13
-rw-r--r--src/app/game/[slug]/page.tsx (renamed from src/app/game/page.tsx)188
4 files changed, 88 insertions, 113 deletions
diff --git a/src/app/game/game.stat.ts b/src/app/game/[slug]/game.stat.ts
index 43136e6..43136e6 100644
--- a/src/app/game/game.stat.ts
+++ b/src/app/game/[slug]/game.stat.ts
diff --git a/src/app/game/game.utils.ts b/src/app/game/[slug]/game.utils.ts
index b2037e5..b2037e5 100644
--- a/src/app/game/game.utils.ts
+++ b/src/app/game/[slug]/game.utils.ts
diff --git a/src/app/game/page.styles.ts b/src/app/game/[slug]/page.styles.ts
index d410339..1b5880b 100644
--- a/src/app/game/page.styles.ts
+++ b/src/app/game/[slug]/page.styles.ts
@@ -221,7 +221,6 @@ export const LineTimingBar = styled.div`
width: 100%;
height: 3px;
background: rgba(255, 255, 255, 0.12);
- border-radius: 2px;
overflow: hidden;
`;
@@ -232,7 +231,6 @@ export const LineTimingFill = styled.div.attrs<{ $pct: number }>((props) => ({
}))<{ $pct: number }>`
height: 100%;
width: 100%;
- border-radius: 2px;
background: #7c3aed;
transform-origin: left;
will-change: transform;
@@ -262,7 +260,6 @@ export const CharBox = styled.span<{
font-weight: 700;
font-family: "Inter", "Segoe UI", "Helvetica Neue", Arial, sans-serif;
padding: 0 3px;
- border-radius: 4px;
transition: all 0.08s ease;
${({ $state }) => {
@@ -333,7 +330,6 @@ export const GameFooter = styled.footer`
export const ControlBtn = styled.button`
width: 40px;
height: 40px;
- border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.20);
background: rgba(255, 255, 255, 0.08);
color: #ffffff;
@@ -354,7 +350,6 @@ export const ProgressWrap = styled.div`
flex: 1;
height: 6px;
background: rgba(255, 255, 255, 0.12);
- border-radius: 3px;
overflow: hidden;
cursor: pointer;
`;
@@ -366,7 +361,6 @@ export const ProgressFill = styled.div.attrs<{ $pct: number }>((props) => ({
}))<{ $pct: number }>`
height: 100%;
background: #7c3aed;
- border-radius: 3px;
transition: width 0.3s linear;
`;
@@ -437,7 +431,6 @@ export const PreviewWrap = styled.div`
export const PreviewBtn = styled.button`
width: 100%;
padding: 10px 16px;
- border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.08);
color: #ffffff;
@@ -486,7 +479,6 @@ export const SongArtistText = styled.p`
export const StartBtn = styled.button`
padding: 14px 40px;
- border-radius: 12px;
background: #7c3aed;
color: #ffffff;
font-size: 18px;
@@ -521,7 +513,6 @@ export const CodeInputRow = styled.div`
export const CodeInputField = styled.input`
flex: 1;
padding: 8px 12px;
- border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.06);
color: #ffffff;
@@ -540,7 +531,6 @@ export const CodeInputField = styled.input`
export const CodeLoadBtn = styled.button`
padding: 8px 16px;
- border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.08);
color: #ffffff;
@@ -604,7 +594,6 @@ export const StatsGrid = styled.div`
export const StatBlock = styled.div`
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
- border-radius: 12px;
padding: 16px;
display: flex;
flex-direction: column;
@@ -631,7 +620,6 @@ export const ActionRow = styled.div`
export const PlayAgainBtn = styled.button`
padding: 10px 28px;
- border-radius: 12px;
background: #7c3aed;
color: #ffffff;
font-size: 15px;
@@ -648,7 +636,6 @@ export const PlayAgainBtn = styled.button`
export const HomeBtn = styled.button`
padding: 10px 28px;
- border-radius: 12px;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.20);
color: #ffffff;
diff --git a/src/app/game/page.tsx b/src/app/game/[slug]/page.tsx
index bce01b3..9d449ce 100644
--- a/src/app/game/page.tsx
+++ b/src/app/game/[slug]/page.tsx
@@ -9,7 +9,8 @@ import {
useState,
Suspense,
} from "react";
-import { useSearchParams, useRouter } from "next/navigation";
+import { useRouter, useParams } from "next/navigation";
+import pb from "../../lib/pocketbase";
import Link from "next/link";
import { FaRedo } from "react-icons/fa";
import { MdLibraryMusic } from "react-icons/md";
@@ -60,10 +61,6 @@ import {
SongTitleText,
SongArtistText,
StartBtn,
- CodeSection,
- CodeInputRow,
- CodeInputField,
- CodeLoadBtn,
ResultsOverlay,
ResultsCard,
ResultsTitle,
@@ -92,7 +89,8 @@ const BACKGROUND_OPACITY_KEY = "lrcType.backgroundOpacity";
const AUDIO_VOLUME_KEY = "lrcType.audioVolume";
function GameInner() {
- const searchParams = useSearchParams();
+ const params = useParams<{ slug: string }>();
+ const slug = params?.slug ?? "";
const router = useRouter();
useEffect(() => {
@@ -121,32 +119,30 @@ function GameInner() {
const [offset, setOffset] = useState(0);
const [loadingLrc, setLoadingLrc] = useState(false);
- const [codeInput, setCodeInput] = useState("");
const [wrongChar, setWrongChar] = useState(false);
const [clearShowing, setClearShowing] = useState(false);
const [comboAnimKey, setComboAnimKey] = useState(0);
const [countdown, setCountdown] = useState(0);
- const [backgroundOpacity, setBackgroundOpacity] = useState(() => {
- if (typeof window === "undefined") return 0;
- const stored = localStorage.getItem(BACKGROUND_OPACITY_KEY);
- if (stored === null) return 0;
- const parsed = Number(stored);
- if (!Number.isFinite(parsed)) return 0;
- return Math.min(100, Math.max(0, parsed));
- });
- const [audioVolume, setAudioVolume] = useState(() => {
- if (typeof window === "undefined") return 100;
- const stored = localStorage.getItem(AUDIO_VOLUME_KEY);
- if (stored === null) return 100;
- const parsed = Number(stored);
- if (!Number.isFinite(parsed)) return 100;
- return Math.min(100, Math.max(0, parsed));
- });
+ const [backgroundOpacity, setBackgroundOpacity] = useState(0);
+ const [audioVolume, setAudioVolume] = useState(100);
const [isPreviewPlaying, setIsPreviewPlaying] = useState(false);
const [skipBacking, setSkipBacking] = useState(false);
const isVideo = useMemo(() => isVideoUrl(audioUrl), [audioUrl]);
-
+ useEffect(() => {
+ const storedOpacity = localStorage.getItem(BACKGROUND_OPACITY_KEY);
+ if (storedOpacity !== null) {
+ const parsed = Number(storedOpacity);
+ if (Number.isFinite(parsed))
+ setBackgroundOpacity(Math.min(100, Math.max(0, parsed)));
+ }
+ const storedVolume = localStorage.getItem(AUDIO_VOLUME_KEY);
+ if (storedVolume !== null) {
+ const parsed = Number(storedVolume);
+ if (Number.isFinite(parsed))
+ setAudioVolume(Math.min(100, Math.max(0, parsed)));
+ }
+ }, []);
useEffect(() => {
localStorage.setItem(BACKGROUND_OPACITY_KEY, String(backgroundOpacity));
@@ -194,7 +190,7 @@ function GameInner() {
const gameLines = useMemo(
() => parseLrcLines(lrcContent, { skipBacking }),
- [lrcContent, skipBacking]
+ [lrcContent, skipBacking],
);
const isReady = !loadingLrc && !!lrcContent && !!audioUrl;
@@ -213,7 +209,9 @@ function GameInner() {
const gRef = useRef(g);
const currentLineContent =
- g.displayedLineIdx >= 0 ? gameLines[g.displayedLineIdx]?.content ?? "" : "";
+ g.displayedLineIdx >= 0
+ ? (gameLines[g.displayedLineIdx]?.content ?? "")
+ : "";
useEffect(() => {
charRefs.current = [];
@@ -317,7 +315,10 @@ function GameInner() {
}
const mediaCurrentMs = currentMs - offsetRef.current;
- const pct = Math.min(100, Math.max(0, (mediaCurrentMs / firstMediaMs) * 100));
+ const pct = Math.min(
+ 100,
+ Math.max(0, (mediaCurrentMs / firstMediaMs) * 100),
+ );
return { pct, remainingMs };
}, [gameLines, currentMs, offset]);
@@ -423,7 +424,7 @@ function GameInner() {
setLoadingLrc(false);
});
}
- if (typeof data.file1 === "string") setAudioUrl(data.file1);
+ if (typeof data.media === "string") setAudioUrl(data.media);
if (typeof data.offset === "number") setOffset(data.offset);
if (typeof data.offset === "string" && data.offset.trim() !== "")
setOffset(Number(data.offset));
@@ -436,13 +437,25 @@ function GameInner() {
}, []);
useEffect(() => {
- const code = searchParams.get("code");
- if (!code) return;
- try {
- const json = atob(code);
- const data = JSON.parse(json) as Record<string, unknown>;
- loadData(data);
- } catch {}
+ if (!slug) return;
+ pb.collection("charts")
+ .getOne(slug)
+ .then((record) => {
+ loadData({
+ media: (record as Record<string, unknown>).media,
+ lrc: (record as Record<string, unknown>).lrc,
+ offset: (record as Record<string, unknown>).offset,
+ title: (record as Record<string, unknown>).title,
+ artist: (record as Record<string, unknown>).artist,
+ });
+ })
+ .catch(() => {
+ try {
+ const json = atob(slug);
+ const data = JSON.parse(json) as Record<string, unknown>;
+ loadData(data);
+ } catch {}
+ });
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handlePreviewToggle = useCallback(() => {
@@ -452,9 +465,12 @@ function GameInner() {
if (media.paused) {
void media.play().catch(() => {
- toast.error("Unable to start preview. Try interacting with the page again.", {
- theme: "dark",
- });
+ toast.error(
+ "Unable to start preview. Try interacting with the page again.",
+ {
+ theme: "dark",
+ },
+ );
});
return;
}
@@ -521,21 +537,6 @@ function GameInner() {
setProgressPct(0);
}, [isVideo]);
- const handleLoadCode = useCallback(() => {
- if (!codeInput.trim()) return;
- try {
- const json = atob(codeInput.trim());
- const data = JSON.parse(json) as Record<string, unknown>;
- loadData(data);
- handleRestart();
- toast.success("Song loaded!", { theme: "dark" });
- } catch {
- toast.error("Invalid code. Please check and try again.", {
- theme: "dark",
- });
- }
- }, [codeInput, loadData, handleRestart]);
-
const handleKeyPress = useCallback(
(char: string) => {
if (phaseRef.current !== "playing") return;
@@ -575,7 +576,10 @@ function GameInner() {
if (intermissionRemaining > 5000) {
e.preventDefault();
const targetMs = firstMs - 3000;
- media.currentTime = Math.max(0, (targetMs - offsetRef.current) / 1000);
+ media.currentTime = Math.max(
+ 0,
+ (targetMs - offsetRef.current) / 1000,
+ );
setCurrentMs(media.currentTime * 1000 + offsetRef.current);
return;
}
@@ -621,18 +625,17 @@ function GameInner() {
}}
>
<MdLibraryMusic style={{ fontSize: 20, color: "#a78bfa" }} />
- LRC-Type
+ TypingMIXX
</Link>
-
<Link
- href="/create"
+ href="/"
style={{
fontSize: 13,
color: "rgba(255,255,255,0.6)",
textDecoration: "none",
}}
>
- Create
+ Home
</Link>
</div>
</GameNavbar>
@@ -643,8 +646,10 @@ function GameInner() {
<StartCard>
{!isReady ? (
<>
- <SongTitleText>LRC-Type</SongTitleText>
- <SongArtistText>Enter a game code to begin!</SongArtistText>
+ <SongTitleText>Loading...</SongTitleText>
+ <SongArtistText>
+ Please wait while we load the chart
+ </SongArtistText>
</>
) : (
<>
@@ -677,21 +682,6 @@ function GameInner() {
/>
</OpacityControl>
- <PreviewWrap>
- <PreviewBtn
- onClick={handlePreviewToggle}
- disabled={!audioUrl}
- suppressHydrationWarning
- >
- {isPreviewPlaying ? "⏸ Pause Preview" : "▶ Preview Audio"}
- </PreviewBtn>
- <PreviewHint>
- {audioUrl
- ? "Use preview to test your volume before starting."
- : "Load a chart to enable audio preview."}
- </PreviewHint>
- </PreviewWrap>
-
{isVideo && (
<OpacityControl>
<OpacityLabel>
@@ -709,27 +699,20 @@ function GameInner() {
/>
</OpacityControl>
)}
- <CodeSection>
- <div
- style={{
- fontSize: 11,
- color: "rgba(255,255,255,0.3)",
- letterSpacing: 1,
- textTransform: "uppercase",
- }}
+ <PreviewWrap>
+ <PreviewBtn
+ onClick={handlePreviewToggle}
+ disabled={!audioUrl}
+ suppressHydrationWarning
>
- Load a chart
- </div>
- <CodeInputRow>
- <CodeInputField
- placeholder="Enter a LRC-Type code..."
- value={codeInput}
- onChange={(e) => setCodeInput(e.target.value)}
- onKeyDown={(e) => e.key === "Enter" && handleLoadCode()}
- />
- <CodeLoadBtn onClick={handleLoadCode}>Load</CodeLoadBtn>
- </CodeInputRow>
- </CodeSection>
+ {isPreviewPlaying ? "⏸ Pause Preview" : "▶ Preview Audio"}
+ </PreviewBtn>
+ <PreviewHint>
+ {audioUrl
+ ? "Use preview to test your volume before starting."
+ : "Load a chart to enable audio preview."}
+ </PreviewHint>
+ </PreviewWrap>
</StartCard>
</StartOverlay>
)}
@@ -812,7 +795,7 @@ function GameInner() {
<UpcomingText>
{gameLines[0] && gameLines[0].content.trim() === ""
? "[INTERMISSION]"
- : gameLines[0]?.content ?? ""}
+ : (gameLines[0]?.content ?? "")}
</UpcomingText>
</UpcomingWrap>
<CurrentWrap style={{ position: "relative" }}>
@@ -820,7 +803,11 @@ function GameInner() {
<LineTimingMeta>
Time to first line:{" "}
<LineTimingValue>
- {Math.max(0, intermissionData.remainingMs / 1000).toFixed(1)}s
+ {Math.max(
+ 0,
+ intermissionData.remainingMs / 1000,
+ ).toFixed(1)}
+ s
</LineTimingValue>
</LineTimingMeta>
</LineTimingRow>
@@ -832,7 +819,8 @@ function GameInner() {
textAlign: "center",
}}
>
- {intermissionData.remainingMs > 5000 && "Press Space to skip long intermissions"}
+ {intermissionData.remainingMs > 5000 &&
+ "Press Space to skip long intermissions"}
</div>
<LineTimingBar>
<LineTimingFill $pct={intermissionData.pct} />
@@ -850,7 +838,7 @@ function GameInner() {
{gameLines[g.displayedLineIdx + 1] &&
gameLines[g.displayedLineIdx + 1].content.trim() === ""
? "[INTERMISSION]"
- : gameLines[g.displayedLineIdx + 1]?.content ?? ""}
+ : (gameLines[g.displayedLineIdx + 1]?.content ?? "")}
</UpcomingText>
</UpcomingWrap>
<CurrentWrap style={{ position: "relative" }}>
@@ -867,7 +855,7 @@ function GameInner() {
<LineTimingValue>
{calculateCPSNeeded(
gameLines[g.displayedLineIdx].content,
- currentLineTime / 1000
+ currentLineTime / 1000,
).toFixed(1)}
</LineTimingValue>
</LineTimingMeta>
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage