Port AtomCMS atom theme faithfully (Tailwind v4 + real CSS/assets + shell)
Make the template match the atom theme, not just approximate it: - Add Tailwind v4 (+forms/typography plugins, postcss) and port the real atom CSS (global.css + atom app.css) into globals.css: nav-item underline, currency + navigation icon classes, site-bg, card-base/hover-lift, text utils — asset paths adapted to /assets. - Copy the real theme assets (backgrounds, icons/currency/navigation, profile, leaderboards) into public/assets. - Rebuild the shell 1:1 from the atom Blade: TopHeader (currency pills + user/admin dropdowns, auth-only), SiteHeader (header image + black/50 overlay, logo+online+Nitro client / guest Login+Create-account CTA), Navigation (white bar, nav-item + Community/Assistance dropdowns), Footer. ThemeVars injects the DB-driven CSS custom properties into :root like app.blade.php. Layout uses the atom body/site-bg + grid-cols-12 max-w-7xl content wrapper. Verified: tsc 0, next build exit 0; curl confirms the atom markup, compiled CSS references the assets, and background-light.jpg + currency/navigation icons all serve 200.
This commit is contained in:
1 parent
e668fa85ec
commit
e19debb795
337 files changed
+1172
-398
No files matched your search
@@ -0,0 +1,128 @@
|
||||
import Link from "next/link";
|
||||
import { auth, signOut } from "@/lib/auth";
|
||||
import { avatarImageUrl } from "@/lib/format";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
function Currency({ icon, amount, label }: { icon: string; amount: number; label: string }) {
|
||||
return (
|
||||
<div className="flex gap-x-3 sm:gap-x-2 items-center">
|
||||
<div className={`h-[25px] w-[25px] rounded-full outline-offset-[3px] ${icon}`} />
|
||||
<div style={{ color: "var(--color-text-muted)" }}>
|
||||
<span className="font-semibold" style={{ color: "var(--color-text)" }}>
|
||||
{amount.toLocaleString()}
|
||||
</span>{" "}
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function TopHeader() {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) return null;
|
||||
const id = Number(session.user.id);
|
||||
|
||||
let credits = 0;
|
||||
let duckets = 0;
|
||||
let diamonds = 0;
|
||||
let look = "";
|
||||
try {
|
||||
const [user, currencies] = await Promise.all([
|
||||
prisma.user.findUnique({ where: { id }, select: { credits: true, look: true } }),
|
||||
prisma.usersCurrency.findMany({ where: { userId: id } }),
|
||||
]);
|
||||
credits = user?.credits ?? 0;
|
||||
look = user?.look ?? "";
|
||||
for (const c of currencies) {
|
||||
if (c.type === 0) duckets = c.amount;
|
||||
if (c.type === 5) diamonds = c.amount;
|
||||
}
|
||||
} catch {
|
||||
// render without live balances
|
||||
}
|
||||
|
||||
const imager =
|
||||
(await siteSettings.get("habbo_imaging_url", "https://www.habbo.com/habbo-imaging/avatarimage")) ??
|
||||
"";
|
||||
const avatar = avatarImageUrl(imager, look, { headOnly: true, direction: 2 });
|
||||
const isStaff = (session.user.rank ?? 0) >= 7;
|
||||
|
||||
return (
|
||||
<div style={{ backgroundColor: "var(--color-surface)" }}>
|
||||
<div className="max-w-7xl min-h-[60px] px-4 flex items-center justify-between mx-auto">
|
||||
<div className="flex gap-x-6">
|
||||
<Currency icon="nav-credit-icon" amount={credits} label="Credits" />
|
||||
<Currency icon="nav-ducket-icon" amount={duckets} label="Duckets" />
|
||||
<Currency icon="nav-diamond-icon" amount={diamonds} label="Diamonds" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-x-3">
|
||||
{isStaff ? (
|
||||
<details className="relative">
|
||||
<summary
|
||||
className="list-none cursor-pointer font-semibold [&::-webkit-details-marker]:hidden"
|
||||
style={{ color: "var(--color-text)" }}
|
||||
>
|
||||
Administration
|
||||
</summary>
|
||||
<div
|
||||
className="absolute right-0 mt-1 min-w-[180px] rounded-md shadow-lg z-50 py-1"
|
||||
style={{ backgroundColor: "var(--color-dropdown)" }}
|
||||
>
|
||||
<Link href="/admin" className="dropdown-item">
|
||||
Admin panel
|
||||
</Link>
|
||||
<Link href="/admin/commandocentrum" className="dropdown-item">
|
||||
Commandocentrum
|
||||
</Link>
|
||||
<Link href="/admin/logs" className="dropdown-item">
|
||||
Logs
|
||||
</Link>
|
||||
</div>
|
||||
</details>
|
||||
) : null}
|
||||
|
||||
<details className="relative">
|
||||
<summary className="list-none cursor-pointer flex items-center [&::-webkit-details-marker]:hidden">
|
||||
{/* biome-ignore lint/performance/noImgElement: external imager */}
|
||||
<img className="w-[54px] h-[62px] bg-no-repeat bg-center" src={avatar} alt="" />
|
||||
<span className="-ml-2 font-semibold">{session.user.name}</span>
|
||||
</summary>
|
||||
<div
|
||||
className="absolute right-0 mt-1 min-w-[180px] rounded-md shadow-lg z-50 py-1"
|
||||
style={{ backgroundColor: "var(--color-dropdown)" }}
|
||||
>
|
||||
<Link href={`/u/${session.user.name}`} className="dropdown-item">
|
||||
My profile
|
||||
</Link>
|
||||
<Link href="/settings" className="dropdown-item">
|
||||
User settings
|
||||
</Link>
|
||||
<Link href="/settings/2fa" className="dropdown-item">
|
||||
Two-factor
|
||||
</Link>
|
||||
<form
|
||||
action={async () => {
|
||||
"use server";
|
||||
await signOut({ redirectTo: "/" });
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
className="dropdown-item w-full text-center"
|
||||
style={{
|
||||
backgroundColor: "var(--button-danger-color)",
|
||||
color: "var(--button-danger-text-color)",
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user