feat(landing): pixel design ported to static Astro + en/es/eu i18n

Adopt the "Carved from stone" pixel design (landing/design/) as the apex
onboarding page, ported from its React/Babel-via-CDN prototype to static
Astro so it works on an offline LAN. Real modded-LAN content replaces the
prototype's fictional public-SMP copy.

- Vendor web fonts locally (tooling/fetch-fonts.sh -> public/fonts/) so the
  page renders without the Google Fonts CDN at party time.
- Port the design system (main.css: mood/hero/bevels) and split markup into
  Astro components (Creeper, PixelIcon, CopyChip, ServerListPanel).
- site.ts holds language-neutral config + theme knobs (mood/hero/headFont/
  dust) that replace the design's in-browser TweaksPanel; LITERALS holds
  never-translated product/in-game terms.
- i18n: English (/), Spanish (/es/), Euskera (/eu/) generated from one
  [...lang].astro via getStaticPaths; copy lives in src/i18n/ui.ts. Nav has
  an EN/ES/EU switcher; html lang + hreflang set per page.
- Status panel shows a static server-list row + honest stat tiles (no fake
  live count). Copy + scroll-reveal are the only client JS.

Build emits to ../www (gitignored). Euskera strings pending a native review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 18:18:36 +02:00
parent 5cec2993d7
commit 3d52951355
60 changed files with 3388 additions and 228 deletions

View File

@@ -0,0 +1,17 @@
---
// MC-GUI slot + copy button. variant "ip" = big gold mono (server address);
// "url" = smaller, wraps (auth / packwiz URLs). Copy handled by the global
// [data-copy] script in [...lang].astro; data-label / data-copied drive the
// (localized) button text and transient feedback.
interface Props {
value: string;
variant?: "ip" | "url";
label?: string;
copiedLabel?: string;
}
const { value, variant = "ip", label = "Copy", copiedLabel = "Copied!" } = Astro.props;
---
<div class:list={["ip-chip", variant === "url" && "url"]}>
<span class="ip-val"><span class="pin">▸</span>{value}</span>
<button type="button" data-copy={value} data-label={label} data-copied={copiedLabel}>{label}</button>
</div>

View File

@@ -0,0 +1,17 @@
---
// 8x8 creeper face — pure markup, styled by .creeper in main.css.
const CREEPER = [
"00000000",
"01100110",
"01100110",
"00011000",
"00111100",
"00111100",
"00100100",
"00000000",
];
const cells = CREEPER.join("").split("");
---
<div class="creeper" aria-hidden="true">
{cells.map((c) => <i class={c === "1" ? "f" : undefined} />)}
</div>

View File

@@ -0,0 +1,20 @@
---
// 7x7 feature glyphs — styled by .picon in main.css.
interface Props {
glyph: "shield" | "diamond" | "orb" | "heart";
gold?: boolean;
}
const { glyph, gold = false } = Astro.props;
const GLYPHS: Record<Props["glyph"], string[]> = {
shield: ["0111110", "1111111", "1111111", "1111111", "0111110", "0011100", "0001000"],
diamond: ["0001000", "0011100", "0111110", "1111111", "0111110", "0011100", "0001000"],
orb: ["0011100", "0111110", "1111111", "1111111", "1111111", "0111110", "0011100"],
heart: ["0110110", "1111111", "1111111", "1111111", "0111110", "0011100", "0001000"],
};
const cells = GLYPHS[glyph].join("").split("");
const onClass = gold ? "go" : "on";
---
<div class="picon" aria-hidden="true">
{cells.map((c) => <i class={c === "1" ? onClass : undefined} />)}
</div>

View File

@@ -0,0 +1,31 @@
---
// Static server-list panel — mirrors the multiplayer-list row. No live count
// (plan 09-landing.md option B); shows version, motd and slot cap.
import Creeper from "./Creeper.astro";
import { site } from "../data/site";
interface Props {
modded: string;
motd: string;
upTo: string;
}
const { modded, motd, upTo } = Astro.props;
const { status } = site;
---
<div class="serverlist">
<div class="icon"><Creeper /></div>
<div class="meta">
<div class="row1">
<span class="title">{site.name}</span>
<span class="ver">Java {site.mcVersion}</span>
</div>
<p class="motd">
<span class="a">⛏ {modded}</span> · {motd}
</p>
</div>
<div class="stat">
<div class="players">
<span class="bars" aria-hidden="true"><i /><i /><i /><i /><i /></span>
<span>{upTo} <b>{status.slots}</b></span>
</div>
</div>
</div>