diff --git a/src/app/(site)/login/page.tsx b/src/app/(site)/login/page.tsx
index 4b44e571..1548a9e2 100644
--- a/src/app/(site)/login/page.tsx
+++ b/src/app/(site)/login/page.tsx
@@ -1,4 +1,4 @@
-import { count, eq } from "drizzle-orm";
+import { count, desc, eq } from "drizzle-orm";
import { headers } from "next/headers";
import Image from "next/image";
import { getTranslations } from "next-intl/server";
@@ -29,7 +29,7 @@ export default async function LoginPage() {
]);
const nonce = (await headers()).get("x-nonce") ?? undefined;
- const [online, totalUsers, recentUsers] = await Promise.all([
+ const [online, totalUsers, recentUsers, latestUsers] = await Promise.all([
cached("online_count", 10_000, () =>
db
.select({ total: count() })
@@ -49,6 +49,12 @@ export default async function LoginPage() {
.where(eq(User.online, "1"))
.limit(6)
.catch(() => []),
+ db
+ .select({ username: User.username, look: User.look })
+ .from(User)
+ .orderBy(desc(User.accountCreated))
+ .limit(6)
+ .catch(() => []),
]);
const onlineLabel = th("online", { count: online, hotel: "" }).trim();
@@ -60,11 +66,11 @@ export default async function LoginPage() {
hotelName={hotelName}
logo={logo}
footer={
-
+
{t("noAccount") ?? "No account?"}{" "}
{t("createOne") ?? "Create one"}
@@ -73,19 +79,19 @@ export default async function LoginPage() {
side={
-
+
{onlineLabel}
-
+
{t("welcomeBack")}
{hotelName}
-
+
{t("welcomeBackSub", { hotelName })}
@@ -95,8 +101,10 @@ export default async function LoginPage() {
-
-
{online}
+
+
+ {online}
+
{onlineLabel}
@@ -114,9 +122,9 @@ export default async function LoginPage() {
)}
-
+
-
+
{totalUsers}
-
+
{thp("statsCitizens")}
+ {latestUsers.length > 0 && (
+
+ {latestUsers.slice(0, 5).map((u) => (
+
+ ))}
+
+ )}
}
>
-
+
{t("title")}
-
{t("subtitle")}
+
{t("subtitle")}
{/* Left: copy */}
-
+
@@ -185,12 +185,12 @@ export default async function Home() {
)}
-
+
{tp("welcome", { hotelName: "" }).trim()}
-
+
{tp("welcomeBody")}
@@ -233,7 +233,7 @@ export default async function Home() {
-
+
{tp("welcomeSubtitle")}
-
+
-
+
-
+
-
+
{tp("statsOnline")}
@@ -293,10 +293,10 @@ export default async function Home() {
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("statsCitizens")}
@@ -309,12 +309,12 @@ export default async function Home() {
{stats.map((s) => (
-
+
{s.value === null ? (
-
+
{te("unavailable")}
) : s.live ? (
@@ -332,7 +332,7 @@ export default async function Home() {
unoptimized
/>
-
+
{s.label}
diff --git a/src/app/(site)/register/page.tsx b/src/app/(site)/register/page.tsx
index e035526d..a3cbfc3e 100644
--- a/src/app/(site)/register/page.tsx
+++ b/src/app/(site)/register/page.tsx
@@ -26,7 +26,7 @@ export default async function RegisterPage() {
]);
const nonce = (await headers()).get("x-nonce") ?? undefined;
- const [online, latestUsers] = await Promise.all([
+ const [online, latestUsers, recentUsers] = await Promise.all([
cached("online_count", 10_000, () =>
db
.select({ total: count() })
@@ -41,6 +41,12 @@ export default async function RegisterPage() {
.orderBy(desc(User.accountCreated))
.limit(6),
).catch(() => []),
+ db
+ .select({ username: User.username, look: User.look })
+ .from(User)
+ .where(eq(User.online, "1"))
+ .limit(6)
+ .catch(() => []),
]);
const benefits = [
@@ -58,28 +64,46 @@ export default async function RegisterPage() {
side={
-
+
{tpr("usersOnline", { count: online })}
-
+
{tpr("title")}
{hotelName}
-
+
{tpr("subtitle")}
+ {recentUsers.length > 0 && (
+
+
+ {recentUsers.slice(0, 5).map((u) => (
+
+ ))}
+
+
+ {tpr("whoIsOnline")}
+
+
+ )}
{benefits.map((b) => (
-
+
{b.title}
-
+
{b.desc}
@@ -121,7 +145,7 @@ export default async function RegisterPage() {
{latestUsers.length > 0 && (
<>
-
+
Latest citizens
diff --git a/src/app/globals.css b/src/app/globals.css
index 146ac131..f67c16ff 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -2097,3 +2097,31 @@ details[open] > summary .details-open\:rotate-180 {
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);
+}
diff --git a/src/components/auth/register-form.tsx b/src/components/auth/register-form.tsx
index 070abeb7..cb79dd1f 100644
--- a/src/components/auth/register-form.tsx
+++ b/src/components/auth/register-form.tsx
@@ -114,7 +114,7 @@ export function RegisterForm({
) : null}
{/* Header Banner */}
-
+
{t("accountDetails")}
-
+
{t("title")}
-
+
{t("subtitle")}
diff --git a/src/components/landing-top-bar.tsx b/src/components/landing-top-bar.tsx
index b798b7d4..3feb04bd 100644
--- a/src/components/landing-top-bar.tsx
+++ b/src/components/landing-top-bar.tsx
@@ -37,15 +37,15 @@ export function LandingTopBar({
unoptimized
/>
)}
-
+
{hotelName}
-