Improve responsive layouts and streamline page loading
All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 8m58s
All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 8m58s
This commit is contained in:
@@ -83,6 +83,24 @@ html[data-theme="light"] :is(h1, h2, h3) {
|
||||
html[data-theme="light"] .hero h1 span {
|
||||
text-shadow: none;
|
||||
}
|
||||
html[data-theme="light"] :is(.compass-art, .floating-tag) {
|
||||
background: var(--panel);
|
||||
color: var(--text);
|
||||
border-color: var(--line);
|
||||
box-shadow:
|
||||
inset 0 1px #fff,
|
||||
0 4px 12px #243a1814;
|
||||
}
|
||||
html[data-theme="light"] .floating-tag strong {
|
||||
color: var(--text);
|
||||
}
|
||||
html[data-theme="light"] .floating-tag > span {
|
||||
color: var(--muted);
|
||||
}
|
||||
html[data-theme="light"] .card-type {
|
||||
background: var(--panel2);
|
||||
color: var(--text);
|
||||
}
|
||||
html[data-theme="light"] :is(.site-header, .site-footer) {
|
||||
background: var(--bg);
|
||||
border-color: var(--line);
|
||||
@@ -387,7 +405,12 @@ html[data-theme="light"] .site-cursor-dot span {
|
||||
.header-inner {
|
||||
max-width: 1600px;
|
||||
}
|
||||
@media (max-width: 1500px) {
|
||||
.nav-toggle,
|
||||
.site-navigation .nav-support,
|
||||
.account-symbol {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 1500px) and (min-width: 1201px) {
|
||||
.site-header {
|
||||
height: auto;
|
||||
}
|
||||
@@ -407,76 +430,140 @@ html[data-theme="light"] .site-cursor-dot span {
|
||||
padding: 12px 0 2px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
.header-inner {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 10px;
|
||||
@media (max-width: 1200px) {
|
||||
.site-header {
|
||||
height: auto;
|
||||
}
|
||||
.header-actions {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
justify-self: end;
|
||||
.header-inner {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
.header-inner > .brand {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
justify-self: start;
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
.header-inner .header-actions {
|
||||
grid-area: 1 / 2;
|
||||
margin: 0;
|
||||
justify-self: end;
|
||||
gap: 8px;
|
||||
}
|
||||
.header-inner .nav-toggle {
|
||||
grid-area: 1 / 3;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-control);
|
||||
color: var(--text);
|
||||
background: var(--panel);
|
||||
}
|
||||
.header-inner nav.site-navigation {
|
||||
display: none;
|
||||
grid-area: 2 / 1 / auto / -1;
|
||||
width: 100%;
|
||||
padding: 12px 0 0;
|
||||
margin: 2px 0 0;
|
||||
border-top: 1px solid var(--line);
|
||||
gap: 4px;
|
||||
font:
|
||||
500 14px/1.5 Arial,
|
||||
Helvetica,
|
||||
sans-serif;
|
||||
}
|
||||
.header-inner nav.site-navigation[data-open="true"] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.site-navigation a {
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.site-navigation a[aria-current="page"] {
|
||||
color: var(--green);
|
||||
background: var(--panel2);
|
||||
}
|
||||
.site-navigation a[aria-current="page"]::after {
|
||||
display: none;
|
||||
}
|
||||
.site-navigation .nav-support {
|
||||
display: block;
|
||||
}
|
||||
.site-navigation a:hover,
|
||||
.nav-toggle:hover {
|
||||
background: var(--panel2);
|
||||
}
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.header-inner {
|
||||
padding: 10px 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
.header-inner .brand {
|
||||
gap: 5px;
|
||||
}
|
||||
.header-inner .brand .brand-wordmark {
|
||||
font-size: 21px;
|
||||
}
|
||||
.header-inner .brand .brand-mark {
|
||||
width: 30px;
|
||||
height: 36px;
|
||||
width: 28px;
|
||||
height: 34px;
|
||||
}
|
||||
.header-inner .brand {
|
||||
gap: 5px;
|
||||
.header-inner .brand sup {
|
||||
display: none;
|
||||
}
|
||||
.header-actions {
|
||||
gap: 4px;
|
||||
.header-inner .header-actions {
|
||||
gap: 6px;
|
||||
}
|
||||
.header-actions .header-report,
|
||||
.header-account > span,
|
||||
.header-account .account-arrow {
|
||||
display: none;
|
||||
}
|
||||
.header-actions .header-account {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-control);
|
||||
}
|
||||
.header-account .account-symbol {
|
||||
display: block;
|
||||
}
|
||||
.header-actions .theme-toggle,
|
||||
.header-inner .nav-toggle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.game-header .brand .brand-wordmark {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.header-actions {
|
||||
grid-row: 2;
|
||||
grid-column: 1 / -1;
|
||||
justify-self: start;
|
||||
margin-left: 0;
|
||||
}
|
||||
.header-inner nav {
|
||||
grid-row: 3;
|
||||
}
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
html[lang="ru"] .header-actions {
|
||||
grid-row: 2;
|
||||
grid-column: 1 / -1;
|
||||
justify-self: start;
|
||||
margin-left: 0;
|
||||
}
|
||||
html[lang="ru"] .header-inner nav {
|
||||
grid-row: 3;
|
||||
}
|
||||
}
|
||||
.difficulty-options button.difficulty-void {
|
||||
border-color: #a855f7;
|
||||
background: linear-gradient(145deg, #21122f, #100c18);
|
||||
color: #e9d5ff;
|
||||
box-shadow: 0 0 16px #a855f738, inset 0 0 20px #a855f710;
|
||||
box-shadow:
|
||||
0 0 16px #a855f738,
|
||||
inset 0 0 20px #a855f710;
|
||||
}
|
||||
.difficulty-options button.difficulty-void strong {
|
||||
color: #e9d5ff;
|
||||
text-shadow: 0 0 10px #c084fc99, 0 0 22px #a855f780;
|
||||
text-shadow:
|
||||
0 0 10px #c084fc99,
|
||||
0 0 22px #a855f780;
|
||||
}
|
||||
.difficulty-options button.difficulty-void.selected,
|
||||
html[data-theme="light"] .difficulty-options button.difficulty-void.selected {
|
||||
color: #f3e8ff;
|
||||
background: linear-gradient(145deg, #482363, #21122f);
|
||||
border-color: #d8b4fe;
|
||||
box-shadow: 0 0 24px #a855f760, inset 0 0 20px #c084fc18;
|
||||
box-shadow:
|
||||
0 0 24px #a855f760,
|
||||
inset 0 0 20px #c084fc18;
|
||||
}
|
||||
html[data-theme="light"] .difficulty-options button.difficulty-void {
|
||||
background: #f3e8ff;
|
||||
@@ -486,6 +573,9 @@ html[data-theme="light"] .difficulty-options button.difficulty-void {
|
||||
html[data-theme="light"] .difficulty-options button.difficulty-void strong {
|
||||
color: #7e22ce;
|
||||
}
|
||||
html[data-theme="light"] .difficulty-options button.difficulty-void.selected strong {
|
||||
html[data-theme="light"]
|
||||
.difficulty-options
|
||||
button.difficulty-void.selected
|
||||
strong {
|
||||
color: #f3e8ff;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { SiteSettingsProvider } from "@/components/SiteSettingsProvider";
|
||||
import { initialSiteSettings } from "@/lib/site-settings";
|
||||
import { rawUser, accountState } from "@/lib/supabase";
|
||||
import { LegacyPasswordNotice } from "@/components/LegacyPasswordNotice";
|
||||
import { Suspense } from "react";
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const { t } = await getI18n();
|
||||
return {
|
||||
@@ -43,11 +44,10 @@ export default async function RootLayout({
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [{ locale, preference, detectedLocale }, settings, account] =
|
||||
await Promise.all([getI18n(), initialSiteSettings(), rawUser()]);
|
||||
const active = account
|
||||
? (await accountState(account.id)) === "active"
|
||||
: false;
|
||||
const [{ locale, preference, detectedLocale }, settings] = await Promise.all([
|
||||
getI18n(),
|
||||
initialSiteSettings(),
|
||||
]);
|
||||
return (
|
||||
<html lang={locale} data-theme="dark" suppressHydrationWarning>
|
||||
<head>
|
||||
@@ -62,7 +62,9 @@ export default async function RootLayout({
|
||||
<SiteSettingsProvider initialSettings={settings}>
|
||||
{children}
|
||||
<SiteCursor />
|
||||
<LegacyPasswordNotice enabled={active && !!account?.email} />
|
||||
<Suspense fallback={null}>
|
||||
<AccountPasswordNotice />
|
||||
</Suspense>
|
||||
</SiteSettingsProvider>
|
||||
</I18nProvider>
|
||||
<Analytics />
|
||||
@@ -70,3 +72,10 @@ export default async function RootLayout({
|
||||
</html>
|
||||
);
|
||||
}
|
||||
async function AccountPasswordNotice() {
|
||||
const account = await rawUser();
|
||||
const active = account
|
||||
? (await accountState(account.id)) === "active"
|
||||
: false;
|
||||
return <LegacyPasswordNotice enabled={active && !!account?.email} />;
|
||||
}
|
||||
|
||||
45
app/page.tsx
45
app/page.tsx
@@ -18,13 +18,12 @@ 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 [maps, rankings] = await Promise.all([
|
||||
catalog(),
|
||||
leaderboard({ limit: 5 }),
|
||||
]);
|
||||
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 (
|
||||
@@ -78,6 +77,7 @@ export default async function Home() {
|
||||
width={1024}
|
||||
height={1024}
|
||||
loading="eager"
|
||||
fetchPriority="high"
|
||||
unoptimized
|
||||
/>
|
||||
<span className="square-pin pin-green" />
|
||||
@@ -142,7 +142,13 @@ export default async function Home() {
|
||||
{maps.length ? (
|
||||
<div className="cards-grid">
|
||||
{maps.slice(0, 4).map((map, index) => (
|
||||
<MapCard map={map} index={index} key={map.id} minecraftPaper />
|
||||
<MapCard
|
||||
map={map}
|
||||
index={index}
|
||||
key={map.id}
|
||||
minecraftPaper
|
||||
loading="lazy"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
@@ -175,7 +181,19 @@ export default async function Home() {
|
||||
)}
|
||||
</section>
|
||||
<SiteTrailer />
|
||||
<LeaderboardPreview data={rankings} />
|
||||
<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>
|
||||
@@ -217,7 +235,7 @@ export default async function Home() {
|
||||
</article>
|
||||
<article className="home-panel">
|
||||
<span className="eyebrow">{t("CHOOSE YOUR CHALLENGE")}</span>
|
||||
<h2>{t("One world. Three ways.")}</h2>
|
||||
<h2>{t("Find your challenge.")}</h2>
|
||||
<div className="mode-list">
|
||||
{[
|
||||
{
|
||||
@@ -238,6 +256,12 @@ export default async function Home() {
|
||||
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`} />
|
||||
@@ -278,3 +302,10 @@ export default async function Home() {
|
||||
</>
|
||||
);
|
||||
}
|
||||
async function HomeLeaderboard({
|
||||
data,
|
||||
}: {
|
||||
data: ReturnType<typeof leaderboard>;
|
||||
}) {
|
||||
return <LeaderboardPreview data={await data} />;
|
||||
}
|
||||
|
||||
261
app/polish.css
261
app/polish.css
@@ -28,6 +28,12 @@ select:focus-visible {
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
/* Fallback for user-provided Cyrillic names on English pages. */
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-family: MineGeoPixel, MineGeoPixelCyrillic, monospace;
|
||||
}
|
||||
html[lang="ru"] h1,
|
||||
html[lang="ru"] h2,
|
||||
html[lang="ru"] h3,
|
||||
@@ -127,7 +133,7 @@ html[lang="ru"] .mode-row h3 span {
|
||||
}
|
||||
/* Keep catalog cards readable instead of squeezing long map names. */
|
||||
.cards-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
|
||||
}
|
||||
.map-card .card-body .map-credit {
|
||||
display: block;
|
||||
@@ -251,6 +257,259 @@ html[lang="ru"] .mode-row h3 span {
|
||||
gap: 20px;
|
||||
margin: 24px 0 32px;
|
||||
}
|
||||
|
||||
/* Shared layout repairs: readable content, predictable cards, and touch targets. */
|
||||
:is(a, button, input, select, textarea, summary):focus-visible {
|
||||
outline: 2px solid var(--green);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
.page-content > h1 {
|
||||
text-wrap: balance;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.lead {
|
||||
text-wrap: pretty;
|
||||
}
|
||||
.map-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.map-card .card-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
gap: 12px;
|
||||
}
|
||||
.map-card .card-body .eyebrow {
|
||||
font: 11px/1.5 monospace;
|
||||
margin: 0;
|
||||
}
|
||||
.map-card .card-body h3,
|
||||
html[lang="ru"] .map-card .card-body h3 {
|
||||
font:
|
||||
400 18px/1.6 MineGeoPixelCyrillic,
|
||||
monospace;
|
||||
letter-spacing: -0.3px;
|
||||
text-shadow: none;
|
||||
min-height: 4.8em;
|
||||
margin: 0;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.map-card .card-body h3 > span {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.map-card .card-body h3 svg {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.map-card .card-body p {
|
||||
font-size: 13px;
|
||||
line-height: 1.65;
|
||||
margin: 0;
|
||||
min-height: 3.3em;
|
||||
}
|
||||
.map-card .card-body .map-credit {
|
||||
margin-top: auto;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.map-card .card-meta {
|
||||
font-size: 11px;
|
||||
gap: 8px;
|
||||
padding-top: 14px;
|
||||
}
|
||||
.catalog-count {
|
||||
margin: 0 0 20px;
|
||||
font-size: 13px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.catalog-empty {
|
||||
padding: 36px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
.catalog-empty p {
|
||||
margin: 0;
|
||||
}
|
||||
.catalog-controls .tabs button {
|
||||
min-height: 44px;
|
||||
}
|
||||
.map-back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--muted);
|
||||
margin-bottom: 28px;
|
||||
min-height: 32px;
|
||||
}
|
||||
.map-back-link:hover {
|
||||
color: var(--green);
|
||||
}
|
||||
.map-detail-page .map-detail-title {
|
||||
font-family: MineGeoPixelCyrillic, monospace;
|
||||
font-size: clamp(24px, 3vw, 34px);
|
||||
line-height: 1.5;
|
||||
max-width: 30ch;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
.map-detail-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.map-detail-actions .map-export {
|
||||
margin: 0;
|
||||
}
|
||||
.map-detail-id {
|
||||
font-size: 12px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.detail-grid > * {
|
||||
min-width: 0;
|
||||
}
|
||||
.start-panel .difficulty-options button {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.detail-grid .card-meta span:last-child {
|
||||
display: flex;
|
||||
}
|
||||
.stat-cube.void-cube {
|
||||
background: #9e6bc4;
|
||||
border-color: #cca6ef;
|
||||
}
|
||||
.mode-row:has(.void-cube) {
|
||||
border-color: #9163b080;
|
||||
}
|
||||
.world-stats strong,
|
||||
.card-meta {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.loading-surface {
|
||||
background: var(--panel2);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-control);
|
||||
}
|
||||
.loading-title {
|
||||
width: min(440px, 75%);
|
||||
height: 48px;
|
||||
margin: 24px 0 36px;
|
||||
}
|
||||
.loading-card {
|
||||
height: 420px;
|
||||
}
|
||||
.route-loading {
|
||||
min-height: 85dvh;
|
||||
}
|
||||
.leaderboard-preview-placeholder {
|
||||
min-height: 420px;
|
||||
}
|
||||
.leaderboard-preview-placeholder .loading-surface {
|
||||
height: 240px;
|
||||
}
|
||||
@media (max-width: 960px) {
|
||||
.map-detail-page .detail-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
.detail-grid > div:first-child {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.start-panel .difficulty-options {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
@media (min-width: 701px) and (max-width: 960px) {
|
||||
.home-page .hero {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 32px;
|
||||
}
|
||||
.hero-copy {
|
||||
max-width: 620px;
|
||||
}
|
||||
.home-page .hero h1 {
|
||||
font-size: clamp(40px, 5.8vw, 54px);
|
||||
line-height: 1.12;
|
||||
text-wrap: balance;
|
||||
}
|
||||
.hero-art {
|
||||
min-height: 350px;
|
||||
width: 100%;
|
||||
max-width: 440px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.hero-art .compass-art {
|
||||
right: calc(50% - 150px);
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.section.page-content {
|
||||
padding: 32px 20px 48px;
|
||||
}
|
||||
.catalog-search {
|
||||
width: 100%;
|
||||
}
|
||||
.catalog-search .search-field {
|
||||
min-height: 46px;
|
||||
padding-inline: 12px;
|
||||
}
|
||||
.catalog-search input {
|
||||
font-size: 16px;
|
||||
}
|
||||
.catalog-controls {
|
||||
gap: 16px;
|
||||
}
|
||||
.catalog-controls .tabs {
|
||||
gap: 4px;
|
||||
}
|
||||
.catalog-controls .tabs button {
|
||||
padding-inline: 10px;
|
||||
}
|
||||
.map-card .card-body {
|
||||
padding: 18px;
|
||||
}
|
||||
.map-detail-page .map-detail-title {
|
||||
font-size: 22px;
|
||||
}
|
||||
.map-back-link {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.detail-grid {
|
||||
gap: 28px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
.detail-grid .start-panel {
|
||||
padding: 20px;
|
||||
}
|
||||
.start-panel .difficulty-options {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.start-panel .difficulty-options span {
|
||||
font-size: 12px;
|
||||
}
|
||||
.hero-actions {
|
||||
align-items: stretch;
|
||||
}
|
||||
.hero-actions > .button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.moderator-setting-row {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
|
||||
1
app/servers/[id]/loading.tsx
Normal file
1
app/servers/[id]/loading.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { PageLoading as default } from "@/components/PageLoading";
|
||||
@@ -7,10 +7,11 @@ import { StartGame } from "@/components/StartGame";
|
||||
import { getMap } from "@/lib/catalog";
|
||||
import { ContributionInstructions } from "@/components/ContributionInstructions";
|
||||
import { ReportContent } from "@/components/ReportContent";
|
||||
import { user } from "@/lib/supabase";
|
||||
import { accountProfile } from "@/lib/profile";
|
||||
import { user, admin, required } from "@/lib/supabase";
|
||||
import { ExportMap } from "@/components/MapTransfer";
|
||||
import { MapCommunity } from "@/components/MapCommunity";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
export default async function ServerPage({
|
||||
params,
|
||||
}: {
|
||||
@@ -18,29 +19,42 @@ export default async function ServerPage({
|
||||
}) {
|
||||
const { t } = await getI18n();
|
||||
const { id } = await params;
|
||||
const map = await getMap(id);
|
||||
const [map, account] = await Promise.all([getMap(id), user()]);
|
||||
if (!map) notFound();
|
||||
const account = await user();
|
||||
const profile = account ? await accountProfile(account) : null;
|
||||
const profileResult = account
|
||||
? await admin()
|
||||
.from("profiles")
|
||||
.select("display_name")
|
||||
.eq("id", account.id)
|
||||
.maybeSingle()
|
||||
: null;
|
||||
if (profileResult?.error) required(profileResult);
|
||||
const profile = profileResult?.data;
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="section page-content">
|
||||
<main className="section page-content map-detail-page">
|
||||
<Link className="map-back-link" href="/servers">
|
||||
<ArrowLeft size={16} aria-hidden="true" />
|
||||
{t("All worlds")}
|
||||
</Link>
|
||||
<span className="eyebrow">
|
||||
{map.source === "plugin"
|
||||
? t("SERVER EXPORT")
|
||||
: t("PLAYER CONTRIBUTION")}{" "}
|
||||
{t("· MINECRAFT 1.21.11")}
|
||||
</span>
|
||||
<h1>{map.name}</h1>
|
||||
<h1 className="map-detail-title">{map.name}</h1>
|
||||
<p className="lead">{map.description}</p>
|
||||
<p className="map-credit">
|
||||
{t("Created by")}{" "}
|
||||
<Username name={map.credit} appearance={map.creditAppearance} />
|
||||
</p>
|
||||
<ReportContent datasetId={id} />
|
||||
{account?.id === map.owner_id && <ExportMap id={id} />}
|
||||
<p className="muted">
|
||||
<div className="map-detail-actions">
|
||||
<ReportContent datasetId={id} />
|
||||
{account?.id === map.owner_id && <ExportMap id={id} />}
|
||||
</div>
|
||||
<p className="muted map-detail-id">
|
||||
{t("Map ID:")} <code>{map.id}</code>
|
||||
</p>
|
||||
<div className="detail-grid">
|
||||
@@ -66,7 +80,10 @@ export default async function ServerPage({
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<StartGame datasetId={id} hostName={profile?.display_name ?? null} />
|
||||
<StartGame
|
||||
datasetId={id}
|
||||
hostName={account ? (profile?.display_name ?? "Explorer") : null}
|
||||
/>
|
||||
</div>
|
||||
{map.allow_contributions && (
|
||||
<ContributionInstructions
|
||||
|
||||
1
app/servers/loading.tsx
Normal file
1
app/servers/loading.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { PageLoading as default } from "@/components/PageLoading";
|
||||
Reference in New Issue
Block a user