/shop now opens on the season pack (its three looks rendered side by side, its name, its price, one button), then rows of big tiles that show only the item itself, its name and its price: the looks, the collection's items by slot, the founder set while its claim is open, the Supporter badge. Hover lifts a tile (none with reduced motion). A tile opens a detail sheet (a <dialog>, full screen on phones): the live 3D viewer on your skin, a look's or the pack's items as small tiles to try on, and Buy or Gift. The basket is gone: one offer, one checkout, through the same POST /v1/web/checkout and pricing.ts rules (items: [id], amount for the Supporter badge, gift: true for a friend), then /shop/pay as before. Thumbnails: the same shared WebGL renderer, framed ~30 degrees round and from above (auras higher, back items from behind, trails with the whole player from behind), whole players fill the card's height, supersampling up to 2048 px, redrawn when a card changes size. The viewer also looks from slightly above. Cut: the intro paragraph, the "three steps" claim explainer, every description line on cards, the per-set item price lists, and the "Good to know" list. Kept: "Ask whoever pays before you buy. Refunds within 14 days for any reason." by Buy/Gift, the seller and Paddle terms, the legal links, NOT_OFFICIAL, contact, the founder claim and its anti-phishing line. Co-authored-by: Horizon agent <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
Horizon's website
Live at https://playhorizon.dev. The game, the launcher and their developer docs are in horizon-rust/horizon; this repository is only the site.
The site is a Next.js 16 app (App Router with Cache Components, React 19, Tailwind CSS 4), designed and developed by Anthony S of DezerX.
Working on it
You need Node 22 or newer.
git clone https://github.com/horizon-rust/horizon-site && cd horizon-site
npm ci
npm run dev # http://localhost:3000
npm run build && npm start # the production build, as Railway runs it
npm run lint
- A push to
maingoes live. Railway builds theDockerfileand deploys https://playhorizon.dev within a few minutes of every push tomain. If the build or the healthcheck fails, the previous version stays online. For anything bigger than a typo, push a branch and open a pull request first.
Layout
app/ the routes: /, /hub, /mods, /mods/<id>, /mods/submit, /mods/policy, /releases,
/benchmarks, /stats, /docs, /docs/<page>, /privacy, /apply, and the staff's
pages inside the hub: /hub/team, /hub/mod, /hub/sign-in, /hub/setup, /hub/staff-help
(the old /staff/* addresses redirect there; /hub/mod has a Cases tab: Kira's numbered mod
cases and appeals), and /hub/appeal: the appeal form for people banned from the Discord
server or the official Minecraft server (the link Kira DMs, or Discord sign-in; Kira's
appeal/* routes behind the same proxy; timeouts and quarantines are appealed on Discord)
app/api/ the registry and players proxies, and release view counts
app/staff/api/ the staff dashboards' proxy to Kira's service
app/data/ /data/roadmap.json and /data/journal.json, which Kira reads
app/sitemap.ts, robots.ts, manifest.ts, opengraph-image.tsx, llms.txt/, og/docs/
search and sharing: sitemap, robots, share cards, the summary for AI search
components/ every section, page part and data loader
components/site.ts the canonical address (SITE_URL), titles, descriptions and pageMetadata()
content/docs/ the player docs (*.md) and their navigation (nav.json)
content/benchmarks/ our benchmark runs
vendor/horizon-site/ the previous site's hub code, unchanged; it builds /data/journal.json
public/ images, icons and /benchmarks.json
Dockerfile, railway.json the Railway service
The /docs/api/* pages come from docs/api/ in the game's repository, read from GitHub.
Where the data comes from
- GitHub's API: releases, contributors, the roadmap's issues and pull requests. Cached for about 15 minutes.
GITHUB_TOKENlifts the rate limit. - Fresh within a minute: every GitHub loader carries a cache tag (
components/github-fresh.ts: the game's main, its issues and pull requests, its releases, horizon-mods' main). Kira gets GitHub's organization webhook and callsPOST /api/revalidate(Authorization: Bearer $REVALIDATE_SECRET, body{"tags": [...]}) for what changed; the tagged data expires at once and Kira loads the main pages again. Files of a main branch are read at its current commit (rawMainUrl), because raw.githubusercontent.com keeps a branch's files for five minutes. Without the call, each loader's owncacheLifeapplies, as before. - Through Kira:
components/github-api.tssends the REST reads toGITHUB_PROXY_URL(Kira's allowlisted proxy), so revalidating on every change doesn't run into the 60 requests an hour this host gets without a token. - The game's
main:README.md(parity numbers),roadmap/roadmap.json(the hub) anddocs/api/*.md. - The mod registry: the index at horizon-rust.github.io/horizon-mods, ratings and submissions through
REGISTRY_UPSTREAM. - The official server's market:
/hub/marketreads Kira's/smp/market.json(the page the SMP mod writes on the server, read by Kira once a minute) through/api/market. - Telemetry: the Players page reads
telemetry.playhorizon.dev(orTELEMETRY_URL) through this site's server. - Cosmetics:
/shop(the founder set, free to claim in the launcher) readscosmetics.playhorizon.dev's/v1/catalogueand the items' files through this site's server, cached for a minute. The page, its nav entry and its sitemap line exist only while the service says the claim window is open; otherwise/shopis a 404. Nobody signs in on the site.
Kira's feeds
/data/roadmap.json is the game repository's roadmap/roadmap.json as it is. /data/journal.json is built by
journalJson() in vendor/horizon-site/hub.js, the previous site's code, from the same GitHub requests the previous
build made. Kira posts each entry id once, so the ids must not change: leave vendor/ and
components/journal-feed.ts alone unless Kira is changed with them. When GitHub does not answer, both feeds answer
503 instead of a partial feed.
Environment
Railway service variables:
| Variable | What it does |
|---|---|
GITHUB_TOKEN |
Lifts GitHub's rate limit at build and at run time |
REVALIDATE_SECRET |
The bearer token Kira sends to POST /api/revalidate (1Password Site revalidate secret; the same value is Kira's KIRA_SITE_REVALIDATE_SECRET). Without it the route answers 404 |
GITHUB_PROXY_URL |
https://mcp.playhorizon.dev/github/proxy: GitHub's REST API read through Kira (its GitHub App and ETag cache, with REVALIDATE_SECRET as the bearer), since this service has no GitHub token; when Kira doesn't answer, reads go to api.github.com directly |
REGISTRY_UPSTREAM |
The registry's submission service; /api/mods/* is proxied to it |
STAFF_UPSTREAM, STAFF_PROXY_KEY |
Kira's staff service and the key both sides hold; /staff/api/* is proxied to it |
RELEASE_VIEWS_SALT |
Salt for the hashed release views. Without it the server keeps one in .data/ |
TELEMETRY_URL |
Optional, defaults to https://telemetry.playhorizon.dev |
KIRA_URL |
Optional, Kira's address for /api/market; defaults to https://kira-production-500e.up.railway.app |
SITE_URL |
Optional, defaults to https://playhorizon.dev; used for canonical links, the sitemap and share cards |
COSMETICS_UPSTREAM |
Optional, defaults to https://cosmetics.playhorizon.dev: the cosmetics service /shop reads (server side only). Point it at a local stand-in for development and tests only, never on production |
SHOP_PREVIEW |
1 shows /shop with a sample seven-day claim window (from the start of the current hour) while the service's window is closed, for previews. Never set it on production (it is ignored where RAILWAY_ENVIRONMENT_NAME is production) |
Release view counts live in .data/, which is reset by every deploy unless a volume is mounted at /app/.data.
Rules the site keeps
- The disclaimer, "Not an official Minecraft product. Not approved by or associated with Mojang or Microsoft.", is under the headline, in the phone menu, the footer and the 404 page.
- No Minecraft logo or wordmark; "Minecraft" appears only descriptively ("for Minecraft: Java Edition 26.3"). No game files are offered or linked.
- Parity is worded as the README words it: 100% of what the harnesses compare.
- No cookies, analytics, trackers or third-party fonts and scripts.
- Motion stops under
prefers-reduced-motion; nothing hijacks the scroll.
Licences
Every asset is listed in LEGAL.md.