diff --git a/.env.example b/.env.example index 6dfe805..e0cc1a0 100644 --- a/.env.example +++ b/.env.example @@ -17,6 +17,10 @@ MINEGEO_FORUM_URL= DISCORD_BOT_TOKEN= DISCORD_MAPS_CHANNEL_ID= DISCORD_PARTIES_CHANNEL_ID= +# Optional overrides for the read-only changelog (defaults to MineGeo news and release webhook). +# The existing bot requires View Channel and Read Message History there. +DISCORD_NEWS_CHANNEL_ID= +DISCORD_RELEASE_WEBHOOK_ID= # Optional bearer secret for scheduled GET /api/integrations/discord/dispatch retries. CRON_SECRET= # Development testing only; production rejects this bypass of CSRF protection. diff --git a/app/about/page.tsx b/app/about/page.tsx index ff98f8d..dde7b57 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -33,7 +33,7 @@ export default async function About() {

{t("Five chances to find your way")}

{t( - "Easy gives you 180 seconds, Normal 90, and Hard 30. Hard disables zoom. A timed-out round scores zero; you can still finish the expedition.", + "Easy gives you 180 seconds, Normal 90, and Hard 30. Void is the hardest: just 10 seconds, a fixed panorama and map, no zoom, no compass or coordinates, and strict scoring. A timed-out round scores zero; you can still finish the expedition.", )}

{t("Closer means better")}

diff --git a/app/appearance.css b/app/appearance.css index 8bd0aec..6303d1a 100644 --- a/app/appearance.css +++ b/app/appearance.css @@ -384,7 +384,10 @@ html[data-theme="light"] .site-cursor-dot span { background: #273b21; box-shadow: 0 0 1px #fff; } -@media (max-width: 1200px) { +.header-inner { + max-width: 1600px; +} +@media (max-width: 1500px) { .site-header { height: auto; } @@ -458,3 +461,31 @@ html[data-theme="light"] .site-cursor-dot span { 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; +} +.difficulty-options button.difficulty-void strong { + color: #e9d5ff; + 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; +} +html[data-theme="light"] .difficulty-options button.difficulty-void { + background: #f3e8ff; + border-color: #a855f7; + color: #581c87; +} +html[data-theme="light"] .difficulty-options button.difficulty-void strong { + color: #7e22ce; +} +html[data-theme="light"] .difficulty-options button.difficulty-void.selected strong { + color: #f3e8ff; +} diff --git a/app/changelogs/page.tsx b/app/changelogs/page.tsx new file mode 100644 index 0000000..945c2c4 --- /dev/null +++ b/app/changelogs/page.tsx @@ -0,0 +1,77 @@ +import Link from "next/link"; +import type { Metadata } from "next"; +import { Header, Footer } from "@/components/Shell"; +import { ReleaseNotes } from "@/components/ReleaseNotes"; +import { ChangelogRefresh } from "@/components/ChangelogRefresh"; +import { getChangelog } from "@/lib/changelog"; +import { getI18n } from "@/lib/i18n"; + +export const metadata: Metadata = { + title: "Changelogs", + description: "The latest MineGeo releases, improvements, and bug fixes.", +}; + +export default async function ChangelogsPage({ + searchParams, +}: { + searchParams: Promise<{ before?: string }>; +}) { + const params = await searchParams; + const before = + typeof params.before === "string" && /^\d{17,20}$/.test(params.before) + ? params.before + : undefined; + const [{ t, formatDate }, feed] = await Promise.all([ + getI18n(), + getChangelog(before), + ]); + return ( + <> +
+
+ {!before && } + {t("WHAT'S NEW")} +

{t("Changelogs")}

+

+ {t("New releases, improvements, and fixes from MineGeo.")} +

+ {before && {t("Latest releases")}} + {feed.unavailable ? ( +
+

{t("Release notes are temporarily unavailable")}

+

{t("Please check back soon.")}

+
+ ) : feed.entries.length === 0 ? ( +

{t("No releases on this page yet.")}

+ ) : ( +
+ {feed.entries.map((entry) => ( +
+
+ {t(entry.kind)} + +
+

{entry.title}

+
+ +
+
+ ))} +
+ )} + {!feed.unavailable && "before" in feed && feed.before && ( + + {t("Older posts")} + + )} +
+