restructure website
This commit is contained in:
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Pathway Gothic One', sans-serif;
|
font-family: 'Pathway Gothic One', sans-serif;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width:50px;
|
width:50px;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import PlayerCell from "./fantasy/PlayerCell";
|
|
||||||
import { type NFLAPIData, type NFLMatchupState } from "../utils/types";
|
import { type NFLAPIData, type NFLMatchupState } from "../utils/types";
|
||||||
|
import { TeamRow } from "./fantasy/TeamRow";
|
||||||
|
|
||||||
export default function FantasyStatus() {
|
export default function FantasyStatus() {
|
||||||
const [personalRoster, setPersonalRoster] = useState<NFLMatchupState | null>(
|
const [personalRoster, setPersonalRoster] = useState<NFLMatchupState | null>(
|
||||||
@@ -35,58 +35,18 @@ export default function FantasyStatus() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border border-white border-solid rounded-lg w-full sm:w-[500px] mb-4 p-2">
|
<div className="border border-white border-solid rounded-lg w-full sm:w-[500px] p-2">
|
||||||
<div className="mb-4 space-y-4">
|
<div className="mb-4 space-y-4">
|
||||||
<a
|
<a
|
||||||
href="https://sleeper.com/leagues/1263620000418119680/matchup"
|
href="https://sleeper.com/leagues/1263620000418119680/matchup"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="underline"
|
className="underline"
|
||||||
>
|
>
|
||||||
<h1 className="font-semibold text-2xl mb-2">Fantasy</h1>
|
<h1 className="font-semibold text-2xl mb-2">fantasy</h1>
|
||||||
</a>
|
</a>
|
||||||
<div className="flex flex-wrap mb-2">
|
|
||||||
<img
|
|
||||||
width="30"
|
|
||||||
className="rounded-lg"
|
|
||||||
src={personalRoster?.owner.metadata.avatar}
|
|
||||||
/>
|
|
||||||
<h1 className="flex justify-center items-center mx-2 font-semibold">
|
|
||||||
{personalRoster?.owner.metadata.team_name} |{" "}
|
|
||||||
{personalRoster?.points}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2 space-x-2 whitespace-nowrap overflow-x-scroll">
|
{personalRoster && <TeamRow roster={personalRoster} />}
|
||||||
{personalRoster?.starters.map((v, k) => {
|
{opposingRoster && <TeamRow roster={opposingRoster} />}
|
||||||
return (
|
|
||||||
<div className="inline-block" key={k}>
|
|
||||||
<PlayerCell player={v} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-wrap mb-2">
|
|
||||||
<img
|
|
||||||
width="30"
|
|
||||||
className="rounded-lg"
|
|
||||||
src={opposingRoster?.owner.metadata.avatar}
|
|
||||||
/>
|
|
||||||
<h1 className="flex justify-center items-center mx-2 font-semibold">
|
|
||||||
{opposingRoster?.owner.metadata.team_name} |{" "}
|
|
||||||
{opposingRoster?.points}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2 space-x-2 whitespace-nowrap overflow-x-scroll">
|
|
||||||
{opposingRoster?.starters.map((v, k) => {
|
|
||||||
return (
|
|
||||||
<div className="inline-block" key={k}>
|
|
||||||
<PlayerCell player={v} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+3
-175
@@ -1,181 +1,9 @@
|
|||||||
import { useState, useEffect, type ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { Flashlight, FlashlightOff } from "lucide-react";
|
|
||||||
import SplatterImage from "../assets/splatter.png";
|
|
||||||
|
|
||||||
export default function Layout({ children }: { children: ReactNode }) {
|
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 (
|
return (
|
||||||
<div
|
<div className="bg-black text-white min-h-screen flex flex-col items-center mx-auto select-none">
|
||||||
className="relative h-screen w-full overflow-hidden bg-black text-white"
|
<div className="w-full sm:w-[500px] relative">{children}</div>
|
||||||
onMouseMove={handleMouseMove}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
onTouchStart={handleTouchStart}
|
|
||||||
onTouchMove={handleTouchMove}
|
|
||||||
onTouchEnd={handleTouchEnd}
|
|
||||||
>
|
|
||||||
<div className="min-h-screen flex flex-col justify-center items-center mx-auto select-none">
|
|
||||||
<div className="border border-white border-solid p-2 rounded-lg w-full sm:w-[500px] mb-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div
|
|
||||||
className="ml-2"
|
|
||||||
onClick={() => setIsFlashlightEnabled(!isFlashlightEnabled)}
|
|
||||||
>
|
|
||||||
{isFlashlightEnabled ? <Flashlight /> : <FlashlightOff />}
|
|
||||||
</div>
|
|
||||||
<h1 className="text-2xl font-bold">welcome !</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isFlashlightEnabled && (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={`absolute inset-0 bg-black pointer-events-none transition-opacity duration-300 ${
|
|
||||||
isActive ? "opacity-95" : "opacity-0"
|
|
||||||
}`}
|
|
||||||
style={{
|
|
||||||
maskImage: `radial-gradient(circle ${isTouchDevice ? "12rem" : "8rem"} at ${pointerPosition.x}px ${pointerPosition.y}px, transparent 0%, black 100%)`,
|
|
||||||
WebkitMaskImage: `radial-gradient(circle ${isTouchDevice ? "12rem" : "8rem"} at ${pointerPosition.x}px ${pointerPosition.y}px, transparent 0%, black 100%)`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 pointer-events-none z-20"
|
|
||||||
style={{
|
|
||||||
maskImage: `radial-gradient(circle ${isTouchDevice ? "12rem" : "8rem"} at ${pointerPosition.x}px ${pointerPosition.y}px, black 0%, transparent 100%)`,
|
|
||||||
WebkitMaskImage: `radial-gradient(circle ${isTouchDevice ? "12rem" : "8rem"} at ${pointerPosition.x}px ${pointerPosition.y}px, black 0%, transparent 100%)`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={SplatterImage}
|
|
||||||
className="absolute bottom-0 select-none"
|
|
||||||
draggable={false}
|
|
||||||
alt="Cordyceps"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
src={SplatterImage}
|
|
||||||
className="absolute top-0 right-0 select-none"
|
|
||||||
draggable={false}
|
|
||||||
alt="Cordyceps"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<audio loop autoPlay={true} src={"/dexter-theme.mp3"} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import ScrollingText from "./ScrollingText";
|
||||||
import type { LastFMMusicObject } from "../utils/types";
|
import type { LastFMMusicObject } from "../utils/types";
|
||||||
|
|
||||||
export default function MusicComponent() {
|
export default function MusicComponent() {
|
||||||
@@ -30,7 +31,7 @@ export default function MusicComponent() {
|
|||||||
<div className="flex align-middle">
|
<div className="flex align-middle">
|
||||||
<img width="50" src={music.images[1]?.url} />
|
<img width="50" src={music.images[1]?.url} />
|
||||||
<div className="pl-2 max-w-25">
|
<div className="pl-2 max-w-25">
|
||||||
<h1 className="truncate">{music.title}</h1>
|
<ScrollingText size="24" speed={0.1} text={music.title} />
|
||||||
<p className="truncate">{music.artist}</p>
|
<p className="truncate">{music.artist}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
export default function Overlay({
|
||||||
|
children,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={onClose}
|
||||||
|
className="absolute bg-black/50 min-h-screen w-full z-1 overflow-hidden flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<div className="max-w-fit max-h-fit">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default function Picture({
|
||||||
|
image,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
image: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={image}
|
||||||
|
className="inline w-[200px] max-h-[100px] object-cover"
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
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",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
string: "i wrote a song for you",
|
|
||||||
url: "https://open.spotify.com/track/744zwLqGKbOZW3RnxkRfHE?si=4d2230964fcc4f2f",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
string: "oh madeline, how you been?",
|
|
||||||
url: "https://open.spotify.com/track/2DDo2QqElDO9PgOHeGtQiy?si=0fdcbaeb64694249",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
string: "she makes this country boy feel like a king",
|
|
||||||
url: "https://open.spotify.com/track/091Ibwvg7tTp7eNGLlT7cT?si=67be323416b24729",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
string: "ain't in it for my health",
|
|
||||||
url: "https://open.spotify.com/track/5Mz9T6QGwScq6Yt7DAXxi8?si=f51ff7f76dd24b00",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
type LyricItem = {
|
|
||||||
string: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const RandomLyric: FC<AutoScrollingTextProps> = ({ speed = 0.5 }) => {
|
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [lyric, setLyric] = useState<LyricItem | null>();
|
|
||||||
|
|
||||||
const getRandomLyric = () => {
|
|
||||||
const randomNumber = Math.floor(Math.random() * lyrics.length);
|
|
||||||
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 (
|
|
||||||
<a href={lyric.url} target="_blank">
|
|
||||||
<div
|
|
||||||
ref={scrollContainerRef}
|
|
||||||
className={`w-37 overflow-hidden rounded-lg`}
|
|
||||||
style={{ whiteSpace: "nowrap" }}
|
|
||||||
>
|
|
||||||
<div ref={contentRef} className="inline-block">
|
|
||||||
|
|
||||||
<span className={`text-xl font-medium text-white hover:underline`}>
|
|
||||||
{lyric?.string}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RandomLyric;
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
const ScrollingText = ({
|
||||||
|
speed = 0.5,
|
||||||
|
size = "37",
|
||||||
|
text,
|
||||||
|
}: {
|
||||||
|
speed?: number;
|
||||||
|
size?: string;
|
||||||
|
text: string;
|
||||||
|
}) => {
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
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, text]);
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={scrollContainerRef}
|
||||||
|
className={`w-${size} overflow-hidden rounded-lg whitespace-nowrap`}
|
||||||
|
>
|
||||||
|
<div ref={contentRef} className="inline-block">
|
||||||
|
|
||||||
|
<span className="text-xl font-medium text-white hover:underline">
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScrollingText;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import type { NFLMatchupState } from "../../utils/types";
|
||||||
|
import PlayerCell from "./PlayerCell";
|
||||||
|
|
||||||
|
export function TeamRow({ roster }: { roster: NFLMatchupState }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex flex-wrap mb-2">
|
||||||
|
<img
|
||||||
|
width="30"
|
||||||
|
className="rounded-lg"
|
||||||
|
src={roster?.owner.metadata.avatar}
|
||||||
|
/>
|
||||||
|
<h1 className="flex justify-center items-center mx-2 font-semibold">
|
||||||
|
{roster?.owner.metadata.team_name} | {roster?.points}
|
||||||
|
pt
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 space-x-2 whitespace-nowrap overflow-x-scroll">
|
||||||
|
{roster?.starters.map((v, k) => {
|
||||||
|
return (
|
||||||
|
<div className="inline-block" key={k}>
|
||||||
|
<PlayerCell player={v} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+123
-18
@@ -1,27 +1,124 @@
|
|||||||
import Layout from "../components/Layout";
|
import Layout from "../components/Layout";
|
||||||
import MusicComponent from "../components/MusicComponent";
|
import MusicComponent from "../components/MusicComponent";
|
||||||
import TVComponent from "../components/TVComponent";
|
import TVComponent from "../components/TVComponent";
|
||||||
import RandomLyric from "../components/RandomLyric";
|
|
||||||
import FantasyStatus from "../components/FantasyStatus";
|
import FantasyStatus from "../components/FantasyStatus";
|
||||||
|
import Picture from "../components/Picture";
|
||||||
|
import Overlay from "../components/Overlay";
|
||||||
|
import ScrollingText from "../components/ScrollingText";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
const images = [
|
||||||
|
"https://storage.alexav.gg/content/1f822215-0768-424d-893a-13bcd10aa98b.jpg",
|
||||||
|
"https://storage.alexav.gg/content/de890eaf-fc73-42a4-9fbe-a62313394a4c.jpg",
|
||||||
|
"https://storage.alexav.gg/content/d057af80-3d03-45ca-88b2-680c1e7c63c1.jpg",
|
||||||
|
"https://storage.alexav.gg/content/1d717aab-3058-405a-b04d-324d7ab3e658.jpg",
|
||||||
|
"https://storage.alexav.gg/content/2f36c1dd-eaf8-4fd3-a3cd-1d25fe8f5a07.jpg",
|
||||||
|
];
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
string: "i wrote a song for you",
|
||||||
|
url: "https://open.spotify.com/track/744zwLqGKbOZW3RnxkRfHE?si=4d2230964fcc4f2f",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
string: "oh madeline, how you been?",
|
||||||
|
url: "https://open.spotify.com/track/2DDo2QqElDO9PgOHeGtQiy?si=0fdcbaeb64694249",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
string: "she makes this country boy feel like a king",
|
||||||
|
url: "https://open.spotify.com/track/091Ibwvg7tTp7eNGLlT7cT?si=67be323416b24729",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
string: "ain't in it for my health",
|
||||||
|
url: "https://open.spotify.com/track/5Mz9T6QGwScq6Yt7DAXxi8?si=f51ff7f76dd24b00",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
type LyricItem = {
|
||||||
|
string: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const [selectedImage, setSelectedImage] = useState<string>("");
|
||||||
|
|
||||||
|
const [lyric, setLyric] = useState<LyricItem | null>();
|
||||||
|
const getRandomLyric = () => {
|
||||||
|
const randomNumber = Math.floor(Math.random() * lyrics.length);
|
||||||
|
setLyric(lyrics[randomNumber]);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
getRandomLyric();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<FantasyStatus />
|
{selectedImage !== "" && (
|
||||||
<div className="border border-white border-solid rounded-lg w-full sm:w-[500px] p-2">
|
<Overlay onClose={() => setSelectedImage("")}>
|
||||||
<div className="grid grid-cols-1 xs:grid-cols-2">
|
<img width="400" height="400" src={selectedImage} />
|
||||||
<div className="mx-auto">
|
</Overlay>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="my-2">
|
||||||
|
<div className="border border-white border-solid p-2 rounded-lg mb-4 flex justify-between">
|
||||||
|
<h1 className="text-2xl font-bold">welcome to my site !</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border border-white border-solid rounded-lg p-2">
|
||||||
|
<h1 className="text-2xl font-bold italic">pictures</h1>
|
||||||
|
|
||||||
|
<div className="whitespace-nowrap overflow-x-auto">
|
||||||
|
<div className="my-2 space-x-2">
|
||||||
|
{images.map((v, k) => {
|
||||||
|
return (
|
||||||
|
<Picture
|
||||||
|
key={k}
|
||||||
|
image={v}
|
||||||
|
onClick={() => setSelectedImage(v)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border border-white border-solid rounded-lg p-2 my-4">
|
||||||
|
<div className="flex flex-wrap justify-between p-2">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold italic opacity-95 inline">
|
<h1 className="text-2xl font-bold italic opacity-95">alex, 18</h1>
|
||||||
alex, 18
|
<ScrollingText text={lyric ? lyric.string : ""} />
|
||||||
</h1>
|
|
||||||
<RandomLyric speed={0.2} />
|
|
||||||
<img
|
<img
|
||||||
src="https://storage.alexav.gg/content/yeehaw.jpg"
|
src="https://storage.alexav.gg/content/yeehaw.jpg"
|
||||||
className="rounded-md w-[150px]"
|
className="rounded-md w-[150px] h-[110px] object-cover object-left-top"
|
||||||
alt="Profile"
|
alt="Profile"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<MusicComponent />
|
<MusicComponent />
|
||||||
<a
|
<a
|
||||||
@@ -33,8 +130,7 @@ export default function App() {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div className="mx-auto">
|
|
||||||
<h1 className="font-bold text-2xl italic">what i do</h1>
|
<h1 className="font-bold text-2xl italic">what i do</h1>
|
||||||
<p>
|
<p>
|
||||||
qa @{" "}
|
qa @{" "}
|
||||||
@@ -48,20 +144,29 @@ export default function App() {
|
|||||||
</a>
|
</a>
|
||||||
, game, code
|
, game, code
|
||||||
</p>
|
</p>
|
||||||
|
<h1
|
||||||
|
className="italic opacity-50 font-semibold underline"
|
||||||
|
onClick={() =>
|
||||||
|
navigator.clipboard.writeText("alex@vierfrantz.com")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
alex@vierfrantz.com
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<hr className="my-2" />
|
||||||
|
|
||||||
<h1 className="font-bold text-2xl italic">obsessed with</h1>
|
<h1 className="font-bold text-2xl italic">obsessed with</h1>
|
||||||
<p>dexter, tlou, twd</p>
|
<p>dexter, tlou, twd</p>
|
||||||
|
|
||||||
|
<hr className="my-2" />
|
||||||
|
|
||||||
<h1 className="font-bold text-2xl italic">status</h1>
|
<h1 className="font-bold text-2xl italic">status</h1>
|
||||||
<TVComponent />
|
<TVComponent />
|
||||||
|
|
||||||
<h1 className="font-bold text-2xl italic">reach me</h1>
|
|
||||||
<a href="mailto:alex@vierfrantz.com">
|
|
||||||
<p className="underline">alex@vierfrantz.com</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<FantasyStatus />
|
||||||
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user