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 (
<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 ── */}
<Reveal>
<div
@@ -327,98 +327,132 @@ export default async function Home() {
{/* ── Features ── */}
<Reveal>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3 sm:gap-4">
{features.map((f) => (
<SurfaceCard
key={f.title}
className="card-glow relative p-4 sm:p-5 text-center hover:-translate-y-1"
>
<div
className="mx-auto mb-3 flex h-11 w-11 sm:h-12 sm:w-12 items-center justify-center rounded-xl"
style={{
background:
"color-mix(in srgb, var(--color-primary) 14%, transparent)",
boxShadow:
"inset 0 0 0 1px color-mix(in srgb, var(--color-primary) 10%, transparent)",
}}
<div className="flex flex-col gap-4">
<div>
<span className="eyebrow">{tp("sectionFeaturesLabel")}</span>
<h2 className="section-title mt-2 text-xl sm:text-2xl">
{tp("sectionFeaturesTitle")}
</h2>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3 sm:gap-4">
{features.map((f) => (
<SurfaceCard
key={f.title}
className="card-glow relative p-4 sm:p-5 text-center hover:-translate-y-1"
>
<Image src={f.icon} alt="" width={24} height={24} unoptimized />
</div>
<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
className="mx-auto mb-3 flex h-11 w-11 sm:h-12 sm:w-12 items-center justify-center rounded-xl"
style={{
background:
"color-mix(in srgb, var(--color-primary) 14%, transparent)",
boxShadow:
"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>
<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>
</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="card-glow relative p-3 sm:p-5 text-center hover:-translate-y-1"
>
<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)" }}
<div className="flex flex-col gap-4">
<div>
<span className="eyebrow">{tp("sectionStatsLabel")}</span>
<h2 className="section-title mt-2 text-xl sm:text-2xl">
{tp("sectionStatsTitle")}
</h2>
</div>
<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="card-glow relative p-3 sm:p-5 text-center hover:-translate-y-1"
>
<Image src={s.icon} alt="" width={14} height={14} unoptimized />
{s.label}
</div>
</SurfaceCard>
))}
<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>
</div>
</Reveal>
{/* ── 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">
<Reveal className="flex w-full flex-col gap-4 md:w-1/3 md:flex-shrink-0">
<SurfaceCard
+28
View File
@@ -2055,3 +2055,31 @@ details[open] > summary .details-open\:rotate-180 {
background-clip: text;
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",
"featuresCommunityDesc": "Make friends and chat",
"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": {
"title": "News",