/** * 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); }