From de0e891b62eca892564a4fb49f020a0067d4eb04 Mon Sep 17 00:00:00 2001 From: pavel444-byte Date: Tue, 20 Oct 2026 14:05:01 +0500 Subject: [PATCH] Add Void difficulty and publish changelogs --- .env.example | 4 + app/about/page.tsx | 2 +- app/appearance.css | 33 ++++- app/changelogs/page.tsx | 77 ++++++++++ app/dashboard/page.tsx | 2 +- app/globals.css | 10 ++ app/leaderboard/page.tsx | 2 +- components/ChangelogRefresh.tsx | 19 +++ components/Game.tsx | 17 ++- components/GuessMap.tsx | 47 +++++- components/MultiplayerGame.tsx | 27 ++-- components/ReleaseNotes.tsx | 45 ++++++ components/Shell.tsx | 1 + components/SiteNav.tsx | 1 + components/StartGame.tsx | 20 ++- components/TournamentLobby.tsx | 24 ++-- docs/changelogs.md | 23 +++ docs/tournaments.md | 2 +- lib/api.ts | 16 ++- lib/changelog-feed.ts | 76 ++++++++++ lib/changelog.ts | 21 +++ lib/service-error.ts | 2 +- scripts/test-difficulty-presets-sql.mjs | 90 +++++++++++- scripts/test-tournaments-sql.mjs | 25 ++++ shared/changelog.ts | 51 +++++++ shared/database.ts | 6 + shared/developer-openapi.ts | 5 +- shared/leaderboard.ts | 1 + shared/locales/ru.json | 21 ++- shared/multiplayer.ts | 2 +- shared/prometheus.ts | 2 +- shared/protocol.ts | 22 ++- shared/tournaments.ts | 11 +- .../20261020083343_void_difficulty_rules.sql | 135 +++++++++++++++++ tests/changelog.test.ts | 136 ++++++++++++++++++ tests/difficulty-presets.test.ts | 78 +++++++++- tests/prometheus.test.ts | 4 +- 37 files changed, 995 insertions(+), 65 deletions(-) create mode 100644 app/changelogs/page.tsx create mode 100644 components/ChangelogRefresh.tsx create mode 100644 components/ReleaseNotes.tsx create mode 100644 docs/changelogs.md create mode 100644 lib/changelog-feed.ts create mode 100644 lib/changelog.ts create mode 100644 shared/changelog.ts create mode 100644 supabase/migrations/20261020083343_void_difficulty_rules.sql create mode 100644 tests/changelog.test.ts 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")} + + )} +
+