import { count, desc, eq } from "drizzle-orm";
import { ChevronDown } from "lucide-react";
import type { Metadata } from "next";
import { headers } from "next/headers";
import Image from "next/image";
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import type { CSSProperties, ReactNode } from "react";
import { AnimatedCounter } from "@/components/animated-counter";
import { LoginForm } from "@/components/auth/login-form";
import { Clock } from "@/components/clock";
import { LanguageSwitcher } from "@/components/language-switcher";
import Link from "@/components/link";
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 { ThemeSwitcher } from "@/components/theme-switcher";
import { TypewriterText } from "@/components/typewriter-text";
import { auth } from "@/lib/auth";
import { cached } from "@/lib/cache";
import { CameraWeb, db, 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 {
countPhotos,
countRooms,
countUsers,
} from "@/lib/services/public-counters";
import { publicReadFailure } from "@/lib/services/public-read";
import { siteSettings } from "@/lib/services/site-settings";
import {
ICON_ARTICLE,
ICON_CAMERA,
ICON_CATALOG,
ICON_FRIENDS,
ICON_LIGHTHOUSE,
ICON_NAV_GOODY,
ICON_NAV_ME,
} from "@/lib/site-icons";
export const metadata: Metadata = {
title: "Home",
description:
"An online virtual world where you can create your own avatar, make friends, chat, and build your own rooms.",
openGraph: {
title: "Home",
description:
"An online virtual world where you can create your own avatar, make friends, chat, and build your own rooms.",
type: "website",
},
};
// ── Local visual language (theme-aware, no big white blocks) ──────────
const PANEL_BG =
"linear-gradient(160deg, color-mix(in srgb, var(--color-primary) 12%, transparent) 0%, color-mix(in srgb, var(--color-surface) 58%, transparent) 52%, color-mix(in srgb, var(--color-accent) 9%, transparent) 100%)";
const PANEL_STYLE: CSSProperties = {
background: PANEL_BG,
backdropFilter: "blur(18px)",
WebkitBackdropFilter: "blur(18px)",
borderColor: "color-mix(in srgb, var(--color-text-muted) 12%, transparent)",
boxShadow:
"0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 6%, transparent), 0 24px 60px -30px color-mix(in srgb, #000 28%, transparent)",
};
const DARK_BAND =
"linear-gradient(120deg, color-mix(in srgb, var(--color-primary) 30%, #0a0e1e) 0%, color-mix(in srgb, var(--color-accent) 26%, #0a0e1e) 52%, color-mix(in srgb, var(--color-primary) 20%, #0a0e1e) 100%)";
const GLASS_TILE_BG =
"linear-gradient(155deg, color-mix(in srgb, var(--color-surface) 60%, transparent) 0%, color-mix(in srgb, var(--color-surface) 44%, transparent) 60%, color-mix(in srgb, var(--color-accent) 6%, transparent) 100%)";
interface GlassPanelProps {
children: ReactNode;
title?: string;
icon?: string;
actionHref?: string;
actionLabel?: string;
bodyClassName?: string;
className?: string;
}
function GlassPanel({
children,
title,
icon,
actionHref,
actionLabel,
bodyClassName,
className,
}: GlassPanelProps) {
const hasHeader = Boolean(title || icon);
return (
{hasHeader && (
{icon && (
)}
{title && (
{title}
)}
{actionHref && actionLabel && (
{actionLabel}
)}
)}
{hasHeader ? (
{children}
) : (
children
)}
);
}
async function getHotelData() {
const [hotelName, logo] = await Promise.all([
resolveHotelName(),
siteSettings.get("cms_logo", ""),
]);
const [
online,
users,
rooms,
totalPhotos,
articles,
recentUsers,
recentPhotos,
] = await Promise.all([
cached("online_count", 10_000, () =>
db
.select({ total: count() })
.from(User)
.where(eq(User.online, "1"))
.then((rows) => rows[0]?.total ?? 0),
).catch(publicReadFailure("home.online")),
cached("total_users", 300_000, countUsers, { staleMs: 300000 }).catch(
publicReadFailure("home.users"),
),
cached("total_rooms", 300_000, countRooms, { staleMs: 300000 }).catch(
publicReadFailure("home.rooms"),
),
cached("total_photos", 300_000, countPhotos, { staleMs: 300000 }).catch(
publicReadFailure("home.photos-count"),
),
getNewsList(4, { throwOnError: true }).catch(
publicReadFailure("home.news"),
),
cached(
"home_online_users",
15_000,
() =>
db
.select({ username: User.username, look: User.look })
.from(User)
.where(eq(User.online, "1"))
.limit(12),
{ staleMs: 30000 },
).catch(publicReadFailure("home.recent-users")),
cached(
"home_recent_photos",
60_000,
() =>
db
.select({ id: CameraWeb.id, url: CameraWeb.url })
.from(CameraWeb)
.orderBy(desc(CameraWeb.timestamp))
.limit(4),
{ staleMs: 120000 },
).catch(publicReadFailure("home.photos")),
]);
return {
hotelName,
online,
users,
rooms,
totalPhotos,
articles,
recentUsers,
recentPhotos,
logo,
};
}
export default async function Home() {
const session = await auth();
if (session?.user?.id) redirect("/me");
const tp = await getTranslations("pages.home");
const te = await getTranslations("publicData");
const th = await getTranslations("header");
const tn = await getTranslations("nav");
const {
hotelName,
online,
users,
rooms,
totalPhotos,
articles,
recentUsers,
recentPhotos,
logo,
} = await getHotelData();
const captcha = await captchaConfig();
const nonce = (await headers()).get("x-nonce") ?? undefined;
const features = [
{
title: tp("featuresAvatar"),
desc: tp("featuresAvatarDesc"),
icon: ICON_FRIENDS,
},
{
title: tp("featuresFurniture"),
desc: tp("featuresFurnitureDesc"),
icon: ICON_CATALOG,
},
{
title: tp("featuresCommunity"),
desc: tp("featuresCommunityDesc"),
icon: ICON_NAV_GOODY,
},
];
type NewsArticle = NonNullable[number];
const renderArticleCard = (a: NewsArticle, featured = false) => (
→
{formatDate(a.createdAt, "date", "")}
{featured && (
{tp("latestArticle")}
)}
{a.title}
{formatDate(a.createdAt, "date", "")}
);
const exploreLinks = [
{ href: "/community", label: tn("community") },
{ href: "/news", label: tn("articles") },
{ href: "/photos", label: tn("photos") },
{ href: "/radio", label: tn("radio") },
{ href: "/shop", label: tn("shop") },
{ href: "/rankings", label: tn("rankings") },
];
const statChips = [
{ value: users, label: tp("statsCitizens"), color: "var(--color-primary)" },
{ value: rooms, label: tp("statsRooms"), color: "var(--color-accent)" },
{
value: totalPhotos,
label: tp("statsPhotos"),
color: "var(--color-primary)",
},
{
value: articles?.length ?? null,
label: tp("statsArticles"),
color: "var(--color-accent)",
},
].filter((s) => s.value !== null) as Array<{
value: number;
label: string;
color: string;
}>;
return (
{/* ── Top bar ── */}
{logo ? (
) : (
)}
{hotelName}
{/* ── Cinematic hero ── */}
{/* Drifting brand-tinted aurora behind the copy */}
{tp("onlineNow")}
{online === null ? (
{te("unavailable")}
) : (
)}
{tp("welcome", { hotelName: "" })}
{tp("welcomeBody")}
{recentUsers !== null && recentUsers.length > 0 && (
{recentUsers.slice(0, 4).map((u) => (
))}
{tp("recentUsers")}
)}
{tp("register")}
{th("login")}
→
{/* Floating stat chips */}
{statChips.map((s) => (
{s.label}
))}
{/* Scroll cue */}
{/* Floating mascot — decorative, hidden on smaller screens. */}
{/* ── Quick explore ── */}
{/* ── Features ── */}
{tp("sectionFeaturesLabel")}
{tp("sectionFeaturesTitle")}
{features.map((f, i) => {
const accentVar =
i % 2 === 0 ? "var(--color-primary)" : "var(--color-accent)";
return (
{`0${i + 1}`}
{f.title}
{f.desc}
);
})}
{/* ── Stats ── */}
{tp("sectionStatsLabel")}
{tp("sectionStatsTitle")}
{[
{
value: users,
label: tp("statsCitizens"),
icon: ICON_FRIENDS,
},
{ value: rooms, label: tp("statsRooms"), icon: ICON_CATALOG },
{
value: totalPhotos,
label: tp("statsPhotos"),
icon: ICON_CAMERA,
},
{
value: articles?.length ?? null,
label: tp("statsArticles"),
icon: ICON_ARTICLE,
},
].map((s) => (
{s.value === null ? (
{te("unavailable")}
) : (
)}
{s.label}
))}
{/* ── Community ── */}
{tp("sectionCommunityLabel")}
{tp("sectionCommunityTitle")}
{recentUsers === null ? (
) : (
recentUsers.length > 0 && (
{recentUsers.map((u) => (
{u.username}
))}
)
)}
{/* News */}
{articles === null ? (
) : articles.length > 0 ? (
{renderArticleCard(articles[0], true)}
{articles.slice(1, 4).map((a) => renderArticleCard(a))}
) : (
{tp("noArticles")}
)}
{/* ── Recent photos ── */}
{recentPhotos === null ? (
) : (
recentPhotos.length > 0 && (
{recentPhotos.slice(0, 4).map((p) => (
))}
)
)}
{/* ── Join the fun ── */}
{tp("joinCtaTitle")}
{tp("joinCtaBody")}
{tp("ctaJoin")}
→
{tp("browseCommunity")}
);
}