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) => (
{a.title}
→
{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} ) : (
)} {hotelName}
{/* ── Cinematic hero ── */}
{/* Drifting brand-tinted aurora behind the copy */}