feat: new homepage

This commit is contained in:
Khairul Hidayat 2024-01-17 01:55:25 +00:00
parent cd049348bc
commit 2783561cfb
19 changed files with 363 additions and 90 deletions

View File

@ -0,0 +1,43 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const collection = new Collection({
"id": "ogs3cfy8l3jo32k",
"created": "2024-01-17 01:46:30.155Z",
"updated": "2024-01-17 01:46:30.155Z",
"name": "wallpapers",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "kxurmv6q",
"name": "artwork",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "eo6iaxf4pkeqynf",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
});
return Dao(db).saveCollection(collection);
}, (db) => {
const dao = new Dao(db);
const collection = dao.findCollectionByNameOrId("ogs3cfy8l3jo32k");
return dao.deleteCollection(collection);
})

View File

@ -0,0 +1,18 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((db) => {
const dao = new Dao(db)
const collection = dao.findCollectionByNameOrId("ogs3cfy8l3jo32k")
collection.listRule = ""
collection.viewRule = ""
return dao.saveCollection(collection)
}, (db) => {
const dao = new Dao(db)
const collection = dao.findCollectionByNameOrId("ogs3cfy8l3jo32k")
collection.listRule = null
collection.viewRule = null
return dao.saveCollection(collection)
})

View File

@ -14,6 +14,7 @@
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"dayjs": "^1.11.10",
"howler": "^2.2.4", "howler": "^2.2.4",
"lucide-react": "^0.306.0", "lucide-react": "^0.306.0",
"pixi.js": "^7.3.3", "pixi.js": "^7.3.3",

View File

@ -4,14 +4,18 @@ import MainLayout from "./components/layouts/MainLayout";
import ErrorBoundaryPage from "./pages/errors/error-boundary/page"; import ErrorBoundaryPage from "./pages/errors/error-boundary/page";
const HomePage = lazy(() => import("./pages/home/page")); const HomePage = lazy(() => import("./pages/home/page"));
const PatPatPage = lazy(() => import("./pages/pat-pat/page"));
const MyFurinaPage = lazy(() => import("./pages/my-furina/page")); const MyFurinaPage = lazy(() => import("./pages/my-furina/page"));
const ArtworksPage = lazy(() => import("./pages/artworks/page")); const ArtworksPage = lazy(() => import("./pages/artworks/page"));
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
Component: MainLayout,
children: [ children: [
{ index: true, Component: HomePage }, { index: true, Component: HomePage },
{
Component: MainLayout,
children: [
{ path: "/pat-pat", Component: PatPatPage },
{ path: "/toodle", Component: MyFurinaPage }, { path: "/toodle", Component: MyFurinaPage },
{ {
path: "/treasures", path: "/treasures",
@ -28,6 +32,8 @@ const router = createBrowserRouter([
</MainLayout> </MainLayout>
), ),
}, },
],
},
]); ]);
const Router = () => { const Router = () => {

View File

@ -23,7 +23,8 @@ const AppBar = () => {
</Link> </Link>
<Navbar> <Navbar>
<NavbarItem path="/" title="Pet the Furina" /> <NavbarItem path="/" title="Home" />
<NavbarItem path="/pat-pat" title="Pat Furina" />
<NavbarItem path="/treasures" title="Treasures‧₊˚" /> <NavbarItem path="/treasures" title="Treasures‧₊˚" />
<NavbarItem path="/toodle" title="Toodle-oo~" /> <NavbarItem path="/toodle" title="Toodle-oo~" />
</Navbar> </Navbar>
@ -53,7 +54,7 @@ const NavbarItem = ({ path, title, isExact = true }: NavbarItemProps) => {
return ( return (
<Link <Link
to={path} to={path}
className="group flex flex-shrink-0 items-center px-2 md:px-0 md:py-4 first:ml-auto last:mr-auto" className="group flex flex-shrink-0 items-center px-2 md:py-4 first:ml-auto last:mr-auto md:first:ml-0 md:last:mr-0"
> >
<img <img
src={ahogeImg} src={ahogeImg}
@ -66,7 +67,7 @@ const NavbarItem = ({ path, title, isExact = true }: NavbarItemProps) => {
<p <p
className={cn( className={cn(
"text-white ml-2 md:ml-4 md:text-xl py-2 md:py-0 border-b-2 md:border-dotted group-hover:border-primary-500 border-transparent transition-all", "text-white ml-2 md:ml-4 py-2 md:py-0 border-b-2 md:border-dotted group-hover:border-primary-500 border-transparent transition-all",
isActive ? "border-primary-500 md:border-white" : "" isActive ? "border-primary-500 md:border-white" : ""
)} )}
> >

View File

@ -10,7 +10,11 @@ type PageMetadataProps = {
const PageMetadata = (props: PageMetadataProps) => { const PageMetadata = (props: PageMetadataProps) => {
return ( return (
<Helmet> <Helmet>
<title>{[props.title, "Furina.id"].filter((i) => !!i).join(" - ")}</title> <title>
{props.title
? [props.title, "Furina.id"].join(" - ")
: "Welcome to Furina.id"}
</title>
<meta <meta
name="description" name="description"
content={props.description || "Welcome to Furina.id"} content={props.description || "Welcome to Furina.id"}

View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' style='fill:none;' viewBox='0 0 500 501'><path style='fill:#0866FF;' d='M500 250.199C500 112.099 388.1 .199219 250 .199219C111.9 .199219 0 112.099 0 250.199C0 367.399 80.7 465.799 189.6 492.799V326.599H138V250.199H189.6V217.299C189.6 132.199 228.1 92.7992 311.6 92.7992C327.4 92.7992 354.8 95.8992 365.9 98.9992V168.299C360 167.699 349.8 167.399 337 167.399C296 167.399 280.2 182.899 280.2 223.299V250.299H361.9L347.9 326.699H280.3V498.499C404.1 483.499 500 378.099 500 250.199Z'/><path d='M347.9 326.599L361.9 250.199H280.2V223.199C280.2 182.799 296 167.299 337 167.299C349.7 167.299 360 167.599 365.9 168.199V98.9988C354.7 95.8988 327.4 92.7988 311.6 92.7988C228.1 92.7988 189.6 132.199 189.6 217.299V250.199H138V326.599H189.6V492.799C208.9 497.599 229.2 500.199 250 500.199C260.3 500.199 270.4 499.599 280.3 498.399V326.599H347.9Z' style='fill:white;'/></svg>

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,13 @@
import patFurina from "./pat-furina.webp";
import treasures from "./treasures.webp";
import facebook from "./facebook.svg";
import twitter from "./twitter.jpg";
import furinamains from "./furinamains.webp";
export const icons = {
patFurina,
treasures,
facebook,
twitter,
furinamains,
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,92 +1,182 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ import titleImg from "@/assets/images/title-img.svg";
import { useEffect, useRef, useState } from "react";
import styles from "./style.module.css";
import { cn } from "@/utility/utils";
import LoadingPage from "../misc/loading-page";
import PageMetadata from "@/components/containers/PageMetadata"; import PageMetadata from "@/components/containers/PageMetadata";
import Modal from "@/components/ui/Modal"; import { cn } from "@/utility/utils";
import useModal from "@/hooks/useModal"; import dayjs from "dayjs";
import Button from "@/components/ui/Button"; import { ComponentProps, useEffect, useMemo, useState } from "react";
import { Link } from "react-router-dom";
import { icons } from "./icons";
import { useQuery } from "react-query";
import pb from "@/utility/api";
const HomePage = () => { const HomePage = () => {
const appRef = useRef<any>();
const cleanRef = useRef<any>();
const targetRef = useRef<HTMLDivElement>(null);
const [isReady, setReady] = useState(false);
useEffect(() => {
if (!appRef.current) {
appRef.current = true;
const init = async () => {
const { default: game } = await import("./game");
const { app, clean } = await game();
targetRef.current?.appendChild(app.view as never);
appRef.current = app;
cleanRef.current = clean;
setReady(true);
};
init();
}
return () => {
if (cleanRef.current) {
cleanRef.current();
}
};
}, [setReady]);
return ( return (
<div> <div className="h-screen w-full bg-slate-900 overflow-hidden relative">
<PageMetadata <PageMetadata title="" />
title="Pet the Furina" <BackgroundSlideshow />
description="Play pet the furina meme game"
keywords="pet furina, pet the furina, pet the meme, furina pat pat, pat furina"
/>
{!isReady ? <LoadingPage /> : null} <DateTime />
<div
ref={targetRef}
className={cn(
"flex flex-col items-center justify-center",
styles.canvasContainer
)}
/>
<Credits />
</div> </div>
); );
}; };
const Credits = () => { const BackgroundSlideshow = () => {
const modal = useModal(); const { data: wallpapers } = useQuery({
queryKey: ["wallpapers"],
queryFn: async () => {
const items = await pb.collection("wallpapers").getFullList({
sort: "@random",
expand: "artwork",
});
return items.map((item) => {
const artwork = item.expand?.artwork;
return pb.files.getUrl(artwork, artwork?.image);
});
},
});
return ( return (
<div className="container pt-4 pb-16"> <>
<Button onClick={modal.onOpen}>Assets Credits</Button> <img
src={titleImg}
alt="title"
className="h-4 md:h-8 absolute top-6 left-6 md:top-8 md:left-8 lg:left-[5%] lg:top-[5%] z-[5]"
/>
<Modal {...modal} title="Big Thanks to:" size="xl"> {wallpapers && wallpapers?.length > 0 ? (
<pre className="font-sans overflow-x-auto"> <BackgroundImage src={wallpapers[0]} />
{` ) : null}
Furina Stickers: </>
Guido_ (https://risibank.fr/media/297778-genshin-archon-hydro-c6-r5-soutine) );
Coll5 (https://risibank.fr/media/317061-furina-focalor-genshin) };
Music: type BackgroundImageProps = {
Kururin Furina Cover by Ariyutta (https://facebook.com/arbi.yudatama) src: string;
pet the peepo by NitroiF (https://www.youtube.com/shorts/ll2Au3CdV2k) };
Hand Sprite: const BackgroundImage = ({ src }: BackgroundImageProps) => {
@soapmangraylace2752 (https://www.youtube.com/shorts/HEguW7Gmu2w) const [isLoaded, setLoaded] = useState(false);
Fijiwaterhelp (https://jailbreak.fandom.com/wiki/User_blog:Fijiwaterhelp/hand_petting) return (
`.trim()} <>
</pre> <img
</Modal> src={src}
alt="img"
className="hidden"
onLoad={() => setTimeout(() => setLoaded(true), 100)}
/>
<div
className={cn(
"absolute w-[100vw] bg-center bg-cover inset-0 opacity-0 transition-opacity duration-500",
isLoaded ? "opacity-100" : ""
)}
style={{ backgroundImage: `url('${src}')` }}
></div>
</>
);
};
const DateTime = () => {
const [time, setTime] = useState(new Date());
useEffect(() => {
const intv = setInterval(() => {
setTime(new Date());
}, 1000);
return () => {
clearInterval(intv);
};
}, [setTime]);
const message = useMemo(() => {
const hours = time.getHours();
let msg = "Day";
if (hours >= 18 && hours <= 2) {
msg = "Night";
} else if (hours > 2 && hours <= 9) {
msg = "Morning";
} else if (hours > 9 && hours <= 15) {
msg = "Day";
} else if (hours > 15 && hours < 18) {
msg = "Evening";
}
return `Good ${msg}~ 💧✨`;
}, [time]);
return (
<div className="absolute left-1/2 -translate-x-1/2 lg:left-[5%] lg:translate-x-0 bottom-1/2 translate-y-1/2 w-full md:w-auto px-4 md:px-0 text-white [text-shadow:_0_1px_5px_rgb(0_0_0_/_60%)] text-center lg:text-left z-10">
<p className="text-md md:text-2xl">{message}</p>
<p className="text-7xl md:text-8xl font-light mt-0.5">
{dayjs(time).format("HH:mm")}
</p>
<p className="text-md md:text-lg font-light mt-2">
{dayjs(time).format("dddd, DD MMM YYYY")}
</p>
<AppNav className="mt-8" />
</div> </div>
); );
}; };
const AppNav = ({ className }: ComponentProps<"div">) => {
return (
<div
className={cn(
"flex items-start justify-center lg:justify-start flex-wrap gap-3 md:gap-x-4 lg:gap-x-6",
className
)}
>
<AppNavItem title="Pat Furina" icon={icons.patFurina} path="/pat-pat" />
<AppNavItem title="Album" icon={icons.treasures} path="/treasures" />
<AppNavItem
title="Facebook"
icon={icons.facebook}
path="https://www.facebook.com/"
iconClassName="p-1"
/>
<AppNavItem
title="X"
icon={icons.twitter}
path="https://twitter.com/"
iconClassName="bg-black"
/>
<AppNavItem
title="Furinamains Discord"
icon={icons.furinamains}
path="https://discord.com/invite/ew8yz3h5at"
iconClassName="bg-[#6b473a]"
/>
</div>
);
};
type AppNavItemProps = {
title: string;
icon: string;
path?: string;
iconClassName?: string;
};
const AppNavItem = ({ title, icon, path, iconClassName }: AppNavItemProps) => {
return (
<Link to={path || "#"} className="flex flex-col items-center w-12 group">
<div
className={cn(
"bg bg-white rounded-lg w-12 h-12 overflow-hidden group-hover:scale-110 transition-all",
iconClassName
)}
>
<img
src={icon}
alt={title}
className="w-full h-full object-contain rounded-lg"
/>
</div>
<p className={cn("text-white text-center mt-2 text-sm")}>{title}</p>
</Link>
);
};
export default HomePage; export default HomePage;

View File

@ -0,0 +1,91 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useRef, useState } from "react";
import styles from "./style.module.css";
import { cn } from "@/utility/utils";
import LoadingPage from "../misc/loading-page";
import PageMetadata from "@/components/containers/PageMetadata";
import Modal from "@/components/ui/Modal";
import useModal from "@/hooks/useModal";
import Button from "@/components/ui/Button";
const PatPatPage = () => {
const appRef = useRef<any>();
const cleanRef = useRef<any>();
const targetRef = useRef<HTMLDivElement>(null);
const [isReady, setReady] = useState(false);
useEffect(() => {
if (!appRef.current) {
appRef.current = true;
const init = async () => {
const { default: game } = await import("./game");
const { app, clean } = await game();
targetRef.current?.appendChild(app.view as never);
appRef.current = app;
cleanRef.current = clean;
setReady(true);
};
init();
}
return () => {
if (cleanRef.current) {
cleanRef.current();
}
};
}, [setReady]);
return (
<div>
<PageMetadata
title="Pet the Furina"
description="Play pet the furina meme game"
keywords="pet furina, pet the furina, pet the meme, furina pat pat, pat furina"
/>
{!isReady ? <LoadingPage /> : null}
<div
ref={targetRef}
className={cn(
"flex flex-col items-center justify-center",
styles.canvasContainer
)}
/>
<Credits />
</div>
);
};
const Credits = () => {
const modal = useModal();
return (
<div className="container pt-4 pb-16">
<Button onClick={modal.onOpen}>Assets Credits</Button>
<Modal {...modal} title="Big Thanks to:" size="xl">
<pre className="font-sans overflow-x-auto">
{`
Furina Stickers:
Guido_ (https://risibank.fr/media/297778-genshin-archon-hydro-c6-r5-soutine)
Coll5 (https://risibank.fr/media/317061-furina-focalor-genshin)
Music:
Kururin Furina Cover by Ariyutta (https://facebook.com/arbi.yudatama)
Hand Sprite:
@soapmangraylace2752 (https://www.youtube.com/shorts/HEguW7Gmu2w)
Fijiwaterhelp (https://jailbreak.fandom.com/wiki/User_blog:Fijiwaterhelp/hand_petting)
`.trim()}
</pre>
</Modal>
</div>
);
};
export default PatPatPage;

View File

@ -1408,6 +1408,11 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
dayjs@^1.11.10:
version "1.11.10"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.4" version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"