This commit is contained in:
2025-12-28 00:30:09 +02:00
parent 331ff6daf3
commit 6202f842b6
8 changed files with 85 additions and 108 deletions

View File

@@ -74,7 +74,7 @@ export default function GamePageContent({
return ( return (
<div className="mx-auto w-full max-w-6xl px-4 sm:px-8 py-6 space-y-6"> <div className="mx-auto w-full max-w-6xl px-4 sm:px-8 py-6 space-y-6">
<div className="grid gap-6 lg:grid-cols-[2fr_1fr]"> <div className="grid gap-6 lg:grid-cols-[2fr_1fr]">
<div className="rounded-2xl overflow-hidden bg-surface0/40 ring-1 ring-surface1/60"> <div className="rounded-2xl overflow-hidden bg-surface0/40 ring-1 ring-surface1/60 aspect-video">
<LazyLoadedImage <LazyLoadedImage
imgId={`GameThumbnail_${game.rootPlaceId}`} imgId={`GameThumbnail_${game.rootPlaceId}`}
alt={game.name} alt={game.name}

View File

@@ -15,20 +15,6 @@ body {
@layer base { @layer base {
:root { :root {
--ctp-base: 240 21.052631735801697% 14.901961386203766%; /* base */
--ctp-mantle: 240 21.311475336551666% 11.96078434586525%; /* mantle */
--ctp-crust: 240 23.404255509376526% 8.627450853586197%; /* crust */
--ctp-text: 226 63.93442749977112% 88.03921341896057%; /* text */
--ctp-subtext0: 227 23.076922595500946% 71.96078300476074%; /* subtext0 */
--ctp-subtext1: 227 35.29411852359772% 80.0000011920929%; /* subtext1 */
--ctp-surface0: 237 16.239316761493683% 22.94117659330368%; /* surface0 */
--ctp-surface1: 234 13.20754736661911% 31.176471710205078%; /* surface1 */
--ctp-surface2: 233 12.05937068939209% 39.607844948768616%; /* surface2 */
--ctp-blue: 217 91.86992049217224% 75.88235139846802%; /* blue */
--ctp-green: 115 54.09836173057556% 76.07843279838562%; /* green */
--ctp-yellow: 41 86.04651093482971% 83.13725590705872%; /* yellow */
--ctp-red: 343 81.25% 74.90196228027344%; /* red */
--background: 240 21.052631735801697% 14.901961386203766%; /* base */ --background: 240 21.052631735801697% 14.901961386203766%; /* base */
--foreground: 226 63.93442749977112% 88.03921341896057%; /* text */ --foreground: 226 63.93442749977112% 88.03921341896057%; /* text */

View File

@@ -44,7 +44,7 @@ export default function RootLayout({
return ( return (
<html lang="en"> <html lang="en">
<body <body
className={`${geistSans.variable} ${geistMono.variable} antialiased overflow-x-hidden`} className={`${geistSans.variable} ${geistMono.variable} antialiased overflow-x-hidden mocha`}
> >
<ReactQueryProvider> <ReactQueryProvider>
<TooltipProvider> <TooltipProvider>

View File

@@ -5,7 +5,8 @@
"": { "": {
"name": "roblox", "name": "roblox",
"dependencies": { "dependencies": {
"@catppuccin/tailwindcss": "^1.0.0", "@catppuccin/palette": "^1.7.1",
"@catppuccin/tailwindcss": "0.1.6",
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
"@ocbwoy3/libocbwoy3": "^0.0.6", "@ocbwoy3/libocbwoy3": "^0.0.6",
"@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-accordion": "^1.2.12",
@@ -76,7 +77,9 @@
"packages": { "packages": {
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@catppuccin/tailwindcss": ["@catppuccin/tailwindcss@1.0.0", "", {}, "sha512-l8pOlcYe2ncGd8a1gUmL5AHmKlxR2+CHuG5kt4Me6IZwzntW1DoLmj89BH+DcsPHBsdDGLrTSv35emlYyU3FeQ=="], "@catppuccin/palette": ["@catppuccin/palette@1.7.1", "", {}, "sha512-aRc1tbzrevOTV7nFTT9SRdF26w/MIwT4Jwt4fDMc9itRZUDXCuEDBLyz4TQMlqO9ZP8mf5Hu4Jr6D03NLFc6Gw=="],
"@catppuccin/tailwindcss": ["@catppuccin/tailwindcss@0.1.6", "", { "peerDependencies": { "tailwindcss": ">=3.0.0" } }, "sha512-V+Y0AwZ5SSyvOVAcDl7Ng30xy+m82OKnEJ+9+kcZZ7lRyXuZrAb2GScdq9XR3v+ggt8qiZ/G4TvaC9cJ88AAXA=="],
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],

View File

@@ -14,13 +14,34 @@ import Link from "next/link";
import { useGameLaunch } from "@/components/providers/GameLaunchProvider"; import { useGameLaunch } from "@/components/providers/GameLaunchProvider";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import GamePageContent from "@/app/games/[id]/content"; import GamePageContent from "@/app/games/[id]/content";
import { Maximize2 } from "lucide-react"; import { Maximize2, ThumbsUp, User } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
interface GameCardProps { interface GameCardProps {
game: ContentMetadata; game: ContentMetadata;
} }
const formatPlayerCount = (count: number) => {
if (count <= 1000) {
return count.toLocaleString();
}
const units = [
{ value: 1_000_000_000, suffix: "B" },
{ value: 1_000_000, suffix: "M" },
{ value: 1_000, suffix: "K" }
];
const unit = units.find(({ value }) => count >= value);
if (!unit) {
return count.toLocaleString();
}
const scaled = count / unit.value;
const digits = scaled < 10 ? 1 : 0;
return `${scaled.toFixed(digits).replace(/\.0$/, "")}${unit.suffix}`;
};
export const GameCard = React.memo(function GameCard({ game }: GameCardProps) { export const GameCard = React.memo(function GameCard({ game }: GameCardProps) {
const { launchGame } = useGameLaunch(); const { launchGame } = useGameLaunch();
const totalVotes = game.totalUpVotes + game.totalDownVotes; const totalVotes = game.totalUpVotes + game.totalDownVotes;
@@ -32,81 +53,52 @@ export const GameCard = React.memo(function GameCard({ game }: GameCardProps) {
return ( return (
<Dialog open={isOpen} onOpenChange={setIsOpen}> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<ContextMenu> <button
<ContextMenuTrigger> type="button"
<button className="text-left"
type="button" onClick={() => setIsOpen(true)}
className="text-left" >
onClick={() => setIsOpen(true)} <div className="space-y-2">
> <div className="group overflow-hidden aspect-video relative bg-muted rounded-2xl ring-1 ring-surface0/60 shadow-sm transition hover:-translate-y-0.5 hover:shadow-lg">
<div className="space-y-2"> <div className="overflow-hidden">
<div className="group overflow-hidden aspect-video relative bg-muted rounded-2xl ring-1 ring-surface0/60 shadow-sm transition hover:-translate-y-0.5 hover:shadow-lg"> {game.primaryMediaAsset ? (
<div className="overflow-hidden"> <LazyLoadedImage
{game.primaryMediaAsset ? ( imgId={
<LazyLoadedImage "GameThumbnail_" +
imgId={ game.rootPlaceId.toString()
"GameThumbnail_" + }
game.rootPlaceId.toString() alt={game.name}
} className="object-cover w-full h-full"
alt={game.name} lazyFetch={false} // ALWAYS fetch immediately
className="object-fill w-full h-full" size="384x216" // match game thumbnail size
lazyFetch={false} // ALWAYS fetch immediately />
size="384x216" // match game thumbnail size ) : (
/> <div className="w-full h-full flex items-center justify-center bg-muted">
) : ( <span className="text-muted-foreground">
<div className="w-full h-full flex items-center justify-center bg-muted"> {":("}
<span className="text-muted-foreground"> </span>
{":("}
</span>
</div>
)}
</div> </div>
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-crust/50 via-transparent to-transparent opacity-0 transition-opacity group-hover:opacity-100" /> )}
<div className="text-blue bg-base/90 font-mono flex right-2 bottom-2 absolute rounded-lg px-2 py-1 text-xs shadow-sm ring-1 ring-surface0/60 backdrop-blur">
{game.playerCount.toLocaleString()}
</div>
</div>
<div>
<p className="text-sm font-semibold text-text line-clamp-2">
{game.name}
</p>
<p className="text-xs text-subtext1">
{rating}% rating
</p>
</div>
</div> </div>
</button> <div className="pointer-events-none absolute inset-0 bg-linear-to-t from-crust/50 via-transparent to-transparent opacity-0 transition-opacity group-hover:opacity-100" />
</ContextMenuTrigger> </div>
<ContextMenuContent className="min-w-[180px] p-1"> <div>
<ContextMenuItem> <p className="text-sm font-semibold text-text line-clamp-1">
<Link href={`/games/${game.rootPlaceId}`}>Open</Link> {game.name}
</ContextMenuItem> </p>
<ContextMenuItem <p className="text-xs text-subtext1 space-x-2">
onClick={() => { <span>
launchGame(game.rootPlaceId.toString()); <ThumbsUp className="inline w-3 h-3 -translate-y-0.5 fill-text" />{" "}
}} {rating}%
> </span>
Play <span>
</ContextMenuItem> <User className="inline w-3 h-3 -translate-y-0.5 fill-text" />{" "}
<ContextMenuSeparator /> {formatPlayerCount(game.playerCount)}
<ContextMenuItem </span>
onClick={() => { </p>
navigator.clipboard.writeText( </div>
`${game.rootPlaceId}` </div>
); </button>
}}
>
Copy placeId
</ContextMenuItem>
<ContextMenuItem
onClick={() => {
navigator.clipboard.writeText(`${game.universeId}`);
}}
>
Copy universeId
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<DialogContent className="max-w-6xl h-[75vh] w-[96vw] max-h-[90vh] overflow-hidden bg-crust ring-1 ring-surface0/60 p-0"> <DialogContent className="max-w-6xl h-[75vh] w-[96vw] max-h-[90vh] overflow-hidden bg-crust ring-1 ring-surface0/60 p-0">
<DialogTitle className="sr-only" hidden> <DialogTitle className="sr-only" hidden>
{game.name} {game.name}

View File

@@ -121,7 +121,7 @@ export const QuickTopUI = React.memo(function () {
) : ( ) : (
<></> <></>
)} )}
<div className="z-50 fixed top-4 right-4 flex gap-2 items-center text-text"> <div className="z-1000 fixed top-4 right-4 flex gap-2 items-center text-text">
<StupidHoverThing text="Change Outfit"> <StupidHoverThing text="Change Outfit">
<button <button
className="rounded-full bg-surface0/70 ring-1 ring-surface1/60 flex items-center justify-center h-10 w-10 text-text shadow-sm transition hover:bg-surface1/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue/60" className="rounded-full bg-surface0/70 ring-1 ring-surface1/60 flex items-center justify-center h-10 w-10 text-text shadow-sm transition hover:bg-surface1/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue/60"
@@ -141,7 +141,8 @@ export const QuickTopUI = React.memo(function () {
} }
> >
<div className="rounded-full bg-surface0/70 ring-1 ring-surface1/60 flex items-center h-10 px-3 gap-2 text-text shadow-sm"> <div className="rounded-full bg-surface0/70 ring-1 ring-surface1/60 flex items-center h-10 px-3 gap-2 text-text shadow-sm">
<RobuxIcon className="w-5 h-5 text-green" /> <RobuxIcon className="w-5 h-5 text-text" />{" "}
{/* keep it text-text */}
<p className="text-sm font-super-mono tabular-nums"> <p className="text-sm font-super-mono tabular-nums">
{robux ? robux.toLocaleString() : "..."} {robux ? robux.toLocaleString() : "..."}
</p> </p>

View File

@@ -9,7 +9,8 @@
"lint": "bunx --bun next lint" "lint": "bunx --bun next lint"
}, },
"dependencies": { "dependencies": {
"@catppuccin/tailwindcss": "^1.0.0", "@catppuccin/palette": "^1.7.1",
"@catppuccin/tailwindcss": "0.1.6",
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
"@ocbwoy3/libocbwoy3": "^0.0.6", "@ocbwoy3/libocbwoy3": "^0.0.6",
"@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-accordion": "^1.2.12",

View File

@@ -1,4 +1,10 @@
import type { Config } from "tailwindcss"; import type { Config } from "tailwindcss";
import { flavors } from "@catppuccin/palette";
const ctpColors: Record<string, string> = {};
for (const [name, color] of Object.entries(flavors.mocha.colors)) {
ctpColors[name] = color.hex;
}
export default { export default {
darkMode: "class", darkMode: "class",
@@ -12,19 +18,7 @@ export default {
colors: { colors: {
background: "hsl(var(--background))", background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))", foreground: "hsl(var(--foreground))",
base: "hsl(var(--ctp-base))", ...ctpColors,
mantle: "hsl(var(--ctp-mantle))",
crust: "hsl(var(--ctp-crust))",
text: "hsl(var(--ctp-text))",
subtext0: "hsl(var(--ctp-subtext0))",
subtext1: "hsl(var(--ctp-subtext1))",
surface0: "hsl(var(--ctp-surface0))",
surface1: "hsl(var(--ctp-surface1))",
surface2: "hsl(var(--ctp-surface2))",
blue: "hsl(var(--ctp-blue))",
green: "hsl(var(--ctp-green))",
yellow: "hsl(var(--ctp-yellow))",
red: "hsl(var(--ctp-red))",
card: { card: {
DEFAULT: "hsl(var(--card))", DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))" foreground: "hsl(var(--card-foreground))"