# The page and its contract The repository ships the app's TypeScript and Preact components (`web/`) and the page that embeds it, `site/index.html`, with its assets (`site/assets/`: the Nunito font, under the SIL Open Font License, included as `OFL-nunito.txt`; the web app manifest; and the app icons, drawn by `node site/make-icons.mjs`) and its service worker, `site/sw.js`. `site/build.sh` puts them together (see `deployment.md`). The page owns layout and look; the app owns behavior. Any other page can embed the pet if it provides the mount point, config and styles below. ## The shipped page `site/index.html` is a complete, standalone page: a pastel "toy" device with the LCD, meters and care buttons, a panel with chat, diary, shelf and settings tabs, and three short notes that explain the pet and list build details. It hosts the app, model, runtime and font together, with an optional external Cloudflare Web Analytics beacon configured at build time. It uses a rounded font, follows `prefers-color-scheme` for light and dark, and is laid out for phones first (one column below 860 px, tested at 360 px). The notes are readable without JavaScript. The brain download/upgrade notice spans the app above both the device and tabs, directly below the page header on mobile. It remains visible when switching tabs, with an accent border and a full-width button on small phones. The viewport requests no touch zoom; `touch-action: pan-x pan-y` on the root allows scrolling while suppressing pinch and double-tap zoom. Browser or accessibility settings may override the viewport request. Text stays selectable outside the game pad; its buttons suppress selection and touch callouts. The hatch form suggests a pet name once when first shown during a visit. Deleting it leaves the field empty, including through download progress and other re-renders; a nonempty name is still required to hatch. When loading fails, a retry button appears beside the egg's error. It starts the brain again without resetting the egg, suggested/edited name or saved pet. It is a template: `@PLACEHOLDERS@` in it are replaced when it is built. `site/build.sh` fills them from `site/release.json`; `test/dev-site.sh` uses the same build for local previews. The page must be served at a URL ending in `/` with `sw.js` beside it, because it registers the worker by that relative URL and the worker's scope is the page's directory. | Placeholder | Value | |---|---| | `@RUNTIME@` | URL of the runtime directory, no trailing slash | | `@MODELS@` | URL prefix holding model directories, with a trailing slash | | `@MODEL_ID@` | model directory name | | `@SIZE@`, `@SIZE_MB@` | size of `onnx/model_q4.onnx` in bytes, and in MB for the text | | `@APP@` | URL of the directory holding the bundled `main.js`, `brain.js` and `tokenling_core_bg.wasm` | | `@ASSETS@` | URL of the directory holding `site/assets/*` | | `@TRAINED@` | one line about the model build for the details list | | `@SOURCE@` | URL of the source code | | `@ANALYTICS@` | Optional Cloudflare Web Analytics module snippet; empty without a configured site token | | `@ANALYTICS_NOTICE@` | Visible traffic/performance analytics disclosure; emitted with the snippet | | `@NEWS@` | JSON string: what the current brain does better, for the upgrade dialog | | `@FEATURES@` | JSON list of feature names the current brain was trained for | | `@REMOTE_ENDPOINT@` | JSON string for an optional default remote-host address; empty by default | | `@OLDER@` | JSON list of older brains the app may keep running, `{"id", "features"}`, newest first | `test/dev-site.sh` fills them for local previews and fails if any are left. `node test/contract.mjs` fails if the app starts using an element id or config key that the page or this document does not cover. ## Local and remote choice `Setup.tsx` asks local or remote before creating an egg. `Hosting.tsx` keeps both options in settings, with a trusted server address, transfer status, reconnect and explicit local-checkpoint recovery. Remote mode explains that the selected host receives saves, chats and memories. Local mode retains its original offline/download behavior. The new `hosting-choices` and `hosting-option` classes style accessible radio groups and their explanations; URL inputs fit the phone width. Interactive markup stays in Preact. The brain notice/status distinguishes browser download from server connection. Remote care/chat call the private host, and debug body edits are local-only. Save exports omit access keys. Static explanatory text describes both modes without requiring JavaScript. ## Loading ```html
``` | Config key | Meaning | |---|---| | `runtime` | URL of the vendored runtime directory (`transformers.min.js` plus the ONNX Runtime `.wasm`/`.mjs` files), no trailing slash | | `models` | URL prefix that holds model directories, with a trailing slash | | `modelId` | directory under `models` to load; also the cache key, so change it for every new model | | `size` | size in bytes of `onnx/model_q4.onnx`, used to label downloads and the upgrade | | `news` | optional one line about what `modelId` does better than older brains, shown in the upgrade dialog | | `features` | optional list of feature names `modelId` was trained for (see "Brain features" below) | | `older` | optional list of older brains, newest first, as `{"id": …, "features": […]}`. A visitor who still has one of them cached keeps using it until they accept the upgrade. List only brains that speak the app's current prompt format; leave the list empty after a format change | | `sw` | optional URL of the service worker, relative to the page (`sw.js`); without it there is no offline copy and no alerts | | `icon` | optional URL of the icon shown on alerts | | `weatherSeed` | optional string mixed into the deterministic weather | | `remoteEndpoint` | optional default remote-host address for setup/settings; local remains the default, and owners can supply their own trusted host | `main.js`, `brain.js` and `tokenling_core_bg.wasm` must be served together under the hashed app directory (`.wasm` as `application/wasm`). The main bundle includes Preact, all components/controllers and the renderer; both bundles load the shared rules module from beside themselves before running, and the page starts `brain.js` as a module worker relative to itself. If the module cannot load, `#pet-root` shows a short error instead of the app. `site/build.sh` hashes the emitted bundles, maps and module, including dependencies, so a build never mixes versions. Keep app, model and runtime on the same origin. The app adds the class `pet-js` to `` once it has booted. Use it to keep the interactive area hidden when JavaScript is off while leaving the explanatory content visible. ## Host elements and component ids The host provides only `pet-root` (an empty mount element) and `pet-config` (the JSON block). Components in `web/components/` create and update all the ids below; embedding pages must not duplicate their markup. The ids remain useful for browser tests, labels and accessibility. CSS must keep `[hidden] { display: none }` winning over grid/flex rules. | Area | Ids | |---|---| | Screen | `pet-lcd` (a `