more landing page stuff (:
Some checks failed
Deploy Website / docker (ubuntu-latest) (push) Failing after 8s

This commit is contained in:
Rainnny7 2024-10-29 15:50:58 -04:00
parent a47cc3c7d8
commit 8f6c556662
12 changed files with 230 additions and 41 deletions

BIN
bun.lockb

Binary file not shown.

@ -36,7 +36,7 @@
"cross-env": "^7.0.3",
"dexie": "^4.0.8",
"dexie-react-hooks": "^1.1.7",
"framer-motion": "^11.5.4",
"framer-motion": "^11.11.10",
"js-cookie": "^3.0.5",
"ky": "^1.7.2",
"lucide-react": "^0.453.0",

@ -6,9 +6,9 @@ import RealtimeScores from "@/components/home/realtime-scores";
export default async function HomePage() {
return (
<main className="-mt-3 w-screen min-h-screen bg-[#0f0f0f]">
<main className="-mt-3 w-screen min-h-screen bg-gradient-to-b from-[#0f0f0f] via-[#1a1a1a] to-[#0f0f0f]">
<div className="flex flex-col items-center">
<div className="max-w-screen-2xl mt-48 flex flex-col gap-56">
<div className="max-w-screen-2xl mt-48 mb-14 flex flex-col gap-64">
<HeroSection />
<DataCollection />
<Friends />

@ -19,3 +19,10 @@ export function validateUrl(url: string) {
return false;
}
}
export function getRandomInteger(min: number, max: number): number {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}

@ -2,20 +2,22 @@ import { Database } from "lucide-react";
export default function DataCollection() {
return (
<div className="px-5 -mt-32 flex flex-col gap-10 select-none">
<div className="px-5 -mt-40 flex flex-col gap-10 select-none">
{/* Header */}
<div className="flex flex-col gap-2.5">
<div className="flex gap-3.5 items-center">
<Database className="size-7 text-pp" />
<h1 className="text-4xl font-bold text-ssr">Data Collection</h1>
<div className="flex gap-3 items-center text-pp">
<Database className="p-2 size-11 bg-ssr/15 rounded-lg" />
<h1 className="text-3xl sm:text-4xl font-bold">Data Collection</h1>
</div>
<p className="opacity-85">posidonium novum ancillae ius conclusionemque splendide vel.</p>
<p className="max-w-5xl text-sm sm:text-base opacity-85">
posidonium novum ancillae ius conclusionemque splendide vel.
</p>
</div>
{/* Content */}
<div className="max-w-[900px]">
<img
className="w-full h-full rounded-xl border border-ssr/20"
className="w-full h-full rounded-2xl border border-ssr/20"
src="/assets/home/data-collection.png"
alt="Data Collection"
draggable={false}

@ -4,18 +4,20 @@ export default function Friends() {
return (
<div className="px-5 -mt-20 flex flex-col gap-10 items-end select-none">
{/* Header */}
<div className="flex flex-col gap-2.5 items-end">
<div className="flex gap-3.5 items-center">
<Database className="size-7 text-pp" />
<h1 className="text-4xl font-bold text-ssr">Friends</h1>
<div className="flex flex-col gap-2.5 text-right items-end">
<div className="flex flex-row-reverse gap-3 items-center text-purple-600">
<Database className="p-2 size-11 bg-purple-800/15 rounded-lg" />
<h1 className="text-3xl sm:text-4xl font-bold">Friends</h1>
</div>
<p className="opacity-85">posidonium novum ancillae ius conclusionemque splendide vel.</p>
<p className="max-w-5xl text-sm sm:text-base opacity-85">
posidonium novum ancillae ius conclusionemque splendide vel.
</p>
</div>
{/* Content */}
<div className="max-w-[900px]">
<img
className="w-full h-full rounded-xl border border-ssr/20"
className="w-full h-full rounded-2xl border border-ssr/20"
src="/assets/home/friends.png"
alt="Friends"
draggable={false}

@ -1,3 +1,5 @@
"use client";
import AnimatedShinyText from "@/components/ui/animated-shiny-text";
import { ArrowRight, UserSearch } from "lucide-react";
import Link from "next/link";
@ -5,15 +7,23 @@ import { Button } from "@/components/ui/button";
import { SiGithub } from "react-icons/si";
import { BorderBeam } from "@/components/ui/border-beam";
import { Separator } from "@/components/ui/separator";
import { motion } from "framer-motion";
export default function HeroSection() {
return (
<div className="flex flex-col gap-3.5 text-center items-center select-none">
<motion.div
className="flex flex-col gap-3.5 text-center items-center"
initial={{ opacity: 0, y: -40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: "easeOut" }}
>
<Alert />
<Title />
</motion.div>
<Buttons />
<AppPreview />
<Separator className="my-14 w-screen" />
<Separator className="my-12 w-screen" />
</div>
);
}
@ -50,7 +60,12 @@ function Title() {
function Buttons() {
return (
<div className="mt-4 flex gap-4">
<motion.div
className="mt-4 flex flex-col xs:flex-row gap-4 items-center"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.35, duration: 0.7, ease: "easeOut" }}
>
<Link href="https://discord.gg/kmNfWGA4A8" target="_blank">
<Button className="max-w-52 flex gap-2.5 bg-pp hover:bg-pp/85 text-white text-base">
<UserSearch className="size-6" />
@ -64,19 +79,24 @@ function Buttons() {
<span>Join our Discord</span>
</Button>
</Link>
</div>
</motion.div>
);
}
function AppPreview() {
return (
<div className="mx-5 my-24 relative max-w-[1280px] shadow-[0_3rem_20rem_-15px_rgba(15,15,15,0.6)] shadow-pp/50 rounded-xl overflow-hidden">
<motion.div
className="mx-5 my-20 relative max-w-[1280px] shadow-[0_3rem_20rem_-15px_rgba(15,15,15,0.6)] shadow-pp/50 rounded-2xl overflow-hidden"
initial={{ opacity: 0, y: -35 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.45, duration: 0.7, ease: "easeOut" }}
>
<BorderBeam colorFrom="#6773ff" colorTo="#4858ff" />
<img
className="w-full h-full border-4 border-pp/20 rounded-xl"
className="w-full h-full border-4 border-pp/20 rounded-2xl"
src="/assets/home/app-preview.png"
draggable={false}
/>
</div>
</motion.div>
);
}

@ -1,26 +1,116 @@
import { Database } from "lucide-react";
import { getRandomInteger } from "@/common/utils";
import { GlobeAmericasIcon } from "@heroicons/react/24/solid";
import { getDifficulty } from "@/common/song-utils";
import { AnimatedList } from "@/components/ui/animated-list";
type ScoreProps = {
songArt: string;
songName: string;
songAuthor: string;
setBy: string;
};
let scores: ScoreProps[] = [
{
songArt: "https://cdn.scoresaber.com/covers/B1D3FA6D5305837DF59B5E629A412DEBC68BBB46.png",
songName: "LORELEI",
songAuthor: "Camellia",
setBy: "ImFascinated",
},
{
songArt: "https://cdn.scoresaber.com/covers/7C44CDC1E33E2F5F929867B29CEB3860C3716DDC.png",
songName: "Time files",
songAuthor: "xi",
setBy: "Minion",
},
{
songArt: "https://cdn.scoresaber.com/covers/8E4B7917C01E5987A5B3FF13FAA3CA8F27D21D34.png",
songName: "RATATA",
songAuthor: "Skrillex, Missy Elliot & Mr. Oizo",
setBy: "Minion",
},
{
songArt: "https://cdn.scoresaber.com/covers/98F73BD330852EAAEBDC695140EAC8F2027AEEC8.png",
songName: "Invasion of Amorphous Trepidation",
songAuthor: "Diabolic Phantasma",
setBy: "Bello",
},
{
songArt: "https://cdn.scoresaber.com/covers/666EEAC0F3EEE2278DCB971AC1D27421A0335801.png",
songName: "Yotsuya-san ni Yoroshiku",
songAuthor: "Eight",
setBy: "ACC | NoneTaken",
},
];
scores = Array.from({ length: 32 }, () => scores).flat();
export default function RealtimeScores() {
return (
<div className="px-5 -mt-20 flex flex-col gap-10 items-end select-none">
<div className="px-5 -mt-20 flex flex-row-reverse gap-10 select-none">
{/* Header */}
<div className="flex flex-col gap-2.5 items-end">
<div className="flex gap-3.5 items-center">
<Database className="size-7 text-pp" />
<h1 className="text-4xl font-bold text-ssr">Realtime Scores</h1>
<div className="flex flex-col gap-2.5 text-right items-end">
<div className="flex flex-row-reverse gap-3 items-center text-yellow-400">
<Database className="p-2 size-11 bg-yellow-800/15 rounded-lg" />
<h1 className="text-3xl sm:text-4xl font-bold">Realtime Scores</h1>
</div>
<p className="opacity-85">posidonium novum ancillae ius conclusionemque splendide vel.</p>
<p className="max-w-5xl text-sm sm:text-base opacity-85">
<span className="text-lg font-semibold text-yellow-500">Nec detracto voluptatibus!</span> Vulputate duis
dolorum iuvaret disputationi ceteros te noluisse himenaeos bibendum dolores molestiae lorem elaboraret porro
brute tation simul laudem netus odio has in tibique.
</p>
</div>
{/* Content */}
<div className="max-w-[900px]">
<img
className="w-full h-full rounded-xl border border-ssr/20"
src="/assets/home/realtime-scores.png"
alt="Realtime Scores"
draggable={false}
/>
<div className="w-full flex flex-col justify-center items-center">
<AnimatedList className="h-96 divide-y divide-muted overflow-hidden">
{scores.map((score, index) => (
<Score key={index} {...score} />
))}
</AnimatedList>
</div>
</div>
);
}
function Score({ songArt, songName, songAuthor, setBy }: ScoreProps) {
return (
<figure className="w-[32rem] py-2 flex flex-col text-sm">
{/* Set By */}
<span>
Set by <span className="text-ssr">{setBy}</span>
</span>
{/* Score */}
<div className="py-3 flex gap-5 items-center">
{/* Position & Time */}
<div className="w-24 flex flex-col gap-1 items-center">
<div className="flex gap-2 items-center">
<GlobeAmericasIcon className="size-5" />
<span className="text-ssr">#{getRandomInteger(1, 900)}</span>
</div>
<span>just now</span>
</div>
{/* Song Art & Difficulty */}
<div className="relative">
<img className="size-16 rounded-md" src={songArt} alt={`Song art for ${songName} by ${songAuthor}`} />
<div
className="absolute inset-x-0 bottom-0 py-px flex justify-center text-xs rounded-t-lg"
style={{
backgroundColor: getDifficulty("Hard").color + "f0", // Transparency value (in hex 0-255)
}}
>
Hard
</div>
</div>
{/* Song Name & Author */}
<div className="flex flex-col gap-1">
<h1 className="text-ssr">{songName}</h1>
<p className="opacity-75">{songAuthor}</p>
</div>
</div>
</figure>
);
}

@ -10,11 +10,13 @@ export default async function SiteStats() {
<div className="px-5 -mt-20 flex flex-col gap-10 select-none">
{/* Header */}
<div className="flex flex-col gap-2.5">
<div className="flex gap-3.5 items-center">
<Database className="size-7 text-pp" />
<h1 className="text-4xl font-bold text-ssr">Site Statistics</h1>
<div className="flex gap-3 items-center text-orange-600">
<Database className="p-2 size-11 bg-orange-800/15 rounded-lg" />
<h1 className="text-3xl sm:text-4xl font-bold">Site Statistics</h1>
</div>
<p className="opacity-85">posidonium novum ancillae ius conclusionemque splendide vel.</p>
<p className="max-w-5xl text-sm sm:text-base opacity-85">
posidonium novum ancillae ius conclusionemque splendide vel.
</p>
</div>
{/* Content */}

@ -13,7 +13,7 @@ export default function Statistic({ icon, title, value }: Statistic) {
return (
<div className="flex flex-col gap-2 text-center items-center text-lg">
{icon}
<h1 className="font-semibold text-ssr">{title}</h1>
<h1 className="font-semibold text-orange-400/85">{title}</h1>
<span>
<CountUp end={value} duration={1.2} enableScrollSpy scrollSpyOnce />
</span>

@ -0,0 +1,59 @@
"use client";
import React, { ReactElement, useEffect, useMemo, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
export interface AnimatedListProps {
className?: string;
children: React.ReactNode;
delay?: number;
}
export const AnimatedList = React.memo(
({ className, children, delay = 1000 }: AnimatedListProps) => {
const [index, setIndex] = useState(0);
const childrenArray = React.Children.toArray(children);
useEffect(() => {
const interval = setInterval(() => {
setIndex((prevIndex) => (prevIndex + 1) % childrenArray.length);
}, delay);
return () => clearInterval(interval);
}, [childrenArray.length, delay]);
const itemsToShow = useMemo(
() => childrenArray.slice(0, index + 1).reverse(),
[index, childrenArray],
);
return (
<div className={`flex flex-col items-center gap-4 ${className}`}>
<AnimatePresence>
{itemsToShow.map((item) => (
<AnimatedListItem key={(item as ReactElement).key}>
{item}
</AnimatedListItem>
))}
</AnimatePresence>
</div>
);
},
);
AnimatedList.displayName = "AnimatedList";
export function AnimatedListItem({ children }: { children: React.ReactNode }) {
const animations = {
initial: { scale: 0, opacity: 0 },
animate: { scale: 1, opacity: 1, originY: 0 },
exit: { scale: 0, opacity: 0 },
transition: { type: "spring", stiffness: 350, damping: 40 },
};
return (
<motion.div {...animations} layout className="mx-auto w-full">
{children}
</motion.div>
);
}

@ -1,5 +1,8 @@
import type { Config } from "tailwindcss";
// eslint-disable-next-line @typescript-eslint/no-require-imports
const defaultTheme = require("tailwindcss/defaultTheme");
const config: Config = {
darkMode: ["class"],
content: [
@ -8,6 +11,10 @@ const config: Config = {
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
screens: {
xs: "475px",
...defaultTheme.screens,
},
extend: {
colors: {
pp: "#4858ff",