Project Plateau
Project Plateau: four glass plates, one living valley, and the road back to camp.
翻訳待ち
No updates detected yet. GitHub releases will appear here.
Greenfield Studio website — play Minigolf Pro free in your browser
翻訳待ち
Loading the full README.
Read the full README ↗The Greenfield Studio website, with Minigolf Pro playable right on the site at /play/.
A plain static site: hand-written HTML/CSS, one small script, no framework, no build step
and no runtime dependencies. The game at /play/ is the Minigolf Pro default web build,
the same one itch.io serves, copied in by a script.
index.html studio home: Watch / Wear / Play doors in the first screen, the play stage, the course, studio, journal, contact
minigolf-pro/ the game's page
ambience/ Greenfield Ambience: the films (list written by tools/sync-ambience.mjs), the worlds
wallpapers/ the worlds as live wallpapers for Windows: status only until a wallpaper is on sale (hand-written; update it when one ships)
journal/ devlog: index, one folder per post, feed.xml (Atom)
press/ press kit (press/files/ + the zip are generated)
privacy/ privacy note for the site; links the game's own policy
404.html "Out of bounds." (root-absolute paths: Pages serves it at any depth)
play/ GENERATED — the game build (never edit by hand, never committed on main)
assets/css/site.css the whole design system; tokens on :root
assets/js/site.js inline play stage, lazy video loops, trailer play button, film player, PLAY_OVERRIDE
assets/media/ web derivatives of minigolf-pro/branding (generated, committed)
assets/fonts/ Jost + IBM Plex Mono, latin subsets, SIL OFL (licences alongside)
tools/ everything below
| task | command |
|---|---|
| preview locally | npm run serve → http://localhost:5501 |
| ship a new game build | in ../minigolf-pro: npm run build, then here: npm run sync-game |
| check everything | npm run audit (add -- --shots for screenshots in .audit/, -- --game to boot the game and fire a real shot) |
| publish | npm run deploy (dry run), then npm run deploy -- --push |
| check the live site | node tools/audit.mjs --url https://greenfieldstudio.org/ --game |
| rebuild images/video | npm run media (reads ../minigolf-pro/branding; set MINIGOLF_REPO if elsewhere) |
| hole screenshots for a post | node tools/capture-holes.mjs 36:asteroid-field (0-based campaign index; writes -640/-1200.webp + an -og.jpg share card; --out <dir> --no-og for other uses, e.g. the world cards in assets/media/worlds/) |
| a new film or Short went public | nothing: the daily workflow does it. To see it sooner: npm run sync-videos, check /ambience/ and the home page, then deploy |
| the YouTube studio banner | node tools/make-banner.mjs --worlds <dir> → .audit/banner/ (rendered from the site's own CSS and dotted shot; world images via capture-holes --out <dir> --no-og) |
| change the menu or footer | edit the template in tools/sync-chrome.mjs, run it (the audit fails if a page drifts) |
| see visitor numbers | Cloudflare dashboard → Analytics → Web analytics → greenfieldstudio.org (/play/ = game loads) |
Every Play link is play/?play=1. That query is the game's own fast path (minigolf-pro R4669): it
skips the menu and lands the visitor in a hole, hole 1 for a newcomer and their saved spot otherwise.
Plain play/ opens the menu. Keep new Play links on ?play=1; the audit checks that it still lands.
Adding a journal post: copy journal/level-critic/ as the template (its og:image is a JPEG
on purpose, since link-preview scrapers don't all take WebP), then list the post in
journal/index.html, journal/feed.xml (new <entry>, bump the feed's <updated>), sitemap.xml,
the "from the journal" block on the home page, and the PAGES list in tools/audit.mjs.
Every number in a post should come from a real tool run or a measurement in the game repo.
Greenfield Ambience films come from the channel's public feed: sync-ambience self-hosts each
thumbnail, records the film in assets/media/ambience/films/films.json and rewrites the list on
/ambience/. Unlisted, private or still-processing uploads aren't in the feed, so they stay off the
site by themselves; a film that is no longer in the feed is removed only if YouTube (oEmbed) says it
is private or deleted, because the feed reaches back just ~15 videos; with no public film the page shows a "coming soon" card. A film plays in
YouTube's privacy-enhanced player (youtube-nocookie.com) inside a dialog, created only on click.
Each card's length, "4K" and frame rate are read from the film's own description, so a spec is never
shown that the video doesn't state.
One page per film. sync-ambience also writes ambience/<slug>/index.html for every film (the card on
/ambience/ links to it), a 1200 × 630 <id>-og.jpg share image, and the film block in sitemap.xml.
A film's slug is chosen once from its title (stored in films.json) and never changes, because it is the
page's address. Each page is built only from the film's own YouTube text: the first paragraph is the lede
and meta description, paragraphs with a link, hashtag or @handle are left out, 0:00 Label lines become
the chapter list, and the length, 4K and frame rate come from the title and description. The player is the
same click-to-load privacy-enhanced player as everywhere (nothing from YouTube before a click), in place.
The generated pages carry the shared menu and footer through sync-chrome (which sync-ambience runs).
tools/film-pages.json is hand-written: add "<video id>": { "repeatCheck": "YYYY-MM-DD" } once
audio_repeat_check.py (and the picture check) has passed on that film's full master, and only then does
the page's readout say loops: none. The page text itself mirrors the YouTube description, so a "No Loops"
sentence there is only as true as the description.
This is the beginning of the README. Read the rest at the original. Read the full README ↗