feat(site): redesign home, login and register with premium dark gaming theme
Replaced the classic Habbo landing style on the home, login and register pages with a modern premium dark-gaming look: always-dark hero canvas with brand glows, grid overlay and ambient orbs, glass panels, gradient text and floating art. Adds shared LandingTopBar, AuthShell and BrandFrank components plus reusable premium CSS utilities. Build, typecheck and lint pass.
This commit is contained in:
1 parent
4d720ee03c
commit
741f01e897
8 files changed
+694
-657
No files matched your search
+219
-239
@@ -4,8 +4,27 @@ 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",
|
||||
@@ -19,28 +38,6 @@ 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(),
|
||||
@@ -138,181 +135,211 @@ 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>
|
||||
<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%)",
|
||||
}}
|
||||
/>
|
||||
<section className="landing-hero w-full overflow-hidden rounded-2xl border border-white/10 sm:rounded-3xl">
|
||||
<AmbientOrbs />
|
||||
<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="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" />
|
||||
<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 bg-white/5 px-3.5 py-1.5 text-[11px] font-extrabold uppercase tracking-widest text-white/80">
|
||||
<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} />
|
||||
)}
|
||||
</span>
|
||||
{online === null ? (
|
||||
<span>{te("unavailable")}</span>
|
||||
) : (
|
||||
<LiveOnlineCount initial={online} hotelName={hotelName} />
|
||||
)}
|
||||
|
||||
<h1 className="mt-6 text-4xl font-black leading-[1.02] tracking-tight text-white 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 text-white/60 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>
|
||||
</div>
|
||||
<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"
|
||||
|
||||
{/* 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 bg-white/5 p-7 shadow-2xl backdrop-blur-xl transition-transform duration-500 hover:rotate-0"
|
||||
style={{
|
||||
background: "var(--color-primary)",
|
||||
color: "var(--color-primary-foreground)",
|
||||
boxShadow:
|
||||
"0 4px 24px color-mix(in srgb, var(--color-primary) 35%, transparent)",
|
||||
"0 40px 90px -30px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.1)",
|
||||
}}
|
||||
>
|
||||
<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=""
|
||||
width={20}
|
||||
height={20}
|
||||
alt={hotelName}
|
||||
width={415}
|
||||
height={128}
|
||||
className="h-auto w-full object-contain drop-shadow-[0_8px_20px_rgba(0,0,0,0.4)]"
|
||||
unoptimized
|
||||
/>
|
||||
{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="mt-4 text-center text-sm leading-relaxed text-white/50">
|
||||
{tp("welcomeSubtitle")}
|
||||
</p>
|
||||
<div className="mt-5 space-y-2.5">
|
||||
<div className="h-2 rounded-full bg-white/10">
|
||||
<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 bg-white/10">
|
||||
<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 text-white">
|
||||
<LiveOnlineCounter initial={online ?? 0} />
|
||||
</p>
|
||||
<p className="mt-1 text-[10px] font-bold uppercase tracking-widest text-white/50">
|
||||
{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" }}
|
||||
>
|
||||
{th("login")}
|
||||
</Link>
|
||||
<p className="m-0 text-lg font-black leading-none text-white">
|
||||
<AnimatedCounter value={users ?? 0} />
|
||||
</p>
|
||||
<p className="mt-1 text-[10px] font-bold uppercase tracking-widest text-white/50">
|
||||
{tp("statsCitizens")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</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 bg-white/5 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 text-white sm:text-3xl">
|
||||
{s.value === null ? (
|
||||
<span className="text-sm font-bold text-white/50">
|
||||
{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 text-white/50 sm:text-xs">
|
||||
{s.label}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Reveal>
|
||||
|
||||
{/* ── Features ── */}
|
||||
@@ -321,21 +348,28 @@ export default async function Home() {
|
||||
{features.map((f) => (
|
||||
<SurfaceCard
|
||||
key={f.title}
|
||||
className="relative p-4 sm:p-5 text-center transition-all duration-300 hover:-translate-y-1 hover:shadow-lg"
|
||||
className="group relative overflow-hidden p-5 sm:p-6 transition-all duration-300 hover:-translate-y-1 hover:shadow-lg"
|
||||
>
|
||||
<div
|
||||
className="mx-auto mb-3 flex h-11 w-11 sm:h-12 sm:w-12 items-center justify-center rounded-xl"
|
||||
className="absolute inset-x-0 top-0 h-1 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-primary) 14%, transparent)",
|
||||
"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)))",
|
||||
boxShadow:
|
||||
"inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 10%, transparent)",
|
||||
"inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 14%, transparent)",
|
||||
}}
|
||||
>
|
||||
<Image src={f.icon} alt="" width={24} height={24} unoptimized />
|
||||
</div>
|
||||
<h3
|
||||
className="text-sm sm:text-base font-extrabold mb-1"
|
||||
className="text-center text-base font-extrabold"
|
||||
style={{
|
||||
color: "var(--color-text-readable)",
|
||||
fontFamily: "var(--font-nunito)",
|
||||
@@ -344,7 +378,7 @@ export default async function Home() {
|
||||
{f.title}
|
||||
</h3>
|
||||
<p
|
||||
className="text-xs sm:text-sm leading-relaxed"
|
||||
className="mt-1 text-center text-sm leading-relaxed"
|
||||
style={{ color: "var(--color-text-muted)" }}
|
||||
>
|
||||
{f.desc}
|
||||
@@ -354,60 +388,6 @@ 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="relative p-3 sm:p-5 text-center transition-all duration-300 hover:-translate-y-1 hover:shadow-lg"
|
||||
>
|
||||
<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">
|
||||
|
||||
Reference in new issue
Block a user