revert(site): restore classic Habbo landing with soft theme polish
CI / check (push) Successful in 2m27s
CI / deploy (push) Successful in 1m45s
CI / publish-container (push) Successful in 1m55s

Replace the premium dark-gaming redesign of home, login and register with
the original classic landing (Habbo sky background, AuthTopBar, SurfaceCard
layout). Keeps the theme background visible again and adds a subtle
theme-aware brand halo behind the hero/Frank plus a soft primary glow on
card hover.

Also upgrades dependencies: next 16.3.5, vite 8.3.0 (typescript 7.0.2 was
already latest). Temporarily lowers pnpm minimumReleaseAge to 60 min so the
fresh 16.3.5 release can be installed; restore to 1440 once it is 24h old.
This commit is contained in:
openhands committed 2026-09-12 16:14:21 +02:00
1 parent cc58f16230
commit d755ab7ce1
11 files changed
+742 -812

No files matched your search

+175 -121
View File
@@ -2,26 +2,21 @@ import { count, desc, eq } from "drizzle-orm";
import { headers } from "next/headers";
import Image from "next/image";
import { getTranslations } from "next-intl/server";
import { AuthShell } from "@/components/auth/auth-shell";
import { BrandFrank } from "@/components/auth/brand-frank";
import { AuthTopBar } from "@/components/auth/auth-top-bar";
import { LoginForm } from "@/components/auth/login-form";
import Link from "@/components/link";
import { Reveal } from "@/components/motion-reveal";
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
import { SurfaceCard } from "@/components/surface-card";
import { cached } from "@/lib/cache";
import { db, User } from "@/lib/db";
import { resolveHotelName } from "@/lib/hotel-name";
import { captchaConfig } from "@/lib/services/captcha";
import { siteSettings } from "@/lib/services/site-settings";
export const metadata = {
title: "Sign in",
};
export default async function LoginPage() {
const t = await getTranslations("pages.login");
const th = await getTranslations("header");
const thp = await getTranslations("pages.home");
const [hotelName, cfg, logo] = await Promise.all([
resolveHotelName(),
captchaConfig(),
@@ -29,7 +24,7 @@ export default async function LoginPage() {
]);
const nonce = (await headers()).get("x-nonce") ?? undefined;
const [online, totalUsers, recentUsers, latestUsers] = await Promise.all([
const [online, recentUsers, latestUsers] = await Promise.all([
cached("online_count", 10_000, () =>
db
.select({ total: count() })
@@ -37,141 +32,200 @@ export default async function LoginPage() {
.where(eq(User.online, "1"))
.then((rows) => rows[0]?.total ?? 0),
).catch(() => 0),
cached("total_users", 300_000, () =>
db
.select({ total: count() })
.from(User)
.then((rows) => rows[0]?.total ?? 0),
).catch(() => 0),
db
.select({ username: User.username, look: User.look })
.from(User)
.where(eq(User.online, "1"))
.limit(6)
.limit(8)
.catch(() => []),
db
.select({ username: User.username, look: User.look })
.from(User)
.orderBy(desc(User.accountCreated))
.limit(6)
.limit(8)
.catch(() => []),
]);
const onlineLabel = th("online", { count: online, hotel: "" }).trim();
return (
<div className="mx-auto w-full max-w-6xl flex flex-col gap-6 pb-16">
<div className="mx-auto w-full max-w-6xl flex flex-col gap-8 pb-16">
<AuthTopBar hotelName={hotelName} logo={logo} />
<Reveal>
<AuthShell
hotelName={hotelName}
logo={logo}
footer={
<p className="m-0 text-sm landing-text-muted">
<div className="flex flex-col gap-8 lg:flex-row">
{/* Left panel */}
<div className="lg:w-80 shrink-0 space-y-4">
<SurfaceCard
className="card-glow relative p-6 text-center"
style={{
borderColor:
"color-mix(in srgb, var(--color-primary) 20%, transparent)",
background: `url(/assets/images/background.png) center/cover no-repeat`,
}}
>
<div
className="absolute inset-0"
style={{
background:
"linear-gradient(135deg, color-mix(in srgb, var(--color-surface) 85%, transparent) 0%, color-mix(in srgb, var(--color-surface) 40%, transparent) 100%)",
}}
/>
<div className="relative">
<div className="brand-halo left-1/2 top-8 h-52 w-52 -translate-x-1/2" />
<Image
src="/assets/images/FrankwithBag.gif"
alt="Frank"
width={130}
height={170}
className="relative object-contain mx-auto drop-shadow-xl animate-float"
unoptimized
priority
/>
<div
className="inline-flex items-center gap-2 px-3.5 py-1 rounded-full text-xs font-bold uppercase tracking-wider mt-4 mb-3 border"
style={{
borderColor:
"color-mix(in srgb, var(--color-primary) 18%, transparent)",
background:
"color-mix(in srgb, var(--color-primary) 10%, transparent)",
color:
"var(--color-primary-readable, var(--color-primary))",
}}
>
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-primary" />
</span>
{th("online", { count: online, hotel: "" }).trim()}
</div>
<h1
className="text-xl font-black"
style={{
color: "var(--color-text-readable)",
fontFamily: "var(--font-nunito)",
}}
>
{t("welcomeBack")}
</h1>
<p
className="text-xs mt-1 mx-auto max-w-[200px] leading-relaxed"
style={{ color: "var(--color-text-muted)" }}
>
{t("welcomeBackSub", { hotelName })}
</p>
</div>
</SurfaceCard>
{recentUsers.length > 0 && (
<SurfaceCard
title={th("online", { count: online, hotel: "" }).trim()}
icon="/assets/images/icons/navigation/goody.png"
bodyClassName="p-4"
className="card-glow"
>
<div className="grid grid-cols-4 gap-1.5">
{recentUsers.map((u) => (
<div
key={u.username}
className="flex flex-col items-center gap-0.5 p-1.5 rounded-lg"
style={{
background:
"color-mix(in srgb, var(--color-primary) 4%, transparent)",
}}
>
<UserAvatarThumbnail
figure={u.look}
alt=""
options={{ direction: 2 }}
className="rounded-md"
/>
<span
className="text-[8px] font-bold truncate w-full text-center leading-tight"
style={{ color: "var(--color-text-readable)" }}
>
{u.username}
</span>
</div>
))}
</div>
</SurfaceCard>
)}
{latestUsers.length > 0 && (
<SurfaceCard
title="Newest citizens"
icon="/assets/images/icons/friends.png"
bodyClassName="p-4"
className="card-glow"
>
<div className="grid grid-cols-4 gap-1.5">
{latestUsers.map((u) => (
<div
key={u.username}
className="flex flex-col items-center gap-0.5 p-1.5 rounded-lg"
style={{
background:
"color-mix(in srgb, var(--color-primary) 4%, transparent)",
}}
>
<UserAvatarThumbnail
figure={u.look}
alt=""
options={{ direction: 2 }}
className="rounded-md"
/>
<span
className="text-[8px] font-bold truncate w-full text-center leading-tight"
style={{ color: "var(--color-text-readable)" }}
>
{u.username}
</span>
</div>
))}
</div>
</SurfaceCard>
)}
<p
className="text-xs text-center"
style={{ color: "var(--color-text-muted)" }}
>
{t("noAccount") ?? "No account?"}{" "}
<Link
href="/register"
className="font-extrabold landing-text-primary hover:underline"
className="font-extrabold hover:underline"
style={{
color: "var(--color-primary-readable, var(--color-primary))",
}}
>
{t("createOne") ?? "Create one"}
</Link>
</p>
}
side={
<div className="flex flex-1 flex-col">
<div className="text-left">
<span className="inline-flex items-center gap-2 rounded-full border border-white/10 landing-chip px-3.5 py-1.5 text-[11px] font-extrabold uppercase tracking-widest landing-text-secondary">
<span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[var(--color-primary)] opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-[var(--color-primary)]" />
</span>
{onlineLabel}
</span>
<h1 className="mt-5 text-3xl font-black leading-[1.08] tracking-tight landing-text-primary sm:text-4xl lg:text-5xl">
{t("welcomeBack")}
<br />
<span className="text-gradient">{hotelName}</span>
</h1>
<p className="mt-4 max-w-md text-sm leading-relaxed landing-text-muted sm:text-base">
{t("welcomeBackSub", { hotelName })}
</p>
</div>
<div className="my-6 flex justify-center sm:my-8">
<BrandFrank size={160} />
</div>
<div className="mt-auto grid grid-cols-2 gap-3">
<div className="rounded-2xl border border-white/10 landing-chip p-4">
<div className="landing-text-primary text-2xl font-black">
{online}
</div>
<p className="mt-1 text-[11px] font-bold uppercase tracking-widest text-[var(--color-primary)]">
{onlineLabel}
</p>
{recentUsers.length > 0 && (
<div className="mt-3 flex items-center -space-x-2.5">
{recentUsers.slice(0, 5).map((u) => (
<UserAvatarThumbnail
key={u.username}
figure={u.look}
alt=""
options={{ direction: 2 }}
className="rounded-full border-2 border-[#0b1020]"
/>
))}
</div>
)}
</div>
<div className="flex flex-col justify-center rounded-2xl border border-white/10 landing-chip p-4">
<div className="flex items-center gap-2">
<span className="landing-text-primary text-2xl font-black">
{totalUsers}
</span>
<Image
src="/assets/images/icons/friends.png"
alt=""
width={18}
height={18}
unoptimized
className="opacity-80"
/>
</div>
<p className="mt-1 text-[11px] font-bold uppercase tracking-widest landing-text-faint">
{thp("statsCitizens")}
</p>
{latestUsers.length > 0 && (
<div className="mt-3 flex items-center -space-x-2.5">
{latestUsers.slice(0, 5).map((u) => (
<UserAvatarThumbnail
key={u.username}
figure={u.look}
alt=""
options={{ direction: 2 }}
className="rounded-full border-2 border-[#0b1020]"
/>
))}
</div>
)}
</div>
</div>
</div>
}
>
<div className="mb-6">
<h2 className="m-0 text-2xl font-black tracking-tight landing-text-primary">
{t("title")}
</h2>
<p className="mt-1.5 text-sm landing-text-muted">{t("subtitle")}</p>
</div>
<LoginForm
captcha={{
provider: cfg.provider,
siteKey: cfg.siteKey || undefined,
field: cfg.field || undefined,
}}
nonce={nonce}
/>
</AuthShell>
{/* Right: form */}
<div className="flex-1">
<SurfaceCard
title={t("title")}
icon="/assets/images/icons/navigation/me.png"
bodyClassName="p-6"
>
<p
className="text-sm mb-6"
style={{ color: "var(--color-text-muted)" }}
>
{t("subtitle")}
</p>
<LoginForm
captcha={{
provider: cfg.provider,
siteKey: cfg.siteKey || undefined,
field: cfg.field || undefined,
}}
nonce={nonce}
/>
</SurfaceCard>
</div>
</div>
</Reveal>
</div>
);
+243 -219
View File
@@ -4,27 +4,8 @@ import { headers } from "next/headers";
import Image from "next/image";
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { AmbientOrbs } from "@/components/ambient-orbs";
import { AnimatedCounter } from "@/components/animated-counter";
import { HomeLoginForm } from "@/components/auth/home-login-form";
import { LandingTopBar } from "@/components/landing-top-bar";
import Link from "@/components/link";
import { LiveOnlineCount } from "@/components/live-online-count";
import { LiveOnlineCounter } from "@/components/live-online-counter";
import { Reveal } from "@/components/motion-reveal";
import { PublicLoadError } from "@/components/public/load-error";
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
import { SurfaceCard } from "@/components/surface-card";
import { TypewriterText } from "@/components/typewriter-text";
import { auth } from "@/lib/auth";
import { cached } from "@/lib/cache";
import { CameraWeb, db, Rooms, User } from "@/lib/db";
import { formatDate } from "@/lib/format-date";
import { resolveHotelName } from "@/lib/hotel-name";
import { captchaConfig } from "@/lib/services/captcha";
import { getNewsList } from "@/lib/services/news-list";
import { publicReadFailure } from "@/lib/services/public-read";
import { siteSettings } from "@/lib/services/site-settings";
export const metadata: Metadata = {
title: "Home",
@@ -38,6 +19,28 @@ export const metadata: Metadata = {
},
};
import { AmbientOrbs } from "@/components/ambient-orbs";
import { AnimatedCounter } from "@/components/animated-counter";
import { HomeLoginForm } from "@/components/auth/home-login-form";
import { Clock } from "@/components/clock";
import { LanguageSwitcher } from "@/components/language-switcher";
import Link from "@/components/link";
import { LiveOnlineCount } from "@/components/live-online-count";
import { LiveOnlineCounter } from "@/components/live-online-counter";
import { Reveal } from "@/components/motion-reveal";
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
import { SurfaceCard } from "@/components/surface-card";
import { ThemeSwitcher } from "@/components/theme-switcher";
import { TypewriterText } from "@/components/typewriter-text";
import { auth } from "@/lib/auth";
import { cached } from "@/lib/cache";
import { CameraWeb, db, Rooms, User } from "@/lib/db";
import { formatDate } from "@/lib/format-date";
import { resolveHotelName } from "@/lib/hotel-name";
import { captchaConfig } from "@/lib/services/captcha";
import { getNewsList } from "@/lib/services/news-list";
import { siteSettings } from "@/lib/services/site-settings";
async function getHotelData() {
const [hotelName, logo] = await Promise.all([
resolveHotelName(),
@@ -135,211 +138,185 @@ export default async function Home() {
},
];
const stats = [
{
value: users,
label: tp("statsCitizens"),
icon: "/assets/images/icons/friends.png",
live: false,
},
{
value: rooms,
label: tp("statsRooms"),
icon: "/assets/images/icons/catalog.png",
live: false,
},
{
value: online,
label: tp("statsOnline"),
icon: "/assets/images/icons/navigation/me.png",
live: true,
},
{
value: articles?.length ?? null,
label: tp("statsArticles"),
icon: "/assets/images/icons/article.gif",
live: false,
},
];
return (
<div className="mx-auto w-full max-w-6xl flex flex-col gap-6 px-1 sm:px-0 md:gap-8 pb-12 md:pb-16">
{/* ── Top Bar ── */}
<Reveal>
<div
className="relative flex items-center justify-between rounded-xl border px-3 py-2.5 sm:px-5 sm:py-3"
style={{
background:
"color-mix(in srgb, var(--color-surface) 60%, transparent)",
backdropFilter: "blur(14px)",
WebkitBackdropFilter: "blur(14px)",
borderColor:
"color-mix(in srgb, var(--color-text-muted) 8%, transparent)",
boxShadow:
"0 1px 3px rgba(0,0,0,0.04), 0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 6%, transparent)",
}}
>
<div className="flex items-center gap-2 sm:gap-3">
{logo ? (
<Image
src={logo}
alt={hotelName}
width={100}
height={28}
className="shrink-0 object-contain"
unoptimized
style={{ maxHeight: 28, width: "auto", height: "auto" }}
/>
) : (
<Image
src="/assets/images/icons/lighthouse.png"
alt={hotelName}
width={28}
height={28}
className="shrink-0"
unoptimized
/>
)}
<span
className="hidden sm:inline text-sm font-extrabold uppercase tracking-wide"
style={{ color: "var(--color-text-readable)" }}
>
{hotelName}
</span>
</div>
<div
className="flex items-center gap-1 sm:gap-1.5 rounded-lg px-1.5 py-1 sm:px-2"
style={{
background:
"color-mix(in srgb, var(--color-text-muted) 4%, transparent)",
}}
>
<LanguageSwitcher />
<div
className="hidden sm:block w-px h-5"
style={{
background:
"color-mix(in srgb, var(--color-text-muted) 12%, transparent)",
}}
/>
<Clock />
<div
className="w-px h-5"
style={{
background:
"color-mix(in srgb, var(--color-text-muted) 12%, transparent)",
}}
/>
<ThemeSwitcher />
</div>
</div>
</Reveal>
{/* ── Hero ── */}
<Reveal>
<section className="landing-hero w-full overflow-hidden rounded-2xl border border-white/10 sm:rounded-3xl">
<div
className="relative overflow-hidden rounded-2xl sm:rounded-3xl border"
style={{
borderColor:
"color-mix(in srgb, var(--color-text-muted) 8%, transparent)",
boxShadow:
"0 4px 24px rgba(0,0,0,0.08), 0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 6%, transparent)",
}}
>
<Image
src="/assets/images/next_header.jpg"
alt=""
fill
priority
sizes="100vw"
style={{ objectFit: "cover", objectPosition: "center 20%" }}
/>
<div
className="absolute inset-0"
style={{
background:
"linear-gradient(135deg, color-mix(in srgb, var(--color-surface) 92%, transparent) 0%, color-mix(in srgb, var(--color-surface) 45%, transparent) 50%, color-mix(in srgb, var(--color-surface) 12%, transparent) 100%)",
}}
/>
<AmbientOrbs />
<LandingTopBar hotelName={hotelName} logo={logo} />
<div className="relative z-10 grid items-center gap-10 px-4 py-10 sm:px-7 sm:py-14 lg:grid-cols-[1.15fr_0.85fr] lg:px-10 lg:py-16">
{/* Left: copy */}
<div className="flex flex-col items-start">
<span className="inline-flex items-center gap-2 rounded-full border border-white/10 landing-chip px-3.5 py-1.5 text-[11px] font-extrabold uppercase tracking-widest landing-text-secondary">
<span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[var(--color-primary)] opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-[var(--color-primary)]" />
</span>
{online === null ? (
<span>{te("unavailable")}</span>
) : (
<LiveOnlineCount initial={online} hotelName={hotelName} />
)}
<div
className="relative flex flex-col items-center gap-5 px-4 py-10 text-center sm:gap-8 sm:px-6 sm:py-12 md:px-12 md:py-20"
style={{ zIndex: 2 }}
>
<div
className="brand-halo left-1/2 top-4 h-48 w-48 -translate-x-1/2 sm:top-6 sm:h-64 sm:w-64"
style={{ zIndex: -1 }}
/>
<div
className="inline-flex items-center gap-2 px-3 py-1 sm:px-4 sm:py-1.5 rounded-full text-[10px] sm:text-xs font-bold uppercase tracking-widest mb-1 border backdrop-blur-sm"
style={{
background:
"color-mix(in srgb, var(--color-primary) 12%, transparent)",
borderColor:
"color-mix(in srgb, var(--color-primary) 20%, transparent)",
color: "var(--color-primary-readable, var(--color-primary))",
}}
>
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-primary" />
</span>
<h1 className="mt-6 text-4xl font-black leading-[1.02] tracking-tight landing-text-primary sm:text-5xl lg:text-6xl">
{tp("welcome", { hotelName: "" }).trim()}
<TypewriterText text={hotelName} className="text-gradient" />
</h1>
<p className="mt-5 max-w-xl text-sm leading-relaxed landing-text-muted sm:text-base md:text-lg">
{tp("welcomeBody")}
</p>
<div className="mt-8 flex flex-wrap gap-3">
<Link
href="/register"
className="btn-shine inline-flex items-center gap-2 rounded-xl font-extrabold text-sm sm:text-base px-6 sm:px-8 py-3.5 sm:py-4 transition-all duration-200 hover:scale-[1.03] active:scale-95"
style={{
background: "var(--color-primary)",
color: "var(--color-primary-foreground)",
boxShadow:
"0 8px 32px color-mix(in srgb, var(--color-primary) 40%, transparent)",
}}
>
<Image
src="/assets/images/EnterHubbly.png"
alt=""
width={20}
height={14}
className="object-contain"
unoptimized
/>
{tp("register")}
</Link>
<Link
href="/login"
className="inline-flex items-center gap-2 rounded-xl border font-bold text-sm sm:text-base px-6 sm:px-8 py-3.5 sm:py-4 transition-all duration-200 hover:scale-[1.03] active:scale-95"
style={{
borderColor: "color-mix(in srgb, #ffffff 22%, transparent)",
color: "#ffffff",
background: "color-mix(in srgb, #ffffff 6%, transparent)",
}}
>
{th("login")}
</Link>
</div>
{online === null ? (
<span>{te("unavailable")}</span>
) : (
<LiveOnlineCount initial={online} hotelName={hotelName} />
)}
</div>
{/* Right: visual */}
<div className="relative hidden items-center justify-center md:flex">
<span className="hero-halo h-80 w-80" />
<div
className="relative z-10 w-full max-w-[26rem] -rotate-2 rounded-3xl border border-white/15 landing-chip p-7 shadow-2xl backdrop-blur-xl transition-transform duration-500 hover:rotate-0"
<h1
className="text-2xl font-black leading-[1.05] tracking-tight mb-2 sm:text-3xl md:text-5xl lg:text-6xl"
style={{
color: "var(--color-text-readable)",
fontFamily: "var(--font-nunito)",
}}
>
<TypewriterText text={tp("welcome", { hotelName })} />
</h1>
<p
className="text-sm leading-relaxed max-w-2xl mb-5 sm:text-base md:text-lg md:mb-8"
style={{ color: "var(--color-text-muted)" }}
>
{tp("welcomeBody")}
</p>
<div className="flex flex-wrap gap-2.5 sm:gap-3 justify-center">
<Link
href="/register"
className="inline-flex items-center gap-2 sm:gap-2.5 font-extrabold text-xs sm:text-sm px-5 sm:px-8 py-3 sm:py-4 rounded-xl transition-all duration-200 hover:scale-[1.03] active:scale-95 shadow-lg"
style={{
background: "var(--color-primary)",
color: "var(--color-primary-foreground)",
boxShadow:
"0 40px 90px -30px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.1)",
"0 4px 24px color-mix(in srgb, var(--color-primary) 35%, transparent)",
}}
>
<div
className="absolute inset-x-0 top-0 h-0.5 rounded-t-3xl"
style={{
background:
"linear-gradient(90deg, var(--color-primary), var(--color-accent))",
}}
/>
<Image
src="/assets/images/EnterHubbly.png"
alt={hotelName}
width={415}
height={128}
className="h-auto w-full object-contain drop-shadow-[0_8px_20px_rgba(0,0,0,0.4)]"
alt=""
width={20}
height={20}
unoptimized
/>
<p className="mt-4 text-center text-sm leading-relaxed landing-text-faint">
{tp("welcomeSubtitle")}
</p>
<div className="mt-5 space-y-2.5">
<div className="h-2 rounded-full landing-chip-strong">
<div
className="h-full rounded-full"
style={{
width: "78%",
background:
"linear-gradient(90deg, var(--color-primary), var(--color-accent))",
}}
/>
</div>
<div className="h-2 rounded-full landing-chip-strong">
<div
className="h-full w-1/2 rounded-full"
style={{
background:
"linear-gradient(90deg, var(--color-primary), var(--color-accent))",
}}
/>
</div>
</div>
</div>
<div className="absolute -left-4 top-8 z-20 rounded-2xl border border-white/15 bg-[#0b1020]/80 p-3.5 pl-4 backdrop-blur-xl animate-float">
<p className="m-0 text-lg font-black leading-none landing-text-primary">
<LiveOnlineCounter initial={online ?? 0} />
</p>
<p className="mt-1 text-[10px] font-bold uppercase tracking-widest landing-text-faint">
{tp("statsOnline")}
</p>
</div>
<div
className="absolute -right-4 bottom-8 z-20 rounded-2xl border border-white/15 bg-[#0b1020]/80 p-3.5 pl-4 backdrop-blur-xl animate-float"
style={{ animationDelay: "1.2s" }}
{tp("register")}
</Link>
<Link
href="/login"
className="inline-flex items-center gap-2 font-bold text-xs sm:text-sm px-5 sm:px-8 py-3 sm:py-4 rounded-xl border-2 transition-all duration-200 hover:scale-[1.03] active:scale-95 backdrop-blur-sm"
style={{
borderColor:
"color-mix(in srgb, var(--color-text-muted) 20%, transparent)",
color: "var(--color-text-readable)",
background:
"color-mix(in srgb, var(--color-surface) 50%, transparent)",
}}
>
<p className="m-0 text-lg font-black leading-none landing-text-primary">
<AnimatedCounter value={users ?? 0} />
</p>
<p className="mt-1 text-[10px] font-bold uppercase tracking-widest landing-text-faint">
{tp("statsCitizens")}
</p>
</div>
{th("login")}
</Link>
</div>
</div>
{/* Stats strip */}
<div className="relative z-10 border-t border-white/10 px-4 py-6 sm:px-7 lg:px-10">
<div className="grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4">
{stats.map((s) => (
<div
key={s.label}
className="rounded-2xl border border-white/10 landing-chip p-4 transition-all duration-300 hover:-translate-y-0.5 hover:border-[color-mix(in_srgb,var(--color-primary)_40%,transparent)]"
>
<div className="flex items-center justify-between gap-2">
<span className="text-2xl font-black tracking-tight landing-text-primary sm:text-3xl">
{s.value === null ? (
<span className="text-sm font-bold landing-text-faint">
{te("unavailable")}
</span>
) : s.live ? (
<LiveOnlineCounter initial={s.value} />
) : (
<AnimatedCounter value={s.value} />
)}
</span>
<Image
src={s.icon}
alt=""
width={18}
height={18}
className="opacity-60"
unoptimized
/>
</div>
<p className="mt-1 text-[10px] font-bold uppercase tracking-widest landing-text-faint sm:text-xs">
{s.label}
</p>
</div>
))}
</div>
</div>
</section>
</div>
</Reveal>
{/* ── Features ── */}
@@ -348,28 +325,21 @@ export default async function Home() {
{features.map((f) => (
<SurfaceCard
key={f.title}
className="group relative overflow-hidden p-5 sm:p-6 transition-all duration-300 hover:-translate-y-1 hover:shadow-lg"
className="card-glow relative p-4 sm:p-5 text-center hover:-translate-y-1"
>
<div
className="absolute inset-x-0 top-0 h-1 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
className="mx-auto mb-3 flex h-11 w-11 sm:h-12 sm:w-12 items-center justify-center rounded-xl"
style={{
background:
"linear-gradient(90deg, var(--color-primary), var(--color-accent))",
}}
/>
<div
className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-2xl transition-transform duration-300 group-hover:scale-110"
style={{
background:
"linear-gradient(135deg, color-mix(in srgb, var(--color-primary) 18%, var(--color-surface)), color-mix(in srgb, var(--color-primary) 8%, var(--color-surface)))",
"color-mix(in srgb, var(--color-primary) 14%, transparent)",
boxShadow:
"inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 14%, transparent)",
"inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 10%, transparent)",
}}
>
<Image src={f.icon} alt="" width={24} height={24} unoptimized />
</div>
<h3
className="text-center text-base font-extrabold"
className="text-sm sm:text-base font-extrabold mb-1"
style={{
color: "var(--color-text-readable)",
fontFamily: "var(--font-nunito)",
@@ -378,7 +348,7 @@ export default async function Home() {
{f.title}
</h3>
<p
className="mt-1 text-center text-sm leading-relaxed"
className="text-xs sm:text-sm leading-relaxed"
style={{ color: "var(--color-text-muted)" }}
>
{f.desc}
@@ -388,6 +358,60 @@ export default async function Home() {
</div>
</Reveal>
{/* ── Stats + Login ── */}
<Reveal>
<div className="grid grid-cols-2 gap-3 sm:gap-4 md:grid-cols-4">
{[
{
value: users,
label: tp("statsCitizens"),
icon: "/assets/images/icons/friends.png",
live: false,
},
{
value: rooms,
label: tp("statsRooms"),
icon: "/assets/images/icons/catalog.png",
live: false,
},
{
value: online,
label: tp("statsOnline"),
icon: "/assets/images/icons/navigation/me.png",
live: true,
},
{
value: articles?.length ?? null,
label: tp("statsArticles"),
icon: "/assets/images/icons/article.gif",
live: false,
},
].map((s) => (
<SurfaceCard
key={s.label}
className="card-glow relative p-3 sm:p-5 text-center hover:-translate-y-1"
>
<div className="mb-0.5 text-2xl sm:text-3xl font-black tracking-tight md:text-4xl">
{s.value === null ? (
<span className="text-base">{te("unavailable")}</span>
) : s.live ? (
<LiveOnlineCounter initial={s.value} />
) : (
<AnimatedCounter value={s.value} />
)}
</div>
<div
className="text-[10px] sm:text-xs font-bold uppercase tracking-widest flex items-center justify-center gap-1 sm:gap-1.5"
style={{ color: "var(--color-text-muted)" }}
>
<Image src={s.icon} alt="" width={14} height={14} unoptimized />
{s.label}
</div>
</SurfaceCard>
))}
</div>
</Reveal>
{/* ── Login + Online users ── */}
<div className="flex flex-col gap-4 md:flex-row">
<Reveal className="flex w-full flex-col gap-4 md:w-1/3 md:flex-shrink-0">
+191 -132
View File
@@ -1,24 +1,21 @@
import { count, desc, eq } from "drizzle-orm";
import { headers } from "next/headers";
import Image from "next/image";
import { getTranslations } from "next-intl/server";
import { AuthShell } from "@/components/auth/auth-shell";
import { BrandFrank } from "@/components/auth/brand-frank";
import { AuthTopBar } from "@/components/auth/auth-top-bar";
import { RegisterForm } from "@/components/auth/register-form";
import Link from "@/components/link";
import { Reveal } from "@/components/motion-reveal";
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
import { SurfaceCard } from "@/components/surface-card";
import { cached } from "@/lib/cache";
import { db, User } from "@/lib/db";
import { resolveHotelName } from "@/lib/hotel-name";
import { captchaConfig } from "@/lib/services/captcha";
import { siteSettings } from "@/lib/services/site-settings";
export const metadata = {
title: "Create account",
};
export default async function RegisterPage() {
const tpr = await getTranslations("pages.register");
const th = await getTranslations("pages.home");
const [hotelName, cfg, logo] = await Promise.all([
resolveHotelName(),
captchaConfig(),
@@ -26,7 +23,7 @@ export default async function RegisterPage() {
]);
const nonce = (await headers()).get("x-nonce") ?? undefined;
const [online, latestUsers, recentUsers] = await Promise.all([
const [online, recentUsers, latestUsers] = await Promise.all([
cached("online_count", 10_000, () =>
db
.select({ total: count() })
@@ -34,148 +31,210 @@ export default async function RegisterPage() {
.where(eq(User.online, "1"))
.then((rows) => rows[0]?.total ?? 0),
).catch(() => 0),
cached("register_online_users", 10_000, () =>
db
.select({ username: User.username, look: User.look })
.from(User)
.where(eq(User.online, "1"))
.limit(8),
).catch(() => []),
cached("register_latest_users", 30_000, () =>
db
.select({ username: User.username, look: User.look })
.from(User)
.orderBy(desc(User.accountCreated))
.limit(6),
.limit(8),
).catch(() => []),
db
.select({ username: User.username, look: User.look })
.from(User)
.where(eq(User.online, "1"))
.limit(6)
.catch(() => []),
]);
const benefits = [
{ title: th("featuresAvatar"), desc: th("featuresAvatarDesc") },
{ title: th("featuresFurniture"), desc: th("featuresFurnitureDesc") },
{ title: th("featuresCommunity"), desc: th("featuresCommunityDesc") },
];
return (
<div className="mx-auto w-full max-w-6xl flex flex-col gap-6 pb-16">
<Reveal>
<AuthShell
hotelName={hotelName}
logo={logo}
side={
<div className="flex flex-1 flex-col">
<div className="text-left">
<span className="inline-flex items-center gap-2 rounded-full border border-white/10 landing-chip px-3.5 py-1.5 text-[11px] font-extrabold uppercase tracking-widest landing-text-secondary">
<span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[var(--color-primary)] opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-[var(--color-primary)]" />
</span>
{tpr("usersOnline", { count: online })}
</span>
<h1 className="mt-5 text-3xl font-black leading-[1.08] tracking-tight landing-text-primary sm:text-4xl lg:text-5xl">
{tpr("title")}
<br />
<span className="text-gradient">{hotelName}</span>
</h1>
<p className="mt-4 max-w-md text-sm leading-relaxed landing-text-muted sm:text-base">
{tpr("subtitle")}
</p>
{recentUsers.length > 0 && (
<div className="mt-6 flex items-center gap-3">
<div className="flex -space-x-2.5">
{recentUsers.slice(0, 5).map((u) => (
<UserAvatarThumbnail
key={u.username}
figure={u.look}
alt=""
options={{ direction: 2 }}
className="rounded-full border-2 border-[#0b1020]"
/>
))}
</div>
<p className="m-0 text-[11px] font-extrabold uppercase tracking-widest landing-text-faint">
{tpr("whoIsOnline")}
</p>
</div>
)}
</div>
<div className="mx-auto w-full max-w-6xl flex flex-col gap-8 pb-16">
<AuthTopBar hotelName={hotelName} logo={logo} />
<div className="mt-6 hidden grid-cols-1 gap-2.5 sm:grid">
{benefits.map((b) => (
<div
key={b.title}
className="flex items-center gap-3 rounded-xl border border-white/10 landing-chip px-4 py-3"
>
<span
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full"
<Reveal>
<div className="flex flex-col gap-8 lg:flex-row lg:items-start">
{/* Left: intro panel */}
<div className="lg:w-96 shrink-0 space-y-4">
<SurfaceCard
className="card-glow relative overflow-hidden p-6"
style={{
borderColor:
"color-mix(in srgb, var(--color-primary) 20%, transparent)",
}}
>
<div
className="absolute inset-0"
style={{
background: `url(/assets/images/background.png) center/cover no-repeat`,
}}
/>
<div
className="absolute inset-0"
style={{
background:
"linear-gradient(135deg, color-mix(in srgb, var(--color-surface) 88%, transparent) 0%, color-mix(in srgb, var(--color-surface) 35%, transparent) 100%)",
}}
/>
<div className="relative">
<div className="flex items-center gap-4">
<div className="shrink-0 animate-float">
<div className="brand-halo left-1/2 top-1/2 h-52 w-52 -translate-x-1/2 -translate-y-1/2" />
<Image
src="/assets/images/FrankwithBag.gif"
alt="Frank"
width={110}
height={145}
className="relative object-contain drop-shadow-xl"
unoptimized
priority
/>
</div>
<div className="flex-1">
<div
className="inline-flex items-center gap-2 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider mb-2 border"
style={{
color: "var(--color-primary)",
borderColor:
"color-mix(in srgb, var(--color-primary) 18%, transparent)",
background:
"color-mix(in srgb, var(--color-primary) 15%, transparent)",
"color-mix(in srgb, var(--color-primary) 10%, transparent)",
color:
"var(--color-primary-readable, var(--color-primary))",
}}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="3.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<title>{b.title}</title>
<polyline points="20 6 9 17 4 12" />
</svg>
</span>
<div className="min-w-0">
<p className="m-0 text-sm font-extrabold landing-text-primary">
{b.title}
</p>
<p className="m-0 truncate text-xs landing-text-muted">
{b.desc}
</p>
<span className="w-2 h-2 rounded-full bg-primary" />
{tpr("usersOnline", { count: online })}
</div>
</div>
))}
</div>
<div className="mt-auto pt-6 sm:pt-8">
<div className="flex items-end justify-between gap-4">
<BrandFrank size={130} />
<div className="mb-1 text-right">
{latestUsers.length > 0 && (
<>
<p className="m-0 text-[11px] font-extrabold uppercase tracking-widest landing-text-faint">
Latest citizens
</p>
<div className="mt-2 flex items-center justify-end -space-x-2.5">
{latestUsers.slice(0, 5).map((u) => (
<UserAvatarThumbnail
key={u.username}
figure={u.look}
alt=""
options={{ direction: 2 }}
className="rounded-full border-2 border-[#0b1020]"
/>
))}
</div>
</>
)}
<h1
className="text-lg font-black leading-tight"
style={{
color: "var(--color-text-readable)",
fontFamily: "var(--font-nunito)",
}}
>
{tpr("title")} — {hotelName}
</h1>
<p
className="text-xs mt-1.5 leading-relaxed"
style={{ color: "var(--color-text-muted)" }}
>
{tpr("subtitle")}
</p>
</div>
</div>
</div>
</div>
}
>
<RegisterForm
hotelName={hotelName}
captcha={{
provider: cfg.provider,
siteKey: cfg.siteKey || undefined,
}}
nonce={nonce}
/>
</AuthShell>
</SurfaceCard>
{recentUsers.length > 0 && (
<SurfaceCard
title={tpr("whoIsOnline")}
icon="/assets/images/icons/navigation/goody.png"
bodyClassName="p-4"
className="card-glow"
>
<div className="grid grid-cols-4 gap-1.5">
{recentUsers.map((u) => (
<div
key={u.username}
className="flex flex-col items-center gap-0.5 p-1.5 rounded-lg"
style={{
background:
"color-mix(in srgb, var(--color-primary) 4%, transparent)",
}}
>
<UserAvatarThumbnail
figure={u.look}
alt=""
options={{ direction: 2 }}
className="rounded-md"
/>
<span
className="text-[8px] font-bold truncate w-full text-center leading-tight"
style={{ color: "var(--color-text-readable)" }}
>
{u.username}
</span>
</div>
))}
</div>
</SurfaceCard>
)}
{latestUsers.length > 0 && (
<SurfaceCard
title="Newest citizens"
icon="/assets/images/icons/friends.png"
bodyClassName="p-4"
className="card-glow"
>
<div className="grid grid-cols-4 gap-1.5">
{latestUsers.map((u) => (
<div
key={u.username}
className="flex flex-col items-center gap-0.5 p-1.5 rounded-lg"
style={{
background:
"color-mix(in srgb, var(--color-primary) 4%, transparent)",
}}
>
<UserAvatarThumbnail
figure={u.look}
alt=""
options={{ direction: 2 }}
className="rounded-md"
/>
<span
className="text-[10px] font-bold truncate w-full text-center leading-tight"
style={{ color: "var(--color-text-readable)" }}
>
{u.username}
</span>
</div>
))}
</div>
</SurfaceCard>
)}
<p
className="text-sm text-center"
style={{ color: "var(--color-text-muted)" }}
>
{tpr("alreadyHaveAccountPre")}{" "}
<Link
href="/login"
className="font-extrabold hover:underline"
style={{
color: "var(--color-primary-readable, var(--color-primary))",
}}
>
{tpr("alreadyHaveAccountLink")}
</Link>
</p>
</div>
{/* Right: form */}
<div className="flex-1 lg:sticky lg:top-6">
<SurfaceCard
title={tpr("title")}
icon="/assets/images/icons/navigation/me.png"
bodyClassName="p-6"
>
<p
className="text-sm mb-6"
style={{ color: "var(--color-text-muted)" }}
>
{tpr("subtitle")}
</p>
<RegisterForm
hotelName={hotelName}
captcha={{
provider: cfg.provider,
siteKey: cfg.siteKey || undefined,
}}
nonce={nonce}
/>
</SurfaceCard>
</div>
</div>
</Reveal>
</div>
);
+16 -123
View File
@@ -1993,135 +1993,28 @@ details[open] > summary .details-open\:rotate-180 {
animation-delay: 0.4s;
}
/* ── Premium landing kit (dark gaming style) ───────────────────
Shared primitives for the redesigned home / login / register
pages. Everything stays theme-aware: brand colors are driven by
--color-primary / --color-accent, and the "always-dark" hero
canvas is mixed from the surface token so it blends with both
light and dark hotel themes. */
.landing-hero {
position: relative;
isolation: isolate;
overflow: hidden;
background:
radial-gradient(
60em 40em at 12% -10%,
color-mix(in srgb, var(--color-primary) 22%, transparent) 0%,
transparent 60%
),
radial-gradient(
50em 36em at 108% 12%,
color-mix(in srgb, var(--color-accent) 18%, transparent) 0%,
transparent 62%
),
linear-gradient(
175deg,
color-mix(in srgb, var(--color-surface) 6%, #05070d) 0%,
color-mix(in srgb, var(--color-surface) 8%, #0a0f1d) 55%,
color-mix(in srgb, var(--color-background) 45%, #0a0f1d) 100%
);
color-scheme: dark;
}
.landing-hero::after {
content: "";
/* ── Classic-landing polish (theme-aware) ────────────────────────────
Keeps the Habbo sky and theme colors; adds subtle depth + glow to the
restored landing pages. Everything is driven by theme CSS variables. */
.brand-halo {
position: absolute;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(
to right,
color-mix(in srgb, #ffffff 5%, transparent) 1px,
transparent 1px
),
linear-gradient(
to bottom,
color-mix(in srgb, #ffffff 5%, transparent) 1px,
transparent 1px
);
background-size: 44px 44px;
-webkit-mask-image: linear-gradient(to bottom, #000 30%, transparent 92%);
mask-image: linear-gradient(to bottom, #000 30%, transparent 92%);
z-index: 0;
}
/* Text with a brand gradient (primary → accent). */
.text-gradient {
background: linear-gradient(
115deg,
var(--color-primary) 10%,
color-mix(in srgb, var(--color-primary) 55%, var(--color-accent)) 48%,
var(--color-accent) 92%
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
/* Primary-tinted glass card used for auth forms / panels. */
.glass-panel {
background: color-mix(in srgb, #0b1020 62%, transparent);
backdrop-filter: blur(22px);
-webkit-backdrop-filter: blur(22px);
border: 1px solid color-mix(in srgb, #ffffff 10%, transparent);
box-shadow:
0 30px 80px -20px rgba(0, 0, 0, 0.55),
inset 0 1px 0 color-mix(in srgb, #ffffff 9%, transparent);
}
/* Soft animated halo behind floating hero art. */
.hero-halo {
position: absolute;
border-radius: 999px;
background: radial-gradient(
circle,
color-mix(in srgb, var(--color-primary) 55%, transparent) 0%,
transparent 68%
color-mix(in srgb, var(--color-primary) 30%, transparent) 0%,
transparent 70%
);
filter: blur(6px);
animation: hero-halo-pulse 5s ease-in-out infinite;
filter: blur(20px);
}
@keyframes hero-halo-pulse {
0%,
100% {
opacity: 0.55;
transform: scale(1);
}
50% {
opacity: 0.9;
transform: scale(1.07);
}
.card-glow {
transition:
box-shadow 0.3s ease,
transform 0.3s ease,
border-color 0.3s ease;
}
@media (prefers-reduced-motion: reduce) {
.hero-halo {
animation: none;
}
}
/* White-on-dark overlay text/backgrounds for the landing canvas. Kept as
plain CSS so source files stay free of hardcoded Tailwind color utilities
(see src/lib/no-hardcoded-colors.test.ts). */
.landing-text {
color: #f8fafc;
}
.landing-text-primary {
color: #ffffff;
}
.landing-text-secondary {
color: rgb(255 255 255 / 0.86);
}
.landing-text-muted {
color: rgb(255 255 255 / 0.62);
}
.landing-text-faint {
color: rgb(255 255 255 / 0.5);
}
.landing-chip {
background: rgb(255 255 255 / 0.05);
}
.landing-chip-strong {
background: rgb(255 255 255 / 0.1);
}
.landing-divider {
background: rgb(255 255 255 / 0.15);
.card-glow:hover {
box-shadow:
0 12px 32px -10px color-mix(in srgb, var(--color-primary) 32%, transparent),
0 0 0 1px color-mix(in srgb, var(--color-primary) 22%, transparent);
}
-47
View File
@@ -1,47 +0,0 @@
import type { ReactNode } from "react";
import { AmbientOrbs } from "@/components/ambient-orbs";
import { LandingTopBar } from "@/components/landing-top-bar";
/**
* Premium dark-gaming shell shared by the login and register pages.
* Renders an always-dark hero canvas (brand glows + grid overlay) with a
* hotel header on top, a brand/side column on the left and the auth form
* (rendered by the page) on the right in a frosted glass panel.
*/
export function AuthShell({
hotelName,
logo,
side,
footer,
children,
}: {
hotelName: string;
logo?: string | null;
side: ReactNode;
footer?: ReactNode;
children: ReactNode;
}) {
return (
<section className="landing-hero w-full overflow-hidden rounded-2xl border border-white/10 sm:rounded-3xl">
{/* Ambient glows */}
<AmbientOrbs />
{/* Header */}
<LandingTopBar hotelName={hotelName} logo={logo} />
{/* Body */}
<div className="relative z-10 grid gap-8 px-4 pb-8 pt-6 sm:px-7 sm:pb-10 sm:pt-8 lg:grid-cols-[1fr_1.05fr] lg:gap-12">
<div className="flex flex-col">{side}</div>
<div className="glass-panel relative h-fit w-full rounded-2xl p-5 sm:p-7 lg:sticky lg:top-6">
{children}
</div>
</div>
{footer ? (
<div className="relative z-10 border-t border-white/10 px-4 py-4 text-center sm:px-7">
{footer}
</div>
) : null}
</section>
);
}
-32
View File
@@ -1,32 +0,0 @@
import Image from "next/image";
/**
* Floating brand art for the premium auth pages: the welcome character
* hovers over a pulsing primary halo with a faint slow-orbiting ring.
*/
export function BrandFrank({ size = 150 }: { size?: number }) {
return (
<div className="relative flex items-center justify-center">
<style>{`@keyframes spin-slow { to { transform: rotate(360deg); } }`}</style>
<span className="hero-halo h-40 w-40 sm:h-48 sm:w-48" />
<span
className="absolute h-52 w-52 rounded-full border border-dashed sm:h-64 sm:w-64"
style={{
borderColor: "color-mix(in srgb, #ffffff 16%, transparent)",
animation: "spin-slow 28s linear infinite",
}}
/>
<div className="animate-float relative z-10 drop-shadow-[0_20px_30px_rgba(0,0,0,0.45)]">
<Image
src="/assets/images/FrankwithBag.gif"
alt=""
width={size}
height={Math.round(size * 1.29)}
className="object-contain"
unoptimized
priority
/>
</div>
</div>
);
}
+53 -25
View File
@@ -113,30 +113,52 @@ export function RegisterForm({
/>
) : null}
{/* Header Banner */}
<div className="mb-7 flex items-center gap-4">
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl border border-white/10 landing-chip">
<Image
src="/assets/images/icons/navigation/me.png"
alt=""
width={22}
height={22}
unoptimized
className="opacity-90"
/>
</div>
<div className="min-w-0">
<p
className="text-[11px] font-extrabold uppercase tracking-widest"
style={{ color: "var(--color-primary)" }}
<div
className="relative overflow-hidden bg-center bg-cover rounded-t-xl"
style={{
backgroundImage: "url(/assets/images/mybobba_banner.png)",
height: "160px",
}}
>
<div
className="absolute inset-0 z-10"
style={{
background:
"linear-gradient(180deg, rgba(0,0,0,0.05) 0%, rgba(0,0,0,0.55) 100%)",
}}
/>
<div className="relative flex items-center justify-center w-full h-full z-20">
<div
className="relative animate-float"
style={{ width: "140px", height: "140px" }}
>
{t("accountDetails")}
</p>
<h2 className="m-0 text-2xl font-black tracking-tight landing-text-primary">
{t("title")}
</h2>
<p className="mt-0.5 truncate text-sm landing-text-muted">
{t("subtitle")}
</p>
<div
className="absolute inset-0 rounded-full overflow-hidden"
style={{ zIndex: -1 }}
>
<div
className="w-full h-full"
style={{
background: "black",
filter: "blur(8px)",
transform: "scale(1.2)",
opacity: 0.6,
}}
/>
</div>
<Image
src="/assets/images/FrankwithBag.gif"
alt=""
fill
className="!static"
style={{
objectFit: "contain",
objectPosition: "center",
width: "100%",
height: "100%",
}}
/>
</div>
</div>
</div>
@@ -149,7 +171,12 @@ export function RegisterForm({
};
formAction(fd);
}}
className="flex flex-col gap-6"
className="p-6 flex flex-col gap-6"
style={{
backgroundColor:
"color-mix(in srgb, var(--color-background) 50%, var(--color-surface))",
borderRadius: "0 0 12px 12px",
}}
>
{(error || state.error) && (
<div
@@ -356,7 +383,8 @@ export function RegisterForm({
termsError ? "animate-shake" : ""
}`}
style={{
backgroundColor: "var(--color-background)",
backgroundColor:
"color-mix(in srgb, var(--color-background) 50%, var(--color-surface))",
border: `1px solid ${
termsError
? "var(--color-danger)"
-53
View File
@@ -1,53 +0,0 @@
import Image from "next/image";
import { Clock } from "@/components/clock";
import { LanguageSwitcher } from "@/components/language-switcher";
import { ThemeSwitcher } from "@/components/theme-switcher";
/**
* Frosted top bar shared by the home hero and the premium auth pages.
* Dark-styled to sit on the "always-dark" landing canvas.
*/
export function LandingTopBar({
hotelName,
logo,
}: {
hotelName: string;
logo?: string | null;
}) {
return (
<div className="relative z-10 flex items-center justify-between gap-3 border-b border-white/10 px-4 py-3 sm:px-7 sm:py-4">
<div className="flex min-w-0 items-center gap-2.5 sm:gap-3">
{logo ? (
<Image
src={logo}
alt={hotelName}
width={110}
height={30}
className="shrink-0 object-contain"
unoptimized
style={{ maxHeight: 30, width: "auto", height: "auto" }}
/>
) : (
<Image
src="/assets/images/icons/lighthouse.png"
alt={hotelName}
width={30}
height={30}
className="shrink-0"
unoptimized
/>
)}
<span className="hidden truncate text-sm font-extrabold uppercase tracking-widest landing-text-secondary sm:inline">
{hotelName}
</span>
</div>
<div className="flex shrink-0 items-center gap-1 rounded-xl border border-white/10 landing-chip px-1.5 py-1 sm:px-2">
<LanguageSwitcher />
<div className="hidden h-5 w-px landing-divider sm:block" />
<Clock />
<div className="h-5 w-px landing-divider" />
<ThemeSwitcher />
</div>
</div>
);
}