initial commit

This commit is contained in:
Simeon Radivoev 2026-01-23 04:56:39 +02:00
commit 3e90445fab
Signed by: simeonradivoev
GPG key ID: 7611A451D2A5D37A
20 changed files with 961 additions and 0 deletions

View file

@ -0,0 +1,225 @@
import React, { useEffect, useState } from "react";
import {
Plus,
Search,
Settings,
Power,
Sun,
Wifi,
BatteryFull,
Gamepad2,
Bluetooth,
Settings2,
Bell,
HardDrive,
} from "lucide-react";
import { createFileRoute, Link, linkOptions } from "@tanstack/react-router";
import "gamepad.css/styles.min.css";
import GamepadIcon from "../components/GamepadIcon";
import Clock from "../components/Clock";
import classNames from "classnames";
export const Route = createFileRoute("/Dashboard")({
component: ConsoleHomeUI,
});
const games = [
{
title: "The Legend of Zelda",
subtitle: "Link's Awakening",
},
{
title: "Captain Toad",
subtitle: "Treasure Tracker",
focused: true,
},
{
title: "Crash Bandicoot",
subtitle: "N. Sane Trilogy",
},
{
title: "Super Mario",
subtitle: "Odyssey",
},
{
title: "Animal Crossing",
subtitle: "New Horizons",
},
];
export default function ConsoleHomeUI() {
const [focus, setFocus] = useState(1);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === "ArrowRight")
setFocus((i) => Math.min(i + 1, games.length - 1));
if (e.key === "ArrowLeft") setFocus((i) => Math.max(i - 1, 0));
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, []);
return (
<div
className="w-full h-full flex flex-col overflow-hidden justify-around"
style={{
background: `linear-gradient(
color-mix(in srgb, var(--color-dark) 60%, transparent),
color-mix(in srgb, var(--color-dark) 60%, transparent)
), url(https://picsum.photos/id/${10 + focus}/1920/1080.webp?blur=10)`,
backgroundSize: "cover",
}}
>
{/* Top bar */}
<header className="h-14 px-6 mt-2 flex items-center justify-between text-white">
<div className="flex items-center gap-3 drop-shadow-sm">
<div className="w-16 h-16 rounded-full bg-alert" />
<div className="w-16 h-16 rounded-full bg-cyan-500 ring-4 ring-primary" />
<button className="w-16 h-16 rounded-full bg-dark flex items-center justify-center">
<Plus className="w-8 h-8" />
</button>
</div>
<div className="flex items-center gap-5 text drop-shadow-sm">
<Clock />
<Wifi className="w-6 h-6" />
<Bluetooth className="w-6 h-6" />
<Bell className="w-6 h-6" />
<div className="flex gap-2 items-center">
<BatteryFull className="w-6 h-6" />
<span className="font-semibold">100%</span>
</div>
<div className="flex gap-2">
<div className="w-16 h-16 rounded-full flex items-center justify-center text-dark bg-white">
<Sun className="w-8 h-8" />
</div>
<div className="w-16 h-16 rounded-full flex items-center justify-center text-dark bg-white">
<Power className="w-8 h-8" />
</div>
</div>
</div>
</header>
{/* Filter bar */}
<div className="flex items-center justify-center px-8 gap-2 py-3 drop-shadow-sm">
<button className="flex w-14 h-14 items-center justify-center bg-dark rounded-full text-white">
<Settings2 className="w-5 h-5" />
</button>
<div className="flex bg-dark rounded-full p-1">
<button className="px-4 h-12 rounded-full text-white/70">All</button>
<button className="px-4 h-12 rounded-full bg-primary drop-shadow-sm text-black font-bold">
Digital
</button>
<button className="px-4 h-12 rounded-full text-white/70">
Physical
</button>
</div>
<button className="flex w-14 h-14 items-center justify-center bg-dark rounded-full text-white">
<Search className="w-5 h-5" />
</button>
</div>
{/* Game carousel */}
<main
className="flex w-full px-8 py-4 overflow-x-scroll items-center gap-6"
style={{ scrollbarWidth: "none" }}
>
{games.map((g, i) => {
const focused = i === focus;
return (
<div
key={g.title}
className={classNames(
`min-w-64 h-82 rounded-2xl bg-dark flex flex-col justify-end overflow-hidden transition-all duration-200 drop-shadow-md`,
{
"ring-7 ring-primary scale-105": focused,
"drop-shadow-lg": focused,
},
)}
>
<div
className="flex-1 bg-white p-4"
style={{
backgroundImage: `url(https://picsum.photos/id/${10 + i}/300/300.webp)`,
}}
></div>
<div className="h-0 flex pr-2 justify-end items-center">
<div className="flex rounded-full bg-white w-10 h-10 justify-center items-center text-dark drop-shadow-sm">
<HardDrive className="w-6 h-6" />
</div>
</div>
<div className="flex flex-col p-4 pt-6 text-light2">
<div className="text-xl font-bold">{g.title}</div>
<div className="text-s">{g.subtitle}</div>
</div>
</div>
);
})}
</main>
{/* Menu */}
<div className="flex w-full items-center justify-center gap-3">
<CircleIcon
to={linkOptions({
to: "/Dashboard",
})}
label="Home"
active
/>
<CircleIcon label="News" />
<CircleIcon label="Shop" />
<CircleIcon label="Album" />
<CircleIcon label="Controllers" />
<CircleIcon label="Settings" highlight />
<span className="flex items-center rounded-full bg-primary text-dark px-4 py-2 font-semibold">
Settings
</span>
</div>
{/* Bottom bar */}
<footer className="px-8 flex flex-col items-center justify-between text-light2">
<div className="flex gap-2 text-sm text-light2">
<span className="flex gap-2 bg-dark pl-2 pr-3 py-1.5 rounded-full items-center text-lg font-semibold drop-shadow-sm">
<GamepadIcon platform="xbox" variant="one" button="a" text="a" />
Continue
</span>
<span className="flex gap-2 bg-dark pl-2 pr-3 py-1.5 rounded-full items-center text-lg font-semibold drop-shadow-sm">
<GamepadIcon platform="xbox" variant="one" button="b" text="b" />
Back
</span>
<span className="flex gap-2 bg-dark pl-2 pr-3 py-1.5 rounded-full items-center text-lg font-semibold drop-shadow-sm">
<GamepadIcon platform="xbox" variant="one" button="x" text="x" />
Close
</span>
<span className="flex gap-2 bg-dark pl-2 pr-3 py-1.5 rounded-full items-center text-lg font-semibold drop-shadow-sm">
<GamepadIcon platform="xbox" variant="one" button="y" text="y" />
Options
</span>
</div>
</footer>
</div>
);
}
function CircleIcon({
to,
active,
highlight,
}: {
to?: any;
active?: boolean;
highlight?: boolean;
label?: string;
}) {
return (
<Link
{...to}
className={`w-20 h-20 rounded-full flex items-center justify-center text-dark drop-shadow-lg
${highlight === true ? "bg-primary" : active === true ? "bg-alert text-white" : "bg-white"}`}
>
<Gamepad2 className="w-10 h-10" />
</Link>
);
}

View file

@ -0,0 +1,110 @@
import React, { useEffect, useState } from "react";
import { Bell, Library, Store, Settings, Gamepad2 } from "lucide-react";
import { createFileRoute } from "@tanstack/react-router";
const games = [
"Halo Infinite",
"Cyberpunk",
"Hades",
"Stardew Valley",
"Neon Skies",
"Void Runner",
"Rogue Light",
"Drift City",
];
export const Route = createFileRoute("/GameDetails")({
loader: ({ params }) => params.postId,
component: GameDetailsUI,
});
export function GameDetailsUI() {
// In a component!
const { postId } = Route.useParams();
return (
<main className="flex-1 p-10 flex flex-col gap-10">
{/* Header */}
<header className="flex items-start justify-between">
<div>
<div className="text-sm text-slate-400">Now Playing</div>
<h1 className="text-3xl font-semibold text-cyan-400">
Halo Infinite
</h1>
<div className="mt-2 text-slate-400 text-sm">
Action · FPS · Sci-Fi
</div>
</div>
<div className="flex items-center gap-2 text-slate-300">
<Bell className="w-5 h-5" />
<span className="text-sm">3</span>
</div>
</header>
{/* Content split */}
<section className="flex gap-10 flex-1">
{/* Cover / media */}
<div className="w-[360px] shrink-0">
<div className="relative h-[480px] rounded-3xl bg-gradient-to-br from-slate-700/60 to-slate-900/90 ring-4 ring-cyan-400/80 shadow-[0_0_50px_rgba(34,211,238,0.6)]" />
{/* Primary action */}
<button className="mt-6 w-full rounded-xl bg-cyan-400 text-black font-semibold py-3 text-lg shadow-[0_0_30px_rgba(34,211,238,0.6)]">
Play
</button>
</div>
{/* Details */}
<div className="flex-1 flex flex-col gap-6">
{/* Description */}
<p className="text-slate-300 leading-relaxed max-w-3xl">
Experience the epic sci-fi saga and master chiefs greatest journey
yet. Explore vast open worlds, engage in tactical combat, and
uncover the mysteries of Zeta Halo.
</p>
{/* Metadata */}
<div className="grid grid-cols-2 gap-6 max-w-3xl">
<Detail label="Developer" value="343 Industries" />
<Detail label="Publisher" value="Xbox Game Studios" />
<Detail label="Release" value="Dec 8, 2021" />
<Detail label="Playtime" value="42 hours" />
</div>
{/* Actions */}
<div className="flex gap-4 mt-4">
<SecondaryButton label="Achievements" />
<SecondaryButton label="DLC" />
<SecondaryButton label="Settings" />
</div>
</div>
</section>
{/* Footer hints */}
<footer className="text-sm text-slate-400 flex gap-6">
<span>A Play</span>
<span>B Back</span>
<span>Y Options</span>
</footer>
</main>
);
}
function Detail({ label, value }: { label: string; value: string }) {
return (
<div>
<div className="text-xs uppercase tracking-wide text-slate-500">
{label}
</div>
<div className="text-slate-200 text-sm mt-1">{value}</div>
</div>
);
}
function SecondaryButton({ label }: { label: string }) {
return (
<button className="px-5 py-3 rounded-xl bg-white/5 hover:bg-white/10 text-slate-200">
{label}
</button>
);
}

View file

@ -0,0 +1,16 @@
import { Link, Outlet, createRootRoute } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import { Gamepad2, Library, Settings, Store } from "lucide-react";
export const Route = createRootRoute({
component: RootComponent,
});
function RootComponent() {
return (
<div className="w-screen h-screen overflow-hidden">
<Outlet />
<TanStackRouterDevtools position="bottom-right" />
</div>
);
}