Files
runicgateway.com/src/data/screens.mjs
wtclaude 8b6efd5c0a
All checks were successful
PR checks / checks (pull_request) Successful in 1m13s
feat(screens): retake the two frames a signed-in character changes
The org lead signed a character into the shard by hand — the step automation
could not reach — so shard-status and app-shard were retaken. The shard page
now reads 1 player online in Britain, and the app's card agrees.

Two things worth noticing in the retake. Presence reaches the public page as
counts and regions rather than names, which is the visibility framework working
unprompted. And the guild board's online column did not move: it refreshes only
when a guild's signature changes, which is the stale-roster defect wearing a
different hat.

A sixth defect, visible in the shipped phone capture: the app interpolates a
count into a fixed plural and says "1 players online". Raised, not fixed —
it wants a plurals resource in the app.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 10:09:52 -05:00

241 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* screens.mjs — every screenshot the site ships, and where it came from.
*
* PLAN.md §13 phase 9, D4 / D42–D45.
*
* ---------------------------------------------------------------------------------------
* ONE LIST, THREE READERS
* ---------------------------------------------------------------------------------------
* `scripts/captureScreens.mjs` reads this to know what to shoot and where to click before
* it shoots; `src/components/Screenshot.astro` reads it to render one figure by id; and
* `scripts/checkScreens.mjs` reads it to prove that every file exists at the declared size
* and that nothing in `public/screens/` is orphaned. A screenshot is therefore a data
* change: add an entry, re-run the capture, and the check tells you if you missed a step.
*
* That split is what makes a re-capture cheap. §1 says the site must not describe a
* product that no longer looks like that, and the way a screenshot goes stale is that
* nobody remembers how it was taken. The route, the viewport, the scroll offset and the
* signed-in state are all here, so the answer to "how do I retake this" is one command.
*
* ---------------------------------------------------------------------------------------
* WHY EVERY WEB SHOT IS THE SAME SIZE
* ---------------------------------------------------------------------------------------
* A 1280×800 viewport at 1.5× device pixels — 1920×1200 in the file. Uniform because the
* marketing pages lay them out in a grid and a grid of mixed aspect ratios reads as an
* accident, and because a check that asserts one pair of numbers cannot drift the way a
* per-file table can. Where a page's interesting part is below the fold, `scrollY` moves
* the frame rather than the size changing.
*
* The phone shots are the device's own portrait size and are declared per family for the
* same reason (see `PHONE`).
*
* ---------------------------------------------------------------------------------------
* WHAT IS IN THEM
* ---------------------------------------------------------------------------------------
* A demo deployment of this platform, wired to a real ServUO shard over a real sidecar
* (D42): the marketplace rows are player vendors the game actually holds, the atlas is
* parsed from the shard's own spawn files, the guild rosters came over the bridge. The
* deployment is branded "Runic Gateway Demo" rather than a real community's name (D43) —
* the screenshots show the platform, not somebody's private shard.
*
* Nothing here is a mock-up, and nothing here was drawn.
*/
/** Web capture geometry. The capture script and the check both read these. */
export const WEB = { viewport: { width: 1280, height: 800 }, scale: 1.5, width: 1920, height: 1200 };
/**
* Phone capture geometry — the emulator's own portrait pixels, unscaled.
*
* An API 35 device rather than the API 36 the plan named: the API 36 image on this machine
* had 200 MB left on its data partition and refused the install, and wiping somebody's
* development device to take a screenshot is not a trade worth making. The app targets both.
*/
export const PHONE = { width: 1440, height: 3088 };
/**
* @typedef {object} Screen
* @property {string} id File stem under `public/screens/`, and the handle a page uses.
* @property {string} route Route on the demo deployment. The capture script's only input.
* @property {boolean} admin Capture signed in as an administrator rather than anonymously.
* @property {number} [scrollY] Pixels to scroll before the shot, when the subject is below the fold.
* @property {string} alt What the screen shows, for somebody who cannot see it.
* @property {string} caption The sentence printed under the figure.
* @property {'web'|'phone'} family Which geometry the file follows.
*/
/** @type {Screen[]} */
export const screens = [
// ── The product's public surfaces ───────────────────────────────────────────────────
{
id: 'shard-status',
route: '/uo/shard',
admin: false,
alt: 'The shard page of a Runic Gateway site, showing the shard online, its gold supply, the state of the shard link, and one player online in Britain.',
caption:
'The shard console. Every number on it came over the bridge from a running game server — including the player who was standing in Britain when this was taken.',
family: 'web',
},
{
id: 'marketplace',
route: '/uo/market',
admin: false,
scrollY: 470,
alt: 'The marketplace page, listing items for sale by player vendors with their prices, shop names and locations, above a search box and price filters.',
caption:
'Player vendors, searchable from the website — the same index the in-game vendor search reads, honouring the same per-vendor opt-out.',
family: 'web',
},
{
id: 'spawn-atlas',
route: '/uo/atlas',
admin: false,
scrollY: 430,
alt: 'The spawn atlas, listing creatures with how many of them spawn and on which facets, above a search box and facet filters.',
caption:
"The spawn atlas is parsed from the shard's own spawn files, so it stays accurate whether or not the server is up.",
family: 'web',
},
{
id: 'guilds',
route: '/uo/guilds',
admin: false,
alt: 'The guilds page, listing each guild on the shard with its abbreviation, how many members are online, and its leader.',
caption:
'Guilds arrive from the shard, not from a form — and a Team on the website is one of them, with its own forum and roster.',
family: 'web',
},
{
id: 'houses',
route: '/uo/houses',
admin: false,
alt: 'The houses page, listing homes that have entered their final decay stage with their owner and location.',
caption:
'Houses in danger, from the same decay data the game uses — a live process, not a nightly export.',
family: 'web',
},
{
id: 'news',
route: '/site/news',
admin: false,
alt: 'The news page, listing posts with their category, date, headline and summary.',
caption:
'News, five-on-friday and the newsletter are one posting system with three categories — and none of it knows what game you run.',
family: 'web',
},
// ── The administration screens the documentation describes ──────────────────────────
{
id: 'admin-dashboard',
route: '/admin',
admin: true,
alt: 'The administration dashboard, showing the site mode, counts of posts, wiki pages and users, and a feed of recent administrative activity.',
caption: 'The dashboard on first sign-in: site mode, what the site holds, and who has done what.',
family: 'web',
},
{
id: 'admin-users',
route: '/admin/users',
admin: true,
alt: 'The users screen, listing accounts with their role and status and the controls to change them.',
caption: 'Users and roles. A change of role takes effect on the next request, not the next login.',
family: 'web',
},
{
id: 'admin-shard',
route: '/admin/uo/link',
admin: true,
alt: 'The shard connection screen, showing the sidecar connected, the websocket ingest live, and fields for the sidecar URL, token and protocol version.',
caption:
'The shard connection, showing a live sidecar. The token is write-only: it is never sent back to any client, including this screen.',
family: 'web',
},
{
id: 'admin-modules',
route: '/admin/modules',
admin: true,
alt: 'The modules screen, showing the Ultima Online module running with the routes it mounts, and the list of hosts modules may be installed from.',
caption:
'A module is installed from a release URL and runs inside the server, so only listed hosts are allowed to serve one.',
family: 'web',
},
{
id: 'admin-appearance',
route: '/admin/appearance',
admin: true,
alt: 'The appearance screen, showing the colour and typography controls a deployment uses to set its own theme.',
caption: 'Branding is data. One image runs as any community, and the app takes its colours from here too.',
family: 'web',
},
// ── The Android app, against the same deployment on the same day (D26) ──────────────
//
// Captured from an emulator pointed at the demo stack through `adb reverse`, signed in as
// an ordinary player. The app takes its name, colours and navigation from the site it is
// connected to, so these are not a neutral app: they are one deployment's app.
{
id: 'app-home',
route: '/',
admin: false,
alt: "The app's home screen, showing the deployment's emblem and name, a live online indicator and its description.",
caption:
"The app is one screen of setup: type the address of a site, and it becomes that community's app.",
family: 'phone',
},
{
id: 'app-shard',
route: '/shard',
admin: false,
alt: "The app's shard screen, showing the shard online with one player and its gold supply, links to champion spawns, guilds, governors and falling houses, and a live activity feed.",
caption:
'The same bridge feeds the phone: the same player online, and an activity list that is live — those houses entered their final decay stage while it was open.',
family: 'phone',
},
{
id: 'app-market',
route: '/market',
admin: false,
alt: "The app's marketplace, listing weapons for sale with their price, the shop selling them and where it stands.",
caption: 'Every player vendor on the shard, searchable from a phone.',
family: 'phone',
},
{
id: 'app-wiki',
route: '/wiki',
admin: false,
alt: "The app's wiki index, listing pages with their category and summary above a search box.",
caption:
"The wiki, the news and the rules are the site's own content, rendered natively rather than in a web view.",
family: 'phone',
},
{
id: 'app-account',
route: '/account',
admin: false,
alt: "The app's account screen for a signed-in player, with username and password controls, two-factor authentication, trusted devices and recovery codes.",
caption:
'Self-service, and the same session model as the website: two-factor, trusted devices and recovery codes all live here.',
family: 'phone',
},
{
id: 'app-drawer',
route: '/',
admin: false,
alt: "The app's navigation drawer for a signed-in player, listing the deployment's own pages above the player's own account, characters, vendors and houses.",
caption:
"The drawer is the deployment's own navigation, not a fixed menu — a site that renames or reorders its pages renames and reorders them here.",
family: 'phone',
},
];
/** One entry by id, or `undefined`. */
export function screenById(id) {
return screens.find((shot) => shot.id === id);
}
/** Every entry in a family, in declaration order. */
export function screensOf(family) {
return screens.filter((shot) => shot.family === family);
}