Improve MineGeo UI and application flows
Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 2m37s

This commit is contained in:
2026-10-18 11:44:04 +05:00
parent ad73132f15
commit a7085e4e80
50 changed files with 4790 additions and 703 deletions

View File

@@ -2,18 +2,18 @@
## Deliverables
- `public/media/minegeo-discord-banner.gif`: 600 × 240, 3-second looping banner at 24 fps. Created in Three.js; the camera, compass sweep, pins and terrain movement return to their initial state at the loop boundary.
- `public/media/minegeo-discord-banner.gif`: 600 × 240, 3-second looping banner at 24 fps. Created in Three.js; the camera, compass sweep and pin targeting return to their initial state at the loop boundary.
- `public/media/minegeo-discord-banner.png`: 1200 × 480 still fallback.
- `public/media/minegeo-trailer.mp4`: 1280 × 720, 30 seconds at 24 fps, H.264/AAC, optimized for progressive web playback. Includes an original synthesized ambient soundtrack with no speech.
- `public/media/minegeo-trailer.mp4`: 1280 × 720, 30 seconds at 30 fps, H.264/AAC, optimized for progressive web playback. Includes an original synthesized ambient soundtrack with no speech.
- `public/media/minegeo-trailer-poster.jpg` and `minegeo-trailer.vtt`: poster and English captions.
The trailer combines a Three.js opening and closing with genuine screenshots of the homepage, map catalog, in-game panorama, and map setup, captured through the browser from the local app against the real published catalog. Screenshots are stored in `cinematics/screenshots/` and animated inside a framed window; they are still captures, not a gameplay recording. A guest game was opened for the panorama capture; no score was submitted. The intro/closing terrain, hut, arch, pins, particles, compass, typography and sound are generated locally; no third-party footage or music is used. Typography uses the project's existing licensed Monocraft font.
The trailer combines a Three.js opening and closing with genuine screenshots of the homepage, map catalog, in-game panorama, and map setup, captured through the browser from the local app against the real published catalog. Screenshots are stored in `cinematics/screenshots/` and presented in a minimal frame with a brief entrance and a still reading hold; they are still captures, not a gameplay recording. A guest game was opened for the panorama capture; no score was submitted. The intro/closing terrain, hut, arch, pins, particles, compass, typography and sound are generated locally; no third-party footage or music is used. Typography uses the project's existing licensed Monocraft font.
The homepage provides native video controls, captions, a descriptive text alternative, and a download link. The video does not autoplay and uses `preload="none"`.
## First visit
`FirstVisitIntro` runs only on the homepage and lasts 4.75 seconds. It uses CSS terrain tiles, a compass/pin reveal, and text fading out before the backdrop reveals the site. The Skip button and Escape dismiss it early. It bypasses the animation when reduced motion is requested and closes if that preference changes during playback.
`FirstVisitIntro` runs only on the homepage and lasts 4 seconds. A single isometric map tile enters, a golden pin settles, and the wordmark enters with a 60ms stagger. Text exits at 3.3 seconds, the map exits at 3.5 seconds, and the backdrop exits at 3.75 seconds. The Skip button and Escape dismiss it early. It bypasses the animation when reduced motion is requested and closes if that preference changes during playback.
The `minegeo:intro:v1` local-storage key remembers a visit per browser/origin. If local storage is unavailable, it falls back to session storage and then in-memory state. The regular page always renders on the server; JavaScript failure cannot leave it hidden. The welcome does not interrupt an already-open dialog.
@@ -29,3 +29,13 @@ node scripts/render-cinematics.mjs --preview
```
The preview binds to `127.0.0.1:3110`. Open `/?mode=banner` for the loop or `/?time=16&still=1` for a trailer still. Temporary frames and synthesized audio stay in ignored `.data/cinematic-render/`; deliverables are written to `public/media/`. The export uses an isolated headless graphics renderer, does not access user browser sessions, and needs no credentials or network assets.
## Motion direction and performance
Rebuilt with the Animate and Animation Performance skills. Entrances and exits use the canonical ease-out curve (0.23, 1, 0.32, 1); the camera move and map-target alignment use ease-in-out (0.77, 0, 0.175, 1). The Discord wordmark stays readable throughout its 3-second loop.
The 30-second trailer opens for 3 seconds, then gives each actual website screenshot 5 seconds: homepage (3–8), catalog (8–13), gameplay (13–18), map setup (18–23). The final 7 seconds hold the brand and site address. Screenshots settle within 300ms and then remain still. The gameplay chapter highlights the existing guess map after 2 seconds. Original audio transitions and captions follow the same chapter boundaries.
The first-visit animation is CSS-only and changes transform/opacity; it has no animated blur, layout properties, perpetual loops or per-frame React updates. Skip and Escape close immediately. Reduced motion bypasses the decorative intro. The offline Three.js renderer caches static terrain shadows; none of that renderer ships to the homepage. Native video playback is opt-in, with no preload or autoplay.
Verification covers timeline/easing tests, browser checks of Skip, Escape, automatic dismissal, repeat visits and reduced-motion bypass, production compilation, and encoded media dimensions/duration/frame count. No physical-phone frame-rate measurement has been made; this is not a verified 60fps claim.

44
docs/tournaments.md Normal file
View File

@@ -0,0 +1,44 @@
# Community tournaments
Signed-in users with active accounts can create a tournament at `/tournaments` and share its `/tournaments/{id}` page. The organizer chooses a published map with at least five distinct panorama locations, a difficulty, a scheduled start, a player cap from 2 to 32, and 1 to 10 tournament rounds. Every tournament round contains exactly five mini-rounds.
The organizer selects either a shared time budget across the five mini-rounds (the default), or a separate timer for each mini-round. Shared budgets accept 15–3000 seconds; per-mini-round timers accept 15–600 seconds. Both use 15-second increments. The shared clock starts when the first mini-round becomes playable, excludes initial loading, and keeps running through later loading and result screens. When it expires, all unplayed mini-rounds close with zero points. Difficulty settings are saved with the tournament, so later preset changes do not alter a running competition.
The schedule form displays the application server's timezone. Its local date/time input is interpreted in that zone, even when the organizer's browser uses another zone. The server validates future starts within 90 days; browser clocks do not decide when gameplay begins. Discord announcements include the server date/time and zone, plus Discord timestamps displayed in each reader's local time.
The organizer joins automatically. Registration and departures close at the scheduled start; the roster is then locked. At least two active registered players are required to start, otherwise the tournament is cancelled. An organizer can cancel their scheduled or running tournament. Scheduled settings can be changed without removing already registered players through a lower cap. Owners cannot leave their own event; cancellation is available instead.
All players receive the same locations within a mini-round. The server decides deadlines, validates guesses, calculates scores, and reveals results only for closed mini-rounds. Tournament pages show final standings at completion. Inactive accounts cannot keep playing or win. Tournament membership credentials remain on the server and ordinary multiplayer joins cannot bypass the tournament roster.
The latest eligible tournament winner is featured in first place on the global, all-difficulties leaderboard for seven days after completion. This does not alter their earned score or rewrite difficulty-specific rankings. A newer eligible winner takes over the featured position; expiry restores the ordinary ordering.
## Discord announcements
An announcement is queued only when the organizer enables the Discord setting. Delivery becomes due 15 minutes before the scheduled start. If the setting is enabled or an event is created within that window, the next dispatcher run can announce it while registration remains open. Started, cancelled, expired, or opted-out events are not announced. Rescheduling an unsent event moves its announcement time. Each tournament has one durable announcement record; editing an already announced event does not ping the channel again.
Tournament posts use the existing server-only `DISCORD_BOT_TOKEN`. The destination is fixed to news channel `1558048830770122772`; no additional channel environment variable is needed. Only roles `1558053709554909205` and `1558052962817933322` are allowed to receive mention notifications. User names and tournament titles are escaped, and users, `@everyone`, and `@here` cannot add notifications.
The bot needs access to the destination channel with Send Messages and Embed Links. The selected roles must be mentionable, or the bot must have the relevant role-mention permission. Bot credentials must stay in server environment configuration and must never use a `NEXT_PUBLIC_` variable.
Delivery uses two-minute database leases, a stable Discord nonce, bounded request timeouts, and up to eight attempts with backoff. Rate limits and temporary failures return work to the queue. Permanent configuration errors stop the attempt and save a safe error message. Discord deduplicates matching nonces only within its recent-message window; an unusually delayed ambiguous network response can still prevent an absolute exactly-once guarantee.
## Runtime and deployment
Apply the checked-in migrations before deploying the application code, in filename order. The relevant additions are:
1. `supabase/migrations/20261018060642_extended_difficulty_rules.sql`
2. `supabase/migrations/20261018060844_tournaments.sql`
Use the project's normal controlled database migration process. If applying these through the Supabase SQL editor, run each complete file once, in that order, after the earlier project migrations. These files are schema changes, not application `.env` settings. Do not clear production data to repair a missing table. If PostgREST reports a stale schema cache after a successful migration, reload its schema cache with `NOTIFY pgrst, 'reload schema';`.
The migration creates the tournament tables, membership and private-room guards, the durable announcement outbox, and server-only RPCs. RLS is enabled and anonymous/authenticated direct access is revoked; the application uses its server-side Supabase secret to perform authorized actions. Keep the application and worker on the same schema version.
`npm start` starts the web application and companion worker. The worker calls `tick_tournaments` and drains due tournament announcements every 30 seconds, independently of panorama rendering. Keep that worker running: scheduling must continue even when nobody has the site open. When hosting only the web application, arrange a scheduled authenticated GET to `/api/integrations/discord/dispatch` at least once per minute with `Authorization: Bearer <CRON_SECRET>`. The endpoint rejects requests without the matching server secret and also advances tournaments before draining announcements.
A missing migration or database connection pauses scheduling; the worker retries and limits its warning to once every five minutes. Repair the schema/connectivity rather than relying on visitors to advance events. Failed Discord deliveries are stored in `tournament_announcements`; fix the bot configuration before retrying. Rescheduling or explicitly changing an unsent announcement setting can requeue it according to the migration's retry rules.
## Verification
`npm test` includes tournament schema-validation, API, timer, and announcement tests. `npm run typecheck -- --incremental false` checks the server/client contracts. `node scripts/test-tournaments-sql.mjs` runs SQL integration tests against an isolated in-memory PostgreSQL-compatible fixture and requires the local test dependency at `.data/sql-verification/node_modules/@electric-sql/pglite`.
The SQL suite covers the 32-player limit, locked owner actions, registration, shared game engine, standings, announcement leases, and restricted direct database access. Announcement unit tests use fake Discord clients; they do not publish messages. Apply production migrations and verify a real opted-in event deliberately after deployment; no external test announcement is required during local development.