feat(site): structured landing sections with eyebrow headings
CI / check (push) Successful in 2m42s
CI / deploy (push) Successful in 1m25s
CI / publish-container (push) Successful in 54s

Give the home page a clear, professional information hierarchy:
- Add eyebrow labels + headings for Features, Live stats and Community
  sections using reusable, theme-aware .eyebrow / .section-title styles.
- Loosen the vertical rhythm (gap-8/10) so each block breathes.
- New messages resolve via the existing English fallback for all locales.
This commit is contained in:
openhands committed 2026-09-12 16:44:21 +02:00
1 parent 3265cf1fad
commit bf5b9918d9
3 files changed
+151 -83

No files matched your search

+116 -82
View File
@@ -139,7 +139,7 @@ export default async function Home() {
]; ];
return ( 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"> <div className="mx-auto w-full max-w-6xl flex flex-col gap-8 px-1 sm:px-0 md:gap-10 pb-12 md:pb-16">
{/* ── Top Bar ── */} {/* ── Top Bar ── */}
<Reveal> <Reveal>
<div <div
@@ -327,98 +327,132 @@ export default async function Home() {
{/* ── Features ── */} {/* ── Features ── */}
<Reveal> <Reveal>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3 sm:gap-4"> <div className="flex flex-col gap-4">
{features.map((f) => ( <div>
<SurfaceCard <span className="eyebrow">{tp("sectionFeaturesLabel")}</span>
key={f.title} <h2 className="section-title mt-2 text-xl sm:text-2xl">
className="card-glow relative p-4 sm:p-5 text-center hover:-translate-y-1" {tp("sectionFeaturesTitle")}
> </h2>
<div </div>
className="mx-auto mb-3 flex h-11 w-11 sm:h-12 sm:w-12 items-center justify-center rounded-xl" <div className="grid grid-cols-1 gap-3 sm:grid-cols-3 sm:gap-4">
style={{ {features.map((f) => (
background: <SurfaceCard
"color-mix(in srgb, var(--color-primary) 14%, transparent)", key={f.title}
boxShadow: className="card-glow relative p-4 sm:p-5 text-center hover:-translate-y-1"
"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
</div> className="mx-auto mb-3 flex h-11 w-11 sm:h-12 sm:w-12 items-center justify-center rounded-xl"
<h3 style={{
className="text-sm sm:text-base font-extrabold mb-1" background:
style={{ "color-mix(in srgb, var(--color-primary) 14%, transparent)",
color: "var(--color-text-readable)", boxShadow:
fontFamily: "var(--font-nunito)", "inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 10%, transparent)",
}} }}
> >
{f.title} <Image
</h3> src={f.icon}
<p alt=""
className="text-xs sm:text-sm leading-relaxed" width={24}
style={{ color: "var(--color-text-muted)" }} height={24}
> unoptimized
{f.desc} />
</p> </div>
</SurfaceCard> <h3
))} className="text-sm sm:text-base font-extrabold mb-1"
style={{
color: "var(--color-text-readable)",
fontFamily: "var(--font-nunito)",
}}
>
{f.title}
</h3>
<p
className="text-xs sm:text-sm leading-relaxed"
style={{ color: "var(--color-text-muted)" }}
>
{f.desc}
</p>
</SurfaceCard>
))}
</div>
</div> </div>
</Reveal> </Reveal>
{/* ── Stats + Login ── */} {/* ── Stats + Login ── */}
<Reveal> <Reveal>
<div className="grid grid-cols-2 gap-3 sm:gap-4 md:grid-cols-4"> <div className="flex flex-col gap-4">
{[ <div>
{ <span className="eyebrow">{tp("sectionStatsLabel")}</span>
value: users, <h2 className="section-title mt-2 text-xl sm:text-2xl">
label: tp("statsCitizens"), {tp("sectionStatsTitle")}
icon: "/assets/images/icons/friends.png", </h2>
live: false, </div>
}, <div className="grid grid-cols-2 gap-3 sm:gap-4 md:grid-cols-4">
{ {[
value: rooms, {
label: tp("statsRooms"), value: users,
icon: "/assets/images/icons/catalog.png", label: tp("statsCitizens"),
live: false, icon: "/assets/images/icons/friends.png",
}, live: false,
{ },
value: online, {
label: tp("statsOnline"), value: rooms,
icon: "/assets/images/icons/navigation/me.png", label: tp("statsRooms"),
live: true, icon: "/assets/images/icons/catalog.png",
}, live: false,
{ },
value: articles?.length ?? null, {
label: tp("statsArticles"), value: online,
icon: "/assets/images/icons/article.gif", label: tp("statsOnline"),
live: false, icon: "/assets/images/icons/navigation/me.png",
}, live: true,
].map((s) => ( },
<SurfaceCard {
key={s.label} value: articles?.length ?? null,
className="card-glow relative p-3 sm:p-5 text-center hover:-translate-y-1" label: tp("statsArticles"),
> icon: "/assets/images/icons/article.gif",
<div className="mb-0.5 text-2xl sm:text-3xl font-black tracking-tight md:text-4xl"> live: false,
{s.value === null ? ( },
<span className="text-base">{te("unavailable")}</span> ].map((s) => (
) : s.live ? ( <SurfaceCard
<LiveOnlineCounter initial={s.value} /> key={s.label}
) : ( className="card-glow relative p-3 sm:p-5 text-center hover:-translate-y-1"
<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 /> <div className="mb-0.5 text-2xl sm:text-3xl font-black tracking-tight md:text-4xl">
{s.label} {s.value === null ? (
</div> <span className="text-base">{te("unavailable")}</span>
</SurfaceCard> ) : 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>
</div> </div>
</Reveal> </Reveal>
{/* ── Login + Online users ── */} {/* ── Login + Online users ── */}
<div>
<span className="eyebrow">{tp("sectionCommunityLabel")}</span>
<h2 className="section-title mt-2 text-xl sm:text-2xl">
{tp("sectionCommunityTitle")}
</h2>
</div>
<div className="flex flex-col gap-4 md:flex-row"> <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"> <Reveal className="flex w-full flex-col gap-4 md:w-1/3 md:flex-shrink-0">
<SurfaceCard <SurfaceCard
+28
View File
@@ -2055,3 +2055,31 @@ details[open] > summary .details-open\:rotate-180 {
background-clip: text; background-clip: text;
color: transparent; color: transparent;
} }
/* Section eyebrow label with a small accent tick. */
.eyebrow {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--color-primary-readable, var(--color-primary));
}
.eyebrow::before {
content: "";
width: 1.25rem;
height: 2px;
border-radius: 999px;
background: currentColor;
opacity: 0.75;
}
/* Section heading on the public landing pages. */
.section-title {
color: var(--color-text-readable);
font-family: var(--font-nunito);
font-weight: 900;
letter-spacing: -0.02em;
}
+7 -1
View File
@@ -282,7 +282,13 @@
"featuresCommunity": "Community", "featuresCommunity": "Community",
"featuresCommunityDesc": "Make friends and chat", "featuresCommunityDesc": "Make friends and chat",
"featuresGames": "Games", "featuresGames": "Games",
"featuresGamesDesc": "Compete and have fun" "featuresGamesDesc": "Compete and have fun",
"sectionFeaturesLabel": "Features",
"sectionFeaturesTitle": "Everything a great hotel needs",
"sectionStatsLabel": "Live stats",
"sectionStatsTitle": "A community that keeps growing",
"sectionCommunityLabel": "Community",
"sectionCommunityTitle": "Make yourself at home"
}, },
"news": { "news": {
"title": "News", "title": "News",