Improve accessibility and polish page styling
Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 11m31s

This commit is contained in:
2026-10-24 14:11:17 +05:00
parent 060e8f27ef
commit 927923dc95
34 changed files with 99 additions and 48 deletions

View File

@@ -6,7 +6,7 @@ export default async function About() {
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content prose"> <main className="section page-content prose" id="main-content" tabIndex={-1}>
<span className="eyebrow">{t("THE EXPLORER’S FIELD GUIDE")}</span> <span className="eyebrow">{t("THE EXPLORER’S FIELD GUIDE")}</span>
<h1> <h1>
{t("You know this place.")} {t("You know this place.")}

View File

@@ -9,7 +9,7 @@ export default async function AdminPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content moderation-page"> <main className="section page-content moderation-page" id="main-content" tabIndex={-1}>
<span className="eyebrow">MINEGEO ADMIN</span> <span className="eyebrow">MINEGEO ADMIN</span>
<h1>{t("Admin Tools.")}</h1> <h1>{t("Admin Tools.")}</h1>
<p className="lead"> <p className="lead">

View File

@@ -30,7 +30,7 @@ export default async function ResetPassword() {
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<section className="auth-card reset-card"> <section className="auth-card reset-card">
<span className="eyebrow">{t("ACCOUNT RECOVERY")}</span> <span className="eyebrow">{t("ACCOUNT RECOVERY")}</span>
<h1>{t("A fresh start.")}</h1> <h1>{t("A fresh start.")}</h1>

View File

@@ -12,7 +12,7 @@ export default async function AppealPage() {
return ( return (
<> <>
<Header /> <Header />
<main className={`section page-content ${styles.page}`}> <main className={`section page-content ${styles.page}`} id="main-content" tabIndex={-1}>
<span className="eyebrow">MINEGEO</span> <span className="eyebrow">MINEGEO</span>
<h1>{ru ? "Доступ и апелляции." : "Account access & appeals."}</h1> <h1>{ru ? "Доступ и апелляции." : "Account access & appeals."}</h1>
{account ? ( {account ? (

View File

@@ -28,7 +28,7 @@ export default async function ChangelogsPage({
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content changelog-page"> <main className="section page-content changelog-page" id="main-content" tabIndex={-1}>
{!before && <ChangelogRefresh />} {!before && <ChangelogRefresh />}
<span className="eyebrow">{t("WHAT'S NEW")}</span> <span className="eyebrow">{t("WHAT'S NEW")}</span>
<h1>{t("Changelogs")}</h1> <h1>{t("Changelogs")}</h1>

View File

@@ -18,7 +18,7 @@ export default async function Draft({
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<span className="eyebrow">{t("YOUR WORLD · YOUR LOCATIONS")}</span> <span className="eyebrow">{t("YOUR WORLD · YOUR LOCATIONS")}</span>
<h1>{t("Map workshop")}</h1> <h1>{t("Map workshop")}</h1>
<DraftEditor id={id} hostName={profile.display_name} /> <DraftEditor id={id} hostName={profile.display_name} />

View File

@@ -28,7 +28,7 @@ export default async function DashboardPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
{account ? ( {account ? (
<> <>
<span className="eyebrow">{t("THE CARTOGRAPHER’S DESK")}</span> <span className="eyebrow">{t("THE CARTOGRAPHER’S DESK")}</span>

View File

@@ -14,7 +14,7 @@ export default async function DeveloperApiPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<DeveloperPanel signedIn={Boolean(account)} /> <DeveloperPanel signedIn={Boolean(account)} />
</main> </main>
<Footer /> <Footer />

View File

@@ -93,7 +93,7 @@ export default async function Docs({
return ( return (
<> <>
<Header /> <Header />
<main className={`section page-content ${styles.layout}`}> <main className={`section page-content ${styles.layout}`} id="main-content" tabIndex={-1}>
<aside> <aside>
<h2>Player docs</h2> <h2>Player docs</h2>
<nav aria-label="Documentation pages" className={styles.menu}> <nav aria-label="Documentation pages" className={styles.menu}>

View File

@@ -14,8 +14,8 @@ export default async function Downloads() {
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<article className="tool-card" style={{ marginBottom: 40 }}> <article className="tool-card desktop-download-card">
<span className="eyebrow">{t("MINEGEO DESKTOP")}</span> <span className="eyebrow">{t("MINEGEO DESKTOP")}</span>
<h2>{t("Your next expedition, on your desktop.")}</h2> <h2>{t("Your next expedition, on your desktop.")}</h2>
<p className="muted"> <p className="muted">
@@ -26,7 +26,7 @@ export default async function Downloads() {
<a className="button primary" href={desktopRelease.file} download> <a className="button primary" href={desktopRelease.file} download>
<Download size={16} /> {t("Download for Windows")} <Download size={16} /> {t("Download for Windows")}
</a> </a>
<p className="muted" style={{ marginTop: 16 }}> <p className="muted desktop-download-version">
{t( {t(
"Windows 10/11 · 64-bit · Version {version} · Internet required", "Windows 10/11 · 64-bit · Version {version} · Internet required",
{ version: desktopRelease.version }, { version: desktopRelease.version },
@@ -87,10 +87,9 @@ export default async function Downloads() {
) ? ( ) ? (
<a <a
key={version} key={version}
className="button primary" className="button primary download-build-link"
href={`/tools/mod/minegeo-fabric-${version}-${release}.jar`} href={`/tools/mod/minegeo-fabric-${version}-${release}.jar`}
download download
style={{ marginRight: 8, marginBottom: 8 }}
> >
<Download size={16} /> {t("Download Fabric mod")} · {version} <Download size={16} /> {t("Download Fabric mod")} · {version}
</a> </a>
@@ -100,7 +99,7 @@ export default async function Downloads() {
</p> </p>
), ),
)} )}
<p className="muted" style={{ fontSize: 11, marginTop: 20 }}> <p className="muted tool-card-note tool-card-note-spaced">
{t( {t(
"Install the JAR in your Minecraft mods folder alongside Fabric API.", "Install the JAR in your Minecraft mods folder alongside Fabric API.",
)} )}
@@ -156,7 +155,7 @@ export default async function Downloads() {
". The manifest appears when export finishes. Upload sends the latest completed export; it does not capture the world.", ". The manifest appears when export finishes. Upload sends the latest completed export; it does not capture the world.",
)} )}
</p> </p>
<p className="muted" style={{ fontSize: 11 }}> <p className="muted tool-card-note">
{t( {t(
"Put the JAR in plugins/ and restart. Set server-name and server-address in plugins/MineGeo/config.yml, then run /minegeo reload. Permission: minegeo.admin. The same plugin JAR supports all three versions.", "Put the JAR in plugins/ and restart. Set server-name and server-address in plugins/MineGeo/config.yml, then run /minegeo reload. Permission: minegeo.admin. The same plugin JAR supports all three versions.",
)} )}

View File

@@ -4,7 +4,7 @@ import { useI18n } from "@/components/I18nProvider";
export default function ErrorPage({ retry }: { retry: () => void }) { export default function ErrorPage({ retry }: { retry: () => void }) {
const { t } = useI18n(); const { t } = useI18n();
return ( return (
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<h1>{t("Lost connection.")}</h1> <h1>{t("Lost connection.")}</h1>
<p> <p>
{t( {t(

View File

@@ -26,7 +26,12 @@ body {
margin: 0; margin: 0;
background: var(--bg); background: var(--bg);
color: var(--text); color: var(--text);
font-family: Arial, Helvetica, sans-serif; font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
font-size: 14px; font-size: 14px;
line-height: 1.6; line-height: 1.6;
} }
@@ -50,7 +55,7 @@ button {
cursor: pointer; cursor: pointer;
} }
button:disabled { button:disabled {
cursor: wait; cursor: not-allowed;
opacity: 0.6; opacity: 0.6;
} }
::selection { ::selection {

View File

@@ -44,16 +44,17 @@ export default async function RootLayout({
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const [{ locale, preference, detectedLocale }, settings] = await Promise.all([ const [{ locale, preference, detectedLocale, t }, settings] =
getI18n(), await Promise.all([getI18n(), initialSiteSettings()]);
initialSiteSettings(),
]);
return ( return (
<html lang={locale} data-theme="dark" suppressHydrationWarning> <html lang={locale} data-theme="dark" suppressHydrationWarning>
<head> <head>
<script dangerouslySetInnerHTML={{ __html: themeInitScript }} /> <script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
</head> </head>
<body> <body>
<a className="skip-link" href="#main-content">
{t("Skip to content")}
</a>
<I18nProvider <I18nProvider
locale={locale} locale={locale}
preference={preference} preference={preference}

View File

@@ -6,6 +6,8 @@ export default async function LoadingLeaderboard() {
className="section page-content leaderboard-page" className="section page-content leaderboard-page"
aria-busy="true" aria-busy="true"
aria-label={t("Loading leaderboard")} aria-label={t("Loading leaderboard")}
id="main-content"
tabIndex={-1}
> >
<span className="eyebrow">{t("THE EXPLORERS’ BOARD")}</span> <span className="eyebrow">{t("THE EXPLORERS’ BOARD")}</span>
<h1>{t("Every block counts.")}</h1> <h1>{t("Every block counts.")}</h1>

View File

@@ -42,7 +42,7 @@ export default async function LeaderboardPage({
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content leaderboard-page" id="main-content"> <main className="section page-content leaderboard-page" id="main-content" tabIndex={-1}>
<Link href="/" className="text-link leaderboard-back"> <Link href="/" className="text-link leaderboard-back">
<ArrowLeft size={14} /> {t("Back to home")} <ArrowLeft size={14} /> {t("Back to home")}
</Link> </Link>

View File

@@ -22,7 +22,7 @@ export default async function LogIn({
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content auth-page"> <main className="section page-content auth-page" id="main-content" tabIndex={-1}>
<SignIn <SignIn
next={next} next={next}
initialError={ initialError={

View File

@@ -10,7 +10,7 @@ export default async function Review() {
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content moderation-page"> <main className="section page-content moderation-page" id="main-content" tabIndex={-1}>
<span className="eyebrow">{t("THE COMMUNITY’S COMPASS")}</span> <span className="eyebrow">{t("THE COMMUNITY’S COMPASS")}</span>
<h1>{t("Moderation Tools.")}</h1> <h1>{t("Moderation Tools.")}</h1>
<p className="lead"> <p className="lead">

View File

@@ -3,7 +3,7 @@ import Link from "next/link";
export default async function NotFound() { export default async function NotFound() {
const { t } = await getI18n(); const { t } = await getI18n();
return ( return (
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<span className="eyebrow">{t("UNKNOWN COORDINATES")}</span> <span className="eyebrow">{t("UNKNOWN COORDINATES")}</span>
<h1>{t("Uncharted territory.")}</h1> <h1>{t("Uncharted territory.")}</h1>
<p>{t("This map or page isn’t available.")}</p> <p>{t("This map or page isn’t available.")}</p>

View File

@@ -30,10 +30,10 @@ export default async function Home() {
<> <>
<FirstVisitIntro /> <FirstVisitIntro />
<Header /> <Header />
<main className="home-page" id="main-content"> <main className="home-page" id="main-content" tabIndex={-1}>
<section className="hero"> <section className="hero">
<div className="hero-copy"> <div className="hero-copy">
<div className="status-label">{t("A MINECRAFT GUESSING GAME")}</div> <div className="status-label">{t("A Minecraft guessing game")}</div>
<h1> <h1>
{t("Guess where you are")} {t("Guess where you are")}
<br /> <br />
@@ -131,8 +131,8 @@ export default async function Home() {
<section className="section catalog-section"> <section className="section catalog-section">
<div className="section-heading"> <div className="section-heading">
<div> <div>
<span className="eyebrow">{t("COMMUNITY MAPS")}</span> <span className="eyebrow">{t("Community maps")}</span>
<h2>{t("Where will you go today?")}</h2> <h2>{t("Explore community worlds.")}</h2>
</div> </div>
<Link className="text-link" href="/servers"> <Link className="text-link" href="/servers">
{t("All maps ({count})", { count: maps.length })}{" "} {t("All maps ({count})", { count: maps.length })}{" "}
@@ -196,7 +196,7 @@ export default async function Home() {
</Suspense> </Suspense>
<section className="section home-panels"> <section className="section home-panels">
<article className="home-panel"> <article className="home-panel">
<span className="eyebrow">{t("HOW TO PLAY")}</span> <span className="eyebrow">{t("How to play")}</span>
<h2>{t("Know your blocks?")}</h2> <h2>{t("Know your blocks?")}</h2>
<ol className="play-steps"> <ol className="play-steps">
<li> <li>
@@ -234,8 +234,8 @@ export default async function Home() {
</Link> </Link>
</article> </article>
<article className="home-panel"> <article className="home-panel">
<span className="eyebrow">{t("CHOOSE YOUR CHALLENGE")}</span> <span className="eyebrow">{t("Choose a difficulty")}</span>
<h2>{t("Find your challenge.")}</h2> <h2>{t("Pick a difficulty.")}</h2>
<div className="mode-list"> <div className="mode-list">
{[ {[
{ {
@@ -287,8 +287,8 @@ export default async function Home() {
<section className="section contribute-banner"> <section className="section contribute-banner">
<Blocks size={38} /> <Blocks size={38} />
<div> <div>
<span className="eyebrow">{t("BUILT BY THE COMMUNITY")}</span> <span className="eyebrow">{t("Made by MineGeo players")}</span>
<h2>{t("Your world deserves to be explored.")}</h2> <h2>{t("Put your world on the map.")}</h2>
<p> <p>
{t("Share your builds and your favorite corners of Minecraft.")} {t("Share your builds and your favorite corners of Minecraft.")}
</p> </p>

View File

@@ -14,7 +14,7 @@ export default async function Pair({
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
{!account ? ( {!account ? (
<> <>
<p className="muted"> <p className="muted">

View File

@@ -1,4 +1,21 @@
/* Responsive corrections shared by both languages. */ /* Responsive corrections shared by both languages. */
.skip-link {
position: fixed;
inset-block-start: 12px;
inset-inline-start: 12px;
z-index: 1000;
padding: 10px 14px;
transform: translateY(calc(-100% - 16px));
border: 1px solid var(--line);
border-radius: var(--radius-control);
color: var(--text);
background: var(--panel);
box-shadow: 0 4px 14px #03060a55;
white-space: nowrap;
}
.skip-link:focus-visible {
transform: translateY(0);
}
.language-switcher { .language-switcher {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -107,6 +124,22 @@ html[lang="ru"] .mode-row h3 span {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.desktop-download-card {
margin-bottom: 40px;
}
.desktop-download-version {
margin-top: 16px;
}
.download-build-link {
margin-inline-end: 8px;
margin-block-end: 8px;
}
.tool-card-note {
font-size: 11px;
}
.tool-card-note-spaced {
margin-top: 20px;
}
.tool-card pre, .tool-card pre,
.token-box code { .token-box code {
max-width: 100%; max-width: 100%;

View File

@@ -8,7 +8,7 @@ export default async function ResultPage({
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<Results id={(await params).id} /> <Results id={(await params).id} />
</main> </main>
<Footer /> <Footer />

View File

@@ -9,7 +9,7 @@
--text: #eef1f4; --text: #eef1f4;
--board-stone: #1a222c; --board-stone: #1a222c;
--board-edge: #35404c; --board-edge: #35404c;
--radius-panel: 16px; --radius-panel: 12px;
--radius-control: 9px; --radius-control: 9px;
} }
body { body {

View File

@@ -33,7 +33,7 @@ export default async function ServerPage({
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content map-detail-page"> <main className="section page-content map-detail-page" id="main-content" tabIndex={-1}>
<Link className="map-back-link" href="/servers"> <Link className="map-back-link" href="/servers">
<ArrowLeft size={16} aria-hidden="true" /> <ArrowLeft size={16} aria-hidden="true" />
{t("All worlds")} {t("All worlds")}

View File

@@ -10,7 +10,7 @@ export default async function Servers() {
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<span className="eyebrow"> <span className="eyebrow">
{t("THE COMMUNITY AT YOUR COORDINATES")} {t("THE COMMUNITY AT YOUR COORDINATES")}
</span> </span>

View File

@@ -35,7 +35,7 @@ export default async function Settings({
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<Link <Link
className="text-link" className="text-link"
href={active ? "/dashboard" : "/ban/appeal"} href={active ? "/dashboard" : "/ban/appeal"}

View File

@@ -13,7 +13,7 @@ export default async function Report({
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<SupportConversation key={id} id={id} /> <SupportConversation key={id} id={id} />
</main> </main>
<Footer /> <Footer />

View File

@@ -18,7 +18,7 @@ export default async function Support({
return ( return (
<> <>
<Header /> <Header />
<main className="section page-content"> <main className="section page-content" id="main-content" tabIndex={-1}>
<SupportInbox pagePath={pagePath} /> <SupportInbox pagePath={pagePath} />
</main> </main>
<Footer /> <Footer />

View File

@@ -15,7 +15,7 @@ export default async function TournamentPage({
return ( return (
<> <>
<Header /> <Header />
<main id="main-content"> <main id="main-content" tabIndex={-1}>
<TournamentLobby id={id} signedIn={!!account} /> <TournamentLobby id={id} signedIn={!!account} />
</main> </main>
<Footer /> <Footer />

View File

@@ -14,7 +14,7 @@ export default async function TournamentsPage({
return ( return (
<> <>
<Header /> <Header />
<main id="main-content"> <main id="main-content" tabIndex={-1}>
<TournamentIndex signedIn={!!account} initialMapId={initialMapId} /> <TournamentIndex signedIn={!!account} initialMapId={initialMapId} />
</main> </main>
<Footer /> <Footer />

View File

@@ -192,7 +192,7 @@ export function Game({ id }: { id: string }) {
const total = const total =
session?.rounds.reduce((n, r) => n + (r.result?.score || 0), 0) || 0; session?.rounds.reduce((n, r) => n + (r.result?.score || 0), 0) || 0;
return ( return (
<main className="game-page"> <main className="game-page" id="main-content" tabIndex={-1}>
{session && current && ( {session && current && (
<Panorama <Panorama
key={session.round} key={session.round}

View File

@@ -167,7 +167,7 @@ export function MultiplayerRoom({ id }: { id: string }) {
{t("Play & maps")} {t("Play & maps")}
</Link> </Link>
</header> </header>
<main className="section page-content multiplayer-lobby"> <main className="section page-content multiplayer-lobby" id="main-content" tabIndex={-1}>
{error && ( {error && (
<p className="error-message" role="alert"> <p className="error-message" role="alert">
{t(error)}{" "} {t(error)}{" "}

View File

@@ -7,6 +7,8 @@ export async function PageLoading() {
className="section page-content route-loading" className="section page-content route-loading"
aria-busy="true" aria-busy="true"
aria-label={t("Loading page")} aria-label={t("Loading page")}
id="main-content"
tabIndex={-1}
> >
<p role="status" className="muted"> <p role="status" className="muted">
{t("Loading page")}… {t("Loading page")}…

View File

@@ -1,5 +1,14 @@
{ {
"MineGeo": "MineGeo", "MineGeo": "MineGeo",
"Skip to content": "Перейти к содержимому",
"A Minecraft guessing game": "Игра-угадайка по Minecraft",
"Community maps": "Карты сообщества",
"Explore community worlds.": "Исследуй миры сообщества.",
"How to play": "Как играть",
"Choose a difficulty": "Выбери сложность",
"Pick a difficulty.": "Выбери сложность.",
"Made by MineGeo players": "Создано игроками MineGeo",
"Put your world on the map.": "Добавь свой мир на карту.",
"Open navigation": "Открыть меню", "Open navigation": "Открыть меню",
"Close navigation": "Закрыть меню", "Close navigation": "Закрыть меню",
"Find your challenge.": "Найди свой вызов.", "Find your challenge.": "Найди свой вызов.",