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

@@ -14,13 +14,34 @@ import Link from "next/link";
import { useGameLaunch } from "@/components/providers/GameLaunchProvider";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import GamePageContent from "@/app/games/[id]/content";
import { Maximize2 } from "lucide-react";
import { Maximize2, ThumbsUp, User } from "lucide-react";
import { useRouter } from "next/navigation";
interface GameCardProps {
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) {
const { launchGame } = useGameLaunch();
const totalVotes = game.totalUpVotes + game.totalDownVotes;
@@ -32,81 +53,52 @@ export const GameCard = React.memo(function GameCard({ game }: GameCardProps) {
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<ContextMenu>
<ContextMenuTrigger>
<button
type="button"
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="overflow-hidden">
{game.primaryMediaAsset ? (
<LazyLoadedImage
imgId={
"GameThumbnail_" +
game.rootPlaceId.toString()
}
alt={game.name}
className="object-fill w-full h-full"
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">
{":("}
</span>
</div>
)}
<button
type="button"
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="overflow-hidden">
{game.primaryMediaAsset ? (
<LazyLoadedImage
imgId={
"GameThumbnail_" +
game.rootPlaceId.toString()
}
alt={game.name}
className="object-cover w-full h-full"
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">
{":("}
</span>
</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>
</button>
</ContextMenuTrigger>
<ContextMenuContent className="min-w-[180px] p-1">
<ContextMenuItem>
<Link href={`/games/${game.rootPlaceId}`}>Open</Link>
</ContextMenuItem>
<ContextMenuItem
onClick={() => {
launchGame(game.rootPlaceId.toString());
}}
>
Play
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
onClick={() => {
navigator.clipboard.writeText(
`${game.rootPlaceId}`
);
}}
>
Copy placeId
</ContextMenuItem>
<ContextMenuItem
onClick={() => {
navigator.clipboard.writeText(`${game.universeId}`);
}}
>
Copy universeId
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<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" />
</div>
<div>
<p className="text-sm font-semibold text-text line-clamp-1">
{game.name}
</p>
<p className="text-xs text-subtext1 space-x-2">
<span>
<ThumbsUp className="inline w-3 h-3 -translate-y-0.5 fill-text" />{" "}
{rating}%
</span>
<span>
<User className="inline w-3 h-3 -translate-y-0.5 fill-text" />{" "}
{formatPlayerCount(game.playerCount)}
</span>
</p>
</div>
</div>
</button>
<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>
{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">
<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"
@@ -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">
<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">
{robux ? robux.toLocaleString() : "..."}
</p>