Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 11m31s
312 lines
11 KiB
TypeScript
312 lines
11 KiB
TypeScript
import { getI18n } from "@/lib/i18n";
|
||
import Link from "next/link";
|
||
import Image from "next/image";
|
||
import {
|
||
ArrowRight,
|
||
Compass,
|
||
MapPin,
|
||
Timer,
|
||
Blocks,
|
||
Upload,
|
||
Download,
|
||
} from "lucide-react";
|
||
import { Header, Footer } from "@/components/Shell";
|
||
import { MapCard } from "@/components/MapCard";
|
||
import { catalog } from "@/lib/catalog";
|
||
import { leaderboard } from "@/lib/leaderboard";
|
||
import { LeaderboardPreview } from "@/components/Leaderboard";
|
||
import desktopRelease from "@/shared/desktop-release.json";
|
||
import { FirstVisitIntro } from "@/components/FirstVisitIntro";
|
||
import { SiteTrailer } from "@/components/SiteTrailer";
|
||
import { Suspense } from "react";
|
||
export const dynamic = "force-dynamic";
|
||
export default async function Home() {
|
||
const { t, formatNumber } = await getI18n();
|
||
const rankings = leaderboard({ limit: 5 });
|
||
const maps = await catalog();
|
||
const servers = new Set(maps.map((map) => map.server_id)).size;
|
||
const locations = maps.reduce((total, map) => total + map.panorama_count, 0);
|
||
return (
|
||
<>
|
||
<FirstVisitIntro />
|
||
<Header />
|
||
<main className="home-page" id="main-content" tabIndex={-1}>
|
||
<section className="hero">
|
||
<div className="hero-copy">
|
||
<div className="status-label">{t("A Minecraft guessing game")}</div>
|
||
<h1>
|
||
{t("Guess where you are")}
|
||
<br />
|
||
{t("in a")} <span>{t("blocky world.")}</span>
|
||
</h1>
|
||
<p>
|
||
{t("Drop into a random corner of a Minecraft world.")}
|
||
<br className="desktop" />{" "}
|
||
{t("Look around, spot the landmarks, and place your pin")}
|
||
<br className="desktop" /> {t("before the clock runs out.")}
|
||
</p>
|
||
<div className="hero-actions">
|
||
<Link href="/servers" className="button primary">
|
||
{t("Start exploring")} <ArrowRight size={18} />
|
||
</Link>
|
||
<a href={desktopRelease.file} className="button" download>
|
||
<Download size={18} /> {t("Download for Windows")}
|
||
</a>
|
||
<Link href="/leaderboard" className="text-link">
|
||
{t("Meet the top explorers")} <ArrowRight size={14} />
|
||
</Link>
|
||
</div>
|
||
<div className="hero-notes">
|
||
<span className="round-blocks" aria-hidden="true">
|
||
<i />
|
||
<i />
|
||
<i />
|
||
<i />
|
||
<i />
|
||
</span>
|
||
<span>{t("Five rounds. No roads. Just your instincts.")}</span>
|
||
</div>
|
||
</div>
|
||
<div className="hero-art" aria-label={t("Minecraft surface map")}>
|
||
<div className="map-art-frame minecraft-paper">
|
||
<Image
|
||
src={maps[0]?.mapImage || "/images/example-map.webp"}
|
||
alt={t(
|
||
"A Minecraft surface map on the original Minecraft parchment texture",
|
||
)}
|
||
width={1024}
|
||
height={1024}
|
||
loading="eager"
|
||
fetchPriority="high"
|
||
unoptimized
|
||
/>
|
||
<span className="square-pin pin-green" />
|
||
<span className="square-pin pin-red" />
|
||
<span className="square-pin pin-gold" />
|
||
</div>
|
||
<span className="compass-art" aria-hidden="true">
|
||
<Compass size={43} />
|
||
<small>N</small>
|
||
</span>
|
||
<div className="floating-tag">
|
||
<MapPin size={14} />
|
||
<strong>X: ??? Z: ???</strong>
|
||
<span>{t("Overworld")}</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<div className="grass-divider" aria-hidden="true">
|
||
<span />
|
||
</div>
|
||
<section className="world-stats" aria-label={t("Community statistics")}>
|
||
<div>
|
||
<i className="stat-cube green-cube" />
|
||
<div>
|
||
<strong>{formatNumber(servers)}</strong>
|
||
<span>{t("COMMUNITY SERVERS")}</span>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<i className="stat-cube gold-cube" />
|
||
<div>
|
||
<strong>{formatNumber(maps.length)}</strong>
|
||
<span>{t("PUBLISHED MAPS")}</span>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<i className="stat-cube cyan-cube" />
|
||
<div>
|
||
<strong>{formatNumber(locations)}</strong>
|
||
<span>{t("PLACES TO DISCOVER")}</span>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<i className="stat-cube red-cube" />
|
||
<div>
|
||
<strong>{t("5 rounds")}</strong>
|
||
<span>{t("EVERY EXPEDITION")}</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section className="section catalog-section">
|
||
<div className="section-heading">
|
||
<div>
|
||
<span className="eyebrow">{t("Community maps")}</span>
|
||
<h2>{t("Explore community worlds.")}</h2>
|
||
</div>
|
||
<Link className="text-link" href="/servers">
|
||
{t("All maps ({count})", { count: maps.length })}{" "}
|
||
<ArrowRight size={15} />
|
||
</Link>
|
||
</div>
|
||
{maps.length ? (
|
||
<div className="cards-grid">
|
||
{maps.slice(0, 4).map((map, index) => (
|
||
<MapCard
|
||
map={map}
|
||
index={index}
|
||
key={map.id}
|
||
minecraftPaper
|
||
loading="lazy"
|
||
/>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<div className="first-map-panel">
|
||
<div className="empty-map-art minecraft-paper" aria-hidden="true">
|
||
<Image
|
||
src="/images/example-map.webp"
|
||
alt=""
|
||
width={180}
|
||
height={180}
|
||
unoptimized
|
||
/>
|
||
<span>{t("YOUR WORLD HERE")}</span>
|
||
</div>
|
||
<div>
|
||
<span className="eyebrow">
|
||
{t("THE FIRST EXPEDITION STARTS WITH YOU")}
|
||
</span>
|
||
<h3>{t("Put your server on the map.")}</h3>
|
||
<p>
|
||
{t(
|
||
"No community maps have been published yet. Export your world with the server plugin, or capture five locations with the Fabric mod. Preview your map and submit it for review.",
|
||
)}
|
||
</p>
|
||
<Link className="button primary" href="/downloads">
|
||
<Upload size={16} /> {t("Get the capture tools")}
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</section>
|
||
<SiteTrailer />
|
||
<Suspense
|
||
fallback={
|
||
<section
|
||
className="section leaderboard-preview-placeholder"
|
||
aria-busy="true"
|
||
>
|
||
<h2>{t("THE EXPLORERS’ BOARD")}</h2>
|
||
<div className="loading-surface" />
|
||
</section>
|
||
}
|
||
>
|
||
<HomeLeaderboard data={rankings} />
|
||
</Suspense>
|
||
<section className="section home-panels">
|
||
<article className="home-panel">
|
||
<span className="eyebrow">{t("How to play")}</span>
|
||
<h2>{t("Know your blocks?")}</h2>
|
||
<ol className="play-steps">
|
||
<li>
|
||
<span>01</span>
|
||
<div>
|
||
<h3>{t("Pick your world")}</h3>
|
||
<p>{t("Choose a community server, map, and difficulty.")}</p>
|
||
</div>
|
||
</li>
|
||
<li>
|
||
<span>02</span>
|
||
<div>
|
||
<h3>{t("Look for the clues")}</h3>
|
||
<p>
|
||
{t(
|
||
"Rotate the panorama. Find builds, biomes, and landmarks.",
|
||
)}
|
||
</p>
|
||
</div>
|
||
</li>
|
||
<li>
|
||
<span>03</span>
|
||
<div>
|
||
<h3>{t("Make your guess")}</h3>
|
||
<p>
|
||
{t(
|
||
"Drop a pin on the map. Closer guesses earn more points.",
|
||
)}
|
||
</p>
|
||
</div>
|
||
</li>
|
||
</ol>
|
||
<Link className="text-link" href="/about">
|
||
{t("Read the field guide")} <ArrowRight size={14} />
|
||
</Link>
|
||
</article>
|
||
<article className="home-panel">
|
||
<span className="eyebrow">{t("Choose a difficulty")}</span>
|
||
<h2>{t("Pick a difficulty.")}</h2>
|
||
<div className="mode-list">
|
||
{[
|
||
{
|
||
name: t("Easy"),
|
||
time: t("180 seconds"),
|
||
detail: t("Take your time. Rotate and zoom."),
|
||
color: "green",
|
||
},
|
||
{
|
||
name: t("Normal"),
|
||
time: t("90 seconds"),
|
||
detail: t("Trust your eye. Rotate and zoom."),
|
||
color: "gold",
|
||
},
|
||
{
|
||
name: t("Hard"),
|
||
time: t("30 seconds"),
|
||
detail: t("Think fast. No zoom allowed."),
|
||
color: "red",
|
||
},
|
||
{
|
||
name: t("Void"),
|
||
time: t("10 seconds"),
|
||
detail: t("The ultimate test. Fixed view, strict scoring."),
|
||
color: "void",
|
||
},
|
||
].map((mode) => (
|
||
<Link href="/servers" className="mode-row" key={mode.name}>
|
||
<i className={`stat-cube ${mode.color}-cube`} />
|
||
<div>
|
||
<h3>
|
||
{t(mode.name)}
|
||
<span>
|
||
<Timer size={12} />
|
||
{t(mode.time)}
|
||
</span>
|
||
</h3>
|
||
<p>{t(mode.detail)}</p>
|
||
</div>
|
||
<ArrowRight size={15} />
|
||
</Link>
|
||
))}
|
||
</div>
|
||
<p className="mode-footnote">
|
||
{t("Up to 5,000 points per round. How close can you get?")}
|
||
</p>
|
||
</article>
|
||
</section>
|
||
<section className="section contribute-banner">
|
||
<Blocks size={38} />
|
||
<div>
|
||
<span className="eyebrow">{t("Made by MineGeo players")}</span>
|
||
<h2>{t("Put your world on the map.")}</h2>
|
||
<p>
|
||
{t("Share your builds and your favorite corners of Minecraft.")}
|
||
</p>
|
||
</div>
|
||
<Link href="/downloads" className="button">
|
||
{t("Create a map")} <ArrowRight size={16} />
|
||
</Link>
|
||
</section>
|
||
</main>
|
||
<Footer />
|
||
</>
|
||
);
|
||
}
|
||
async function HomeLeaderboard({
|
||
data,
|
||
}: {
|
||
data: ReturnType<typeof leaderboard>;
|
||
}) {
|
||
return <LeaderboardPreview data={await data} />;
|
||
}
|