From f50ed9de546491440be06fee850b3e151727f662 Mon Sep 17 00:00:00 2001 From: Alex Frantz Date: Sat, 17 May 2025 02:51:49 -0400 Subject: [PATCH] add lyric, game status, split components --- src/App.tsx | 295 +++++++------------------------ src/components/GameComponent.tsx | 62 +++++++ src/components/Layout.tsx | 175 ++++++++++++++++++ src/components/RandomLyric.tsx | 113 ++++++++++++ src/main.tsx | 4 +- 5 files changed, 418 insertions(+), 231 deletions(-) create mode 100644 src/components/GameComponent.tsx create mode 100644 src/components/Layout.tsx create mode 100644 src/components/RandomLyric.tsx diff --git a/src/App.tsx b/src/App.tsx index c174442..e41f4ac 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,243 +1,82 @@ -import React, { useState, useEffect } from "react"; -import { Flashlight, FlashlightOff, Twitter, Github } from "lucide-react"; -import CordycepsImage from "./assets/cordyceps.png"; +import { Twitter, Github } from "lucide-react"; import MusicComponent from "./components/MusicComponent"; import TVComponent from "./components/TVComponent"; +import Layout from "./components/Layout"; +import GameComponent from "./components/GameComponent"; +import RandomLyric from "./components/RandomLyric"; -export default function MouseFlashlight() { - const [pointerPosition, setPointerPosition] = useState({ x: 500, y: 500 }); - const [isActive, setIsActive] = useState(false); - const [isTouchDevice, setIsTouchDevice] = useState(false); - const [isFlashlightEnabled, setIsFlashlightEnabled] = useState(false); - - // Detect touch device on component mount - useEffect(() => { - const isTouchCapable = - "ontouchstart" in window || navigator.maxTouchPoints > 0; - - setIsTouchDevice(isTouchCapable); - }, []); - - // Handle mouse events for desktop - const handleMouseMove = (e: React.MouseEvent) => { - setPointerPosition({ x: e.clientX, y: e.clientY }); - setIsActive(true); - }; - - const handleMouseLeave = () => { - setIsActive(false); - }; - - // Handle touch events for mobile - const handleTouchMove = (e: React.TouchEvent) => { - if (e.touches && e.touches[0]) { - e.preventDefault(); // Prevent scrolling while touching - const touch = e.touches[0]; - setPointerPosition({ x: touch.clientX, y: touch.clientY }); - setIsActive(true); - } - }; - - const handleTouchStart = (e: React.TouchEvent) => { - if (e.touches && e.touches[0]) { - const touch = e.touches[0]; - setPointerPosition({ x: touch.clientX, y: touch.clientY }); - setIsActive(true); - } - }; - - const handleTouchEnd = () => { - // On mobile, we'll keep the last position visible for a moment - setTimeout(() => { - setIsActive(false); - }, 1000); - }; - - useEffect(() => { - // Define window event handlers with correct types - const windowMouseMove = (e: MouseEvent) => { - setPointerPosition({ x: e.clientX, y: e.clientY }); - setIsActive(true); - }; - - const windowMouseLeave = () => { - setIsActive(false); - }; - - const windowTouchStart = (e: TouchEvent) => { - if (e.touches && e.touches[0]) { - const touch = e.touches[0]; - setPointerPosition({ x: touch.clientX, y: touch.clientY }); - setIsActive(true); - } - }; - - const windowTouchMove = (e: TouchEvent) => { - if (e.touches && e.touches[0]) { - e.preventDefault?.(); // Optional chaining in case preventDefault isn't available - const touch = e.touches[0]; - setPointerPosition({ x: touch.clientX, y: touch.clientY }); - setIsActive(true); - } - }; - - const windowTouchEnd = () => { - // On mobile, we'll keep the last position visible for a moment - setTimeout(() => { - setIsActive(false); - }, 1000); - }; - - if (isFlashlightEnabled) { - // Add desktop event listeners - window.addEventListener("mousemove", windowMouseMove); - window.addEventListener("mouseleave", windowMouseLeave); - - // Add mobile event listeners - window.addEventListener("touchstart", windowTouchStart, { - passive: false, - }); - window.addEventListener("touchmove", windowTouchMove, { passive: false }); - window.addEventListener("touchend", windowTouchEnd); - } else { - // Remove desktop event listeners - window.removeEventListener("mousemove", windowMouseMove); - window.removeEventListener("mouseleave", windowMouseLeave); - - // Remove mobile event listeners - window.removeEventListener("touchstart", windowTouchStart); - window.removeEventListener("touchmove", windowTouchMove); - window.removeEventListener("touchend", windowTouchEnd); - } - - // Cleanup when component unmounts - return () => { - window.removeEventListener("mousemove", windowMouseMove); - window.removeEventListener("mouseleave", windowMouseLeave); - window.removeEventListener("touchstart", windowTouchStart); - window.removeEventListener("touchmove", windowTouchMove); - window.removeEventListener("touchend", windowTouchEnd); - }; - }, [isFlashlightEnabled]); - +export default function App() { return ( -
-
-
-
-
setIsFlashlightEnabled(!isFlashlightEnabled)} - > - {isFlashlightEnabled ? : } + +
+
+
+
+

+ alex, 18 +

+ + Profile + +
-

welcome !

-
+
+

what i do

+

+ qa @{" "} + + medal.tv + + , game, code +

-
-
-
-
-

- alex, 18 -

- Profile +

obsessed with

+

tlou, twd, rdr, dexter

- -
-
-
-

what i do

-

- qa @{" "} - - medal.tv - - , game, code -

+

status

+ + -

obsessed with

-

tlou, twd, rdr

- -

watching

- - -

socials

- +

socials

+
- - {isFlashlightEnabled && ( - <> -
-
- Cordyceps -
-
+ ); } diff --git a/src/components/GameComponent.tsx b/src/components/GameComponent.tsx new file mode 100644 index 0000000..6a762f7 --- /dev/null +++ b/src/components/GameComponent.tsx @@ -0,0 +1,62 @@ +import { useState, useEffect } from "react"; + +type GameObject = { + name: string; + location: string; + startedOn: number; + finished: boolean; +}; + +export default function GameComponent() { + const [game, setGame] = useState(null); + const [failedFetch, setFailedFetch] = useState(false); + + useEffect(() => { + async function fetchGame() { + const response = await fetch( + "https://api.alexav.gg/v4/tracker/latestGame" + ); + if (response.ok) { + const data = await response.json(); + if (data.data && data.data.length <= 0) { + setFailedFetch(true); + } else setGame(data.data as GameObject); + } else setFailedFetch(true); + } + + setTimeout(() => { + fetchGame(); + }, 60000); + fetchGame(); + }, []); + + if (game) { + return ( + + ); + } else { + if (failedFetch) { + return ( +
+

failed to get game :(

+
+ ); + } else { + return ( +
+

loading game..

+
+ ); + } + } +} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx new file mode 100644 index 0000000..ec75a59 --- /dev/null +++ b/src/components/Layout.tsx @@ -0,0 +1,175 @@ +import { useState, useEffect, type ReactNode } from "react"; +import { Flashlight, FlashlightOff } from "lucide-react"; +import CordycepsImage from "../assets/cordyceps.png"; + +export default function Layout({ children }: { children: ReactNode }) { + const [pointerPosition, setPointerPosition] = useState({ x: 500, y: 500 }); + const [isActive, setIsActive] = useState(false); + const [isTouchDevice, setIsTouchDevice] = useState(false); + const [isFlashlightEnabled, setIsFlashlightEnabled] = useState(false); + + // Detect touch device on component mount + useEffect(() => { + const isTouchCapable = + "ontouchstart" in window || navigator.maxTouchPoints > 0; + + setIsTouchDevice(isTouchCapable); + }, []); + + // Handle mouse events for desktop + const handleMouseMove = (e: React.MouseEvent) => { + setPointerPosition({ x: e.clientX, y: e.clientY }); + setIsActive(true); + }; + + const handleMouseLeave = () => { + setIsActive(false); + }; + + // Handle touch events for mobile + const handleTouchMove = (e: React.TouchEvent) => { + if (e.touches && e.touches[0]) { + e.preventDefault(); // Prevent scrolling while touching + const touch = e.touches[0]; + setPointerPosition({ x: touch.clientX, y: touch.clientY }); + setIsActive(true); + } + }; + + const handleTouchStart = (e: React.TouchEvent) => { + if (e.touches && e.touches[0]) { + const touch = e.touches[0]; + setPointerPosition({ x: touch.clientX, y: touch.clientY }); + setIsActive(true); + } + }; + + const handleTouchEnd = () => { + // On mobile, we'll keep the last position visible for a moment + setTimeout(() => { + setIsActive(false); + }, 1000); + }; + + useEffect(() => { + // Define window event handlers with correct types + const windowMouseMove = (e: MouseEvent) => { + setPointerPosition({ x: e.clientX, y: e.clientY }); + setIsActive(true); + }; + + const windowMouseLeave = () => { + setIsActive(false); + }; + + const windowTouchStart = (e: TouchEvent) => { + if (e.touches && e.touches[0]) { + const touch = e.touches[0]; + setPointerPosition({ x: touch.clientX, y: touch.clientY }); + setIsActive(true); + } + }; + + const windowTouchMove = (e: TouchEvent) => { + if (e.touches && e.touches[0]) { + e.preventDefault?.(); // Optional chaining in case preventDefault isn't available + const touch = e.touches[0]; + setPointerPosition({ x: touch.clientX, y: touch.clientY }); + setIsActive(true); + } + }; + + const windowTouchEnd = () => { + // On mobile, we'll keep the last position visible for a moment + setTimeout(() => { + setIsActive(false); + }, 1000); + }; + + if (isFlashlightEnabled) { + // Add desktop event listeners + window.addEventListener("mousemove", windowMouseMove); + window.addEventListener("mouseleave", windowMouseLeave); + + // Add mobile event listeners + window.addEventListener("touchstart", windowTouchStart, { + passive: false, + }); + window.addEventListener("touchmove", windowTouchMove, { passive: false }); + window.addEventListener("touchend", windowTouchEnd); + } else { + // Remove desktop event listeners + window.removeEventListener("mousemove", windowMouseMove); + window.removeEventListener("mouseleave", windowMouseLeave); + + // Remove mobile event listeners + window.removeEventListener("touchstart", windowTouchStart); + window.removeEventListener("touchmove", windowTouchMove); + window.removeEventListener("touchend", windowTouchEnd); + } + + // Cleanup when component unmounts + return () => { + window.removeEventListener("mousemove", windowMouseMove); + window.removeEventListener("mouseleave", windowMouseLeave); + window.removeEventListener("touchstart", windowTouchStart); + window.removeEventListener("touchmove", windowTouchMove); + window.removeEventListener("touchend", windowTouchEnd); + }; + }, [isFlashlightEnabled]); + + return ( +
+
+
+
+
setIsFlashlightEnabled(!isFlashlightEnabled)} + > + {isFlashlightEnabled ? : } +
+

welcome !

+
+
+ {children} +
+ + {isFlashlightEnabled && ( + <> +
+
+ Cordyceps +
+
+ ); +} diff --git a/src/components/RandomLyric.tsx b/src/components/RandomLyric.tsx new file mode 100644 index 0000000..dc2bb0e --- /dev/null +++ b/src/components/RandomLyric.tsx @@ -0,0 +1,113 @@ +import { useEffect, useRef, useState, FC } from "react"; + +interface AutoScrollingTextProps { + speed?: number; +} +const lyrics: LyricItem[] = [ + { + string: "and we're laying on the roof of my car", + url: "https://open.spotify.com/track/4Pt2R2EJ3rZBRn2JpH7WnJ?si=7a167c7b22354d24", + }, + { + string: "your funeral was beautiful", + url: "https://open.spotify.com/track/4ZJ4vzLQekI0WntDbanNC7?si=914245f4e48a4c00", + }, + { + string: "she was always the one", + url: "https://open.spotify.com/track/21n769QrJtrmN0pXuBkYOm?si=b4a5e7f627b54fd9", + }, + { + string: "how lucky are we?", + url: "https://open.spotify.com/track/5iJKGpnFfvbjZJeAtwXfCj?si=a0b160c9cead4863", + }, + { + string: "i'm yours to take darlin'", + url: "https://open.spotify.com/track/2nATTe1i7aMzIZ8klEpgQ7?si=1bcd8e6aa9324b81", + }, + { + string: "wet, hot, american nights", + url: "https://open.spotify.com/track/3c9EsIo34kil8Oj1reaozB?si=8c0aaf0fdb7a4e5f", + }, + { + string: "it ain't the whiskey that gets me", + url: "https://open.spotify.com/track/0AsrOS8HktHe9TaB1i9W3v?si=c186a29f4e804d05", + }, +]; + +type LyricItem = { + string: string; + url: string; +}; + +const RandomLyric: FC = ({ speed = 0.5 }) => { + const scrollContainerRef = useRef(null); + const contentRef = useRef(null); + const [lyric, setLyric] = useState(); + + const getRandomLyric = () => { + const randomNumber = Math.floor(Math.random() * lyrics.length); + console.log(randomNumber); + setLyric(lyrics[randomNumber]); + }; + useEffect(() => { + getRandomLyric(); + }, []); + + useEffect(() => { + const scrollContainer = scrollContainerRef.current; + const contentElement = contentRef.current; + + if (!scrollContainer || !contentElement) return; + + // Create a clone of the content element + const clone = contentElement.cloneNode(true) as HTMLDivElement; + scrollContainer.appendChild(clone); + + let animationId: number; + let scrollPosition = 0; + + const animate = () => { + if (!scrollContainer || !contentElement) return; + + // Increment scroll position based on speed + scrollPosition += speed; + + // Reset position when first element is scrolled out of view + if (scrollPosition >= contentElement.offsetWidth) { + scrollPosition = 0; + } + + // Apply scroll position + scrollContainer.scrollLeft = scrollPosition; + + animationId = requestAnimationFrame(animate); + }; + + animationId = requestAnimationFrame(animate); + + return () => { + cancelAnimationFrame(animationId); + }; + }, [speed, lyric]); + + if (lyric) { + return ( + +
+
+    + + {lyric?.string} + +
+
+
+ ); + } +}; + +export default RandomLyric; diff --git a/src/main.tsx b/src/main.tsx index d2eccc8..1ffc8ff 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,6 +2,4 @@ import { createRoot } from "react-dom/client"; import "./assets/main.css"; import App from "./App.tsx"; -createRoot(document.getElementById("root")!).render( - , -); +createRoot(document.getElementById("root")!).render();