|
| 1 | +# Design: one page per catalog entry |
| 2 | + |
| 3 | +Date: 2026-09-24. Status: approved in conversation, awaiting spec review. |
| 4 | + |
| 5 | +## Why |
| 6 | + |
| 7 | +The repo is gaining stars daily. In the 14 days to 2026-09-24: 232 unique visitors, |
| 8 | +ChatGPT the second-largest referrer (29 unique, behind github.com at 30), and the most |
| 9 | +visited paths after the front page are individual catalog markdown files on GitHub. |
| 10 | + |
| 11 | +The Pages site does not serve those visitors well: |
| 12 | + |
| 13 | +- It is one page. Entry "permalinks" are `#entry-<id>` fragments, which search engines |
| 14 | + and AI crawlers do not index separately, so no entry can be found or cited on its own. |
| 15 | +- The entry dialog shows frontmatter and the first paragraph only. The Notes (the |
| 16 | + catches), the dated Evidence quotes and the Related links exist only on GitHub. |
| 17 | + |
| 18 | +Goals, from the maintainer: be useful to visitors, reach more people, look credible. |
| 19 | +Converting visitors into contributors is not a goal of this project. |
| 20 | + |
| 21 | +## What success looks like |
| 22 | + |
| 23 | +- Someone arriving from a search engine or an AI assistant lands on the entry they |
| 24 | + asked about and sees its licence, commercial stance and credit requirement at once. |
| 25 | +- Every entry has a stable, indexable, shareable URL carrying the full entry. |
| 26 | +- The site shows everything the markdown file does, so GitHub is no longer the only |
| 27 | + place to read an entry's catches and evidence. |
| 28 | + |
| 29 | +## Decisions taken |
| 30 | + |
| 31 | +| Question | Decision | |
| 32 | +| --- | --- | |
| 33 | +| Page content | The full entry: summary, facts, Notes, Evidence, Related | |
| 34 | +| Clicking an entry on the homepage | Navigates to its page; the dialog is retired | |
| 35 | +| Clickable area | The whole card, via a stretched title link | |
| 36 | +| Markdown rendering | A small built-in renderer; no dependencies | |
| 37 | +| URL scheme | `/entry/<id>/` (ids are unique catalog-wide, so moving an entry between categories keeps its URL) | |
| 38 | +| Deprecated entries | Get a page with a banner, marked `noindex`, left out of the sitemap | |
| 39 | +| Out of scope | Pages for category READMEs and `docs/`; homepage visual changes | |
| 40 | + |
| 41 | +## Page anatomy |
| 42 | + |
| 43 | +URL: `<siteUrl>/entry/<id>/`. Title: `<name> (<license>) | Free Game Dev Assets`. Meta |
| 44 | +description: the entry summary (first body paragraph, as `summaryFromBody` produces it). |
| 45 | + |
| 46 | +Top to bottom: |
| 47 | + |
| 48 | +1. Site top bar, then a breadcrumb: Catalog, then the category (linking to the homepage |
| 49 | + with `?cat=<category>`), then the entry name. |
| 50 | +2. `h1` with the entry name; the summary paragraph. |
| 51 | +3. "At a glance" facts: |
| 52 | + - Licence, with `license_spdx` when set |
| 53 | + - Commercial use, using the site's existing labels (commercial OK, non-commercial, |
| 54 | + per-file review, commercial ?) |
| 55 | + - Credit required; when `attribution_string` is set, the string with a Copy button |
| 56 | + - Status, with the legend's one-line meaning |
| 57 | + - Verified date with the existing fresh / aging / stale cue |
| 58 | + - Publisher, formats, and `camera_perspective` / `grid_dimensions` when set |
| 59 | +4. Actions: **Go to source** (the entry `url`, primary), **View the file on GitHub**, |
| 60 | + **Report a problem with this entry** (the existing `correction.yml` issue template). |
| 61 | +5. The rendered body from the first `##` onwards (Notes, Evidence, Related). |
| 62 | +6. Previous and next entry within the same category (same order as the homepage |
| 63 | + grouping), then the site footer. |
| 64 | + |
| 65 | +Deprecated entries additionally show a banner stating the entry is deprecated and why, |
| 66 | +taken from its `- Deprecated:` line. |
| 67 | + |
| 68 | +Without JavaScript the page is complete; the only script is the Copy button. |
| 69 | + |
| 70 | +## Build architecture |
| 71 | + |
| 72 | +`site/build.mjs` stays the orchestrator. Two new modules: |
| 73 | + |
| 74 | +- **`site/lib/markdown.mjs`**: `renderEntryBody(markdown, context)` returns HTML. |
| 75 | +- **`site/lib/entry-page.mjs`**: `entryPageHtml(entry, bodyHtml, neighbours, site)` |
| 76 | + returns a full HTML document. |
| 77 | + |
| 78 | +`build.mjs` writes `dist/entry/<id>/index.html` for every entry (about 4 MB in total) |
| 79 | +and adds `page` (the entry's URL, relative to the site root) to each entry in |
| 80 | +`data.json` and `data.js`. `site/README.md` documents the new field. |
| 81 | + |
| 82 | +### Renderer scope |
| 83 | + |
| 84 | +Measured on 2026-09-24 across all 319 entry bodies: paragraphs, `#` and `##` headings, |
| 85 | +bullet lists (three entries nest them), bold, italic (`*x*` and `_x_`), inline code, |
| 86 | +links (relative in all 319, external in 91), bare URLs (10) and backslash escapes (4). |
| 87 | +No tables, code fences, blockquotes, images, raw HTML or `###`. |
| 88 | + |
| 89 | +- Supported blocks: `##` heading (rendered as `h2`), paragraph (consecutive lines |
| 90 | + joined), bullet list with nesting by indentation. The `#` title line is dropped. |
| 91 | +- Supported inline: `**bold**`, `*italic*`, `_italic_`, `` `code` ``, markdown links |
| 92 | + (bracketed text followed by a parenthesised URL), bare `http(s)://` URLs, backslash |
| 93 | + escapes. |
| 94 | +- All text is HTML-escaped before inline markup is applied. Code spans are not further |
| 95 | + processed. |
| 96 | +- Anything else at block level (a line starting with `|`, three backticks, `>`, `![`, a |
| 97 | + raw HTML tag, `###` or deeper) throws an error naming the file and line. The build |
| 98 | + fails; it never renders unsupported syntax silently. |
| 99 | + |
| 100 | +### Link rewriting |
| 101 | + |
| 102 | +Resolved against the entry's own path in the repo: |
| 103 | + |
| 104 | +- Target is another catalog entry (`x.md`, `../<category>/x.md`): relative page URL |
| 105 | + `../x/`, which works under the `/Free-Game-Dev-Assets/` base and in a local preview. |
| 106 | +- Target is any other repo file (a category `README.md`, `docs/*.md`): its GitHub blob |
| 107 | + URL, built from `site.repo`. |
| 108 | +- Absolute `http(s)` URLs are left as they are. |
| 109 | +- A relative target that does not exist fails the build. |
| 110 | + |
| 111 | +## Homepage changes |
| 112 | + |
| 113 | +- Card titles and the Safe starting points table link to `entry/<id>/`, in both the |
| 114 | + prerendered HTML and `app.js`. |
| 115 | +- Whole card clickable: the title link's `::after` covers the card; other links in the |
| 116 | + card sit above it with `position: relative` and a higher `z-index`. One named link per |
| 117 | + card remains for screen readers and keyboard users. |
| 118 | +- The entry dialog, its pager, `openEntry` and the dialog CSS are removed. |
| 119 | +- Filters already live in the query string, so Back returns to the same filtered list. |
| 120 | + Scroll position: on following an entry link, the page stores `scrollY` in |
| 121 | + `sessionStorage` keyed by the full homepage URL, and restores it after the list is |
| 122 | + re-rendered. Storage failures are ignored. |
| 123 | +- Old links: a homepage load with `#entry-<id>` for a known id calls |
| 124 | + `location.replace("entry/<id>/")`. Without JavaScript the fragment still scrolls to |
| 125 | + the card, because cards keep their ids. |
| 126 | +- `404.html` gains a line pointing to the catalog search. |
| 127 | + |
| 128 | +Unchanged: search, filters, grouping, scroll-spy, the Search button, keyboard |
| 129 | +shortcuts, the mobile layout. |
| 130 | + |
| 131 | +## Search engines and AI assistants |
| 132 | + |
| 133 | +Per entry page: |
| 134 | + |
| 135 | +- `<link rel="canonical">` to the page's own URL. |
| 136 | +- Open Graph and Twitter tags (title, description, URL, the existing `og-card.png`). |
| 137 | +- JSON-LD: a `WebPage` whose `mainEntity` is a `CreativeWork` with `name`, `url` (the |
| 138 | + source), `description`, `publisher` when set, `dateModified` (the `verified` date), |
| 139 | + and `license` as a URL when `license_spdx` is set (`https://spdx.org/licenses/<id>.html`), |
| 140 | + omitted otherwise. |
| 141 | +- `noindex` on deprecated entries. |
| 142 | + |
| 143 | +Site-wide: |
| 144 | + |
| 145 | +- `sitemap.xml` lists the homepage and every non-deprecated entry page, with `lastmod` |
| 146 | + from `verified`. The `#entry-` URLs are removed. |
| 147 | +- `llms.txt`: what the catalog is, its rules (links and metadata only, licences |
| 148 | + checked at source, what `needs-review` means), then one line per non-deprecated |
| 149 | + entry: name, licence, commercial, credit required, page URL. |
| 150 | +- `llms-full.txt`: the same header, then each entry's summary and Notes as plain text. |
| 151 | +- Both are generated from the same data as the pages, on every build. |
| 152 | + |
| 153 | +## Testing |
| 154 | + |
| 155 | +Added to `site/checks.test.mjs` (runs in both workflows): |
| 156 | + |
| 157 | +- Renderer fixtures: one accepted case per supported feature, including nested lists |
| 158 | + and escapes; one rejected case per unsupported block type; escaping (an entry |
| 159 | + containing `<script>` renders as text); link rewriting for each target kind, |
| 160 | + including a missing relative target failing. |
| 161 | + |
| 162 | +Added to the build (fails on any violation): |
| 163 | + |
| 164 | +- Across all generated pages: no leaked markdown (a double asterisk, a closing bracket |
| 165 | + followed by an opening parenthesis, a backtick), exactly one |
| 166 | + `h1`, a canonical link, a non-empty title, and every internal link resolving to a |
| 167 | + generated file. |
| 168 | + |
| 169 | +Before shipping, checked in a browser as in earlier QoL passes: contrast in light and |
| 170 | +dark, keyboard order, the stretched link, back-navigation scroll restore, the |
| 171 | +`#entry-` redirect, and layout at 375x800 and 800x400. |
| 172 | + |
| 173 | +## Risks |
| 174 | + |
| 175 | +- **Page count and build time.** 319 small files; the build is pure string work. Watch |
| 176 | + the Pages artifact size; no action expected. |
| 177 | +- **Renderer drift.** A future entry using new syntax fails the build by design, with |
| 178 | + the file and line. The fix is to extend the renderer and its fixtures, or rewrite the |
| 179 | + entry. |
| 180 | +- **Link rot on GitHub blob URLs** for docs and READMEs, if files move. The validator |
| 181 | + already checks relative links on the markdown side, so a move breaks there first. |
| 182 | +- **Search engines keeping old fragment URLs.** The redirect covers visitors; the new |
| 183 | + sitemap replaces the old URLs for crawlers. |
| 184 | + |
| 185 | +## Follow-ons, not in this project |
| 186 | + |
| 187 | +- Pages for category READMEs (the "choosing" tables) and `docs/` guides. |
| 188 | +- Starter stacks by game type (sub-project A), visible trust signals (C), README first |
| 189 | + screen (D), and the maintainer-only items (E), as mapped in the brainstorm. |
0 commit comments