feat(landing): rework — es default, 2-step join, roster + account polish
- Default language Spanish at root /, English moves to /en/ (eu unchanged):
getStaticPaths {lang:undefined}->es + LANG_*_PATH maps swapped in ui.ts.
- Section order: hero -> join -> status -> members -> mods -> features.
- Join flow 3 -> 2 steps (drop the Connect/IP step; launcher handles the
address). Remove the hero IP CopyChip. Header CTA renamed Play/Jugar/Jolastu
and gains Register + Login links on all four pages.
- Account page: hide the whole login block once authenticated (not just the
form), add an avatar Refresh button (cache-busted NMSR re-fetch), flatten the
avatar (no bevel). New i18n key account.refresh.
- Register page: new REGISTRATION_SHOW_INVITE env (default false/hidden) toggles
the invite-code field independently of the REGISTRATION_MODE backend gate.
- Members roster: drop the `admin` account, render full-body via a new
ROSTER_AVATAR_MODE (default fullbodyiso); portrait tiles.
- Style: flatten emboss on feature icons (.picon) + player tiles (.roster-tile).
PixelIcon gains optional image support (/icons/<name>.png|svg).
- Favicon now /favicon.png (committed) on all pages.
- Docs: plan/09-landing.md Assets section (logo/favicon/features-icons drop
points) + default-language note; .env.example documents the two new vars.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
c233b1bfbc
commit
c8a6c77a8e
12 files changed
+267
-207
No files matched your search
@@ -1,9 +1,8 @@
|
||||
---
|
||||
import { site, LITERALS, HEAD_FONTS } from "../data/site";
|
||||
import { ui, LANGS, LANG_LABEL, LANG_PATH, LANG_REGISTER_PATH, type Lang } from "../i18n/ui";
|
||||
import { site, HEAD_FONTS } from "../data/site";
|
||||
import { ui, LANGS, LANG_LABEL, LANG_PATH, LANG_REGISTER_PATH, LANG_ACCOUNT_PATH, type Lang } from "../i18n/ui";
|
||||
import Creeper from "../components/Creeper.astro";
|
||||
import PixelIcon from "../components/PixelIcon.astro";
|
||||
import CopyChip from "../components/CopyChip.astro";
|
||||
import ServerCard from "../components/ServerCard.astro";
|
||||
import PlayerRoster from "../components/PlayerRoster.astro";
|
||||
import ModList from "../components/ModList.astro";
|
||||
@@ -12,8 +11,8 @@ import "../styles/main.css";
|
||||
// One static page per locale: en at "/", es at "/es/", eu at "/eu/".
|
||||
export function getStaticPaths() {
|
||||
return [
|
||||
{ params: { lang: undefined }, props: { locale: "en" as Lang } },
|
||||
{ params: { lang: "es" }, props: { locale: "es" as Lang } },
|
||||
{ params: { lang: undefined }, props: { locale: "es" as Lang } },
|
||||
{ params: { lang: "en" }, props: { locale: "en" as Lang } },
|
||||
{ params: { lang: "eu" }, props: { locale: "eu" as Lang } },
|
||||
];
|
||||
}
|
||||
@@ -24,6 +23,7 @@ const { theme, launcherManifest } = site;
|
||||
const headFont = HEAD_FONTS[theme.headFont] ?? HEAD_FONTS.pixelify;
|
||||
const launcherName = launcherManifest.product;
|
||||
const registerPath = LANG_REGISTER_PATH[locale];
|
||||
const accountPath = LANG_ACCOUNT_PATH[locale];
|
||||
|
||||
// Per-OS download buttons (join step 2) from the synced manifest. Missing
|
||||
// manifest → empty builds → no buttons (the build never fails on it).
|
||||
@@ -60,7 +60,7 @@ const dustBits = Array.from({ length: 16 }, (_, i) => {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{site.name} — {t.hero.howToJoin}</title>
|
||||
<meta name="description" content={`${site.name}: ${t.hero.tagline}`} />
|
||||
<link rel="icon" type="image/png" href="/logo.png" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="stylesheet" href="/fonts/fonts.css" />
|
||||
{LANGS.map((l) => <link rel="alternate" hreflang={l} href={LANG_PATH[l]} />)}
|
||||
</head>
|
||||
@@ -73,11 +73,11 @@ const dustBits = Array.from({ length: 16 }, (_, i) => {
|
||||
<span class="name">ULICRAFT</span>
|
||||
</a>
|
||||
<nav class="nav-links">
|
||||
<a href="#join">{t.nav.join}</a>
|
||||
<a href="#status">{t.nav.status}</a>
|
||||
<a href="#members">{t.nav.members}</a>
|
||||
<a href="#features">{t.nav.features}</a>
|
||||
<a href="#mods">{t.nav.mods}</a>
|
||||
<a href="#join">{t.nav.join}</a>
|
||||
<a href="#features">{t.nav.features}</a>
|
||||
</nav>
|
||||
<span class="nav-spacer"></span>
|
||||
<div class="lang-switch" aria-label="Language">
|
||||
@@ -85,6 +85,8 @@ const dustBits = Array.from({ length: 16 }, (_, i) => {
|
||||
<a href={LANG_PATH[l]} class:list={[l === locale && "on"]} aria-current={l === locale ? "true" : undefined}>{LANG_LABEL[l]}</a>
|
||||
))}
|
||||
</div>
|
||||
<a class="mc-btn sm" href={registerPath}>{t.nav.register}</a>
|
||||
<a class="mc-btn sm" href={accountPath}>{t.nav.login}</a>
|
||||
<a class="mc-btn primary sm" href="#join">{t.nav.play}</a>
|
||||
</div>
|
||||
</header>
|
||||
@@ -110,8 +112,7 @@ const dustBits = Array.from({ length: 16 }, (_, i) => {
|
||||
<h1 class="hero-tagline">{t.hero.tagline}</h1>
|
||||
<p class="hero-sub">{t.hero.sub}</p>
|
||||
<div class="hero-ip-row">
|
||||
<CopyChip value={site.serverAddress} variant="ip" label={t.hero.copyIp} copiedLabel={t.copied} />
|
||||
<a class="mc-btn" href="#join">{t.hero.howToJoin}</a>
|
||||
<a class="mc-btn primary" href="#join">{t.hero.howToJoin}</a>
|
||||
</div>
|
||||
<div class="hero-meta">
|
||||
<span>Java {site.mcVersion}</span>
|
||||
@@ -131,82 +132,6 @@ const dustBits = Array.from({ length: 16 }, (_, i) => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- STATUS -->
|
||||
<section id="status" class="pad">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<span class="eyebrow">{t.status.eyebrow}</span>
|
||||
<h2 class="section-title">{t.status.title}</h2>
|
||||
<p class="lead">{t.status.lead}</p>
|
||||
</div>
|
||||
<div class="reveal">
|
||||
<ServerCard
|
||||
modded={t.status.modded}
|
||||
motd={t.status.motd}
|
||||
upTo={t.status.upTo}
|
||||
/>
|
||||
</div>
|
||||
<div class="stat-grid">
|
||||
{site.stats.map((s, i) => (
|
||||
<div class="tile reveal" style={`transition-delay:${i * 60}ms`}>
|
||||
<div class="k">{s.key}</div>
|
||||
<div class="l">{t.statLabels[i]}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MEMBERS (registered players roster) -->
|
||||
<section id="members" class="pad" style="background: var(--bg-2)">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<span class="eyebrow">{t.members.eyebrow}</span>
|
||||
<h2 class="section-title">{t.members.title}</h2>
|
||||
<p class="lead">{t.members.lead}</p>
|
||||
</div>
|
||||
<div class="reveal">
|
||||
<PlayerRoster emptyLabel={t.members.empty} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FEATURES -->
|
||||
<section id="features" class="pad">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<span class="eyebrow">{t.features.eyebrow}</span>
|
||||
<h2 class="section-title">{t.features.title}</h2>
|
||||
<p class="lead">{t.features.lead}</p>
|
||||
</div>
|
||||
<div class="feat-grid">
|
||||
{site.features.map((f, i) => (
|
||||
<div class="feat reveal" style={`transition-delay:${(i % 2) * 80}ms`}>
|
||||
<PixelIcon glyph={f.glyph} gold={f.gold} />
|
||||
<div>
|
||||
<h3>{t.features.items[i].h}</h3>
|
||||
<p>{t.features.items[i].p}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MODS (installed-pack list, auto-generated) -->
|
||||
<section id="mods" class="pad" style="background: var(--bg-2)">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<span class="eyebrow">{t.mods.eyebrow}</span>
|
||||
<h2 class="section-title">{t.mods.title}</h2>
|
||||
<p class="lead">{t.mods.lead}</p>
|
||||
</div>
|
||||
<div class="reveal">
|
||||
<ModList emptyLabel={t.mods.empty} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- HOW TO JOIN -->
|
||||
<section id="join" class="pad">
|
||||
<div class="wrap">
|
||||
@@ -247,21 +172,82 @@ const dustBits = Array.from({ length: 16 }, (_, i) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- STEP 3 -->
|
||||
<div class="step reveal">
|
||||
<div class="num">3</div>
|
||||
<div class="body">
|
||||
<span class="kicker">{t.join.s3.kicker}</span>
|
||||
<h3>{t.join.s3.h}</h3>
|
||||
<p>
|
||||
{t.join.s3.pa} <kbd>{LITERALS.multiplayer}</kbd> → <kbd>{LITERALS.addServer}</kbd>{t.join.s3.pb}
|
||||
</p>
|
||||
<div class="actions">
|
||||
<CopyChip value={site.serverAddress} variant="ip" label={t.join.s3.copy} copiedLabel={t.copied} />
|
||||
<!-- STATUS -->
|
||||
<section id="status" class="pad">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<span class="eyebrow">{t.status.eyebrow}</span>
|
||||
<h2 class="section-title">{t.status.title}</h2>
|
||||
<p class="lead">{t.status.lead}</p>
|
||||
</div>
|
||||
<div class="reveal">
|
||||
<ServerCard
|
||||
modded={t.status.modded}
|
||||
motd={t.status.motd}
|
||||
upTo={t.status.upTo}
|
||||
/>
|
||||
</div>
|
||||
<div class="stat-grid">
|
||||
{site.stats.map((s, i) => (
|
||||
<div class="tile reveal" style={`transition-delay:${i * 60}ms`}>
|
||||
<div class="k">{s.key}</div>
|
||||
<div class="l">{t.statLabels[i]}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MEMBERS (registered players roster) -->
|
||||
<section id="members" class="pad" style="background: var(--bg-2)">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<span class="eyebrow">{t.members.eyebrow}</span>
|
||||
<h2 class="section-title">{t.members.title}</h2>
|
||||
<p class="lead">{t.members.lead}</p>
|
||||
</div>
|
||||
<div class="reveal">
|
||||
<PlayerRoster emptyLabel={t.members.empty} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MODS (installed-pack list, auto-generated) -->
|
||||
<section id="mods" class="pad" style="background: var(--bg-2)">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<span class="eyebrow">{t.mods.eyebrow}</span>
|
||||
<h2 class="section-title">{t.mods.title}</h2>
|
||||
<p class="lead">{t.mods.lead}</p>
|
||||
</div>
|
||||
<div class="reveal">
|
||||
<ModList emptyLabel={t.mods.empty} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FEATURES -->
|
||||
<section id="features" class="pad">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<span class="eyebrow">{t.features.eyebrow}</span>
|
||||
<h2 class="section-title">{t.features.title}</h2>
|
||||
<p class="lead">{t.features.lead}</p>
|
||||
</div>
|
||||
<div class="feat-grid">
|
||||
{site.features.map((f, i) => (
|
||||
<div class="feat reveal" style={`transition-delay:${(i % 2) * 80}ms`}>
|
||||
<PixelIcon glyph={f.glyph} gold={f.gold} />
|
||||
<div>
|
||||
<h3>{t.features.items[i].h}</h3>
|
||||
<p>{t.features.items[i].p}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in new issue
Block a user