{"generated":"https://docs.wptom.com/docs.json","pages":[{"path":"/account/billing/","url":"https://docs.wptom.com/account/billing/","title":"Billing, invoices and licenses","description":"Receipts, invoices with your company details, license statuses, and refunds.","markdown":"Open **Billing** in your account ([app.wptom.com/account/billing](https://app.wptom.com/account/billing)).\n\n## Invoices\n\nPayments are one-time, with no renewals. Invoices and billing details live in the secure Paddle\nportal: click **Open Paddle portal** to download invoices or add your company name, address and\nVAT number. (The button appears after your first purchase.) Your receipt is also emailed by\nPaddle right after paying.\n\n## Licenses\n\nThe **Licenses** table lists each license with its plan, full key, status and start date:\n\n| Status | Meaning |\n|---|---|\n| `active` | Works in the CLI and your account |\n| `revoked` | Refunded: no longer works |\n| `suspended` | Paused because of a payment dispute (chargeback). The CLI says `This license is suspended. Email tom@wptom.com.` |\n\n## Receipts\n\nThe **Receipts** table lists your purchases: date, product, amount and status (`paid`,\n`refunded` or `disputed`).\n\n## Refunds\n\nEmail tom@wptom.com within 14 days of your purchase. See [Buy a template → Refunds](/account/buy/#refunds)."},{"path":"/account/buy/","url":"https://docs.wptom.com/account/buy/","title":"Buy a template","description":"Plans, what each includes, paying with Paddle, and what arrives after you pay.","markdown":"Free templates need no account: create them with `npx wptom new`. PRO templates need a license.\nEvery license is a **one-time payment** with lifetime updates; there's no subscription.\n\n## Plans\n\n| Plan | Price | What you get |\n|---|---|---|\n| **Single template** | $39 | One PRO template and all its updates. Unlimited projects, yours and your clients'. Up to 3 devices. Upgrade to All-access later: what you paid counts. |\n| **All-access** | $99 | Every PRO template, now and future, and all their updates. Unlimited projects, yours and your clients'. Up to 3 devices. |\n| **Team** | $249 | Everything in All-access for up to 5 people: 5 seats with 3 devices each, in one purchase. |\n\nPrices are in USD; tax is added where it applies and shown before you pay.\n\n## Paying\n\n1. On a template's page on wptom.com, click **Buy** (single template) or **Get all-access**.\n2. The checkout page at app.wptom.com shows what's included, the price, and any credit (see\n   below). Click **Continue to payment**.\n3. Pay in the secure Paddle window: card (Visa, Mastercard, American Express), PayPal, Apple Pay\n   or Google Pay.\n\nYou can pay as a guest: your account is created from the email you pay with. Already signed\nin? The license goes to your account.\n\n[Paddle](https://www.paddle.com) is our reseller and merchant of record: it handles the\npayment, tax (VAT, sales tax) and your invoice. We never see your card details.\n\n## After paying\n\n- The page says **\"Thanks, you're in.\"**\n- Within a minute you get an email **\"Your WPTom … license\"** with your license key (it looks\n  like `WPT-AB2C-D3EF-G4HJ`) and an **Open my templates** button. You don't need to type the key\n  anywhere; it identifies your purchase if you write to us.\n- Your receipt arrives separately, from Paddle.\n\nThen [connect your computer](/cli/login/) with `npx wptom login` and\n[create your site](/getting-started/first-site/).\n\n## Upgrading to All-access\n\nBought single templates and want everything? On the All-access checkout, signed in, the page\nsubtracts what you already paid for templates (\"Credit for templates you own\"). If you paid\nmore than All-access costs, it says so: email tom@wptom.com and we upgrade you for free.\n\n## Refunds\n\n14-day money-back guarantee, no questions asked: email tom@wptom.com within 14 days of your\npurchase. The refund goes back to your payment method through Paddle (usually within 5–10\ndays), and the license stops working. Details: [Refund policy](https://wptom.com/refunds)."},{"path":"/account/devices/","url":"https://docs.wptom.com/account/devices/","title":"Devices","description":"Which computers can use your license, the limit, and how to remove one.","markdown":"Each computer where you run [`wptom login`](/cli/login/) becomes a **device**. Your license\nworks on up to **3 devices** (Team: 3 per seat, 15 in all).\n\n## The limit\n\nWhen you connect a fourth computer, nothing is blocked: the approval page warns\n\"the oldest will be signed out\", and the device you used least recently is disconnected. The\nterminal then says `Device limit reached (3): signed out your least recently used device.`\n\n## See and remove devices\n\nIn your account, open **Devices** ([app.wptom.com/account/devices](https://app.wptom.com/account/devices)).\nEach device shows its name (your computer's name and system, e.g. `mac · macOS`), the country\nand when it was last used. **Sign out** removes it: the CLI on that computer stops working\nuntil you run `wptom login` there again.\n\nOn the computer itself, [`wptom logout`](/cli/logout/) does the same.\n\n## Sold or lost a computer?\n\nSign it out under **Devices**. Its device token stops working immediately; your license key\nisn't stored on any computer, so there's nothing else to change."},{"path":"/account/sign-in/","url":"https://docs.wptom.com/account/sign-in/","title":"Sign in and your account","description":"Sign in to app.wptom.com with an email link or GitHub, and what each page of your account shows.","markdown":"Your account lives at [app.wptom.com](https://app.wptom.com). It's created when you first pay\n(or sign in) with an email.\n\n## Sign in\n\nThere are no passwords. On [app.wptom.com/login](https://app.wptom.com/login):\n\n- **Continue with GitHub**, if your GitHub account uses the same email, or\n- type your email and click **Email me a link**. An email **\"Your WPTom sign-in link\"**\n  arrives; click **Sign in**. The link works once and expires in 15 minutes.\n\nNo email? Check spam, wait a minute, and ask for a new link. Use the email you paid with: that's\nwhere your license is.\n\n## Your account\n\nThe menu at the top has:\n\n- **Overview**: your plan, your license key (partly hidden), how many templates you can use and\n  how many devices are connected, and the command to connect the CLI.\n- **Templates** (My templates): every template, whether your license includes it, its latest\n  version, and the command to create a site from it, e.g. `npx wptom new my-site -t aurora`.\n- **Devices**: the computers connected with `wptom login` (see [Devices](/account/devices/)).\n- **Billing**: your licenses (with their full key and status), your receipts, and a link to the\n  Paddle portal for invoices (see [Billing](/account/billing/)).\n- **Profile**: your name (used to greet you and on receipts). The email can't be changed here;\n  write to tom@wptom.com if you need to.\n\n**Sign out** is in the menu under your name."},{"path":"/cli/","url":"https://docs.wptom.com/cli/","title":"The WPTom CLI","description":"What the WPTom command does, how to run it, every command and option at a glance, and the files it keeps.","markdown":"`wptom` is the command-line tool for WPTom templates. It creates a site from a template, keeps\nit up to date without losing your edits, connects your computer to your license, and gives\nyour AI assistant an MCP server. It's [open source on npm](https://www.npmjs.com/package/wptom),\nhas no install scripts and needs Node.js 22 or newer.\n\n## Run it\n\nYou don't have to install anything: `npx` downloads the latest version and runs it.\n\n```sh\nnpx wptom <command>\n```\n\nThe first time, npm asks `Ok to proceed? (y)`: press <kbd>Enter</kbd>.\n\nPrefer a permanent `wptom` command (shorter, and works offline for the commands that don't\nneed the network)?\n\n```sh\nnpm install -g wptom\n```\n\nThen type `wptom <command>`. Update it with the same command. Every page in these docs works\nwith either form; `npx wptom` always runs the newest version.\n\n:::note\n`zsh: command not found: wptom` means it isn't installed globally: use `npx wptom`, or install\nit as above.\n:::\n\n## Commands\n\n| Command | What it does |\n|---|---|\n| [`wptom new [dir] -t <template>`](/cli/new/) | Create a site from a template |\n| [`npm create wptom@latest`](/cli/create/) | The same, fully interactive |\n| [`wptom update`](/cli/update/) | Bring in the template's latest version, keeping your edits |\n| [`wptom list`](/cli/list/) | Show every template and whether your license includes it |\n| [`wptom login`](/cli/login/) | Connect this computer to your license |\n| [`wptom whoami`](/cli/whoami/) | Show the connected account and device |\n| [`wptom logout`](/cli/logout/) | Disconnect this computer |\n| [`wptom mcp`](/cli/mcp/) | Start the MCP server for AI assistants |\n\n`wptom create` is the same as `wptom new`; `wptom ls` the same as `wptom list`.\n\n## Options\n\n| Option | Used with | What it does |\n|---|---|---|\n| `-t`, `--template <slug>` | `new` | The template, e.g. `aurora` (asked if left out) |\n| `--cms <name>` | `new` | Where the content lives: `files`, `sanity`, or a template variant such as `emdash` (asked when the template has variants) |\n| `--dry-run` | `update` | Show what would change, write nothing |\n| `--no-browser` | `login` | Print the approval link instead of opening a browser |\n| `-v`, `--version` | | Print the CLI version |\n| `-h`, `--help` | | Show the list of commands and options |\n\n```\n$ wptom --help\n w  wptom. v0.3.0\n\nUsage\n  wptom new [dir] -t <template>   create a site from a template\n  wptom update [--dry-run]        pull the latest version, keep your edits\n  wptom list                      show templates and what you can use\n  wptom login                     connect this computer to your license\n  wptom whoami                    show the connected account\n  wptom logout                    disconnect this computer\n  wptom mcp                       MCP server for AI assistants (Cursor, Claude Code…)\n…\n```\n\nAn unknown command prints `Unknown command \"…\"` and the help. Something to celebrate? Try\n`wptom --party`.\n\n## Files it uses\n\n| Where | What |\n|---|---|\n| `~/.config/wptom/config.json` | After `wptom login`: the device token, your email and plan. Readable only by you. Your license key is never stored. (`$XDG_CONFIG_HOME/wptom/` if you set that.) |\n| `<your site>/.wptom/meta.json` | Which template, version and variant the site was created from. Commit it. |\n| `<your site>/.wptom/base/` | A pristine copy of that version, so `wptom update` can tell your edits from upstream changes. Git-ignored; don't edit it. |\n\nThe CLI talks only to app.wptom.com (templates, login) and docs.wptom.com (the MCP server's\ndocs). It sends no analytics.\n\nWhen something goes wrong, see [Errors and fixes](/cli/errors/)."},{"path":"/cli/create/","url":"https://docs.wptom.com/cli/create/","title":"npm create wptom","description":"Create a site interactively, without installing anything first.","markdown":"```sh\nnpm create wptom@latest\n```\n\nThis is [`wptom new`](/cli/new/) for when you'd rather answer questions than type options.\nIt asks:\n\n1. **Which template?** A list of templates; ones your license doesn't include show 🔒 and\n   can't be picked. Free templates are marked `free`, others `PRO`.\n2. **Where should we create your site?** The folder, e.g. `./my-aurora-site` (it must be new or\n   empty).\n3. **Where should the content live?** Only for templates with variants (see\n   [wptom new](/cli/new/#where-the-content-lives)).\n\nThen it downloads the template and prints the next steps, exactly like `wptom new`.\n\nYou can pass the folder and options too (npm needs `--` before options):\n\n```sh\nnpm create wptom@latest my-site -- -t aurora --cms emdash\n```\n\nFor PRO templates, run [`npx wptom login`](/cli/login/) first."},{"path":"/cli/errors/","url":"https://docs.wptom.com/cli/errors/","title":"Errors and fixes","description":"Every message the WPTom CLI can show when something goes wrong, what it means and what to do.","markdown":"Find your message below (search the page with <kbd>Ctrl</kbd>/<kbd>⌘</kbd> <kbd>F</kbd>). Still\nstuck? Email tom@wptom.com with the command you ran and the full output.\n\n## Setup and terminal\n\n| Message | What to do |\n|---|---|\n| `command not found: wptom` | Use `npx wptom …`, or install it: `npm install -g wptom`. [More](/cli/#run-it) |\n| `command not found: npx` / `node` | Install Node.js: [step by step](/getting-started/install-node/). |\n| `Unsupported engine` / `requires Node >= 22` | Your Node.js is too old: install the LTS version. |\n| `Unknown command \"…\"` | A typo; the help lists every command: `npx wptom --help`. |\n| `Can't reach https://app.wptom.com. Check your connection.` | No internet, or a firewall/proxy blocks app.wptom.com. Try again on another network. |\n| `Unexpected response from https://app.wptom.com (…).` | A temporary server problem. Wait a minute and try again. |\n\n## wptom new\n\n| Message | What to do |\n|---|---|\n| `my-site isn't empty. Pick a new folder.` | Use a new folder name, or empty that folder. |\n| `aurora is a PRO template. Run wptom login first.` | Connect this computer: [`npx wptom login`](/cli/login/). |\n| `Aurora isn't included in your license. Get all-access at wptom.com.` | Your license is for another template. [Upgrade](/account/buy/#upgrading-to-all-access). |\n| `No template called \"…\".` | Check the name with [`npx wptom list`](/cli/list/). |\n| `… has no published version yet.` / `… isn't uploaded yet.` | The template isn't released yet. |\n| `aurora has no \"…\" option. Pick one of: files, sanity, emdash.` | Use one of the listed values with `--cms`. |\n| `Checksum mismatch: the download is corrupted. Try again.` | The download was damaged on the way. Run the command again. |\n| `Download failed (…). Try again.` | A network hiccup or an expired download link (they last 5 minutes). Run it again. |\n\n## wptom update\n\n| Message | What to do |\n|---|---|\n| `No .wptom/meta.json here. Run wptom update inside a site created with wptom new.` | `cd` into your site's folder first. |\n| `.wptom/base is missing, so your edits can't be told apart from upstream.` | Restore the `.wptom/base` folder from a backup, or create a fresh site and move your content over. |\n| `aurora@… no longer offers the \"…\" option. Nothing changed.` | The new version dropped your variant; ask us how to move over. |\n| `aurora has no published version.` | No release to update to. |\n| Conflict markers (`<<<<<<<`) in files | Expected when you and the update changed the same lines: [resolve them](/getting-started/updates/#resolving-a-conflict). |\n\n## Login and license\n\n| Message | What to do |\n|---|---|\n| `Nobody approved the code in time. Run wptom login again.` | Codes last 10 minutes. Run it again and click **Approve** in the browser. |\n| `This login request expired or was denied. Run wptom login again.` | Someone clicked **Deny**, or it expired. Run it again. |\n| `Could not start login. Try again in a moment.` | A temporary server problem. Try again. |\n| `Already logged in as … Run wptom logout first to switch accounts.` | You're connected already. [`wptom logout`](/cli/logout/) to switch. |\n| `Not logged in. Run wptom login.` | Connect this computer with [`npx wptom login`](/cli/login/). |\n| `This device was signed out. Run wptom login again.` | Removed under Devices or by a login past the device limit. Log in again. |\n| `This license is suspended. Email tom@wptom.com.` | A payment dispute paused your license. Email us. |\n| Browser shows **No license yet** | The account you signed in with has no license. Sign in with the email you paid with. |\n| Browser shows **Code expired** | Run `npx wptom login` again for a new code. |\n\n## wptom mcp\n\n| Message (in your assistant) | What to do |\n|---|---|\n| `No project found in …` | Open your site's folder in the assistant, or ask it to pass the folder as `directory`. |\n| `No components in …/src/components.` | The folder isn't a WPTom site, or it has no components folder. |\n| `Can't reach https://docs.wptom.com…` | No internet; the docs tools need it. The project tools work offline. |"},{"path":"/cli/list/","url":"https://docs.wptom.com/cli/list/","title":"wptom list","description":"Every template, its tier and latest version, and whether you can use it.","markdown":"```sh\nnpx wptom list\n```\n\n```\n◇  1 template ────────────────────────────────╮\n│                                              │\n│     aurora  PRO   1.0.0   Aurora · SaaS      │\n│                                              │\n├──────────────────────────────────────────────╯\n```\n\nEach line: a lock 🔒 if your license doesn't include it, the template's short name (use it with\n`wptom new -t …`), `free` or `PRO`, the latest version, its name and category. With locked\ntemplates it adds: `🔒 PRO needs all-access: wptom.com/#pricing, then wptom login.`\n\n`wptom ls` does the same. Works without logging in (then every PRO template shows 🔒)."},{"path":"/cli/login/","url":"https://docs.wptom.com/cli/login/","title":"wptom login","description":"Connect this computer to your license with a code you approve in the browser.","markdown":"```sh\nnpx wptom login\n```\n\nConnects this computer to your WPTom license so PRO templates work in the CLI. Free templates\ndon't need it. You do it once per computer.\n\n## Step by step\n\n1. Run the command. It shows a code and opens your browser:\n\n   ```\n   ◇  Connect this computer ─────────────────────────────╮\n   │                                                       │\n   │  5X78-X588                                            │\n   │                                                       │\n   │  Open https://app.wptom.com/device?code=5X78-X588     │\n   │  and check that the code matches.                     │\n   │                                                       │\n   ├───────────────────────────────────────────────────────╯\n   ◒  Waiting for approval in your browser...\n   ```\n\n2. In the browser, sign in if asked ([how](/account/sign-in/)), with the email you bought\n   with.\n3. The page **Connect this device?** shows the code, this computer's name (e.g.\n   `mac · macOS`), your account and how many devices are in use. If the code matches the one\n   in your terminal, click **Approve**. (Not you? **Deny**: nothing is connected and the code\n   stops working.)\n4. The page says **You're connected**, and the terminal:\n\n   ```\n   ◇  Logged in as you@email.com · All-access\n   ```\n\nThe code is valid for 10 minutes. If nobody approves it in time:\n`Nobody approved the code in time. Run wptom login again.`\n\n## Options\n\n| Option | Use it when |\n|---|---|\n| `--no-browser` | The computer has no browser (a server over SSH, a container). The link is printed; open it on your phone or any other device and approve there. |\n\n## Good to know\n\n- **Devices**: a license works on 3 computers (Team: 15). Approving a fourth signs out the\n  one used least recently, and the terminal says\n  `Device limit reached (3): signed out your least recently used device.` See\n  [Devices](/account/devices/).\n- **Already logged in?** `Already logged in as you@email.com. Run wptom logout first to switch accounts.`\n- **No license on the account?** The browser page says **No license yet** with a link to\n  all-access. Free templates still work.\n- **Several licenses** (e.g. a single template and All-access)? The broadest active one is\n  used, and access is checked across all of them.\n- **What's stored**: only a device token, in `~/.config/wptom/config.json`, readable by you\n  only. Your license key never leaves the website.\n\nRelated: [`wptom whoami`](/cli/whoami/), [`wptom logout`](/cli/logout/)."},{"path":"/cli/logout/","url":"https://docs.wptom.com/cli/logout/","title":"wptom logout","description":"Disconnect this computer from your license.","markdown":"```sh\nnpx wptom logout\n```\n\n```\n◆  Logged out. This computer is no longer connected.\n```\n\nSigns this device out on wptom.com (it disappears from **Account → Devices**) and deletes the\ntoken from `~/.config/wptom/config.json`. Use it before selling or giving away a computer, or to\nswitch to another account (then run [`wptom login`](/cli/login/)).\n\nYour sites keep working: logging out only stops downloading PRO templates and updates on this\ncomputer. Not logged in? `Not logged in.`\n\nLost the computer? Sign it out from another one under [Devices](/account/devices/)."},{"path":"/cli/mcp/","url":"https://docs.wptom.com/cli/mcp/","title":"wptom mcp (AI assistants)","description":"Give Cursor, Claude Code, VS Code, JetBrains IDEs (PhpStorm, WebStorm…), Codex or OpenCode your template's components, your version and these docs, through the WPTom MCP server.","markdown":"```sh\nnpx -y wptom mcp\n```\n\nAI coding assistants are much better at changing your site when they don't have to guess.\n`wptom mcp` is an [MCP](https://modelcontextprotocol.io) server: a small helper your assistant\nstarts by itself and asks questions while it works. It is read-only: it never changes your\nfiles.\n\n## What your assistant can do with it\n\n| Tool | What it gives the assistant |\n|---|---|\n| `get_project_info` | Which template, version and variant your site was created from, whether a newer version is out (and that `npx wptom update` brings it), your `@wptom/*` packages, and the files that matter (`AGENTS.md`, the content model, the brand) |\n| `list_components` | Every component in your project with what it does and its props |\n| `get_component` | One component's full documentation: what it does, every prop with its type and description, where the file is and how to use it |\n| `search_docs` | Searches these docs (docs.wptom.com) |\n| `get_doc` | Reads one docs page in full |\n| `list_templates` | The template catalog, and which templates your license includes |\n\nComponents are read from **your project**, so the answers match your version, including\ncomponents you've added or changed (document their props with comments and the assistant\nsees those too). It also tells the assistant to read `AGENTS.md` first.\n\nAsk it things like *\"Add a testimonials section to the pricing page\"*, *\"Is my template up to\ndate?\"* or *\"How do I connect Stripe?\"*: it looks up the components, your version and the docs\nbefore it answers or edits.\n\n## Add it to your assistant\n\nYou need [Node.js](/getting-started/install-node/); nothing else to install. The server runs\nin the folder your assistant has open, so open your site's folder.\n\n### Cursor\n\nOne click: **Install in Cursor** on [wptom.com](https://wptom.com/#ai), or add it under\n**Cursor Settings → MCP → Add new MCP server** with command `npx` and arguments `-y wptom mcp`.\nOr put this in `.cursor/mcp.json` in your project:\n\n```json\n{ \"mcpServers\": { \"wptom\": { \"command\": \"npx\", \"args\": [\"-y\", \"wptom\", \"mcp\"] } } }\n```\n\n### VS Code\n\nOne click: **VS Code** in the install menu on [wptom.com](https://wptom.com/#ai). Or create\n`.vscode/mcp.json` in your project:\n\n```json\n{ \"servers\": { \"wptom\": { \"type\": \"stdio\", \"command\": \"npx\", \"args\": [\"-y\", \"wptom\", \"mcp\"] } } }\n```\n\n### Claude Code\n\n```sh\nclaude mcp add wptom -- npx -y wptom mcp\n```\n\n### Codex\n\n```sh\ncodex mcp add wptom -- npx -y wptom mcp\n```\n\n### OpenCode\n\nIn `opencode.json`:\n\n```json\n{ \"mcp\": { \"wptom\": { \"type\": \"local\", \"command\": [\"npx\", \"-y\", \"wptom\", \"mcp\"] } } }\n```\n\n### JetBrains IDEs (PhpStorm, WebStorm, IntelliJ IDEA…)\n\nCopy the configuration with **JetBrains IDEs** in the install menu on\n[wptom.com](https://wptom.com/#ai), or use this:\n\n```json\n{ \"mcpServers\": { \"wptom\": { \"command\": \"npx\", \"args\": [\"-y\", \"wptom\", \"mcp\"] } } }\n```\n\n**AI Assistant** (the chat in your IDE):\n\n1. Open **Settings** (<kbd>Ctrl</kbd> <kbd>Alt</kbd> <kbd>S</kbd>, on macOS <kbd>⌘</kbd> <kbd>,</kbd>) →\n   **Tools → AI Assistant → Model Context Protocol (MCP)**.\n2. Click **Add**, choose **STDIO**, and paste the JSON above.\n3. Set the working directory to your site's folder (or leave it on the project), click **OK**\n   and **Apply**.\n\n**Junie** (JetBrains' coding agent): **Settings → Tools → Junie → MCP Settings → Add**, then\npaste the JSON into the `mcp.json` that opens. Or put it in `.junie/mcp/mcp.json` at the root\nof your project, so everyone on the project gets it.\n\nMCP support arrived in recent versions of AI Assistant and Junie; if you don't see these\nsettings, update the IDE and the plugin.\n\n### Any other MCP client\n\nStart `npx -y wptom mcp` as a stdio server. Its tools are listed with descriptions and input\nschemas; all of them are read-only.\n\n## Check it works\n\nAsk your assistant: *\"Use the WPTom tools: which template and version is this project?\"* It\nshould call `get_project_info` and answer with your template and version. In Cursor and VS Code\nyou can also see the server and its six tools in the MCP settings.\n\n## Good to know\n\n- **Privacy**: it reads your project's `package.json`, `.wptom/meta.json` and\n  `src/components/` on your computer, and fetches only docs.wptom.com/docs.json and the\n  template catalog from app.wptom.com. Nothing about your project is sent anywhere.\n- **Login**: not needed. If this computer is logged in ([`wptom login`](/cli/login/)),\n  `list_templates` shows which PRO templates your license includes.\n- **Several projects**: the tools take an optional `directory`, so one assistant session can\n  ask about another site.\n- **Always current**: `npx -y wptom mcp` runs the latest CLI each time the assistant starts it."},{"path":"/cli/new/","url":"https://docs.wptom.com/cli/new/","title":"wptom new","description":"Create a site from a template, every option and prompt, and what you get.","markdown":"```sh\nnpx wptom new my-site -t aurora\n```\n\nDownloads the latest version of a template and creates a site from it in a new folder.\n`wptom create` does the same. Prefer answering questions? [`npm create wptom@latest`](/cli/create/).\n\n## Usage\n\n```\nwptom new [dir] [-t <template>] [--cms <name>]\n```\n\n| Part | Meaning | If you leave it out |\n|---|---|---|\n| `dir` | The folder to create, e.g. `my-site` or `../clients/acme` | Asked, suggesting `./my-<template>-site` |\n| `-t`, `--template` | The template's short name (its *slug*), e.g. `aurora`. [`wptom list`](/cli/list/) shows them | Asked: a list of templates, with ones you can't use marked 🔒 |\n| `--cms` | Where the content lives (below) | Asked, if the template has variants |\n\nThe folder must not exist yet, or be empty. Otherwise: `my-site isn't empty. Pick a new folder.`\n\nPRO templates need [`wptom login`](/cli/login/) first; without it you get\n`aurora is a PRO template. Run wptom login first.`\n\n## Where the content lives\n\nSome templates come in variants. `--cms` picks one; without it, `wptom new` asks:\n\n```\n◆  Where should the content live?\n│  ● Files or Sanity (static site, any host)\n│  ○ EmDash (EmDash inside the site: admin at /_emdash/admin, rendered on request on Cloudflare Workers (D1 + R2).)\n```\n\n| `--cms` | You get |\n|---|---|\n| `files` (also `keystatic`, `sanity` or `default`) | The default: content as Markdown and JSON files in your project, edited in Keystatic; switch to Sanity with one setting. A static site for any host. See [Content and CMS](/content/overview/). |\n| `emdash` | EmDash runs inside your site on Cloudflare, with a WordPress-like admin. See [EmDash](/content/emdash/). |\n\nAn option the template doesn't have: `aurora has no \"wordpress\" option. Pick one of: files, sanity, emdash.`\nNothing is created in that case.\n\nThe choice is remembered, so [`wptom update`](/cli/update/) keeps updating the same variant.\n\n## What happens\n\n1. The CLI asks app.wptom.com for the template (with your device token for PRO), downloads\n   the release and checks its checksum (`Checksum mismatch: the download is corrupted. Try again.`\n   if it doesn't match).\n2. It applies the variant you picked.\n3. It writes the files, names the project in `package.json` after your folder, and saves\n   `.wptom/meta.json` and `.wptom/base/` (see [files](/cli/#files-it-uses)).\n4. It prints the next steps:\n\n```\n◇  Created my-site from aurora@1.0.0\n│\n◇  Next steps ───╮\n│                │\n│  cd my-site    │\n│  npm install   │\n│  npm run dev   │\n│                │\n├────────────────╯\n│\n└  Updates later: wptom update keeps your edits.\n```\n\nNothing is installed or run for you: `npm install` and `npm run dev` are yours to run\n([step by step](/getting-started/first-site/#3-install-and-start-it)).\n\n## Examples\n\n```sh\nnpx wptom new                                 # asks for everything\nnpx wptom new client-site -t aurora           # into ./client-site\nnpx wptom new shop -t aurora --cms sanity     # content in Sanity (same files, CMS set later)\nnpx wptom new blog -t aurora --cms emdash     # the EmDash variant\n```"},{"path":"/cli/update/","url":"https://docs.wptom.com/cli/update/","title":"wptom update","description":"Bring in a template's latest version and keep your edits. Every case, every symbol, conflicts.","markdown":"```sh\nnpx wptom update --dry-run   # see what would change\nnpx wptom update\n```\n\nRun it **inside your site's folder**. It downloads the template's latest released version and\nmerges it into your project. Your changes stay. New to this? Follow\n[Keep your site up to date](/getting-started/updates/) step by step.\n\n## Usage\n\n```\nwptom update [--dry-run]\n```\n\n| Option | Meaning |\n|---|---|\n| `--dry-run` | Show what would change and stop. Nothing is written. |\n\nUp to date already? `aurora is up to date (1.0.0).` and nothing else happens.\n\n## How it decides, file by file\n\nIt compares three versions of every file: the one you started from (`.wptom/base/`), yours\nnow, and the new release.\n\n| Situation | What happens | Shown as |\n|---|---|---|\n| You didn't touch the file | It takes the new version | `~ path` |\n| Only you changed it | Yours stays | (not listed) |\n| You both changed it, different lines | Merged line by line | `⇄ path  merged with your edits` |\n| You both changed the same lines | Conflict markers for you to resolve | `! path  conflict: resolve the <<<<<<< markers` |\n| A binary file (photo, font) changed on both sides | Yours stays; the new one is saved as `path.wptom-new` | `? path.wptom-new  new version saved next to yours` |\n| New file in the release | Added | `+ path` |\n| New file, but you already have a different one there | Yours stays; theirs saved as `.wptom-new` | `? path.wptom-new` |\n| Removed in the release, you never changed it | Deleted | `- path` |\n| Removed in the release, but you changed it | Kept | `= path  kept (removed upstream but edited)` |\n| You deleted it | Stays deleted | `= path  kept (deleted by you)` |\n\nThe summary line counts them: `3 updated · 1 conflicts · 0 removed`. With conflicts or\n`.wptom-new` files, it asks `Some files need your attention afterwards. Apply the update?`\nfirst; answer **No** and nothing changes (`Update cancelled. Nothing changed.`).\n\nAfter updating, `.wptom/base/` and `.wptom/meta.json` move to the new version, and it ends with\n`Updated to aurora@1.1.0.` If there were conflicts:\n`Resolve the conflict markers, then run your build. git diff shows everything that changed.`\n\n## Conflicts\n\nA conflict looks like this in the file:\n\n```\n<<<<<<< yours: src/content/singletons/site.json\n  \"tagline\": \"Ship faster with Northlight\",\n=======\n  \"tagline\": \"Find the signal in your product data.\",\n>>>>>>> wptom update\n```\n\nKeep what you want, delete the three marker lines, save. [Step by step with VS\nCode](/getting-started/updates/#resolving-a-conflict).\n\n## Good to know\n\n- **Commit first.** Then `git diff` shows exactly what the update changed and `git restore .`\n  undoes it.\n- **Git must be installed**: merging uses `git merge-file`. Without Git, every file changed on\n  both sides becomes a conflict with your version and the new one one after the other.\n- **Run `npm install` afterwards**: a new version can bump packages (including the `@wptom/*`\n  integrations).\n- **Variants stay**: a site created with `--cms emdash` is updated with the EmDash variant of the\n  new version. If a release dropped that variant: `aurora@2.0.0 no longer offers the \"emdash\"\n  option. Nothing changed.`\n- **Your project name** in `package.json` is kept.\n- PRO templates need an active license on this computer ([`wptom login`](/cli/login/)).\n\n## Errors\n\n| Message | Fix |\n|---|---|\n| `No .wptom/meta.json here. Run wptom update inside a site created with wptom new.` | `cd` into your site's folder. Sites not made with `wptom new` can't be updated this way. |\n| `.wptom/base is missing, so your edits can't be told apart from upstream.` | Restore `.wptom/base/` (e.g. from a backup), or create a fresh site and move your content over. |\n| `aurora has no published version.` | The template has no release yet. |\n\nMore: [Errors and fixes](/cli/errors/)."},{"path":"/cli/whoami/","url":"https://docs.wptom.com/cli/whoami/","title":"wptom whoami","description":"Show which account and plan this computer is connected to.","markdown":"```sh\nnpx wptom whoami\n```\n\n```\n●  you@email.com · All-access\n│  This device: mac · macOS\n```\n\nShows the account this computer is connected to, its plan (`Single template`, `All-access` or\n`Team`), and this device's name. If the license isn't active, its status follows the plan\n(e.g. `· suspended`).\n\n| Message | Meaning |\n|---|---|\n| `Not logged in. Free templates work without an account; run wptom login for PRO.` | This computer isn't connected. |\n| `This device was signed out. Run wptom login again.` | It was removed under **Account → Devices**, signed out by a newer login past the limit, or the license was refunded. The CLI forgets the old token. |"},{"path":"/content/content-model/","url":"https://docs.wptom.com/content/content-model/","title":"Change the content model","description":"Add a field or a section type, and keep every editor in sync.","markdown":"The content model, `src/content/model.ts`, lists every field your pages read. Keystatic's\neditor and Sanity's Studio are generated from it, so a change there reaches both.\n\n## Add a field\n\n1. Add the field to the right part of `src/content/model.ts`, e.g. a subtitle on blog posts:\n\n   ```ts\n   subtitle: z.string().optional().meta({ title: 'Subtitle' }),\n   ```\n\n2. Use it in the page or component that shows it.\n3. Keystatic picks it up at once (the dev server restarts by itself).\n4. Using Sanity: run `pnpm cms sanity schema` and redeploy the Studio (`cd studio && npm run deploy`).\n   Using EmDash: run `npm run emdash:schema` against your site (see [EmDash](/content/emdash/#change-the-content-model)).\n\nOptional fields (`.optional()`) are safest: existing content stays valid. Rules such as\n`.regex()`, `.min()` and `.max()` are checked in the editors as well as in the build.\n\n## Add a section type\n\nHome and landing pages are lists of sections. A new section type needs:\n\n1. its fields in the `sections` list of the model,\n2. a component in `src/components/sections/`,\n3. one line in `src/components/Sections.astro`.\n\nThen, with Sanity, regenerate the schema and redeploy the Studio as above; with EmDash, run\n`npm run emdash:schema`."},{"path":"/content/emdash/","url":"https://docs.wptom.com/content/emdash/","title":"EmDash","description":"The EmDash variant of a template - EmDash runs inside your site, on Cloudflare.","markdown":"[EmDash](https://github.com/emdash-cms/emdash) is an open-source CMS with an editor that feels\nfamiliar if you've used WordPress. In the EmDash variant of a template, EmDash runs **inside your\nsite**: the admin lives at `/_emdash/admin`, and what you publish there is live on the site at\nonce. There's no build to wait for and no separate CMS service.\n\nThe variant runs on [Cloudflare Workers](https://workers.cloudflare.com): your content is stored\nin a D1 database and your photos in an R2 bucket, both in your own Cloudflare account. Locally,\n`npm run dev` emulates both, so you don't need an account to try it.\n\n## Files and Sanity vs. EmDash\n\n|  | Files / Sanity variant | EmDash variant |\n|---|---|---|\n| Pages | Built ahead of time (static) | Rendered on request |\n| After publishing | Your host rebuilds the site (a minute or two) | Live at once |\n| Hosting | Anywhere (Vercel, Netlify, Cloudflare…) | Cloudflare Workers |\n| Editor | Keystatic or Sanity Studio | EmDash admin, in your site |\n| Cost | Free on most hosts' free plans | Workers Paid ($5 a month) for a live site |\n\nYou choose the variant when you create the site:\n\n```sh\nwptom new my-site -t aurora --cms emdash\n```\n\n## Try it locally\n\n```sh\ncd my-site\nnpm install\nnpm run dev\n```\n\nOpen the site. The first time, it sets EmDash up for you: it creates an admin account (you're\nsigned in as \"Dev Admin\") and loads the demo content, including the photos. Then:\n\n- the site: the address `npm run dev` prints\n- the admin: that address plus `/_emdash/admin`\n\nEdit something, press **Publish**, reload the site: the change is there.\n\n## How the content is organised\n\nThe admin's sidebar matches the site:\n\n- **Pages**: the home page, landing pages, the pricing page and every other page, legal pages,\n  site settings.\n- **Blog**: your posts.\n- **Content**: changelog, docs and the interface texts (buttons, labels, emails).\n\nSingle pages (home, pricing, site settings…) have exactly one entry each. Keep its slug as it is\n(`home`, `pricingPage`…): the site finds the page by that slug.\n\nPage sections (hero, steps, pricing, FAQ…) are blocks: add, reorder and remove them in the page.\nLists such as pricing plans or FAQ questions are blocks too, one per item. Large sets of texts,\nsuch as the interface texts, are grouped (navigation, footer, chat, emails…) so each group is\none form.\n\n### Writing\n\nPosts, docs, changelog entries and legal pages use EmDash's rich text editor: headings, lists,\nquotes, links, bold and italic, code and images. Quotes and dashes are typeset for you (\" becomes\n\" and \", -- becomes –), and code blocks are highlighted like the rest of the site.\n\nEmDash's editor has no tables yet. Use a list where you'd use a small table.\n\n### Drafts and publishing\n\nSaving keeps a draft; visitors keep seeing the published version until you press **Publish**.\nScheduled entries publish on their own (EmDash checks every minute).\n\n### Photos\n\nUpload photos in the admin (Media, or straight from an image field). The site serves them resized\nfor each screen in AVIF and WebP, through Cloudflare Images. Its free plan includes 5,000 unique\nresized versions a month, which is plenty for most sites; past that, versions made before keep\nworking and new ones fail until the next month or a paid plan.\n\n## Deploy to Cloudflare\n\nYou need a Cloudflare account. For a live site, use the **Workers Paid** plan ($5 a month): the\nfree plan allows 10 ms of CPU time per request, and EmDash pages take more to render, so visitors\nwould get occasional errors. The free plan is fine for trying the variant out.\n\n1. **Set your domain first.** In `astro.config.mjs`, set `site` to your production address, e.g.\n   `https://www.example.com`. EmDash ties sign-in (passkeys) to this address and uses it to resize\n   photos, so set it before the first deploy.\n2. **Name your site.** In `wrangler.jsonc`, change `name` (the Worker), `database_name` and\n   `bucket_name` if you like.\n3. **Log in and deploy:**\n\n   ```sh\n   npx wrangler login\n   npm run deploy\n   ```\n\n   The first deploy creates the D1 database and the R2 bucket for you, and prints the Worker's\n   address (`https://<name>.<you>.workers.dev`).\n4. **Add your domain.** Check that the workers.dev address responds, then add your domain (it must\n   be on Cloudflare in the same account): in the Cloudflare dashboard (Workers & Pages → your\n   Worker → Settings → Domains & Routes), or in `wrangler.jsonc`:\n\n   ```jsonc\n   \"routes\": [{ \"pattern\": \"www.example.com\", \"custom_domain\": true }]\n   ```\n\n5. **Set up EmDash on your domain.** Open `https://www.example.com/_emdash/admin` and finish the\n   setup wizard: you become the administrator and create your passkey. Do this on your real\n   domain: passkeys only work on the address where they were created. Until setup is done, the\n   first person to finish it becomes the administrator, so do it right after deploying.\n6. **Feature settings.** Add what you use from `.env.example` (email, analytics, payments, chat)\n   in the dashboard (your Worker → Settings → Variables and Secrets) or with\n   `npx wrangler secret put NAME`, then deploy again.\n\nLater deploys are just `npm run deploy`. Your content stays in D1 and R2.\n\n### Editors\n\nInvite editors from the admin (Users). They sign in with passkeys. Sign-in links by email need an\nemail provider configured in EmDash.\n\n## Change the content model\n\nWhen you add or change fields in `src/content/model.ts` (see\n[Change the content model](/content/content-model/)), bring EmDash in line:\n\n1. In the admin, create an API token (Settings → API Tokens) with the `schema:write` scope.\n2. Run, against your site:\n\n   ```sh\n   EMDASH_URL=https://www.example.com EMDASH_TOKEN=ec_pat_… npm run emdash:schema\n   ```\n\n   New fields, block types and collections are added and changed ones updated; your content\n   stays. If EmDash can't change a field automatically (say, a text field that should become a\n   list), the script tells you and you change it in the admin (Content Types).\n3. Deploy the site (`npm run deploy`).\n\nLocally, use the address `npm run dev` prints as `EMDASH_URL` and a token from your local admin."},{"path":"/content/keystatic/","url":"https://docs.wptom.com/content/keystatic/","title":"Keystatic","description":"Edit your site's content in Keystatic, locally or on the live site.","markdown":"[Keystatic](https://keystatic.com) edits the content files in your project. Everything stays in\nyour repository: no database, no extra service.\n\n## Editing locally\n\nRun `pnpm dev` and open `/keystatic` on the local site. Saving writes the files on disk; commit\nand push them like any other change.\n\nThe editor is organised like the site: **Pages** (home, landing pages, every single page, legal,\nsite settings, brand), **Blog** and **Content** (changelog, docs, interface texts). Fields have\nthe same rules as the build (required fields, formats such as colors), so you can't save\nsomething the site would reject.\n\n## Editing on the live site\n\nIn production, Keystatic saves each change as a commit to your GitHub repository, and your host\nrebuilds the site.\n\n1. Set `PUBLIC_KEYSTATIC_GITHUB_REPO=owner/name` in `.env` and run `pnpm dev`.\n2. Open `/keystatic` and follow the steps: Keystatic creates a GitHub App and writes four values\n   into `.env` (`PUBLIC_KEYSTATIC_GITHUB_APP_SLUG`, `KEYSTATIC_GITHUB_CLIENT_ID`,\n   `KEYSTATIC_GITHUB_CLIENT_SECRET`, `KEYSTATIC_SECRET`).\n3. Add all of them to your host's environment variables and redeploy.\n4. In the GitHub App settings, add the callback URL\n   `https://<your-domain>/api/keystatic/github/oauth/callback`.\n\nEditors then log in with GitHub at `https://<your-domain>/keystatic`. Once the repository is\nset, saves in `pnpm dev` become commits too.\n\n## Photos\n\nEach page and collection keeps its photos in its own folder under `src/assets/photos/`, named\nthe way Keystatic names them. Upload photos through the editor rather than reusing one file in\nseveral places: when an entry is saved, Keystatic removes photos that entry no longer uses."},{"path":"/content/languages/","url":"https://docs.wptom.com/content/languages/","title":"Languages","description":"Add a language to a template that is ready for more languages.","markdown":"Every template is ready for more languages. It ships in English, and you can add any language\nwithout changing code: the pages, the language switcher, search engine hints and\nthe CMS follow.\n\n## Add a language\n\n1. Add it to `src/i18n/locales.json`:\n\n   ```json\n   {\n     \"default\": \"en\",\n     \"locales\": [\n       { \"code\": \"en\", \"label\": \"English\" },\n       { \"code\": \"de\", \"label\": \"Deutsch\" }\n     ]\n   }\n   ```\n\n2. Copy the default language's content into the new language's folders and translate it:\n   `src/content/singletons/de/`, and a `de/` folder in each collection (for example\n   `src/content/blog/de/`, `src/content/docs/de/`), with the same file names.\n3. Run `pnpm dev`. The new language lives under `/de/…`; the default stays at the root.\n\nLinks in the content (`/pricing`) lead to the same language's pages by themselves. Anything you\nhaven't translated yet shows in the default language, marked as such for screen\nreaders and search engines, so you can translate page by page.\n\n## What changes on the site\n\n- A language switcher appears in the header and the phone menu (only with two or more languages).\n- Every page links its translations (`hreflang`), and the sitemap and `/llms.txt` list every\n  language.\n- Dates follow the language (e.g. 2. September 2026).\n\n## In the CMS\n\n- **Keystatic**: every page and collection appears once per language (\"Home page · Deutsch\").\n  The brand is shared by all languages.\n- **Sanity**: run `pnpm cms sanity schema` and deploy the Studio. Single pages get one document\n  per language; posts, docs and other collections get a **Language** field.\n- **EmDash**: languages come from `src/i18n/locales.json`; EmDash offers a translation of each\n  entry in its editor."},{"path":"/content/overview/","url":"https://docs.wptom.com/content/overview/","title":"Content and CMS","description":"Where your site's content lives, and how to edit it in Keystatic or Sanity.","markdown":"Everything a visitor can read is content you can edit without touching code: page sections,\nheadings, buttons, labels, error messages, emails, SEO texts, photos and their alt text.\n\n## One content model\n\nEach template describes its content once, in `src/content/model.ts`. The editors are generated\nfrom it, so they always offer exactly the fields your pages use:\n\n- **Keystatic** (default): your content stays in the project as Markdown and JSON files, and\n  Keystatic is a friendly editor for them at `/keystatic`. No service, no extra cost.\n  See [Keystatic](/content/keystatic/).\n- **Sanity**: your content lives in a Sanity project and your team edits it in Sanity Studio.\n  Good when clients or several editors work on the site. See [Sanity](/content/sanity/).\n- **EmDash**: a separate variant of the template, chosen when you create the site\n  (`wptom new my-site -t aurora --cms emdash`). EmDash runs inside your site on Cloudflare, with a\n  WordPress-like admin, and what you publish is live at once. See [EmDash](/content/emdash/).\n\nYour pages don't change between them: the site reads the same fields from every source.\n\n## Where the content lives\n\n| What | Files |\n|---|---|\n| Single pages (home, pricing, waitlist, 404…) | `src/content/singletons/*.json` |\n| Site settings, navigation, footer | `src/content/singletons/site.json` |\n| Small interface texts (buttons, labels, emails) | `src/content/singletons/ui.json` |\n| Brand (colors, fonts, shape) | `src/content/singletons/brand.json` |\n| Blog, changelog, docs, legal | `src/content/<name>/*.md` |\n| Landing pages | `src/content/landing/*.json` |\n| Photos | `src/assets/photos/<page or collection>/` (EmDash: its media library) |\n\n## Choosing the source (files or Sanity)\n\nIn the files/Sanity variant, set `CMS` in `.env` (or in your host's environment variables):\n\n```sh\nCMS=files    # default: the files above, edited in Keystatic\nCMS=sanity   # read from your Sanity project\n```\n\nContent is read when the site is built, so the published site stays fast and static. After\nediting, the site rebuilds: with Keystatic every save is a commit, with Sanity a webhook\nstarts the build. (In the EmDash variant, pages are rendered on request instead, so there's no\nrebuild.)"},{"path":"/content/sanity/","url":"https://docs.wptom.com/content/sanity/","title":"Sanity","description":"Move your site's content to Sanity and edit it in Sanity Studio.","markdown":"With [Sanity](https://www.sanity.io) your content lives in a Sanity project and your team edits\nit in Sanity Studio, a hosted editor generated from your site's content model. The site reads\nthe published content when it builds.\n\n## Set up\n\n1. Create a project at [sanity.io/manage](https://www.sanity.io/manage). A public dataset\n   (the default `production`) needs no token for reading.\n2. Under **API → Tokens**, create a token with the **Editor** role, for uploading the content.\n3. Add to `.env`:\n\n   ```sh\n   SANITY_PROJECT_ID=your-project-id\n   SANITY_DATASET=production\n   SANITY_WRITE_TOKEN=the-editor-token   # keep it on your machine\n   ```\n\n4. Generate the Studio's schema and upload your content and photos:\n\n   ```sh\n   pnpm cms sanity schema\n   pnpm cms sanity seed\n   ```\n\n   Seeding fills an empty project. If documents already exist it stops, so edits made in Sanity\n   are never lost by accident; add `--force` to replace them.\n\n5. Build from Sanity: set `CMS=sanity` (with `SANITY_PROJECT_ID` and `SANITY_DATASET`) in your\n   host's environment variables.\n\n## Sanity Studio\n\nThe Studio is in the `studio/` folder of your project.\n\n```sh\ncd studio\ncp .env.example .env    # your project id\nnpm install\nnpm run dev             # try it locally\nnpm run deploy          # host it at <name>.sanity.studio\n```\n\nThe first deploy asks you to log in and pick a name. It prints an app id; put it in\n`studio/.env` as `SANITY_STUDIO_APP_ID` so later deploys don't ask again.\n\nThe Studio's navigation matches the site. Single pages (home, pricing, site settings…) open\ndirectly and can't be duplicated or deleted. Photos can be cropped, and the crop shows on the\nsite. Articles are written in a Markdown editor.\n\nAfter changing the content model, run `pnpm cms sanity schema` again and redeploy the Studio.\n\n## Rebuild when you publish\n\nIn [sanity.io/manage](https://www.sanity.io/manage) → **API → Webhooks**, add a webhook:\n\n- URL: your host's deploy hook (Vercel: Project → Settings → Git → Deploy Hooks; Netlify: Build\n  hooks; Cloudflare: Settings → Builds → Deploy hooks)\n- Trigger on: create, update, delete\n- Filter: the one `pnpm cms sanity schema` prints\n\nSaving a draft doesn't start a build; publishing does.\n\n## Private datasets and previews\n\n- Private dataset: create a token with the **Viewer** role and set it as `SANITY_READ_TOKEN`\n  in your host's environment variables.\n- Preview with unpublished changes: a separate deployment with `SANITY_PERSPECTIVE=drafts` and\n  the Viewer token shows drafts too."},{"path":"/getting-started/before-you-start/","url":"https://docs.wptom.com/getting-started/before-you-start/","title":"Before you start","description":"What you need on your computer, and the few terminal basics the guides use.","markdown":"A WPTom template is a complete website project. You'll run it on your computer, change it, and\nput it online. You don't need to be a developer: if you can follow a recipe, you can follow\nthese guides. This page lists what to set up once.\n\n## What you need\n\n| What | Why | Cost |\n|---|---|---|\n| A computer with macOS, Windows or Linux | To run the site while you work on it | |\n| [Node.js](/getting-started/install-node/) 22.12 or newer | Runs the WPTom CLI and builds the site | Free |\n| A code editor, e.g. [VS Code](https://code.visualstudio.com) | To open and change the project's files | Free |\n| [Git](https://git-scm.com/downloads) | Saves versions of your work; `wptom update` uses it to merge updates | Free |\n| A [GitHub](https://github.com) account | Stores your project online, so a host can publish it | Free |\n| A host, e.g. [Vercel](https://vercel.com) | Puts your site on the internet | Free plan to start |\n| A WPTom license, for PRO templates | Unlocks the template in the CLI | [Pricing](https://wptom.com/pricing) |\n\nFree templates work without a WPTom account.\n\n:::tip\nUsing an AI assistant such as Cursor, Claude Code, VS Code with Copilot or PhpStorm's AI Assistant? Every template comes\nwith instructions for it (`AGENTS.md`), and the [WPTom MCP server](/cli/mcp/) lets it read\nthese docs and your components. It can do most of the steps below with you.\n:::\n\n## The terminal, in two minutes\n\nMost steps are one command typed in a **terminal**: a window where you type commands\ninstead of clicking.\n\n- **macOS**: press <kbd>⌘</kbd> <kbd>Space</kbd>, type `Terminal`, press <kbd>Enter</kbd>.\n- **Windows**: press <kbd>Win</kbd>, type `Terminal` (or `PowerShell`), press <kbd>Enter</kbd>.\n- **Linux**: open your distribution's terminal (often <kbd>Ctrl</kbd> <kbd>Alt</kbd> <kbd>T</kbd>).\n- **VS Code** has one built in: menu **Terminal → New Terminal**. It opens in your project\n  folder, which is handy.\n\nIn the guides, a command looks like this:\n\n```sh\nnode --version\n```\n\nType it (or copy it) and press <kbd>Enter</kbd>. Don't type the `$` some pages show in front\nof example output; it only marks where the command starts.\n\nThree things you'll use all the time:\n\n| Command | What it does |\n|---|---|\n| `cd my-site` | **c**hange **d**irectory: go into the folder `my-site` |\n| `cd ..` | go back up one folder |\n| <kbd>Ctrl</kbd> <kbd>C</kbd> | stop the command that's running (e.g. the local site) |\n\nThe terminal always works \"in\" one folder. When a step says \"in your project folder\", first\n`cd` into it.\n\n## Next\n\n1. [Install Node.js](/getting-started/install-node/) (skip if `node --version` shows 22.12 or\n   newer).\n2. [Create your first site](/getting-started/first-site/)."},{"path":"/getting-started/deploy/","url":"https://docs.wptom.com/getting-started/deploy/","title":"Put your site online","description":"Publish your site on Vercel through GitHub, connect your domain, and publish every change with one command.","markdown":"WPTom templates are set up for [Vercel](https://vercel.com): it builds your site from GitHub\nand republishes it on every change. The free Hobby plan is enough for personal sites; Vercel's\nterms ask for the Pro plan for commercial use. This takes about 15 minutes the first time.\n\nUsing the EmDash variant? It runs on Cloudflare instead: see\n[EmDash → Deploy to Cloudflare](/content/emdash/#deploy-to-cloudflare).\n\n## 1. Put your project on GitHub\n\nGitHub stores your project online; Vercel reads it from there.\n\n1. Sign in at [github.com](https://github.com) (create a free account if you need one).\n2. Click **+** (top right) → **New repository**.\n3. Name it (e.g. `my-site`), choose **Private**, and leave \"Add a README\" and the other boxes\n   **unticked** (your project has them already). Click **Create repository**.\n4. GitHub shows a page with commands. In your project folder, run (with your own address from\n   that page):\n\n   ```sh\n   git add .\n   git commit -m \"My site\"\n   git branch -M main\n   git remote add origin https://github.com/your-name/my-site.git\n   git push -u origin main\n   ```\n\n   The first push may ask you to sign in to GitHub in the browser.\n\nReload the GitHub page: your files are there. Check that `.env` is **not** among them (it\nnever should be; `.gitignore` keeps it out).\n\n## 2. Create the site on Vercel\n\n1. Go to [vercel.com](https://vercel.com) and **Sign Up** with **Continue with GitHub**.\n2. Click **Add New… → Project**.\n3. Find your repository in the list and click **Import** (if it's missing, click\n   **Adjust GitHub App Permissions** and allow access to it).\n4. Vercel recognises Astro by itself. Leave **Build Command** and **Output Directory** as they\n   are.\n5. Open **Environment Variables** and add the keys from your `.env` that you use (name and\n   value, one by one; Vercel also lets you paste the whole `.env` content). See\n   [Turn on the features](/getting-started/features/). You can add them later too.\n6. Click **Deploy**. A minute later you get an address like `my-site.vercel.app`. Open it:\n   your site is live.\n\n## 3. Your own domain\n\n1. In Vercel: your project → **Settings → Domains** → enter `example.com` → **Add**.\n2. Vercel shows the DNS records to create (usually an `A` record for `example.com` and a\n   `CNAME` for `www`). Add them where you bought the domain (its \"DNS\" settings).\n3. Wait until Vercel shows a green check (minutes, sometimes a few hours). HTTPS is set up\n   automatically.\n4. Tell the site its address: in `astro.config.mjs`, set `const site = 'https://example.com'`.\n   It's used for links in search results, the sitemap and social previews. Commit and push.\n\n## 4. Publish changes\n\nEvery push to `main` publishes the site again:\n\n```sh\ngit add .\ngit commit -m \"New pricing\"\ngit push\n```\n\nVercel builds it and switches over when it's ready (about a minute). If a build fails, the old\nversion stays online and Vercel shows the error under **Deployments**; most often a required\nfield is empty: run `npm run build` on your computer to see the same message.\n\n## Editing on the live site\n\nThe content editor runs on your computer by default. To let you (or a client) edit on the\nlive site at `/keystatic`, connect it to GitHub: every save becomes a commit and Vercel\nrepublishes. Steps: [Keystatic → Editing on the live site](/content/keystatic/). Prefer a\nhosted CMS? [Sanity](/content/sanity/).\n\n## Other hosts\n\nThe pages are static, so Netlify, Cloudflare Pages and others work too. The parts that run on\nthe server (waitlist, checkout, chat) need the host's Astro adapter instead of\n`@astrojs/vercel`: follow Astro's [deployment guides](https://docs.astro.build/en/guides/deploy/)\nand change the adapter in `variant.config.mjs`."},{"path":"/getting-started/features/","url":"https://docs.wptom.com/getting-started/features/","title":"Turn on the features","description":"Email, waitlist, payments, AI chat, analytics, demo booking and search, one key at a time.","markdown":"Everything in a WPTom template works before you set anything up: forms log what they would\nsend, the chat explains how to turn it on, search uses a built-in index. Each feature switches\non when you add its key. Keys are secrets: they go in a file called `.env` on your computer\nand in your host's settings, never in your code or in Git.\n\n## Where keys go\n\n1. In your project folder, copy `.env.example` to `.env`:\n   - macOS / Linux: `cp .env.example .env`\n   - Windows (PowerShell): `Copy-Item .env.example .env`\n2. Open `.env` in your editor. Every setting has a comment saying what it does and where to get\n   it. Fill in only what you use; leave the rest empty.\n3. Restart `npm run dev` (<kbd>Ctrl</kbd> <kbd>C</kbd>, then `npm run dev`).\n4. When the site is online, add the same names and values in your host's settings (on Vercel:\n   **Project → Settings → Environment Variables**) and redeploy. `.env` itself is never\n   uploaded (it's in `.gitignore`).\n\n:::caution\nSome keys cost money when used (AI chat, email above the free tier). Set a spending limit in\neach provider's dashboard.\n:::\n\nThe settings below are the ones Aurora uses; other templates use the subset they need, and\ntheir `.env.example` lists them.\n\n## Waitlist (Supabase)\n\nSignups from the waitlist form are stored in a [Supabase](https://supabase.com) table.\n\n1. Create a free Supabase project.\n2. Open **SQL Editor**, paste this and click **Run**:\n\n   ```sql\n   create table public.waitlist (\n     email text primary key,\n     source text,\n     created_at timestamptz not null default now()\n   );\n   alter table public.waitlist enable row level security;\n   ```\n\n   (Row level security without policies means only your site's secret key can write.) Another\n   table name? Set `WAITLIST_TABLE`.\n3. Copy the project URL and a **secret** key (Project Settings → API Keys) into\n   `SUPABASE_URL` and `SUPABASE_SECRET_KEY`. The secret key stays on the server.\n\nWithout them, signups are kept in memory while `npm run dev` runs (for trying it out only).\n\n## Email (Resend)\n\nWelcome emails for the waitlist and receipts after a purchase go through\n[Resend](https://resend.com).\n\n1. Create a Resend account and verify your domain (Resend shows the DNS records to add).\n2. Create an API key and put it in `RESEND_API_KEY`.\n3. Set `EMAIL_FROM`, e.g. `\"Your product <hello@yourdomain.com>\"` (an address on the verified\n   domain).\n\nWithout a key, emails are printed in the terminal instead of sent.\n\n## AI chat (Claude)\n\nThe chat answers visitors from your own docs, FAQ and pricing.\n\n1. Create a key at [console.anthropic.com](https://console.anthropic.com) and set a monthly\n   spend limit there.\n2. Put it in `ANTHROPIC_API_KEY`. `CHAT_MODEL` is optional (the default model is picked for\n   you).\n3. Change what the chat is told in the content editor: **Interface texts → Chat**.\n\nWithout a key, the chat window explains how to turn it on.\n\n## Payments (Stripe)\n\nPlans on your pricing page can start a [Stripe](https://stripe.com) Checkout.\n\n1. In Stripe, create a Product with a monthly and a yearly Price for each plan.\n2. Put your secret key in `STRIPE_SECRET_KEY`.\n3. Add a webhook endpoint (Developers → Webhooks) for `https://your-site/api/stripe/webhook`,\n   listening to `checkout.session.completed`, and put its signing secret in\n   `STRIPE_WEBHOOK_SECRET`. After a payment, the buyer gets your welcome email.\n4. In the content editor, **Pricing page → Plans → Stripe checkout**, paste each plan's Price\n   ids.\n\nA plan without Price ids keeps its normal button link.\n\n## Analytics\n\nFill in one or more; each stays off until its id is set.\n\n- **Plausible** (cookieless): `PUBLIC_PLAUSIBLE_SCRIPT_ID`, the script id from Site settings →\n  Site installation (e.g. `pa-AbC123`).\n- **Umami** (cookieless): `PUBLIC_UMAMI_WEBSITE_ID`, plus `UMAMI_ORIGIN` if you host Umami\n  yourself.\n- **Google Analytics 4**: `PUBLIC_GA_MEASUREMENT_ID` (e.g. `G-ABC123`). GA uses cookies, so a\n  consent banner appears and GA loads only after the visitor accepts.\n\n## Demo booking (Cal.com)\n\nPut your [Cal.com](https://cal.com) booking link in `PUBLIC_CALCOM_LINK` (e.g.\n`yourname/demo`). `PUBLIC_CALCOM_ORIGIN` only if you host Cal.com yourself. Without a link,\nthe demo page explains how to connect it.\n\n## Search\n\nSearch (<kbd>⌘</kbd> <kbd>K</kbd> or the search button) works out of the box with an index\nbuilt with the site. For large sites, switch to [Algolia](https://www.algolia.com): set\n`PUBLIC_ALGOLIA_APP_ID`, `PUBLIC_ALGOLIA_INDEX` and `PUBLIC_ALGOLIA_SEARCH_KEY` (the\nsearch-only key), and after each deploy run `npm run search:sync` with `ALGOLIA_ADMIN_KEY` set\non your computer only.\n\n## Editing content on the live site\n\nBy default you edit content on your computer and publish by pushing to Git. To edit on the\nlive site instead, connect Keystatic to GitHub: see [Keystatic](/content/keystatic/). To use\nSanity, see [Sanity](/content/sanity/)."},{"path":"/getting-started/first-site/","url":"https://docs.wptom.com/getting-started/first-site/","title":"Your first site","description":"Create a site from a template, run it on your computer, and change its texts, photos and colors. Step by step.","markdown":"This guide takes about 15 minutes. You'll end with a site running on your computer that you've\nchanged yourself. You need [Node.js and Git](/getting-started/install-node/) installed, and for\na PRO template, a license (see [Buy a template](/account/buy/)).\n\n## 1. Connect your license (PRO templates only)\n\nSkip this for free templates. In a terminal:\n\n```sh\nnpx wptom login\n```\n\n`npx` downloads and runs the WPTom CLI; the first time it asks `Ok to proceed? (y)`: press\n<kbd>Enter</kbd>. The CLI shows a code like `5X78-X588` and opens your browser:\n\n1. Sign in to app.wptom.com with the email you bought with (a sign-in link arrives by email,\n   or use **Continue with GitHub**).\n2. Check that the code on the page matches the one in your terminal, then click **Approve**.\n3. Back in the terminal you'll see `Logged in as you@email.com · All-access`.\n\nYou do this once per computer. Details: [`wptom login`](/cli/login/).\n\n## 2. Create the site\n\nGo to the folder where you keep projects (e.g. `cd Documents`), then:\n\n```sh\nnpx wptom new my-site -t aurora\n```\n\n- `my-site` is the folder it creates; name it after your project.\n- `-t aurora` picks the template. `npx wptom list` shows them all.\n\nIf the template comes in variants, the CLI asks where the content should live:\n\n```\n◆  Where should the content live?\n│  ● Files or Sanity (static site, any host)\n│  ○ EmDash (EmDash inside the site: admin at /_emdash/admin, rendered on request on Cloudflare Workers (D1 + R2).)\n```\n\nNot sure? Keep **Files or Sanity** (press <kbd>Enter</kbd>). Your content is then plain files in\nyour project, edited in a visual editor, and you can still move it to Sanity later. Read\n[Content and CMS](/content/overview/) to compare.\n\nWhen it's done:\n\n```\n◇  Created my-site from aurora@1.0.0\n│\n◇  Next steps ───╮\n│                │\n│  cd my-site    │\n│  npm install   │\n│  npm run dev   │\n│                │\n├────────────────╯\n│\n└  Updates later: wptom update keeps your edits.\n```\n\n## 3. Install and start it\n\nRun the three commands it suggests:\n\n```sh\ncd my-site\nnpm install\nnpm run dev\n```\n\n`npm install` downloads what the site needs (a minute or two, once). `npm run dev` starts the\nsite on your computer and prints a line starting with **Local** followed by an address (on\nyour own computer: the word `localhost` and a port number, usually 4321). Open that address in\nyour browser; in most terminals you can <kbd>⌘</kbd>/<kbd>Ctrl</kbd>-click it. That's your site, with the template's demo content:\n\n![The Aurora template running locally](../../../assets/screenshots/aurora-home.webp)\n\nLeave this terminal running while you work; every change you save shows up in the browser\nright away. To stop it, press <kbd>Ctrl</kbd> <kbd>C</kbd>.\n\n## 4. Open the project in your editor\n\nIn VS Code: **File → Open Folder…** and pick `my-site`. The files that matter most:\n\n| File or folder | What's in it |\n|---|---|\n| `src/content/` | All texts, posts, docs and settings (what the editor below changes) |\n| `src/content/singletons/brand.json` | Colors, fonts, corners and button shape |\n| `src/assets/photos/` | The photos |\n| `.env.example` | Settings for email, payments, analytics, AI chat… (all optional) |\n| `AGENTS.md` | How the site is built, for you and your AI assistant |\n| `README.md` | The short version of this guide |\n\nYou rarely need to touch the code: most changes happen in the content editor.\n\n## 5. Change your content in the editor\n\nWith `npm run dev` running, open the same address with `/keystatic` added at the end. This is Keystatic, the content editor. It edits the\nfiles in your project, so nothing is online yet.\n\n![The Keystatic dashboard with every page and collection](../../../assets/screenshots/keystatic-dashboard.webp)\n\nThe sidebar matches your site:\n\n- **Pages**: the home page, landing pages, pricing, blog page, and every other page.\n- **Site settings**: your product name, tagline, navigation and footer.\n- **Brand**: colors, fonts, corners, button shape.\n- **Blog**, **Changelog**, **Docs**: your posts and articles.\n- **Interface texts**: buttons, labels, messages and emails.\n\n### Your product name and navigation\n\nOpen **Site settings**. Change **Product name**, **Tagline** and **Description**, adjust the\nnavigation links and footer columns, then click **Save** (top right). Reload your site: the\nheader, footer, browser tab and search previews all use the new name.\n\n### The home page\n\nOpen **Home page**. A page is a list of **sections** (hero, product, steps, testimonials,\npricing, FAQ…):\n\n![Editing the home page: SEO fields and the list of sections](../../../assets/screenshots/keystatic-home.webp)\n\n- Click a section to edit its texts, buttons and photo.\n- Drag the handle on the left to reorder sections; the bin icon removes one.\n- **Add** inserts a new section of any type.\n\nClick **Save** after each change.\n\n### A blog post\n\nOpen **Blog**, then a post, or **+** to write a new one:\n\n![Writing a blog post in Keystatic](../../../assets/screenshots/keystatic-blog-post.webp)\n\nThe text is on the left (headings, lists, links, images, code). On the right: title, slug (the\nweb address, e.g. `/blog/ask-in-chat`), description, date, author and cover photo. Tick\n**Draft** to keep a post off the site until it's ready.\n\n### Photos\n\nWherever there's a photo field, **Choose file** uploads a new one. Use large photos (at least\n2000 px wide): the site makes smaller, faster versions for every screen by itself. Always fill\nin the photo description (alt text): screen readers read it out.\n\nThe template's demo photos are yours to use on any site you build with it (see `LICENSES.md`).\n\n### Prices\n\n**Pricing page** holds your plans (name, price per month and per year, features, button), the\ncomparison table and the FAQ, plus the **Currency** of your prices (e.g. `USD`, `EUR`). Leave\na price empty to show \"Custom\".\n\n## 6. Make it yours: colors and fonts\n\nOpen **Brand**:\n\n![The Brand settings: accent and second color, corners, button shape and fonts](../../../assets/screenshots/keystatic-brand.webp)\n\n- **Accent color**: buttons, links and highlights. Type a color like `#0f9c8a`. The site\n  works out lighter and darker shades for the dark and light theme by itself, and they're\n  always readable (WCAG AA). If a color can't be made readable, it tells you.\n- **Second color**: small highlights such as check marks.\n- **Corners** and **Button shape**: sharp, soft or round; pill or rounded buttons.\n- **Fonts**: pick the text and heading fonts. Fonts are set when the site starts, so after\n  changing them, stop `npm run dev` (<kbd>Ctrl</kbd> <kbd>C</kbd>) and start it again.\n\nTry accents first in the template's live demo: it has a color switcher (bottom left).\n\n## 7. Check the finished site\n\n```sh\nnpm run build\n```\n\nThis makes the final, optimized site (the same thing your host will do). If anything is wrong,\nfor example a required field you left empty, it stops and says what and where. Fix it in the\neditor and build again.\n\n## 8. Save your work with Git\n\nGit keeps a history of your changes and lets [`wptom update`](/cli/update/) merge new template\nversions safely. In the project folder:\n\n```sh\ngit init\ngit add .\ngit commit -m \"My site\"\n```\n\nFrom now on, after a round of changes: `git add .` and `git commit -m \"What I changed\"`.\n\n## Next\n\n- [Put your site online](/getting-started/deploy/)\n- [Turn on the features](/getting-started/features/): email, waitlist, payments, AI chat,\n  analytics, demo booking\n- [Keep it up to date](/getting-started/updates/)\n- Your template's own page, e.g. [Aurora](/templates/aurora/)"},{"path":"/getting-started/install-node/","url":"https://docs.wptom.com/getting-started/install-node/","title":"Install Node.js and Git","description":"Step by step on macOS, Windows and Linux, and how to check it worked.","markdown":"WPTom templates need **Node.js 22.12 or newer** (it includes `npm` and `npx`, which the guides\nuse) and **Git**.\n\n## Check what you have\n\nOpen a terminal ([how](/getting-started/before-you-start/#the-terminal-in-two-minutes)) and\nrun:\n\n```sh\nnode --version\ngit --version\n```\n\nIf you see `v22.12.0` or higher (e.g. `v24.10.0`) and a Git version, you're done: go to\n[your first site](/getting-started/first-site/). If you see \"command not found\" or an older\nversion, install them below.\n\n## macOS\n\n1. Go to [nodejs.org](https://nodejs.org) and download the **LTS** version (the one marked\n   \"Recommended for most users\").\n2. Open the downloaded `.pkg` file and click through the installer.\n3. For Git, run `git --version` in the terminal. If Git is missing, macOS offers to install the\n   \"command line developer tools\": click **Install** and wait.\n4. Close the terminal, open a new one, and check again with `node --version` and\n   `git --version`.\n\n## Windows\n\n1. Go to [nodejs.org](https://nodejs.org), download the **LTS** Windows installer (`.msi`) and\n   run it. Keep the default options.\n2. Download Git from [git-scm.com/download/win](https://git-scm.com/download/win) and install it\n   with the default options.\n3. Close every terminal window, open a new **Terminal** or **PowerShell**, and check with\n   `node --version` and `git --version`.\n\n:::note\nIf PowerShell says running scripts is disabled when you use `npm`, run\n`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once and answer `Y`.\n:::\n\n## Linux\n\nUse your distribution's package manager for Git (e.g. `sudo apt install git`). For Node.js, the\npackaged version is often too old: install the LTS from\n[nodejs.org/download](https://nodejs.org/en/download) (it shows a copy-paste command for your\nsystem, e.g. with `nvm`), then open a new terminal and check the versions.\n\n## Several Node.js versions?\n\nIf other projects need an older Node.js, a version manager lets you switch per project:\n[nvm](https://github.com/nvm-sh/nvm) on macOS and Linux, [nvm-windows](https://github.com/coreybutler/nvm-windows)\non Windows. With nvm: `nvm install --lts` then `nvm use --lts`.\n\n## Next\n\n[Create your first site](/getting-started/first-site/)."},{"path":"/getting-started/quick-start/","url":"https://docs.wptom.com/getting-started/quick-start/","title":"Quick start","description":"From nothing to a site running on your computer in five commands. New to this? Each step links to the detailed guide.","markdown":"Already have Node.js 22.12+ and Git? Five commands. New to the terminal? Start with\n[Before you start](/getting-started/before-you-start/) instead: it explains every step.\n\n```sh\nnpx wptom login                    # PRO templates only: approve this computer in the browser\nnpx wptom new my-site -t aurora    # create the site (asks where the content should live)\ncd my-site\nnpm install                        # once\nnpm run dev                        # open the address it prints\n```\n\nThen:\n\n| Next | Guide |\n|---|---|\n| Change texts, photos, prices in the editor at `/keystatic` | [Your first site](/getting-started/first-site/#5-change-your-content-in-the-editor) |\n| Colors, fonts, corners | [Make it yours](/getting-started/first-site/#6-make-it-yours-colors-and-fonts) |\n| Email, waitlist, payments, AI chat, analytics | [Turn on the features](/getting-started/features/) |\n| Publish on Vercel with your domain | [Put your site online](/getting-started/deploy/) |\n| New template versions, keeping your edits | [Keep your site up to date](/getting-started/updates/) |\n| Let your AI assistant read your components and these docs | [wptom mcp](/cli/mcp/) |"},{"path":"/getting-started/troubleshooting/","url":"https://docs.wptom.com/getting-started/troubleshooting/","title":"Troubleshooting","description":"The common problems when installing, running, building and publishing a site, and how to fix them.","markdown":"Messages from the `wptom` command itself are in [CLI errors and fixes](/cli/errors/).\n\n## Installing\n\n**`npm install` fails or hangs.** Check your internet connection, then run it again. If it\nmentions permissions (`EACCES`), don't use `sudo`: your Node.js installation is set up for\nanother user; reinstall Node.js from [nodejs.org](https://nodejs.org).\n\n**`npm warn … install scripts` / `approve-scripts`.** Recent npm versions don't run packages'\ninstall scripts unless the project allows them. Templates list the ones they need in\n`package.json` (`allowScripts`), so nothing to do; if npm asks about a package you added\nyourself, check it and allow it with `npm approve-scripts`.\n\n**`EBADENGINE` or \"requires Node >= 22\".** Install the current LTS of\n[Node.js](/getting-started/install-node/).\n\n## Running locally\n\n**`Port 4321 is in use`.** Another `npm run dev` is still running (maybe in another terminal or\neditor window). Stop it with <kbd>Ctrl</kbd> <kbd>C</kbd>, or use the address the command prints:\nAstro picks the next free port by itself.\n\n**My change doesn't show.** Save the file (VS Code: <kbd>Ctrl</kbd>/<kbd>⌘</kbd> <kbd>S</kbd>),\nthen reload the browser. Fonts and `astro.config.mjs` are read when the site starts: stop and\nstart `npm run dev` after changing them.\n\n**`/keystatic` is empty or \"not found\".** It only exists while `npm run dev` runs, unless you\nconnected it to GitHub for the live site ([Keystatic](/content/keystatic/)).\n\n**A feature says it isn't set up.** That's on purpose until you add its key:\n[Turn on the features](/getting-started/features/).\n\n## Building\n\n**`npm run build` stops with a message about a field.** A required field is empty or has the\nwrong format; the message names the file and the field. Fix it in the content editor.\n\n**`Brand: the accent is too dark to see on the dark theme` or `Brand: no AA-safe … for this\ncolor`.** The accent or second color in Brand can't be made readable in both themes. Pick a\nlighter or more saturated color. `Brand: \"…\" is not a hex color` means a typo: colors look like\n`#6d4bff`.\n\n**Images fail to build.** A photo file is damaged or not an image. Replace it in the editor.\n\n## Publishing\n\n**The build works on my computer but fails on Vercel.** Usually a key that's in your `.env`\nisn't in Vercel's **Environment Variables**, or a file you didn't commit. Compare, push, and\nredeploy (**Deployments → … → Redeploy**).\n\n**My changes aren't online.** Did you commit and push? `git status` lists uncommitted changes;\nthen `git add .`, `git commit -m \"…\"`, `git push`.\n\n**The site shows the wrong address in search results or shares.** Set your domain in\n`astro.config.mjs` (`const site = 'https://example.com'`), commit and push.\n\n## Updating\n\nSee [Keep your site up to date](/getting-started/updates/): conflicts, undoing an update, and\nsites created without the CLI.\n\n## Still stuck?\n\nEmail tom@wptom.com with what you did, what you expected, and the full message (copy it from\nthe terminal). Using an AI assistant? Give it the error and let it use the\n[WPTom MCP server](/cli/mcp/) to look things up."},{"path":"/getting-started/updates/","url":"https://docs.wptom.com/getting-started/updates/","title":"Keep your site up to date","description":"Get new template versions without losing your changes, step by step, including what to do with a conflict.","markdown":"Templates get new versions: fixes, new sections, new integrations. Your license includes them\nfor life. `wptom update` brings a new version into your site and **keeps everything you\nchanged**. Full reference: [`wptom update`](/cli/update/).\n\n## Is there a new version?\n\n- [What's new](https://wptom.com/changelog) on wptom.com lists every release.\n- In your project, `npx wptom update --dry-run` checks and shows what would change, without\n  changing anything.\n- Your AI assistant can check too, through the [MCP server](/cli/mcp/) (`get_project_info`).\n\n## Update, step by step\n\n1. **Save your work first.** In the project folder:\n\n   ```sh\n   git add .\n   git commit -m \"Before update\"\n   ```\n\n   This is your safety net: you can always go back.\n\n2. **See what will happen:**\n\n   ```sh\n   npx wptom update --dry-run\n   ```\n\n   ```\n   ◇  aurora 1.0.0 → 1.1.0\n   ◇  3 updated · 0 conflicts · 0 removed ──────────╮\n   │                                                 │\n   │  ~ src/components/Header.astro                  │\n   │  ⇄ src/content/singletons/ui.json  merged with your edits │\n   │  + src/components/sections/Logos.astro          │\n   │  = src/styles/global.css  kept (yours)          │\n   ├─────────────────────────────────────────────────╯\n   └  Dry run: nothing was written.\n   ```\n\n   | Mark | Meaning |\n   |---|---|\n   | `~` | You never changed this file: it takes the new version |\n   | `⇄` | You and the update both changed it, in different places: merged |\n   | `+` | A new file in this version |\n   | `-` | Removed in this version (you never changed it) |\n   | `!` | Both changed the same lines: a conflict for you to resolve |\n   | `?` | A binary file (e.g. a photo) changed on both sides: the new one is saved next to yours |\n   | `=` | Kept as it is (you changed it and the update didn't, or you deleted it) |\n\n3. **Update:**\n\n   ```sh\n   npx wptom update\n   ```\n\n   If there are conflicts, it asks `Some files need your attention afterwards. Apply the\n   update?` first.\n\n4. **Install, check, commit:**\n\n   ```sh\n   npm install\n   npm run build\n   git add .\n   git commit -m \"Update to 1.1.0\"\n   ```\n\n   `npm install` picks up new or updated packages. If `npm run build` passes, push to publish.\n\n## Resolving a conflict\n\nA conflict means you and the update changed the same lines. The file then contains both\nversions between markers:\n\n```\n<<<<<<< yours: src/content/singletons/site.json\n  \"tagline\": \"Ship faster with Northlight\",\n=======\n  \"tagline\": \"Find the signal in your product data.\",\n>>>>>>> wptom update\n```\n\n1. Open the file. VS Code highlights conflicts and offers **Accept Current** (yours),\n   **Accept Incoming** (the update) or **Accept Both** above each one.\n2. Keep what you want and delete the marker lines (`<<<<<<<`, `=======`, `>>>>>>>`).\n3. Search the project for `<<<<<<<` to be sure none is left, then build and commit.\n\nFor a photo or other binary file that changed on both sides, you keep yours and the new one is\nsaved next to it as `photo.jpg.wptom-new`: compare, keep one, delete the other.\n\n## Something went wrong?\n\nUndo the whole update with Git:\n\n```sh\ngit restore .\ngit clean -fd\n```\n\nThis puts every file back as it was at your last commit (and removes files the update added).\n\n:::note\nCreated your site without the CLI (e.g. copied the files)? Updates need the `.wptom` folder that\n`wptom new` creates. Create a fresh site with `wptom new` and move your content into it once.\n:::"},{"path":"/","url":"https://docs.wptom.com/","title":"WPTom docs","description":"Everything about WPTom Astro templates, step by step, from installing Node.js to publishing your site and keeping it updated.","markdown":"import { Card, CardGrid, LinkCard } from '@astrojs/starlight/components';\n\n## New here? Follow these in order\n\n<CardGrid>\n  <LinkCard title=\"1. Before you start\" description=\"What you need, and the terminal in two minutes.\" href=\"/getting-started/before-you-start/\" />\n  <LinkCard title=\"2. Install Node.js and Git\" description=\"Step by step on macOS, Windows and Linux.\" href=\"/getting-started/install-node/\" />\n  <LinkCard title=\"3. Your first site\" description=\"Create it, run it, change its texts, photos and colors.\" href=\"/getting-started/first-site/\" />\n  <LinkCard title=\"4. Turn on the features\" description=\"Email, waitlist, payments, AI chat, analytics.\" href=\"/getting-started/features/\" />\n  <LinkCard title=\"5. Put it online\" description=\"Vercel, GitHub and your own domain.\" href=\"/getting-started/deploy/\" />\n  <LinkCard title=\"6. Keep it up to date\" description=\"New versions without losing your edits.\" href=\"/getting-started/updates/\" />\n</CardGrid>\n\n## Reference\n\n<CardGrid>\n  <Card title=\"The WPTom CLI\" icon=\"seti:shell\">Every command and option: [overview](/cli/), [new](/cli/new/), [update](/cli/update/), [login](/cli/login/), [mcp](/cli/mcp/), [errors](/cli/errors/).</Card>\n  <Card title=\"Content and CMS\" icon=\"document\">Keystatic, Sanity or EmDash, and how to change the content model: [overview](/content/overview/).</Card>\n  <Card title=\"Your account\" icon=\"seti:lock\">[Buying](/account/buy/), [signing in](/account/sign-in/), [devices](/account/devices/), [billing](/account/billing/).</Card>\n  <Card title=\"Templates\" icon=\"puzzle\">What's in each template and where to change it: [Aurora](/templates/aurora/).</Card>\n</CardGrid>\n\nUsing an AI assistant? Connect the [WPTom MCP server](/cli/mcp/) and it reads these docs for you.\nFor tools: [/llms.txt](/llms.txt) and [/docs.json](/docs.json)."},{"path":"/templates/aurora/","url":"https://docs.wptom.com/templates/aurora/","title":"Aurora","description":"What's in the Aurora template and where to change it.","markdown":"Aurora is a SaaS landing page: home, pricing, changelog, blog and docs, plus a waitlist, a demo\nbooking page and landing pages built from sections.\n\nAurora comes in two variants, chosen when you create the site: files/Sanity (static, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages\nand sections are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home, landing pages | Pages → Home page / Landing pages (a list of sections) |\n| Pricing (plans, FAQ, comparison table) | Pages → Pricing page |\n| Blog, changelog, docs | Blog, Content → Changelog / Docs |\n| Waitlist, demo, checkout success, 404 | Pages → the page's own entry |\n| Privacy, terms | Pages → Legal |\n| Navigation, footer, call to action | Pages → Site settings |\n| Buttons, labels, emails, chat texts | Content → Interface texts |\n\nFAQ questions live on the pricing page; each one picks whether it shows on the home page, the\npricing page or both.\n\nPrices are whole numbers in the pricing page's currency (`currency`, e.g. USD, EUR or GBP); the\nplan cards, search results and structured data all use it.\n\n## Languages\n\nAurora is ready for more languages: see [Languages](/content/languages/). The search index,\nthe AI chat's knowledge and emails use the default language.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`). Keep it in step when you change how the site\n  is built.\n- `/llms.txt` lists your pages, docs and posts for AI assistants. It is generated from the\n  content; add a line in `src/pages/llms.txt.ts` when you add a page of your own.\n- Pages carry JSON-LD: the site on the home page, your plans and FAQ on the pricing page, posts\n  and docs articles. Blog posts also share their cover as the social preview image.\n\nThe blog lists four posts per page: `/blog`, then `/blog/page/2` and so on.\n\n## Sections\n\nHome and landing pages are built from these sections, in any order: Hero (full-width photo),\nProduct (photo and features), Steps, Story (photo and quote), Testimonials, Pricing plans, FAQ\nand Closing call to action (with the waitlist form).\n\n## Brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) sets the accent and secondary\ncolors, corner radius, button shape and fonts. Every other shade is derived from the two\ncolors and checked for readable contrast in both themes; a color that can't be made readable\nstops the build with a message.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\npnpm search:sync             # push the search index to Algolia, if you use it\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/blaze/","url":"https://docs.wptom.com/templates/blaze/","title":"Blaze","description":"What's in the Blaze template, how its trail map, stages and pages work, and where to change things.","markdown":"Blaze is a site for a long-distance trail: trail clubs, hiking guides and trekking outfits, hut-to-hut\nroutes, pilgrim ways, ultra races, anyone who explains one route stage by stage. It is built like a\ntopographic map: real contour lines behind every page, a display serif, and the trail mark (a red\nstripe between two white ones) as the eyebrow, the bullet and the underline of marked words. On the\nhome page the whole trail is drawn from OpenStreetMap over real terrain relief, with its elevation\nprofile underneath; every stage has a zoomed-in map, its profile and numbers measured from the map.\nThe demo is a fictional field guide, Trailmark, for a real trail: the Heroes' Trail of the SNP across\nSlovakia (Cesta hrdinov SNP), from Devín Castle to the Dukla Pass, in English and Slovak.\n\nBlaze comes in two variants, chosen when you create the site: files/Sanity (static pages, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages are the\nsame in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Stages, guides, journal, 404 | Pages → the page's own entry |\n| Guided trips (the trips, how a week goes, booking, passport and deposits, questions) | Pages → Trips page |\n| Plan (the months, trail marks, details, packing list, contact form, questions) | Pages → Plan page |\n| Stages (from, to, kilometres, range, difficulty, on the way, water, where to sleep, description) | Content → Stages |\n| Guides (role, home ground, qualifications, languages, biography, portrait) | Content → Guides |\n| Journal posts | Content → Journal |\n| Name, the trail's name, contact, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, map texts, chat, emails, game texts | Content → Interface texts |\n| Privacy, terms, safety | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are underlined with the trail mark, as in\n\"From Devín to \\*Dukla\\*\". A line break in the home page's headline starts a new line.\n\n## The trail map\n\nThe map is drawn once, on your computer, by `pnpm map`, from what `map.config.json` names:\n\n- `trail.relation`: the trail's OpenStreetMap route relation. Find the trail on\n  [openstreetmap.org](https://www.openstreetmap.org), open the route and copy its relation number.\n- `trail.start`: a point near the start, so the line runs the right way.\n- `area.relation`: a border to draw round it (a country or a region, also a relation), or\n  `area.bbox` (`west`, `east`, `south`, `north`) for a plain rectangle.\n- `terrain`: the zoom of the relief, how much it exaggerates the hills, how soft it is.\n\n`pnpm map -- fetch` downloads the trail, the border and open terrain tiles (SRTM, EU-DEM and others)\ninto `.map-cache/`, chains the trail's pieces into one line, measures it, and writes\n`src/data/trail.json` (the trail, the outline, a point per kilometre with its height, the elevation\nprofile) and the relief in `public/map/`: two masks the site paints in your colors (so the night and\nday maps and every accent use the same files), coarse tiles for the whole area, sharp tiles along the\ntrail for the stage maps, and the contour lines behind the pages. `-- terrain` downloads only the\nterrain again, `-- build` and `-- render` redo the steps without downloading. The maps credit\nOpenStreetMap and the terrain data; keep the credits.\n\nOn the home page the trail draws itself, the relief fades in after the page has loaded and the map\ntilts a little with the mouse. Point at the trail or the profile and a hiker goes to that kilometre,\nwith its height and stage; a click or tap pins it with a link to the stage, and the arrow keys walk it\n(Shift for 10 km). On phones the map is wider than the screen and scrolls sideways. The names of\nranges and places on the map are content (Home → The map → Names on the map), placed by coordinates.\n\n## Stages\n\nA stage has a number, where it starts and ends (names, and kilometres from the start of the trail:\npoint at the trail on the home page to read them), its range, a difficulty, a summary, what is on the\nway, water and where to sleep, and a description. **Distance, climbing, descent, the highest point and\nthe walking time are measured from the map** between the two kilometres, so they always match it; the\nwalking time uses the standard hiking formula (4 km an hour, 300 m up and 500 m down an hour). Stages\nare listed by range on the stages page, each card with its profile; a stage's page shows it zoomed in\non the relief, the rest of the trail faint, with its profile and the previous and next stages.\n\n## Guided trips, booking and payments\n\nThe trips page holds up to four trips, each with its stretch of the trail (`fromKm`, `toKm`) drawn on\nits card (mark one as the favourite), how a guided week goes, booking a planning call with Cal.com and\nwhat you sell with Stripe (the demo: a trail passport, a map set and a deposit), each with a one-time\nPrice id. Without keys, the booking shows a preview of the calendar and the payment buttons explain how\nto switch them on.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Book a planning call | Trips page, loads on click; a preview of the calendar until connected | Cal.com |\n| Passport, maps, deposits | Trips page (one-time Price ids) | Stripe |\n| Newsletter | Home and journal pages, with a welcome email | Supabase |\n| Contact form | Plan page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers about the stages (with their numbers), planning, trips and guides; never promises that a section is safe or open | Claude |\n| Search | ⌘K or the search button: stages, guides, journal, trips, plan, in the page's language | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The map (the opening), The story (why the\ntrail has its name: a few paragraphs beside a timeline), The climb (the whole\nprofile, walked by a hiker as you scroll, with marked places), Stages (cards with their profiles),\nTrail marks (big words between trail marks, sliding as you scroll), Three steps (along a dashed\ntrail), The guides, Numbers, Journal, Newsletter and Closing call (a wide photo whose top edge is cut\nalong the trail's real profile). With reduced motion the trail stands drawn, the profile complete and\nnothing moves.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the **accent**\n(the trail mark: buttons, the trail on every map, marks), the **second** (the forest: the night map,\nthe land and the panels take its hue) and the **third** (dawn: the hiker, summits, the current tab).\nButtons are darkened until their white text is readable and every pair is checked; a color that can't\nget there stops the build with a message. Brand also sets the fonts (headings and text, all with\nCentral European letters) and the corners. The night map is the default; the paper map comes on when\nthe visitor's system prefers light, or with the toggle, and the browser remembers the choice.\n\n## Languages\n\nBlaze ships in English and Slovak, with a switcher in the header and the menu; Slovak pages live\nunder `/sk/`. Add or remove languages as described in [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Follow the blaze: at every fork in a foggy forest a trail mark shows on one of three\ntrees for a moment; take that path (left, straight on or right: the buttons, the arrow keys or a swipe)\nbefore the fog closes in. Later forks add green and yellow marks of other trails to throw you off: only\nthe red one is yours. Every fork adds to the kilometres walked; the best walk is kept in the visitor's\nbrowser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, stages (with their numbers), guides and journal, in every language.\n- Pages carry JSON-LD: an `Organization`, stages and trips as `TouristTrip`s, the guides as `Person`s,\n  questions as a `FAQPage`, and each journal post as a `BlogPosting`.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm map -- fetch            # draw the trail map from OpenStreetMap and terrain data\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/bloom/","url":"https://docs.wptom.com/templates/bloom/","title":"Bloom","description":"What's in the Bloom template and where to change it.","markdown":"Bloom is a site for anyone who sells something that grows, by the season: a florist, a flower\nfarm, a garden centre or nursery, a wedding and event florist, a maker of herbal teas or cosmetics.\nIt's made like a botanical watercolour herbarium: the page is a sheet of watercolour paper with\ndeckled edges, pictures are watercolour plates mounted with photo corners and typed specimen\nlabels, and on the home page a plant grows from seed to bloom as the story scrolls past. Bouquets\nto order, a flower calendar, wedding flowers, workshops, a journal and a visit page complete it.\n\nBloom comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Shop, weddings, workshops, journal, visit, 404 | Pages → the page's own entry |\n| Bouquets (price, size, season, stems, specimen label, ordering) | Content → Bouquets |\n| Journal posts | Content → Journal |\n| Name, description, email, phone, address, navigation, header button, profiles, social picture | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are set in the accent on a stroke of paint, as in\n\"Flowers that \\*grew here\\*, cut this morning\".\n\n## The growing plant\n\nThe Story section (From seed to vase) keeps a plate on screen while its chapters scroll past, and\nthe plant on it grows with them: the seed splits, the stem draws itself, six leaves unfurl, a bud\nswells and the flower opens petal by petal. It is drawn in code (`src/components/Plant.astro`) in\nthe brand's colours, so it follows any palette; the chapters (two to five) come from the content.\nSmall buds beside each section heading open as the heading scrolls in, and on wide screens a vine\nclimbs the edge of the sheet as you read. Visitors who prefer reduced motion see everything in\nfull bloom.\n\n## Bouquets and ordering\n\n- A **bouquet** has a tagline, summary, price, size, season, vase life, its stems, delivery, a tall\n  picture (4:5), a specimen label and a colour.\n- **Ordering**: turn on \"Sell with Stripe Checkout\" and put a one-time Price id there; the\n  bouquet's page gets a working button. Buyers come back to the shop, and the Stripe webhook\n  (`/api/stripe/webhook`) emails them, where you can add your own steps.\n- **In season** (home page): twelve months, each with what flowers then; the seasons wear their\n  own colours and the current month is marked.\n- **Quotes** on the home page: only real quotes with their source.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Ordering bouquets | Each bouquet's page | Stripe |\n| Wedding consultations | Weddings page; a preview of the calendar until it's connected | Cal.com |\n| Newsletter and the workshop list | Home, journal and workshops pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your bouquets, seasons, weddings, workshops, visit page and journal | Claude |\n| Search | ⌘K or the search button: bouquets, journal, weddings, workshops, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The plate (opening), In season, The\nstory, Bouquets, Feature (a picture and a text, e.g. weddings or workshops), Numbers, Quotes, From\nthe journal, Newsletter sign-up and Closing call.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (buttons, links and petals), the **second** (leaves and fresh fills) and the **third**\n(flower centres and sunny fills). Buttons are darkened until their white text is readable, the\nsecond and third are lightened until dark text on them is; a color that can't get there stops the\nbuild with a message. The site opens in the day theme whatever the visitor's system is set to; the night theme, a deep green garden after dark, comes on with the toggle (plates stay on paper).\nBrand also sets the fonts (an old-style book face for headings, the text font and the typewriter\nlabel font) and the corners.\n\n## Languages\n\nBloom is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Pipe the water: turn the pipe tiles so water from the rain barrel reaches every\nplant, and each one blooms as it's watered. Tap or click a tile to turn it, or use the arrow keys\nand Enter; three gardens, each bigger, scored by how few turns you needed. The best score is kept\nin the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, bouquets and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the business as a `Florist`, bouquets as `Product`s, workshops as `Event`s,\n  the visit questions as a `FAQPage`, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/burin/","url":"https://docs.wptom.com/templates/burin/","title":"Burin","description":"What's in the Burin template, how its pages and animations work, and where to change things.","markdown":"Burin is a site for history: publishers and history magazines, museums and exhibitions, guided-walk\ncompanies, history podcasts and societies, school projects. It explains events through the prints\nmade of them at the time, and it is set edge to edge on laid paper: the laid and chain lines of\nhandmade paper, the plate mark round the window, printer's black, rubric red, old gold and verdigris,\nheadings in an old roman cut for print (IM Fell). On the home page a print of the event is read letter\nby letter as you scroll, the way old prints label their keys. The demo is a fictional publisher in\nPrague, Annals, and its home page is about a real morning: the execution of twenty-seven leaders of the\nBohemian revolt on the Old Town Square on 21 June 1621.\n\nBurin comes in two variants, chosen when you create the site: files/Sanity (static pages, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages are the\nsame in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Events, prints, notes, 404 | Pages → the page's own entry |\n| Walks and schools (the main offer, packages, how a walk goes, booking, lesson packs, questions) | Pages → Learn page |\n| Contact (message form, map, hours, details, questions) | Pages → Contact page |\n| Events (date, year, place, era, story, print, facts, timeline, who's who, further reading, questions) | Content → Events |\n| Prints (who made it, when, how, credit and licence, source, the event, how to read it) | Content → Prints |\n| Notes | Content → Notes |\n| Name, phone, address, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, emails, game texts and cards | Content → Interface texts |\n| Privacy, terms, image licences | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are printed in rubric red with a fine rule drawn under them,\nas in \"The morning of the \\*twenty-seven\\*\".\n\n## Prints: one archive, used everywhere\n\nEvery picture on the site is a print in Content → Prints, with its maker, date, technique, credit and\nlicence, and a page of its own (with \"Look closer\", the print at full size to pan around). Events,\ntimeline moments, the people on the roll and the sections name a print by its file name\n(`the-scaffold`), so a picture is uploaded once. Use prints you may publish: public domain (most\nmuseums release their old prints under CC0) or your own. By day prints sit in the paper like ink.\n\n## Read the plate\n\nThe home page opens with a print and its key: a few letters (A, B, C…), each with a title, a line, its\nplace on the print (across and down, in %) and how close to look. The headline, lead and buttons sit\nabove the print from the first frame. As you scroll the stage stays, the headline folds away and the\nprint is magnified on each letter in turn while its explanation shows above; at the end the whole print\nreturns with every letter on it. On load the print turns over like a sheet pulled off the copper plate.\nTo place a letter, open the print, find the spot and note its position as a percentage of the width and\nheight.\n\n## The other sections\n\n- **Timeline**: moments with a date as it should read, a year and an optional print; the row slides\n  sideways while you scroll, a ribbon unrolls under it, and a large year in the background counts from\n  the first moment to the last. Mark one moment as the one the story is about.\n- **Hour by hour**: parts of a day, each with the hour the clock's hand points to.\n- **The roll**: groups (lords, knights, burghers…) and the people in them, each with a line, how they\n  died (a sword or a rope; change the words in Interface texts) and an optional portrait; a filter by\n  group that needs no script, and a portrait beside the roll.\n- **A print and its story**: a print, a text and up to four numbers stamped in old-gold seals.\n- **Where it happened**: the map, with a label on the pin.\n- **More events**, **Walks and lesson packs**, **Notes**, **Newsletter** and the **Closing call** over a\n  long view of a city that pans as you scroll.\n\nWith reduced motion nothing moves: the print shows with all its letters and the key as a list, the\ntimeline is a row you scroll yourself.\n\n## Walks, booking and payments\n\nThe Learn page holds the main offer on a ticket with its small print, up to four packages (mark one as\nthe popular one), how a walk goes as stops on a route, booking with Cal.com, and lesson packs and\ntickets you sell with Stripe: each one gets a one-time Price id. Without keys, the booking shows a\npreview of the calendar and the payment buttons explain how to switch them on.\n\n## The map\n\nThe map is drawn from [OpenStreetMap](https://www.openstreetmap.org/copyright) in your colors, with a\npin and its label. It is part of the page (no embed, no cookies) and follows the theme and your\naccent. To draw your own place, put its coordinates in `map.config.json` and run `pnpm map -- fetch`.\n`detail: \"streets\"` (the default in Burin) draws the streets and buildings round one place; `\"area\"`\nthe main roads, railways, water and woods of a district.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Book a walk or a talk | Learn page, loads on click; a preview of the calendar until connected | Cal.com |\n| Lesson packs and tickets | Learn page (one-time Price ids) | Stripe |\n| Newsletter | Home and notes pages, with a welcome email | Supabase |\n| Message form | Contact page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers about the events, people, prints and walks from the site's content only | Claude |\n| Search | ⌘K or the search button: events, prints, notes, walks, contact | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the **accent**\n(rubric red: buttons, initials, marked words, the letters of the key), the **second** (old gold: seals,\nthe ribbon, the ticket) and the **third** (verdigris). Buttons are darkened until their white text is\nreadable, the second and third are lightened until ink text on them is; a color that can't get there\nstops the build with a message. Brand also sets the fonts and the corners (square like a printed page,\nor slightly rounded). The site opens in the day theme even when the visitor's system is dark; the toggle\nswitches to the night, dark copper, and the browser remembers the choice.\n\n## Languages\n\nBurin is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Out of order: a card names an event; put it in its place on the line of events already\nplaced, earlier to the left, later to the right (tap a gap, or ← → and Enter). A card in the wrong gap\ncosts a seal and goes where it belongs anyway, its year shown; three seals and the game ends. The\nevents are in Interface texts → 404 game → cards (a year each, all different).\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, events, prints and notes for AI assistants, in every language.\n- Pages carry JSON-LD: the publisher as an `EducationalOrganization` with its walks and packages as\n  offers, events as `Article`s about an `Event`, prints as `VisualArtwork`s, how a walk goes as a\n  `HowTo`, questions as a `FAQPage`, and each note as a `BlogPosting`.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm map -- fetch            # draw the map round map.config.json from OpenStreetMap\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/chalk/","url":"https://docs.wptom.com/templates/chalk/","title":"Chalk","description":"What's in the Chalk template and where to change it.","markdown":"Chalk is a pub and taproom site drawn on the chalkboard behind the bar: chalk headlines with\ncircled words, hand-drawn doodles that draw themselves in, kraft paper by day and a slate board\nat night. The tap list is the heart of it; then come the kitchen menu, the week's nights and\nspecials, a small shop, a journal and a visit page that says whether you're open right now.\n\nChalk comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| On tap, food (the menu), what's on, shop, journal, visit, 404 | Pages → the page's own entry |\n| Name, contact, address, opening hours, navigation, header button, profiles | Pages → Site settings |\n| Beers | Bar → Beers |\n| Events | Bar → Events |\n| Gift cards and merch | Bar → Shop items |\n| Journal posts | Content → Journal |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, a word wrapped in `*stars*` gets a chalk circle drawn around it, as in\n\"Sixteen taps and a very \\*good\\* chair\".\n\n## The tap list\n\nEach beer has a name, brewery, style, strength (ABV) and a list of prices per size (a third, a\nhalf, a pint, or anything you like). Give it a **tap number** and it's on the board, sorted by\ntap; leave the number empty and it shows under \"Coming soon\". An optional **badge** adds a chalk\ntag (\"New\", \"Last keg\"), and **chalk** picks its color. The tap list can be filtered by style;\nthe filter is plain radio buttons, so it works without JavaScript. The home page shows the first\nfew taps.\n\n## The menu\n\nPages → Food page holds the menu in sections (snacks, plates, something sweet…), each dish with a\ndescription, a price and dietary marks: `v` vegetarian, `vg` vegan, `gf` gluten free (the\nlegend's words are editable). Write your allergen note below it.\n\n## Events\n\nAn event either repeats **every week** on a day (a quiz on Tuesdays) or happens **once** on a\ndate. Weekly nights make the board on the home page and the events page; one-offs show under\n\"Coming up\", soonest first, and drop off after their date with the next build. Events can have a\nphoto.\n\n## Opening hours\n\nSite settings hold the hours for every day (leave a day empty when you're closed) and your time\nzone. The footer and the visit page list them, and a small line says \"Open now, till 23:00\",\n\"Opens today at 16:00\" or \"Closed, opens Tuesday at 16:00\", worked out in the visitor's browser.\nA closing time after midnight counts as the same night. Search engines get the hours too.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Shop | Shop page: a Buy button per item with a Stripe Price id (one-time) | Stripe |\n| Book a table | Visit page, loads on click | Cal.com |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| Fresh kegs (newsletter) | Home, tap list and journal, with a welcome email | Supabase |\n| Ask the bar | Chat on every page, answers from your taps, menu, events, hours and shop | Claude |\n| Search | ⌘K or the search button: beers, menu, events, shop, visit, journal | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\nAfter a payment, the Stripe webhook (`/api/stripe/webhook`) emails the buyer; add your own steps\nthere, such as sending the gift card code.\n\n## The map\n\nThe visit page shows a map picture (Pages → Visit page → Map) with your address. Add your\n**latitude** and **longitude** and it also offers directions (Google Maps, in a new tab) and a\nlive OpenStreetMap, which loads only when the visitor opens it, so nothing is requested from a\nthird party before that.\n\n## Sections\n\nThe home page is built from these sections, in any order: Hero, On tap now, The week, Food (up\nto three photos), The place (with the opening hours), Latest journal posts, Newsletter sign-up\nand Closing call to action.\n\n## Chalks and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three chalks: the\n**first chalk** (buttons, circles, the active tap filter) and a **second** and **third** for\ntags, doodles and colored headings. On a chalk fill the text is slate, so a chalk is lightened\nuntil that reads; used as text it is lightened on the board and darkened on paper until it\npasses WCAG AA, and a color that can't get there stops the build with a message. Brand also\nsets the **chalk texture** (on headlines and the background) and the fonts: text, headings and\na handwriting face used only for small asides.\n\n## Languages\n\nChalk is ready for more languages: see [Languages](/content/languages/). The day names for the\nopening hours are in Interface texts.\n\n## The 404 page\n\nA small game, Pour the pint: hold to pour (press and hold, or hold Space) and let go before it\nspills. The head settles, and the closer the top ends up to the line on the glass, the better.\nFive pints a round; the tap glugs and pours faster each time. The best score is kept in the\nvisitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including what makes the look.\n- `/llms.txt` lists your pages, what's on tap, the regular nights, opening hours and journal\n  posts for AI assistants, in every language.\n- Pages carry JSON-LD: the bar with its opening hours, the tap list and food as menus with\n  prices, events, shop items, questions and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/deckle/","url":"https://docs.wptom.com/templates/deckle/","title":"Deckle","description":"What's in the Deckle template and where to change it.","markdown":"Deckle is a site for independent magazines and journals (print quarterlies with a website,\nliterary and food journals, studio publications, newsletters that come out in issues, long-form\nblogs that want to feel like print), set like a printed magazine you could pick up: uncoated\npaper with grain, photos with torn deckled edges, letterpressed serif headlines and covers that\ncatch a raking light. It has your stories (each with its own page), your issues with their\ncovers, contents and editor's letters, subscriptions and single issues, and an about page for\npitches and advertisers.\n\nDeckle comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Stories, issues, subscribe, about, 404 | Pages → the page's own entry |\n| Stories | Content → Stories |\n| Issues (covers, contents, editor's letters) | Content → Issues |\n| Magazine name, tagline, email, where it's made, navigation, header button, profiles, colophon | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are set in italic in the accent, as in \"Brine,\n\\*slowly\\*\".\n\n## Issues and stories\n\nEach **issue** has a number, a theme (\"Salt\"), a season, a 4:5 cover photo, up to four cover\nlines, the **cover stock** (the second or third color as paper, the accent as book cloth), its\npages and price, and the editor's letter as its text. An issue can be **sold out**: its page then\nsays so instead of offering it.\n\nEach **story** has a standfirst, a **department** (the stories page filters by it, so keep them\nshort and consistent: \"Essays\", \"Food\", \"Places\"), the author and an optional photo credit, a 3:2\nlead photo with its caption, a **color** for its swatch, and the issue it was printed in (the\nissue's address, e.g. `salt`) with its **page in print**: together they build the issue's\ncontents page. A story without an issue is web-only. Reading time is counted from the text.\n\nStories are written in Markdown: `>` makes a pull quote, `---` a section break (`* * *`), `##` a\nsmall-capitals subhead, and footnotes (`[^1]`) are listed at the end. The first paragraph gets a\ndrop cap and the last an end mark.\n\n## Covers, plates and the bookmark ribbon\n\nCovers are drawn by the site from the issue's fields: the masthead (your magazine's name) is\nletterpressed across the stock, the photo is set in as a plate with a deckled edge, the cover\nlines are printed under it. On the home page and the issue page, the cover catches a soft light\nonce as the page opens. Back issues stand on a shelf; hovering one draws it out.\n\nOn a story, a silk ribbon in the accent hangs from the top of the page on wide screens and\nlengthens as you read. Visitors who prefer reduced motion see the light at rest and the ribbon\nsimply hanging.\n\n## Subscriptions and single issues\n\nPages → Subscribe page → Plans holds up to four plans, each a **yearly subscription** or a\n**one payment** (a gift), with what's included and a Stripe Price id. The plan marked\n**Featured** is bound in cloth. Issues have their own one-time Stripe Price id: the issue page\nthen shows a buy button. Without Price ids the buttons say how to set them up. The subscribe page\nalso lists your **stockists** and questions.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Subscriptions, gifts, single issues | Subscribe page, home page, issue pages | Stripe |\n| Pitch form | About page; pitches go to the email in Site settings | Resend |\n| Book a call (advertising, stockists) | About page, loads on click | Cal.com |\n| The Sunday Letter (newsletter) | Home and stories pages, under every story, with a welcome email | Supabase |\n| Ask the archive | Chat on every page, answers from your stories, issues, plans and questions | Claude |\n| Search | ⌘K or the search button: stories, issues, subscribe, about | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\nAfter a payment, the Stripe webhook (`/api/stripe/webhook`) emails the reader; add your own\nsteps there, such as adding a subscriber to the mailing list for the print run.\n\n## Sections\n\nThe home page is built from these sections, in any order: Cover (the current issue, or one you\npick, with its first stories), Lead story (a spread), In this issue (the contents page), Latest\nstories, Pull quote, Back issues (the shelf), Subscribe (the plans) and Newsletter sign-up.\nSections that show an issue or a story take its address; leave it empty for the newest issue and\nthe newest featured story.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** is the ink (buttons, italics in headlines, rules, the bookmark ribbon, book cloth),\nthe **second** and **third** are paper stocks (covers, the newsletter, selected states). Buttons\nand cloth are darkened until their paper-white text is readable, the stocks lightened until ink\non them is; a color that can't get there stops the build with a message. The paper, links and\nthe night theme (black card stock under a lamp) are derived from them. Brand also sets the fonts\n(a serif display face for headlines, a serif for reading, and the labels in the text font or a grotesk) and the corners\n(square, or softly rounded).\n\n## Languages\n\nDeckle is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Stop the press: the proofs are back, one line at a time, and every line hides one\nmisprint. Tap the wrong word (or move along the words with the arrow keys and press Enter)\nbefore the press runs in 60 seconds; a word that was right costs three seconds. The lines are\ncontent (Interface texts → 404 game), with each misprint written `{right|wrong}`. The best score\nis kept in the visitor's browser.\n\n## Printing\n\nStories print as plain text: no navigation, forms or chat, ink on white, with link addresses\nwritten out.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including what makes the look.\n- `/llms.txt` lists your pages, stories and issues for AI assistants, in every language.\n- Pages carry JSON-LD: the magazine as a periodical, each issue, each story as an article, the\n  plans and the questions.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/docket/","url":"https://docs.wptom.com/templates/docket/","title":"Docket","description":"What's in the Docket template, how its pages and animations work, and where to change things.","markdown":"Docket is a site for a law firm: small and mid-size firms, solo lawyers, notaries, mediators and\nlegal clinics. It is set like a legal document on pleading paper (line numbers and a double rule in\nthe margin, section marks, footnotes, exhibit stickers on photos). The home page opens with a memo\nto the visitor whose redactions lift one by one as you scroll, until the senior partner's\nsignature writes itself and a RECEIVED stamp lands. The demo is a fictional firm, Halden & Rowe.\n\nDocket comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Practice areas, people, how we work, insights, contact, 404 | Pages → the page's own entry |\n| Practice areas (who for, steps, fee, questions) | Content → Practice areas |\n| People (role, admitted, languages, areas, biography) | Content → People |\n| Insights | Content → Insights |\n| Name, address, regulatory statement, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, document labels, disclaimer, chat, emails, game texts | Content → Interface texts |\n| Privacy, terms, complaints | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are set in italic and highlighted as they scroll into\nview, as in \"Legal help, with \\*nothing in the small print\\*\".\n\n## The memo\n\nThe first section of the home page (The memo) holds a memorandum to the visitor: to, from, re,\nyour reference, and two to seven lines. Each line has a part that is blacked out at first and\nrevealed as the visitor scrolls: keep them short, one promise each. The signer's name is drawn as\na signature (each name gets its own). With reduced motion the memo stands revealed and signed.\n\n## Regulation and fees\n\nWhat your regulator requires on every page (who regulates you, your number) goes in Site settings →\nRegulatory statement and appears in the footer. The complaints procedure is a legal page. The demo\nmarks everything a firm must state in square brackets; replace them before you publish. Don't\npublish case results or testimonials your rules don't allow. Fees appear on each practice area and\nin the Fees section of the home page, with a numbered footnote for VAT and third-party costs.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Paid first consultation | Contact page (a one-time Price id) | Stripe |\n| Book a first consultation | Contact page, loads on click; a preview of the calendar until connected | Cal.com |\n| The Brief (newsletter) | Home and insights pages, with a welcome email | Supabase |\n| Enquiry form | Contact page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers about the firm, fees and how you work; never gives legal advice | Claude |\n| Search | ⌘K or the search button: practice areas, people, insights, how we work, contact | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The memo, Practice areas (a drawer of\ncase files: folders rise as you point at them), How we work (the steps, untied from their red\ntape as you scroll on wide screens), Fees, People (exhibits that fan out), A statement (a pull\nquote with a photo), Latest insights, Newsletter sign-up and Closing call.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (oxblood: buttons, the margin rule, stamps and the red ribbon), the **second** (the\nmanila folder: fills and the highlighter) and the **third** (the folder tab). Buttons are darkened\nuntil their ivory text is readable, the second and third are lightened until ink text on them is;\na color that can't get there stops the build with a message. Brand also sets the fonts (text,\nheadings, and the typewriter face for labels, references and stamps) and the corners.\n\n## Languages\n\nDocket is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Balance the scales: a claim's weight sits on the left pan; load numbered exhibits\nonto the right pan (tap them, or press 1-6) until the scales level. Five cases against the clock;\nthe best score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, practice areas, people and insights for AI assistants, in every\n  language.\n- Pages carry JSON-LD: the firm as a `LegalService`, practice areas as `Service`s with their fee,\n  people as `Person`s, how you work as a `HowTo`, questions as a `FAQPage`, and each insight.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/ember/","url":"https://docs.wptom.com/templates/ember/","title":"Ember","description":"What's in the Ember template and where to change it.","markdown":"Ember is a site for campsites, glamping sites, caravan parks and cabin stays, set like a\nnational-park field guide: map paper with topographic contour lines, photos mounted as numbered\nplates, trail-blaze colors and round passport stamps. It has your stays (each with its own page\nand a deposit to hold the dates), the camp with an illustrated map whose trail draws itself,\nfield notes and a visit page with directions.\n\nEmber comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Stays, camp, field notes, visit, 404 | Pages → the page's own entry |\n| Stays (pitches, tents, cabins) | Content → Stays |\n| Field notes | Content → Field notes |\n| Camp name, email, phone, place, coordinates, season, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nStays marked **Featured** come first, then by **Order**. Each stay has a kind (\"Tent pitch\",\n\"Cabin\"), a price as shown and what it is per, how many it sleeps, its size, where it is on the\nmap, what's there, an optional stamp (**Badge**), a **color** for its trail blaze and a photo.\nField notes pick a color too.\n\nIn headlines, words wrapped in `*stars*` get a painted trail-blaze underline, as in\n\"Pitch your tent where the \\*lake\\* begins\".\n\n## The camp map\n\nThe camp page (Pages → Camp page → Camp map) holds an illustrated map of your camp (3:2,\nwithout text) and its **places**: each has a name, a line of text, a color and a position in\npercent from the left and top of the picture. The places are numbered in their order, shown as\npins on the map and listed in the legend beside it, and a dashed trail walks from pin to pin. In\nbrowsers with scroll-driven animations the trail draws itself and the pins pop up as the map\nscrolls into view; other browsers, and visitors who prefer reduced motion, see the whole trail.\nIt needs no JavaScript. The home page shows the same map with its **Camp map** section.\n\nThe camp page also has your story with a photo, numbers and the **camp code** (house rules).\n\n## Booking and deposits\n\nEvery stay page has a **Hold your dates** button. With a Stripe Price id on the stay (a one-time\nprice: the deposit), it takes the deposit with Stripe Checkout; the guest then confirms the\ndates by email. If you already use a booking system, put its link in the stay's **Booking link**\nand the button opens it instead. **Fully booked** hides the button.\n\n## The visit page\n\nAn illustrated map of the area with a pin (placed in percent), your address and arrival details.\nAdd your **latitude** and **longitude** (Pages → Visit page → Map) and the page also offers\ndirections (Google Maps, in a new tab) and a live OpenStreetMap, which loads only when the\nvisitor opens it. Below: a contact form and booking activities (canoe hire, a guided walk…)\nwith Cal.com.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Deposits | Stay pages: a button per stay with a Stripe Price id (one-time) | Stripe |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| Book an activity | Visit page, loads on click | Cal.com |\n| Newsletter | Home and field notes pages, with a welcome email | Supabase |\n| Ask the warden | Chat on every page, answers from your stays, questions, camp map and code, and field notes | Claude |\n| Search | ⌘K or the search button: stays, field notes, camp, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\nAfter a deposit is paid, the Stripe webhook (`/api/stripe/webhook`) emails the guest; add your\nown steps there, such as noting the booking in your calendar.\n\n## Sections\n\nThe home page is built from these sections, in any order: Hero (a photo plate with a season\nstamp and a strip of facts), Stays, Camp map, A day at camp (moments from morning to the\ncampfire, under a strip of sky the sun crosses as you scroll), Numbers, Latest field notes,\nNewsletter sign-up and Closing call to action (a night panel with a campfire).\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three trail colors: the\n**accent** (buttons, the trail-blaze underline, markers) and a **second** and **third** color for\nmarkers, pins and panels (the newsletter is in the second, the camp code in the third). Text on\na color is always ink, so each color is lightened until it is readable; a color that can't get\nthere stops the build with a message. The map paper, links and the night theme (the woods at\nnight, with an ember glow) are derived from them. Brand also sets the fonts (text, headings,\ncode) and the corners (square like a printed guide, or slightly rounded).\n\n## Languages\n\nEmber is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Follow the stars: lost in the woods at night, watch the stars light up one after\nanother, then follow them in the same order (click or tap them, or press 1–7). Each round adds a\nstar; a wrong star ends the game and shows the way you came. The best score is kept in the\nvisitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including what makes the look.\n- `/llms.txt` lists your pages, stays and field notes for AI assistants, in every language.\n- Pages carry JSON-LD: the campground with its amenities, each stay with its price and\n  occupancy, the booking questions and each note.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/encore/","url":"https://docs.wptom.com/templates/encore/","title":"Encore","description":"What's in the Encore template and where to change it.","markdown":"Encore is a festival site printed like a gig poster: two fluorescent inks on paper, overprinted\na hair out of register, with halftone photos and paper grain. The home page is a poster with a\ncountdown to the gates; then come the lineup (set as a poster, with a page per act), the\nprogramme (a timetable per stage), tickets, info and news. Headlines slide into register as\nthey scroll in, buttons stamp down when pressed, and pages feed in like a sheet through a print\ndrum.\n\nEncore comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Lineup, programme, tickets, info, news, 404 | Pages → the page's own entry |\n| Dates, place, gates opening, days, stages | Festival → Festival |\n| Acts | Festival → Lineup |\n| Tickets | Festival → Tickets |\n| News posts | Content → News |\n| Festival name, email, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, countdown, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\n## The lineup and the programme\n\nDays and stages are defined once, in **Festival**: each day has an id (`fri`), a name, a short\nname and a date; each stage an id (`main`), a name and a line about it. Every act then names\nits **day** and **stage** by id, its **start** and **end** (24-hour time in 5-minute steps;\ntimes before 6 am belong to the night before) and its **billing**:\n\n- **Headliner**: the top of the poster, printed biggest in two inks; first ink on the\n  timetable.\n- **Main line**, **Second line**, **New names**: the poster's next lines, each smaller.\n\nFrom that one entry the site builds the poster, the timetable, the act's page, search, the\nchat's answers and the structured data search engines read. An act on a day or stage that\ndoesn't exist stops the build with a message saying which.\n\nActs can have a photo (printed in the act's **ink**); without one they get a card with their\nname set big on a halftone block. **Order** sorts acts within a line of the poster; a\n**Listen** link adds a button to the act's page.\n\nThe lineup page has a day picker, and the programme a day per tab. Both are plain radio\nbuttons and CSS, so they work without JavaScript. On phones the timetable becomes a list per\nstage.\n\n## The countdown\n\nThe hero counts down to **Gates open** (Festival, a date and time with its time zone, e.g.\n`2027-07-16T14:00:00+01:00`). After that moment it says the gates are open. Turn it off in the\nhero section.\n\n## Tickets\n\nEach ticket has a price as shown, a line under its name, what's included, an optional stamp\n(**Badge**), an **ink** and its small print (the text below the fields). With a Stripe Price id\n(one-time) its Buy button starts a Stripe Checkout; **Sold out** shows a stamp instead. After a\npayment, the Stripe webhook (`/api/stripe/webhook`) emails the buyer a confirmation to show at\nthe gate; add your own steps there, such as a guest list or a ticket scanner.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Tickets | Tickets page and home: a Buy button per ticket with a Stripe Price id | Stripe |\n| Contact form | Info page; messages go to the email in Site settings | Resend |\n| Book a call | Info page, for traders, press and partners; loads on click | Cal.com |\n| Lineup alerts | Home and news pages, with a welcome email | Supabase |\n| Ask the festival | Chat on every page, answers from the lineup, times, tickets, info and news | Claude |\n| Search | ⌘K or the search button: acts, news, programme, tickets, info | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## The map\n\nThe info page shows a site map picture (Pages → Info page → Getting there) with the address.\nAdd your **latitude** and **longitude** and it also offers directions (Google Maps, in a new\ntab) and a live OpenStreetMap, which loads only when the visitor opens it, so nothing is\nrequested from a third party before that. Without coordinates it says how to add them.\n\n## Sections\n\nThe home page is built from these sections, in any order: Hero (the poster, with the countdown),\nLineup poster, Ticker (a band of words rolling by), Tickets, The place (a photo, a few words and\nbig facts), Latest news, Newsletter sign-up and Closing call to action.\n\n## Inks and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds the two inks: the\n**first ink** (buttons, stamps, the overprint on headlines) and the **second ink** (headlines,\nphotos, the footer band). Text on an ink is black or paper, whichever reads; an ink used as\ntext is darkened (day) or lightened (night) until it passes WCAG AA, and a color that can't get\nthere stops the build with a message. Brand also sets the **misregistration** (none, slight or\nloose), the **paper grain** and the fonts (text, poster, times and prices).\n\nPhotos are printed in one ink whatever colors they were taken in, so they follow your inks. The\nsite map on the info page is shown as it is.\n\n## Languages\n\nEncore is ready for more languages: see [Languages](/content/languages/). Translate the\nFestival entry too (day names, stage notes).\n\n## The 404 page\n\nA small rhythm game, Soundcheck: notes fall down three lanes; hit each lane as its note crosses\nthe line (tap the lane, press F, J or K, or use the buttons). Close to the line is Perfect, a\nlittle off is Good, and every ten hits in a row raise the multiplier. The tempo rises as the set\ngoes on; three misses in a row end it. The best score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including what makes the look.\n- `/llms.txt` lists your pages, every act with its day, time and stage, tickets and news for AI\n  assistants, in every language.\n- Pages carry JSON-LD: the festival as an event with its ticket offers, each act as a\n  performance, the questions and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/fold/","url":"https://docs.wptom.com/templates/fold/","title":"Fold","description":"What's in the Fold template and where to change it.","markdown":"Fold is a site for a children's bookshop, a children's brand or toy maker, a story café or puppet\ntheatre, a library or a school's reading project. It's made like a cut-paper pop-up book: the page\nis a thick page of a book, pictures are cut out of paper and taped down, buttons are pull-tabs, and\non the home page a pop-up book stays on screen while each chapter of the story makes its paper\nscene fold up out of the pages. Books by age to order, events, school visits, a journal and a visit\npage complete it.\n\nFold comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Books, events, schools, journal, visit, 404 | Pages → the page's own entry |\n| Books (author, ages, format, price, cover, bookplate, ordering) | Content → Books |\n| Journal posts | Content → Journal |\n| Name, description, email, phone, address, navigation, header button, profiles, social picture | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are cut out of yellow paper and stuck on, as in\n\"Stories that \\*pop out\\* of the page\".\n\n## The pop-up book\n\nThe pop-up story section keeps an open book on screen while its chapters (two to four) scroll\npast. Each chapter picks one of four scenes, a castle on a hill, the woods and an owl, the sea and\na boat, or home under the moon, and as the chapter comes into view the scene's paper layers fold up\nout of the pages one after another, then fold away for the next. The scenes are drawn in code\n(`src/components/PopUpScene.astro`) in the brand's colours, so they follow any palette. Visitors who\nprefer reduced motion see the last scene standing.\n\n## Books and ordering\n\n- A **book** has an author, an illustrator, a tagline, a summary, the ages it suits, its format,\n  length and price, a cover (4:5), a bookplate and a colour.\n- **Ordering**: turn on \"Sell with Stripe Checkout\" and put a one-time Price id there; the book's\n  page gets a working button. Buyers come back to the books page, and the Stripe webhook\n  (`/api/stripe/webhook`) emails them, where you can add your own steps.\n- **Shelves** (home page): age bands, each with what it holds.\n- **Quotes** on the home page: only real quotes with their source.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Ordering books | Each book's page | Stripe |\n| Class visits | Schools page; a preview of the calendar until it's connected | Cal.com |\n| Newsletter and the events list | Home, journal and events pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your books, shelves, events, school visits, visit page and journal | Claude |\n| Search | ⌘K or the search button: books, journal, events, schools, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The opening, Shelves, The pop-up story,\nBooks, Feature (a picture and a text, e.g. story time or schools), Numbers, Quotes, From the\njournal, Newsletter sign-up and Closing call.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (buttons, links and the red paper), the **second** (sky and sea) and the **third** (sun,\nmoon and warm fills). Buttons are darkened until their white text is readable, the second and third\nare lightened until dark text on them is; a color that can't get there stops the build with a\nmessage. The site opens in the day theme whatever the visitor's system is set to; the night theme,\na deep blue bedtime sky, comes on with the toggle (pictures stay on paper). Brand also sets the\nfonts (a rounded storybook face for headings, the text font and the label font) and the corners.\n\n## Languages\n\nFold is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Tangram: seven paper pieces and a shadow to fill, a cat, a house and a boat. Drag a\npiece and it settles onto the grid, tap it to turn it, or use Tab, the arrow keys, R to turn and F\nto flip. Any arrangement that fills the shadow counts. The best score is kept in the visitor's\nbrowser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, books and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the shop as a `BookStore`, books as `Book`s, events as `Event`s, the visit\n  questions as a `FAQPage`, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/handy/","url":"https://docs.wptom.com/templates/handy/","title":"Handy","description":"What's in the Handy template, how its pages and animations work, and where to change things.","markdown":"Handy is a site for a handyman or a small home-repair business: odd-job services, property\nmaintenance, flat-pack assembly and \"husband for an hour\" services. It is set edge to edge on a\npegboard: a pale board with rows of holes and the outlines of tools, hi-vis orange, safety yellow and\npainter's tape blue, and chunky headings. On the home page a cutaway of a home runs across the whole\nwindow and, as you scroll, the jobs on the to-do list get done one by one: the drip stops, the picture\nlevels, the bulb comes on, the shelf straightens, the door stops creaking, and the to-do note ticks them\noff. The demo is a fictional business, Sorted Handyman Services.\n\nHandy comes in two variants, chosen when you create the site: files/Sanity (static pages, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages are the\nsame in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Services, jobs, tips, 404 | Pages → the page's own entry |\n| Prices (hourly rate, packages, how a job works, booking, deposits and vouchers, questions) | Pages → Prices page |\n| Contact (quote form, areas, map and its ring, hours, details, questions) | Pages → Contact page |\n| Services (tool, price from, how long, what's included, typical jobs, questions, photo) | Content → Services |\n| Jobs (where, when, time, cost, before and after photos, story) | Content → Jobs |\n| Tips | Content → Tips |\n| Name, phone, address, insurance, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, emails, game texts | Content → Interface texts |\n| Privacy, terms, guarantee | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` get a strip of blue painter's tape underneath, as in \"Your\nto-do list, \\*sorted\\*\".\n\n## The to-do list\n\nThe home page's opening has a to-do list of exactly five jobs, in the order the drawing fixes them: a\ndripping tap, a crooked picture, a dead bulb, a sagging shelf and a squeaky door. Change the words to\nyour own (keep each to the same kind of job, since the drawing doesn't change), and the title of the\nnote.\n\n## Jobs: before and after\n\nEach job has a before and an after photo of the same view. On the cards (home page, jobs page, a\nservice's page) the after photo wipes in as the card scrolls past; on the job's own page a slider lets\nvisitors compare them (drag, or the arrow keys). Take both photos from the same spot.\n\n## Prices, booking and payments\n\nThe prices page holds your hourly rate on a big price tag with its small print (minimum, call-out,\nmaterials, waste), up to four packages (mark one as the popular one), how a job works as steps on a\nstrip of painter's tape (also on the home page), booking a visit with Cal.com, and deposits and gift\nvouchers you take with Stripe: each one gets a one-time Price id. Without keys, the booking shows a\npreview of the calendar and the payment buttons explain how to switch them on.\n\n## The map of the area you cover\n\nThe map is drawn from [OpenStreetMap](https://www.openstreetmap.org/copyright) in your colors, with a\npin on your base and a dashed ring for the area you cover (Contact page → Map → Radius, in km; 0 hides\nit). It is part of the page (no embed, no cookies) and follows the theme and your accent. To draw your\nown area, put your base's coordinates in `map.config.json` and run `pnpm map -- fetch`. `detail:\n\"area\"` (the default in Handy) draws the main roads, railways, water and woods of a district; make\n`size` about four times your radius in each direction (24,000 × 12,000 m for 5 km). `detail:\n\"streets\"` draws the streets and buildings round one address instead.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Book a visit | Prices page, loads on click; a preview of the calendar until connected | Cal.com |\n| Deposits and gift vouchers | Prices page (one-time Price ids) | Stripe |\n| Tips by email | Home and tips pages, with a welcome email | Supabase |\n| Quote form | Contact page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers about services, prices, jobs and areas; never gives safety advice on gas or electrics | Claude |\n| Search | ⌘K or the search button: services, jobs, tips, prices, contact | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: Your to-do list (the opening), Services\n(cards that swing in on pegboard hooks), Recent jobs (before and after), How a job works (steps on a\nstrip of tape that unrolls), Numbers (on a tape measure that pulls out), The area covered (the map),\nTips, Tips by email and Closing call (a safety-yellow band between strips of caution tape). With\nreduced motion, the home stands fixed and ticked off and nothing moves.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the **accent**\n(hi-vis orange: buttons, marks, the ticks), the **second** (safety yellow: the to-do note, the price\ntag, the tape measure) and the **third** (painter's tape blue). Buttons are darkened until their white\ntext is readable, the second and third are lightened until ink text on them is; a color that can't get\nthere stops the build with a message. Brand also sets the fonts and the corners. The site opens in the\nday theme even when the visitor's system is dark; the toggle switches to the night, a charcoal board,\nand the browser remembers the choice.\n\n## Languages\n\nHandy is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Screw it: drive each screw into a plank until its head sits flush, with circles of your\nfinger or mouse round it or the arrow keys, then press \"That's flush\" (or Enter). Stop short and it\nsticks out; one turn too many and the thread strips. Every screw has its own thread, so watch the head,\nnot the turns. Three planks of five screws; the best score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, services, jobs and tips for AI assistants, in every language.\n- Pages carry JSON-LD: the business as a `HomeAndConstructionBusiness` with the areas it serves and its\n  packages, services as `Service`s with their price, jobs as `CreativeWork`s, how a job works as a\n  `HowTo`, questions as a `FAQPage`, and each tip as a `BlogPosting`.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm map -- fetch            # draw the map round map.config.json from OpenStreetMap\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/hush/","url":"https://docs.wptom.com/templates/hush/","title":"Hush","description":"What's in the Hush template and where to change it.","markdown":"Hush is a site for a horror attraction: a walk-through haunt or scare maze, a horror escape room,\nimmersive theatre, a ghost tour, a dungeon or dark-history cellar, a seasonal Halloween event. It's\nmade like a walk through a vaulted cellar by torchlight: the whole page, full width, is a brick\nvault in the dark with a faint film grain, and one warm beam follows the visitor's pointer (on\ntouch screens it drifts by itself). Walk-throughs are case files with their dread level in small\nwax seals; on the home page the photo of the way in stays dark except where the torch falls, and\neach room comes up out of black as you scroll down to the lowest vault. Between pages the lights go\nout and come back up. The tone is adult and quiet: dread rather than cartoon scares.\n\nHush comes in two variants, chosen when you create the site: files/Sanity (static pages, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages are\nthe same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Walk-throughs, groups, the bar, journal, visit, 404 | Pages → the page's own entry |\n| Walk-throughs (tagline, dread, depth, minimum age, time, group size, price, when it runs, warnings, a photo, tickets) | Content → Walk-throughs |\n| Journal posts | Content → Journal |\n| Name, description, email, phone, address, navigation, header button, profiles, social picture | Pages → Site settings |\n| Buttons, labels, the five dread levels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are set in italics in the candle light (oxblood in the\nlight theme), as in \"Some doors are left open \\*on purpose\\*\".\n\n## Walk-throughs and tickets\n\n- A **walk-through** has a tagline, a summary, its **dread** level (1 to 5), its depth, the minimum\n  age, the running time, the group size, the price, when it runs (days and times, as you want them\n  shown), its warnings (darkness, strobe, touch…), a photo (4:5) and a colour. The names of the five\n  levels are in Interface texts → Walk-throughs; what each level means is on the walk-throughs page\n  (the dread scale).\n- **Tickets**: turn on \"Sell tickets with Stripe Checkout\" on a walk-through and put a one-time\n  Price id there; its page then has a working button. Buyers come back to `/experiences`, and the\n  Stripe webhook (`/api/stripe/webhook`) emails them, where you can add your own steps.\n- **Groups and private hire**: what groups can book, how it works, and booking a date on Cal.com.\n- **The bar**: opening hours, the drinks, special nights and a list to join.\n- **Visit**: opening hours, safety and the safe word, who shouldn't come, a map, getting here,\n  questions and a contact form.\n- **Quotes** on the home page: only real quotes with their source. Age limits, warnings and health\n  advice are yours to keep true to your attraction.\n\n## The map\n\nThe visit page shows the streets round your entrance, drawn in the brand's colours from\nOpenStreetMap. Put your entrance's coordinates in `map.config.json` (`center`) and run\n`npm run map`; it downloads the area once and writes `src/data/location-map.json`. A pin marks the\nentrance with your name, and the card links to directions in Google Maps, Apple Maps and\nOpenStreetMap.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Tickets | Each walk-through's page | Stripe |\n| Groups and private hire | Groups page (`/groups`); a preview of the calendar until it's connected | Cal.com |\n| Newsletter and the special nights list | Home, journal and bar pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your walk-throughs, the dread scale, groups, the bar, the visit page and journal | Claude |\n| Search | ⌘K or the search button: walk-throughs, journal, groups, the bar, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The threshold (with tonight's doors,\nand \"What the torch finds\": a second version of the photo, same size and framing, shown only\ninside the torch's beam; the demo has someone standing in the doorway),\nThe descent (three to five rooms, top to bottom, each with a depth and a tall photo), Walk-throughs,\nHouse rules, Feature (a photo and a text, e.g. the bar or private hire), Numbers, Quotes, From the\njournal, Newsletter sign-up and Closing call.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (oxblood: buttons, the wax seals), the **second** (the candle: the torch beam, links in\nthe dark, warm fills) and the **third** (verdigris: cool fills and the faint tint of the dark).\nButtons are darkened until their text is readable, the fills are lightened until ink text on them\nis; a color that can't get there stops the build with a message. The site opens in the dark theme\nwhatever the visitor's system is set to; the light theme, limewashed plaster upstairs, comes on\nwith the toggle. Brand also sets the fonts (a tall display serif for headings, the text font and a\ntyped face for labels) and the corners.\n\n## Languages\n\nHush is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Don't look: a dark room lit only by your torch. Move the light with the mouse, a\nfinger or the arrow keys and take the key (click, tap or Enter). Something stands in the room with\nyou: while the light rests on it, it comes a step closer, and three steps end the game. The battery\nruns down in every room. Five rooms; the best score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, walk-throughs and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the attraction as a `TouristAttraction`, walk-throughs and special nights as\n  `Event`s, groups as a `Service`, the bar as a `BarOrPub`, the visit questions as a `FAQPage`,\n  and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm map                     # redraw the map from map.config.json\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/iris/","url":"https://docs.wptom.com/templates/iris/","title":"Iris","description":"What's in the Iris template, how its viewfinder, galleries and pages work, and where to change things.","markdown":"Iris is a site for a local photographer who shoots a bit of everything: weddings, portraits,\nfamilies, newborns, events, headshots, products and pets, from a small studio or on location. It is\nbuilt like a darkroom and a light table: at night (the default, following the visitor's system) a\nwarm near-black room under a red safelight, by day a warm white light table, with fine film grain\nover both. Headings are set in a high-contrast serif with its italic for marked words, camera data in\nspaced capitals. The demo is a fictional studio, Nora Vale Photography.\n\nIris comes in two variants, chosen when you create the site: files/Sanity (static pages, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages are the\nsame in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Sessions, journal, 404 | Pages → the page's own entry |\n| About (the approach, a photo; the people come from Team) | Pages → About page |\n| Pricing (packages, how booking works, booking, deposits and gift vouchers, questions) | Pages → Pricing page |\n| Studio (hours, before you come, map, details, contact form, questions) | Pages → Studio page |\n| Sessions (line, summary, how long, price, delivery, included, how it goes, questions, cover, gallery) | Content → Sessions |\n| Team (role, what they shoot with, biography, portrait) | Content → Team |\n| Journal posts | Content → Journal |\n| Name, contact, area covered, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, lightbox, chat, emails, game texts | Content → Interface texts |\n| Privacy, terms, image use | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are set in italics and underlined in the accent, as in\n\"Every kind of \\*day\\*\".\n\n## Through the lens\n\nThe home page opens with a viewfinder. The photo sits behind an aperture whose blades open when the\npage loads; corner brackets, a frame counter and the camera settings sit on top, and focus brackets\nfollow the pointer. Press the shutter button (or wait a few seconds) and the blades snap shut and\nopen on the next photo, while the word in the headline changes with it: \"Photographs of your\n*weddings*\", then *newborns*, *families*… Each photo is a slide in Home → Through the lens with its\nword, its description and its settings. Only the first photo is part of the first paint; the others\nload after the page. With reduced motion the blades stay open and photos change only on the button.\n\n## Sessions and galleries\n\nEvery session has a page: the cover across the window, how long it takes, the price and what you\ndeliver, the description and what's included, how it goes, the gallery and questions. Galleries take\nphotos in any shape and open in a lightbox (previous and next with the buttons, the arrow keys or a\nswipe; Escape closes it). Each photo is also a plain link to its full-size file, so it works without\nscript. The home page's film strip lists the sessions on a strip of film that slides as you scroll;\nits contact sheet takes frames in turn from every gallery, with a loupe under the pointer.\n\n## Pricing, booking and payments\n\nThe pricing page holds up to four packages (mark one as the favourite), how booking works in three\nsteps, booking a call with Cal.com, and what you sell with Stripe (the demo: a session deposit, a\nwedding deposit and a gift voucher), each with a one-time Price id. Without keys, the booking shows a\npreview of the calendar and the payment buttons explain how to switch them on.\n\n## The map\n\nThe \"Find the studio\" map is drawn from [OpenStreetMap](https://www.openstreetmap.org/copyright) in\nyour colors: streets, green spaces, water and buildings round the studio, a pin with your name, your\naddress and directions links. It is part of the page (no embed, no cookies). Put the studio's\ncoordinates in `map.config.json` and run `pnpm map -- fetch`.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Book a call | Pricing page, loads on click; a preview of the calendar until connected | Cal.com |\n| Deposits and gift vouchers | Pricing page (one-time Price ids) | Stripe |\n| Newsletter | Home and journal pages, with a welcome email | Supabase |\n| Contact form | Studio page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers about sessions, prices, booking and the studio; never promises a date | Claude |\n| Search | ⌘K or the search button: sessions, people, journal, pricing, studio | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: Through the lens (the opening), Sessions on\na film strip, From shoot to delivery (prints developing in a tray as they scroll in), Contact sheet,\nMeet the photographer, Numbers, How to find the studio (the map), Journal, Newsletter and Closing\ncall (a wide photo in viewfinder corners).\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the **accent**\n(the safelight: buttons, marks, the record light), the **second** (film amber: frame numbers and light\nfills) and the **third** (negative cyan: focus brackets). Buttons are darkened until their white text\nis readable and every pair is checked; a color that can't get there stops the build with a message.\nBrand also sets the fonts (headings and text) and the corners (square like a print, or rounded).\n\n## Languages\n\nIris is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Rule of thirds: a balloon drifts across a scene. Steer the viewfinder (pointer, drag or\nthe arrow keys) so it sits on one of the four thirds points and press the shutter (a click, Space or\nthe button): up to 100 points by how close it sits, nothing if it's outside the frame. Five frames,\neach drifting faster, printed on a little contact strip; the best score is kept in the visitor's\nbrowser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, sessions, people and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the studio as a `ProfessionalService` with its packages as offers, sessions as\n  `Service`s with their price, the people as `Person`s, booking as a `HowTo`, questions as a\n  `FAQPage`, and each journal post as a `BlogPosting`.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm map -- fetch            # draw the map round map.config.json from OpenStreetMap\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/jab/","url":"https://docs.wptom.com/templates/jab/","title":"Jab","description":"What's in the Jab template, how its pages and animations work, and where to change things.","markdown":"Jab is a site for a boxing or fitness gym: boxing clubs, conditioning gyms, martial arts and\nkickboxing studios, personal trainers with a studio. It is set edge to edge like a fight poster:\nnewsprint with a halftone screen that drifts as you scroll, black ink, signal red, volt yellow and\ncobalt, and headlines set huge in an ultra-condensed face. On the home page the headline fills the\nwidth of the window and, as you scroll, a glove throws a three-punch combo at a heavy bag in a ring\nthat runs across the whole window: bursts of halftone rays, the bag swinging, the ropes shuddering,\nthe round counter ticking on. The visit page (and the home page) shows a map of the streets round the\ngym. The demo is a fictional club, Haymaker Boxing Club.\n\nJab comes in two variants, chosen when you create the site: files/Sanity (static pages, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages are the\nsame in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Classes, coaches, journal, 404 | Pages → the page's own entry |\n| Membership (plans, your first session, booking, passes and gift cards, questions) | Pages → Membership page |\n| Visit (opening hours, what to bring, map, details, contact form, questions) | Pages → Visit page |\n| Classes (level, intensity, how long, price, times, rounds, questions, photo) | Content → Classes |\n| Coaches (nickname, role, record, qualifications, classes, biography, portrait) | Content → Coaches |\n| Journal posts | Content → Journal |\n| Name, phone, address, affiliation, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, timetable, chat, emails, game texts | Content → Interface texts |\n| Privacy, terms, safety | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are printed in white capitals on a strip of red tape, as in\n\"Train like a \\*fighter\\*\". The home page's headline is set as wide as the window: on phones each line\nbreak in it starts a new line, on wider screens it runs on one line.\n\n## Classes and the timetable\n\nEach class has a line, a summary, a level (\"Beginners\", \"All levels\"), an intensity from 1 to 5 (shown\nas five blocks), how long it takes, the drop-in price with an optional note, its times in the week\n(day, time and the coach) and its rounds: the steps of a class, shown on the red rounds band. The\n**timetable** is built from these times: the home page's Timetable section and the classes page show the\nwhole week on a scoreboard (a row you swipe on phones), and today's column lights up. Change a time in\nthe class and every page follows.\n\n## Coaches\n\nCoaches are shown as fight posters: the portrait in black and white with a halftone screen (it turns to\ncolour when you point at it), the nickname in quotes, the name, the role and the record, with a \"VS\"\nstamp between each pair. Use a 4:5 portrait from the waist up.\n\n## Membership, booking and payments\n\nThe membership page holds the plans (mark one as the champion: it is shown bigger, under a\nchampionship belt), your first session as three rounds with a round clock that ticks to 3:00 as you\nscroll, booking a free trial with Cal.com and the passes and gift cards you sell with Stripe: each one\ngets a one-time Price id. Without keys, the booking shows a preview of the calendar and the payment\nbuttons explain how to switch them on.\n\n## The map\n\nThe \"Find us\" map is drawn from [OpenStreetMap](https://www.openstreetmap.org/copyright) in your\ncolors: streets, green spaces, water and buildings round the gym, a pin with your name, your address\nand \"Get directions\" links to Google Maps, Apple Maps and OpenStreetMap. It is part of the page (no\nembed, no cookies, nothing loaded from another site) and follows the theme and your accent. To draw\nyour own area, put the gym's coordinates in `map.config.json` (`center`, and `size` in metres) and run\n`pnpm map -- fetch`: it downloads the area once and writes `src/data/location-map.json`. The map's title\nand text are on the Visit page (and in the home page's \"Find us\" section); the attribution link stays,\nas OpenStreetMap's licence asks.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Book a free trial | Membership page, loads on click; a preview of the calendar until connected | Cal.com |\n| Passes and gift cards | Membership page (one-time Price ids) | Stripe |\n| Newsletter | Home and journal pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers about classes, the timetable, membership, the coaches and opening hours; never gives medical advice | Claude |\n| Search | ⌘K or the search button: classes, coaches, journal, membership, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: In the ring (the opening), Classes (bouts on\na fight card that slam in), Giant words (rows that slide across the window as you scroll), Timetable,\nYour first session (three rounds on a red band), The coaches (fight posters), Numbers (a scoreboard\nwhose figures roll up), Journal, Find us (the map; its streets draw themselves and the pin drops in),\nNewsletter and Closing call (a volt-yellow band with a photo on a turning starburst). With reduced\nmotion, the ring stands still with the glove on the bag, the notes appear as tags and nothing moves.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the **accent**\n(signal red: buttons, marks, the gloves and the bag), the **second** (volt yellow: the tape and bright\nfills) and the **third** (cobalt: the ring floor, a rope and fills). Buttons are darkened until their\nwhite text is readable, the second and third are lightened until ink text on them is; a color that\ncan't get there stops the build with a message. Brand also sets the fonts (headings and text) and the\ncorners (square like a poster, or slightly rounded). The site opens in the day theme even when the\nvisitor's system is dark; the toggle switches to the night, the poster printed in reverse on black,\nand the browser remembers the choice.\n\n## Languages\n\nJab is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Slip: a sparring partner shows a tell, then throws a jab from your left, a cross from\nyour right or a hook at head height. Slip the other way or duck before the glove lands, with the arrow\nkeys, the three buttons or a swipe; moving during the tell is too early. Three rounds of eight\npunches, faster each round; the best score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, classes, coaches and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the gym as an `ExerciseGym` with its plans as offers, classes as `Service`s with\n  their price, the coaches as `Person`s, your first session as a `HowTo`, questions as a `FAQPage`, and\n  each journal post as a `BlogPosting`.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm map -- fetch            # draw the map round map.config.json from OpenStreetMap\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/junction/","url":"https://docs.wptom.com/templates/junction/","title":"Junction","description":"What's in the Junction template and where to change it.","markdown":"Junction is a site for journeys along a route: a scenic or heritage railway, a tour operator, a\ntourist region or trail, a boat or bus tour, a festival or conference with a programme. It's made\nlike a model railway: a panorama the train crosses as you scroll, the stations down a track with a\nlittle train riding past them, journeys sold as tickets, a departure board and a visit page with\nthe line on a map. It runs from edge to edge of the window.\n\nJunction comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Journeys, timetable, journal, visit, 404 | Pages → the page's own entry |\n| Journeys (what you sell, with the route) | Content → Journeys |\n| Timetable (days and departures) | Content → Timetable |\n| Journal posts | Content → Journal |\n| Name, description, email, address, the line (name, length, since), navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, the departure board, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` get a brass lining, as in \"All aboard for the \\*valley\\*\".\n\n## The departure and the line\n\nThe home page opens with **the departure**: a wide panorama (about 7:3) that holds still while\nyou scroll through it, while the train runs along the track from the platform and out at the\nright; its wheels turn and smoke drifts back. The next departures from the timetable show on a\nboard beside the headline.\n\n**The line** lists your stations in order (two to seven), each with its nameboard, distance, a\ntitle, a few lines and a photo; a small train rides down the track in the middle of the screen as\nthey pass, and each station's signal drops to clear. Both are CSS only; visitors who prefer\nreduced motion see the train waiting at the platform.\n\nThe train, track, signals and nameboards are drawn by the template in your brand's colours, so\nthey follow every palette.\n\n## Journeys and tickets\n\nJourneys marked **Featured** come first, then by **Order**. Each has a tagline, summary, duration,\nfare (\"£24\") and what it's for (\"adult return\"), when it runs, the **route** (stops with times\nand an optional note, drawn down a track on its page), what's included, a photo and a colour.\n\nTo sell tickets online, turn on **Tickets → Sell tickets with Stripe Checkout** and add a\none-time Stripe Price id: the journey's page shows a button that takes the payment with Stripe\nCheckout and brings the customer back to the journey. Until your Stripe key and the Price id are\nset, the page says what's missing. After paying, the Stripe webhook (`/api/stripe/webhook`) emails\nthe customer; add your own steps there, such as your seat plan.\n\n## The timetable\n\nEach running day is an entry: a label (\"Saturday 18 October\"), a date that sorts the days, an\noptional special day (\"Steam Gala\") and note, and its departures (time, journey, station,\nplatform, and a status: on time, few seats, full or special). The home page shows the first days\non a departure board, the timetable page all of them. Remove days that have passed, or mark them\nas drafts.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Tickets | Journey pages with tickets turned on | Stripe |\n| Groups and private hire | Visit page, loads on click | Cal.com |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| Newsletter | Home and journal pages, with a welcome email | Supabase |\n| AI chat | On every page, answers from your journeys, timetable, visit page and journal | Claude |\n| Search | ⌘K or the search button: journeys, timetable, journal, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The departure, The line, Journeys,\nTimetable (the next days), Numbers, Latest from the journal, Newsletter sign-up and Closing call\n(a photo from edge to edge).\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** is the livery (buttons, the train, the track, nameboards), the **second** the brass\nlining (tickets, highlights, the departure board), the **third** the painted sky (soft fills, the\ntrain's windows). Buttons are darkened until their cream text is readable, the second and third\nare lightened until ink text on them is; a color that can't get there stops the build with a\nmessage. The cream baseboard and the night run (deep blue, for the dark theme and the departure\nboard) are derived from them. Brand also sets the fonts (text, headings, and the mono font for\ntimes and boards) and the corners (rounded like enamel signs, or square).\n\n## Languages\n\nJunction is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Switch the points: trains in your brand's colours leave the yard one by one; tap a\njunction (or press 1, 2 or 3) to throw its points so each train reaches the platform of its own\ncolour. Three wrong platforms and the signal box closes; the best score is kept in the visitor's\nbrowser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including what makes the look.\n- `/llms.txt` lists your pages, journeys and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the line as a `TouristAttraction`, each journey as a `TouristTrip` with its\n  stops and fare, the questions on the visit page, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/kelvin/","url":"https://docs.wptom.com/templates/kelvin/","title":"Kelvin","description":"What's in the Kelvin template, how its pages and animations work, and where to change things.","markdown":"Kelvin is a site for a local air conditioning contractor: AC and HVAC installers, heat pump and\nmini-split fitters, ventilation and refrigeration contractors, one-van cooling businesses. It is set\nedge to edge on a weather map's isotherms, the wavy temperature contours that cool from sun amber to\ncool blue as you scroll, with cool blue, sun amber and frost mint and a wide display face for headings.\nOn the home page a cutaway of a desert home runs across the window and, as you scroll, it cools room by\nroom while it stays hot outside. The demo is a fictional business in Mesa, Arizona, Saguaro Air.\n\nKelvin comes in two variants, chosen when you create the site: files/Sanity (static pages, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages are the\nsame in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Services, installs, tips, 404 | Pages → the page's own entry |\n| Prices (service call, plans, how an install works, booking, tune-ups and deposits, questions) | Pages → Prices page |\n| Contact (quote form, areas, map and its ring, hours, details, questions) | Pages → Contact page |\n| Services (icon, price from, how long, what's included, common jobs, questions, photo) | Content → Services |\n| Installs (where, when, the system, time, cost, before and after photos, story) | Content → Jobs |\n| Tips | Content → Tips |\n| Name, phone, address, licence, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, emails, game texts | Content → Interface texts |\n| Privacy, terms, warranty | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are painted with the cool gradient, as in \"Outside 112°.\n\\*Inside 72°.\\*\".\n\n## The cooldown\n\nThe home page opens with the headline, a line and two buttons (one can be a `tel:` link) above a\ncutaway of a home. Set the temperature outside (it stays put), the inside temperature it starts from and\nthe one it settles at, the unit (°F or °C) and the labels. As you scroll, the inside readout counts down\nfrom one to the other while the rooms cool. With reduced motion the home is shown cool and nothing\nmoves.\n\n## Installs: before and after\n\nEach install has a before and an after photo of the same view (the old unit and the new one). On the\ncards the after photo wipes in as the card scrolls past; on the install's own page a slider lets visitors\ncompare them (drag, or the arrow keys). Take both photos from the same spot.\n\n## Prices, booking and payments\n\nThe prices page holds your service call on a thermostat dial with its small print, up to four plans\n(mark one as the popular one), how an install works along a duct (also on the home page), booking a\nvisit with Cal.com, and tune-ups and deposits you take with Stripe: each one gets a one-time Price id.\nWithout keys, the booking shows a preview of the calendar and the payment buttons explain how to switch\nthem on.\n\n## The map of the area you cover\n\nThe map is drawn from [OpenStreetMap](https://www.openstreetmap.org/copyright) in your colors, with a\npin on your base and a dashed ring for the area you cover (Contact page → Map → Radius, in km; 0 hides\nit). It is part of the page (no embed, no cookies) and follows the theme and your accent. To draw your\nown area, put your base's coordinates in `map.config.json` and run `pnpm map -- fetch`. `detail:\n\"area\"` draws the main roads, railways, water and woods of a district; in a big city, `roads` (e.g.\n`[\"motorway\", \"trunk\", \"primary\", \"secondary\"]`) keeps the map light.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Book a visit | Prices page, loads on click; a preview of the calendar until connected | Cal.com |\n| Tune-ups and deposits | Prices page (one-time Price ids) | Stripe |\n| Tips by email | Home and tips pages, with a welcome email | Supabase |\n| Quote form | Contact page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers about services, prices, plans and areas; never tells anyone to handle refrigerant, electrics or gas, and gives the emergency number | Claude |\n| Search | ⌘K or the search button: services, installs, tips, prices, contact | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The cooldown (the opening), Services (cards\nthat open like louvres), Recent installs (before and after), How an install works (steps along a duct\nthat fills with cool air), Numbers (on thermostat displays), The area covered (the map), Tips, Tips by\nemail and the Closing call (a night band with a 24/7 light).\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the **accent**\n(cool blue: buttons, marks, the cold end of the gradient), the **second** (sun amber: the heat, the\noutside readout) and the **third** (frost mint). Buttons are darkened until their white text is readable,\nthe second and third are lightened until ink text on them is; a color that can't get there stops the\nbuild with a message. Brand also sets the fonts and the corners. The site opens in the day theme even\nwhen the visitor's system is dark; the toggle switches to the night, a deep night sky, and the browser\nremembers the choice.\n\n## Languages\n\nKelvin is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Keep it 72: four rooms heat up in the sun, each at its own pace. Point the one cooling\nzone at a room (tap it, or keys 1 to 4) and keep every room below 85° for as long as you can; the sun\ngets stronger as time goes on. The best time is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, services, installs and tips for AI assistants, in every language.\n- Pages carry JSON-LD: the business as a `HomeAndConstructionBusiness` with the areas it serves and its\n  plans, services as `Service`s with their price, installs as `CreativeWork`s, how an install works as a\n  `HowTo`, questions as a `FAQPage`, and each tip as a `BlogPosting`.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm map -- fetch            # draw the map round map.config.json from OpenStreetMap\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/kerf/","url":"https://docs.wptom.com/templates/kerf/","title":"Kerf","description":"What's in the Kerf template and where to change it.","markdown":"Kerf is a site for craftspeople and small trade businesses (joiners, carpenters, cabinet\nmakers, stonemasons, upholsterers, restorers), set like a joiner's workshop drawing: grid paper,\nphotos pinned up with masking tape, dimension lines that draw themselves, pencil notes and a title\nblock in the footer. It has your services (each with its own page, a \"from\" price and an optional\ndeposit), finished work with the timber and a cut list, how a commission goes along a tape\nmeasure, a journal, and a contact page for quotes and measure-up visits.\n\nKerf comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Services, work, process, journal, contact, 404 | Pages → the page's own entry |\n| Services (what you make, prices, lead times) | Content → Services |\n| Work (finished jobs) | Content → Work |\n| Journal posts | Content → Journal |\n| Business name, email, phone, workshop address, area, title block, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nServices marked **Featured** come first, then by **Order**. Each has a kind (\"Fitted\nfurniture\", \"Repairs\"), a price as shown (\"from £9,500\") and what it is for, a lead time, what's\nincluded, a **color** for its swatch, a photo and an optional **dimension** drawn over it.\n\nWork is sorted by the date it was fitted. Each job has a number (\"Job 0418\"), a **kind** (the\nwork page filters by it, so keep the kinds short and consistent), where it is, the timber and\nfinish, its time on the bench, a cover photo with an optional dimension and a pencil note, a\ndetail photo with its caption, and a **cut list** (part, size, quantity), shown as a table.\n\nIn headlines, words wrapped in `*stars*` get a pencil underline, as in \"Made to \\*measure\\*\".\n\n## Dimension lines\n\nPhotos can carry sizes like a drawing: the hero has a **width** and a **height**, services and\njobs a **width** (in millimetres, written as you'd write them on a drawing, e.g. \"3 600\"). In\nbrowsers with scroll-driven animations the lines draw out from the size as the photo scrolls into\nview; other browsers, and visitors who prefer reduced motion, see them drawn. Leave a size empty\nto hide its line. They're decoration: the facts that matter are also in the text.\n\n## The process and the tape measure\n\nPages → Process page → Steps holds three to eight steps, each with a title, a line of text and\nhow long it takes. They sit along a tape measure (across the page on large screens, down the left\nedge on phones) that pulls out of its case as the steps scroll by. The home page shows the same\nsteps with its **How a commission goes** section. The process page also has a photo of the\nworkshop with your story, what to prepare before the visit, your materials and the guarantee.\n\n## Quotes, visits and deposits\n\nThe contact page has a quote form, booking a measure-up visit with Cal.com and a **deposit**\n(Pages → Contact page → Pay a deposit) for accepted quotes that aren't one of your services, such\nas repairs. Services can have their own deposit: with a **Deposit as shown** and a Stripe Price\nid (one-time) on the service, its page shows a deposit button that takes it with Stripe Checkout.\n\nThe contact page also shows an illustrated map of your area with the workshop's pin and a circle\nfor the area you cover (both placed in percent). Add your **latitude** and **longitude** (Pages →\nContact page → Map) and it also offers directions (Google Maps, in a new tab) and a live\nOpenStreetMap, which loads only when the visitor opens it.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Deposits | Service pages with a Stripe Price id, and the contact page | Stripe |\n| Quote form | Contact page; messages go to the email in Site settings | Resend |\n| Book a measure-up | Contact and process pages, loads on click | Cal.com |\n| Newsletter | Home and journal pages, with a welcome email | Supabase |\n| Ask the workshop | Chat on every page, answers from your services, prices, process, questions, work and journal | Claude |\n| Search | ⌘K or the search button: services, work, journal, process, contact | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\nAfter a deposit is paid, the Stripe webhook (`/api/stripe/webhook`) emails the customer; add\nyour own steps there, such as putting the job in your calendar.\n\n## Sections\n\nThe home page is built from these sections, in any order: Hero (a pinned photo with its\ndimensions and a pencil note, and a strip of facts), Services, Selected work, How a commission\ngoes (the tape measure), Numbers, Latest from the journal, Newsletter sign-up and Closing call to\naction (a blueprint with a drawn cabinet).\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** is the pencil (buttons, dimension lines, underlines, ticks), the **second** the tape\nmeasure (the tape, the newsletter panel, selected states) and the **third** the blueprint (a cool\nfill; its hue also tints the night theme). Buttons are darkened until their white text is\nreadable, the second and third are lightened until graphite text on them is; a color that can't\nget there stops the build with a message. The paper, links and the night theme (a blueprint,\nchalk on deep blue) are derived from them. Brand also sets the fonts (text, headings, and the\nmono font for sizes and labels) and the corners (square like a drawing sheet, or slightly\nrounded).\n\n## Languages\n\nKerf is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Mark the cut: a plank on the bench and an order (\"Cut at 640 mm\"). Mark the cut by\neye (click or tap the plank, or move the pencil with the arrow keys and press Enter); the tape\nthen pulls out and shows how many millimetres off you were. Ten planks, the first three with\nmarks at the quarters; the lowest total wins, and the best is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including what makes the look.\n- `/llms.txt` lists your pages, services, work and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the business with its area, each service with its starting price, the\n  process as steps, the questions, each job and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/lantern/","url":"https://docs.wptom.com/templates/lantern/","title":"Lantern","description":"What's in the Lantern template and where to change it.","markdown":"Lantern is a site for a theatre: a puppet or children's theatre, a storytelling night, a touring\ncompany, a café or bookshop with a programme. It's made like a shadow-puppet show: the whole page,\nfull width, is a paper screen lit from behind, glowing in the middle and flickering like a candle,\nwith a velvet pelmet across the top and drapes at the sides of wide screens. Photos are pinned to it with brass split pins,\ntickets are typewritten stubs, and on the home page puppets on rods cross a lit screen while the\nscenery changes as you scroll. Between pages the curtains close and open.\n\nLantern comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Programme, schools & groups, workshops, journal, visit, 404 | Pages → the page's own entry |\n| Shows (tagline, ages, running time, price, performances, puppets, notes, a scene, tickets) | Content → Shows |\n| Journal posts | Content → Journal |\n| Name, description, email, phone, address, navigation, header button, profiles, social picture | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are set in the curtain red, in italics, as in \"Stories\ntold in \\*light and shadow\\*\".\n\n## Behind the screen\n\nThe story section keeps a lit screen on screen while its chapters (three to five) scroll past.\nEach chapter picks a puppet (a fox, a bird, a ship, a dragon or an owl) and its scenery (hills and\na moon, the sea, a mountain or a sleeping town): the scenery fades in and the puppet crosses the\nscreen on its rod, bobbing like a puppet in a hand. Everything is drawn in code\n(`src/components/ShadowStage.astro`) in the brand's colours. Visitors who prefer reduced motion see\nthe last scene standing.\n\n## Shows and tickets\n\n- A **show** has a tagline, a summary, the ages it's for, its running time, the price, its\n  performances (each a date and times, as you want them shown), the puppets in it, notes such as\n  relaxed performances, a scene from it (4:5) and a colour.\n- **Tickets**: turn on \"Sell tickets with Stripe Checkout\" on a show and put a one-time Price id\n  there; the show's page then has a working button. Buyers come back to the programme, and the\n  Stripe webhook (`/api/stripe/webhook`) emails them, where you can add your own steps.\n- **School and group visits**: what's on offer, how a visit works, and booking a date on Cal.com.\n- **Workshops**: the sessions with their dates, places and prices, and a list to join.\n- **Quotes** on the home page: only real quotes with their source.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Tickets | Each show's page | Stripe |\n| School and group visits | Schools & groups page (`/schools`); a preview of the calendar until it's connected | Cal.com |\n| Newsletter and the workshop list | Home, journal and workshops pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your programme, visits, workshops, the café, access and journal | Claude |\n| Search | ⌘K or the search button: shows, journal, visits, workshops, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The opening (with tonight's show on a\nticket stub), Behind the screen, What's on, Feature (a photo and a text, e.g. school visits or the\ncafé), Numbers, Quotes, From the journal, Newsletter sign-up and Closing call.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (the curtain red: buttons, links and the curtains), the **second** (the lamp: the glow\nof the screen and warm fills) and the **third** (the night: cool fills). Buttons are darkened until\ntheir text is readable, the fills are lightened until ink text on them is; a color that can't get\nthere stops the build with a message. The site opens in the day theme whatever the visitor's system\nis set to; the night theme, the screen seen from the dark auditorium, comes on with the toggle.\nBrand also sets the fonts (a theatrical display face for headings, the text font and a typewriter\nface for the programme) and the corners.\n\n## Languages\n\nLantern is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Shadow match: an outline is drawn on the screen, and a cut-out in front of the lamp\ncasts a shadow. Turn it and bring it nearer or further until the shadow fills the outline: drag on\nthe screen, scroll, use the buttons or the arrow keys, and press \"That's it!\" when it fits (a\nnear-perfect fit ends the round by itself). Three objects. The best score is kept in the visitor's\nbrowser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, shows and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the theatre as a `PerformingArtsTheater`, shows as `TheaterEvent`s, visits as\n  a `Service`, workshops as `Event`s, the visit questions as a `FAQPage`, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/lava/","url":"https://docs.wptom.com/templates/lava/","title":"Lava","description":"What's in the Lava template and where to change it.","markdown":"Lava is a developer portfolio built around a lava lamp: home, work with case studies, about,\nnotes and contact. Every page has a temperature, from 40 °C on the home page to 100 °C on the\ncontact page; the hotter the page, the faster the wax rises. Moving between pages floods the\nscreen with wax.\n\nLava comes in two variants, chosen when you create the site: files/Sanity (static, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages\nare the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Work, about, contact, notes, 404 | Pages → the page's own entry |\n| Projects (case studies) | Content → Projects |\n| Notes (short articles) | Content → Notes |\n| Your name, role, email, navigation, profiles | Pages → Site settings |\n| Your photo | Pages → About page |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nProjects marked **Featured** come first, then by **Order**. The home page shows as many as its\nSelected work section asks for.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Contact form | Contact page; messages go to the email in Site settings | Resend |\n| Book a call | Contact page, loads on click | Cal.com |\n| Paid sessions | Contact page → Paid sessions (a Stripe Price id per offer) | Stripe |\n| Newsletter for notes | Notes page, with a welcome email | Supabase |\n| Ask about my work | Chat on every page, answers from your about page, projects and notes | Claude |\n| Search | ⌘K or the search button: projects, notes, about | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: Hero, Selected work, Skills (with\nyour photo and the skill groups from the about page), Latest notes and Closing call to action.\n\n## Palette and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) picks a **palette**: Classic\n(blue, orange, amber), Raspberry, Forest, Ink or Ocean, or **Custom** with your own three colors:\nthe lamp (the dark theme's background) and two waxes. Text, buttons, links and the light theme\nare derived from them and checked for readable contrast; a color that can't be made readable\nstops the build with a message. Brand also sets the fonts (text, headings, labels), corners\nand button shape.\n\n## The lava\n\nThe wax is drawn by a small WebGL program that starts after the page has loaded, so it never\nslows the first view. Without a graphics card, without JavaScript, or with reduced motion\nturned on, simple CSS wax (or a still frame) takes its place. Each page's temperature and the\nlanes the wax flows in are in `src/lib/temperature.ts`; the wax keeps clear of the text, since\nlight text on wax isn't readable.\n\n## Languages\n\nLava is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Pop the wax: pop the blobs before they reach the top of the lamp, with a click,\na tap or the keys 1–9. The best score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, projects and notes for AI assistants, in every language.\n- Pages carry JSON-LD: you as a person on the home page, each project and note.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/mint/","url":"https://docs.wptom.com/templates/mint/","title":"Mint","description":"What's in the Mint template, how its pages and animations work, and where to change things.","markdown":"Mint is a site for a dental practice: family dentists, dental clinics, orthodontists, hygienists and\ncosmetic or implant clinics. It is set edge to edge on a wall of fresh white tiles with mint grout, a\nsheen that slides across them as you scroll and bubbles of foam drifting up. On the home page a big\ngrin runs right across the window and, as you scroll, a toothbrush with striped paste sweeps along the\nteeth: foam rises, the plaque goes, each tooth turns glossy white with a sparkle and the gums go a\nhealthy pink, while a tube of toothpaste and a dental mirror float beside the headline. The visit\npage (and the home page) shows a map of the streets round the practice. The demo is a fictional\npractice, Brightwater Dental.\n\nMint comes in two variants, chosen when you create the site: files/Sanity (static pages, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages are the\nsame in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Treatments, team, journal, 404 | Pages → the page's own entry |\n| Prices (first visit, price list, booking, deposits and gift cards, questions) | Pages → Prices page |\n| Visit (opening hours, emergencies, map, details, contact form, questions) | Pages → Visit page |\n| Treatments (summary, how long, price, steps, questions, photo) | Content → Treatments |\n| Team (role, qualifications, registration, languages, treatments, biography, portrait) | Content → Team |\n| Journal posts | Content → Journal |\n| Name, phone, address, registration statement, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, emails, game texts | Content → Interface texts |\n| Privacy, terms, complaints | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` get a swipe of fresh mint underneath and a little sparkle\nthat twinkles, as in \"Smile like you \\*mean it\\*\".\n\n## Treatments and the team\n\nEach treatment has a line, a summary, how long it takes, the price as you'd show it (\"£48\", \"from\n£2,400\") with an optional note, the steps of how it goes, questions and a 3:2 photo. Its page shows\nwho on the team does it: list the treatments by file name on each person. Team portraits are shown in\nround dental mirrors, so use a square photo with the face in the middle.\n\n## Prices, booking and payments\n\nThe prices page holds your first visit (its steps run along a strand of floss that pulls taut as you\nscroll, and the home page's \"Your first visit\" section shows the same steps), the price list in groups,\nbooking a check-up with Cal.com and the deposits and gift cards you take with Stripe: each option gets a\none-time Price id. Without keys, the booking shows a preview of the calendar and the payment buttons\nexplain how to switch them on.\n\n## The map\n\nThe \"Find us\" map is drawn from [OpenStreetMap](https://www.openstreetmap.org/copyright) in your\ncolors: streets, green spaces, water and buildings round the practice, a pin with your name, your\naddress and \"Get directions\" links to Google Maps, Apple Maps and OpenStreetMap. It is part of the\npage (no embed, no cookies, nothing loaded from another site) and follows the theme and your accent.\nTo draw your own area, put the practice's coordinates in `map.config.json` (`center`, and `size` in\nmetres) and run `pnpm map -- fetch`: it downloads the area once and writes `src/data/location-map.json`.\nThe map's title and text are on the Visit page (and in the home page's \"Find us\" section); the\nattribution link stays, as OpenStreetMap's licence asks.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Book a check-up | Prices page, loads on click; a preview of the calendar until connected | Cal.com |\n| Deposits and gift cards | Prices page (one-time Price ids) | Stripe |\n| Check-up reminders | Home and journal pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers about treatments, prices, the team and opening hours; never diagnoses | Claude |\n| Search | ⌘K or the search button: treatments, team, journal, prices, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: Brush up the smile (the opening), Treatments\n(cards with a stripe of toothpaste squeezed across the top), Your first visit (steps along the floss),\nThe team (portraits in dental mirrors that tilt into place), Numbers, Emergency (a gum-pink band with\nthe phone number), Journal, Check-up reminders, Find us (the map; its streets draw themselves and the\npin drops in) and Closing call. With reduced motion, the smile stands clean and sparkling and nothing\nmoves.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the **accent**\n(deep mint: buttons, marks and the toothbrush), the **second** (gum pink) and the **third** (fresh\nmint: fills, the gums' partner and the tile grout). Buttons are darkened until their white text is\nreadable, the second and third are lightened until ink text on them is; a color that can't get there\nstops the build with a message. Brand also sets the fonts (headings and text) and the corners. The\nsite opens in the day theme even when the visitor's system is dark; the toggle switches to the night,\ndeep teal tiles under a soft lamp, and the browser remembers the choice.\n\n## Languages\n\nMint is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Brush up: plaque builds up on a row of teeth; scrub it away by dragging back and forth\nacross a tooth, or pick a tooth with the arrow keys and scrub with the space bar. Three rounds of 15\nseconds, the plaque coming faster each time; the best score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, treatments, team and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the practice as a `Dentist`, treatments as `MedicalProcedure`s with their\n  price, the team as `Person`s, your first visit as a `HowTo`, questions as a `FAQPage`, and each\n  journal post as a `BlogPosting`.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm map -- fetch            # draw the map round map.config.json from OpenStreetMap\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/putty/","url":"https://docs.wptom.com/templates/putty/","title":"Putty","description":"What's in the Putty template and where to change it.","markdown":"Putty is a site for a bakery, a cake studio or a café, a sweet shop or a toy maker with a counter:\nanyone whose products are made by hand and sold by the day. It's made like a claymation: the page is\na modelling board, pictures are pressed into slabs of plasticine and pinned with a ball of clay,\nbuttons are clay pills that squash when pressed, and on the home page a lump of clay is remodelled\ninto a ball of dough, a loaf, a croissant and a cake as the story scrolls past, while Bun the dough\nball walks by. The baking week, bakes to pre-order, celebration cakes, baking classes, a journal and\na visit page complete it.\n\nPutty comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Menu, cakes, classes, journal, visit, 404 | Pages → the page's own entry |\n| Bakes (price, size, when baked, keeps, ingredients, allergens, price tag, pre-orders) | Content → Bakes |\n| Journal posts | Content → Journal |\n| Name, description, email, phone, address, navigation, header button, profiles, social picture | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are rolled in butter-coloured clay, as in \"Squished with\nlove, baked \\*this morning\\*\".\n\n## The clay story\n\nThe story section keeps a board on screen while its chapters (two to four) scroll past. Each\nchapter picks one of four shapes, a ball of dough, a loaf, a croissant or a cake, and the clay on\nthe board squashes and springs into that shape in the jerky steps of stop-motion as the chapter\ncomes into view. Bun, the dough-ball mascot, walks across the bottom of the board as you read. The\nshapes and Bun are drawn in code (`src/components/Clay.astro`, `Bun.astro`) in the brand's colours,\nso they follow any palette. Visitors who prefer reduced motion see the last shape standing.\n\n## Bakes and pre-orders\n\n- A **bake** has a tagline, summary, price, size, when it is baked, how it keeps, its ingredients\n  and allergens, a picture (4:5), a price tag and a colour. The bake's page and the AI chat repeat\n  the allergens exactly as you write them.\n- **Pre-orders**: turn on \"Sell with Stripe Checkout\" and put a one-time Price id there; the bake's\n  page gets a working button. Buyers come back to the menu, and the Stripe webhook\n  (`/api/stripe/webhook`) emails them, where you can add your own steps.\n- **The baking week** (home page): seven days, Monday first, each with what comes out of the oven;\n  today is marked.\n- **Quotes** on the home page: only real quotes with their source.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Pre-orders | Each bake's page | Stripe |\n| Cake consultations | Cakes page; a preview of the calendar until it's connected | Cal.com |\n| Newsletter and the class list | Home, journal and classes pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your bakes (with allergens), the baking week, cakes, classes, visit page and journal | Claude |\n| Search | ⌘K or the search button: bakes, journal, cakes, classes, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The opening, The baking week, The clay\nstory, Bakes, Feature (a picture and a text, e.g. cakes or classes), Numbers, Quotes, From the\njournal, Newsletter sign-up and Closing call.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (buttons, links and the plum clay), the **second** (mint fills and clay) and the\n**third** (butter fills, crusts and icing). Buttons are darkened until their white text is\nreadable, the second and third are lightened until dark text on them is; a color that can't get\nthere stops the build with a message. The site opens in the day theme whatever the visitor's system\nis set to; the night theme, the bakery after closing in deep plum, comes on with the toggle\n(pictures stay on cream clay). Brand also sets the fonts (a round, chunky face for headings, the\ntext font and the label font) and the corners.\n\n## Languages\n\nPutty is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Sort it: clay balls are jumbled in jars; move the top ball onto a jar with the same\ncolour on top, or into an empty jar, until every jar holds one colour. Tap a jar and then another,\nor use the keys 1 to 7. Three rounds, each with one more colour; every deal is checked to be\nsolvable, and each colour also has its own mark so it never relies on colour alone. The best score\nis kept in the visitor's browser. The game follows the light and dark theme.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, bakes and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the bakery as a `Bakery`, bakes as `Product`s, classes as `Event`s, the\n  visit questions as a `FAQPage`, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/quest/","url":"https://docs.wptom.com/templates/quest/","title":"Quest","description":"What's in the Quest template and where to change it.","markdown":"Quest is a site for an indie game and its studio: a game launch, a small studio, a game jam, a\nstreamer with a game. It's made like a 16-bit game: the home page is a level where a pixel hero\nruns across the window as you scroll, collecting coins, and every section is a world. Games with\nscreenshots and a supporter edition, a devlog, a press kit and support complete it. The title\nscreen and the closing call run from edge to edge of the window.\n\nQuest comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Games, devlog, press kit, support, 404 | Pages → the page's own entry |\n| Games (screenshots, stores, supporter edition) | Content → Games |\n| Devlog posts | Content → Devlog |\n| Name, description, emails, address, navigation, header button, profiles, social picture | Pages → Site settings |\n| Buttons, labels, chat, search, emails, the level strip, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` light up in the accent, as in \"Carry the \\*last ember\\*\nhome\".\n\n## The level\n\nUnder the header runs a strip of ground with coins: as the page scrolls, the hero runs from left to\nright, the coins pop as it passes and the counter counts them. The run frames change only while\nyou scroll. The hero is a sprite sheet in `public/sprites/fox-run.png` (four frames of 68 × 40\npixels, facing right): replace it with your own character in the same size. Visitors who prefer\nreduced motion see the hero waiting at the start.\n\nSmall pixel icons (the coin, the lantern in the logo, the feature icons) are drawn in code in\n`src/lib/pixels.ts`, as rows of letters in the brand's colours.\n\n## Games and the supporter edition\n\n- A **game** has a tagline, summary, status (\"Coming 2027\"), genre, players, platforms, store\n  buttons, key art and screenshots (16:9; they open large without JavaScript).\n- **Supporter edition**: turn on \"Sell with Stripe Checkout\" and put a one-time Price id there; the\n  game's page gets a working button. Buyers come back to the games page, and the Stripe webhook\n  (`/api/stripe/webhook`) emails them, where you can add your own steps (a key on launch day).\n- The **trailer** section takes a YouTube video id; the video loads only when the poster's play\n  button is pressed.\n- **Quotes** on the home page: only real quotes with their source.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Supporter edition | Each game's page | Stripe |\n| Press and creator calls | Press kit page; a preview of the calendar until it's connected | Cal.com |\n| Newsletter | Home and devlog pages, with a welcome email | Supabase |\n| Contact form | Support page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your games, press kit, support answers and devlog | Claude |\n| Search | ⌘K or the search button: games, devlog, press, support | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The level (title screen), Trailer,\nFeatures, Screenshots, Numbers, Quotes, From the devlog, Newsletter sign-up and Closing call.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (buttons and highlights), the **second** (the sky) and the **third** (moss). Buttons are\ndarkened until their white text is readable, the second and third are lightened until dark text on\nthem is; a color that can't get there stops the build with a message. The night theme is a deep\nindigo. Brand also sets the fonts (a pixel font for headings, the text font, and the label font)\nand the corners.\n\n## Languages\n\nQuest is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Maze run: the fox is lost in a maze; reach the glowing door before the lantern's\nlight runs out, and the maze darkens as it does. Arrow keys, WASD, swipes or the on-screen buttons;\nthree mazes, each bigger. The best score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, games and devlog for AI assistants, in every language.\n- Pages carry JSON-LD: the studio as an `Organization`, games as `VideoGame`s, the support\n  questions as a `FAQPage`, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/razor/","url":"https://docs.wptom.com/templates/razor/","title":"Razor","description":"What's in the Razor template and where to change it.","markdown":"Razor is a site for a barbershop, a grooming studio, a single-chair barber or a beard bar, in\nneo-brutalism. The page is the shop's wall of white subway tiles, full width, with a black and\nwhite checkerboard floor above the footer; on wide screens a barber pole stands at the right edge\nand its stripes turn as you scroll. Thick ink borders, hard shadows, flat blocks of red, blue and\nbrass and tall signage type. On the home page a \"now serving\" ticket rolls up to its number, the\nprices hang on a black letter board, the barbers are trading cards with stats, and a mirror wipes\nfrom one step of a haircut to the next as the steps scroll past.\n\nRazor comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Barbers, services, apprentices, journal, visit, 404 | Pages → the page's own entry |\n| Barbers (role, tagline, since, specialties, card stats, days in, signature cut, portrait) | Content → Barbers |\n| Journal posts | Content → Journal |\n| Name, description, email, phone, address, navigation, header button, profiles, social picture | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are set on a brass block, as in \"Walk in rough. Walk out\n\\*sharp.\\*\".\n\n## The cut\n\nThe cut section keeps a framed mirror on screen while its steps (three to six) scroll past. Each\nstep has a time, a title, a text and a tall photo; as a step comes into view its photo wipes in over\nthe last one in hard steps, and a giant step number sits beside it. Visitors who prefer reduced\nmotion see the last photo.\n\n## Prices, gift cards and booking\n\n- **The price board** (Pages → Services page): groups such as Hair, Beard and Extras, each line a\n  name, a price, a time and a note. The services page shows the whole board; the home page's board\n  shows its first lines.\n- **Gift cards**: turn on \"Sell with Stripe Checkout\" on a gift card and put a one-time Price id\n  there; the button then works. Buyers come back to the services page, and the Stripe webhook\n  (`/api/stripe/webhook`) emails them, where you can add your own steps.\n- **Booking a barber**: Cal.com, on the services page.\n- **Barbers**: each has a trading card (a colour band with their role, the portrait, up to three\n  stats on the card and up to five on their page), specialties, the days they're in, and a\n  signature cut.\n- **Quotes** on the home page: only real quotes with their source.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Gift cards | Services page | Stripe |\n| Booking a barber | Services page; a preview of the calendar until it's connected | Cal.com |\n| Newsletter and the apprentice list | Home, journal and apprentices pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your prices, barbers, opening hours, walk-ins and journal | Claude |\n| Search | ⌘K or the search button: barbers, journal, services, apprentices, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The opening (with the \"now serving\"\nticket), The cut, The price board, Barbers, Feature (a photo and a text, e.g. hot towel shaves or\nkids' cuts), Numbers, Quotes, From the journal, Newsletter sign-up and Closing call.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (buttons, links and the red of the pole), the **second** (blue blocks and the pole's\nblue) and the **third** (brass blocks, tags and headline highlights). Buttons are darkened until\ntheir white text is readable, the fills are lightened until ink text on them is; a color that\ncan't get there stops the build with a message. The site opens in the day theme whatever the\nvisitor's system is set to; the night theme, the shop after hours with black tiles and cream\nshadows, comes on with the toggle. Brand also sets the fonts (a tall condensed face for headings,\nthe text font and the label font) and the corners (square or slightly rounded).\n\n## Languages\n\nRazor is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Line up: trace the dashed line on a head with the clippers, as closely as you can.\nDrag along it with a mouse, finger or pen, or hold Space and steer the drifting clippers with ↑\nand ↓. Three cuts (a hairline, a beard line, a lightning bolt); points come from how much of the\nline you covered and how much of your stroke stayed on it. The best score is kept in the\nvisitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, barbers and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the shop as a `HairSalon`, barbers as `Person`s, services as a `Service`,\n  the apprentices page as a `JobPosting`, the visit questions as a `FAQPage`, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/slab/","url":"https://docs.wptom.com/templates/slab/","title":"Slab","description":"What's in the Slab template and where to change it.","markdown":"Slab is a studio and agency site in neo-brutalism: thick lines, hard shadows, flat color blocks\nand big type. It has a home page, services with fixed-price offers, work with case studies,\nabout, a journal and contact. Headlines press into place word by word, a ticker band scrolls\nyour services, buttons press into their shadow and pages wipe in from the side.\n\nSlab comes in two variants, chosen when you create the site: files/Sanity (static, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages\nare the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Services, work, about, journal, contact, 404 | Pages → the page's own entry |\n| Case studies | Content → Projects |\n| Journal posts | Content → Journal |\n| Studio name, tagline, email, location, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nProjects marked **Featured** come first, then by **Order**. Each project has a **headline\nresult** (a number and a label) shown on its card, and a **color** for it. Journal posts pick a\ncolor too.\n\nIn headlines, a word wrapped in `*stars*` lands on a color block, as in\n\"We design and build software people \\*actually\\* use.\"\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Contact form | Contact page; messages go to the email in Site settings | Resend |\n| Book a call | Contact page, loads on click | Cal.com |\n| Fixed-price offers | Services page → Fixed-price offers (a Stripe Price id per offer) | Stripe |\n| Newsletter | Journal page, with a welcome email | Supabase |\n| Ask the studio | Chat on every page, answers from your services, prices, work, about page and journal | Claude |\n| Search | ⌘K or the search button: work, journal, services, about | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: Hero (with up to three stickers),\nTicker band, Services (from the services page), Selected work, How we work (steps), Numbers,\nLatest journal posts and Closing call to action.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three fills: the\n**accent** (buttons, highlights, the hero block) and a **second** and **third** color for cards\nand stickers. Text on a fill is always ink, so each fill is lightened until it is readable; a\ncolor that can't get there stops the build with a message. Paper, ink, links and the dark theme\nare derived from the fills. Brand also sets the fonts (text, headings, labels) and the corners\n(square or slightly rounded).\n\n## Languages\n\nSlab is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Stack the slabs: a slab slides back and forth above a stack; drop it with a\nclick, a tap, Space or the Drop button. Whatever hangs over is cut off, so the next slab is\nnarrower. The best height is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including what makes the look.\n- `/llms.txt` lists your pages, case studies and journal posts for AI assistants, in every\n  language.\n- Pages carry JSON-LD: the studio, its services and questions, each case study and post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/stride/","url":"https://docs.wptom.com/templates/stride/","title":"Stride","description":"What's in the Stride template, how to draw your own city and course, and where to change things.","markdown":"Stride is a site for city races: race organisers, running clubs, charity runs and cycling events.\nIts maps are the real city, drawn from OpenStreetMap by a command in the template, and every\ncourse follows the real streets: it is measured along the line, gets kilometre markers and an\nelevation profile from open elevation data. On the home page the course draws itself as you\nscroll, the camera following the runner while a race clock ticks. The demo is a 10K and a 5K in\nLisbon. It runs from edge to edge of the window.\n\nStride comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Your city\n\nThe template draws the map itself, so it can be any city:\n\n1. Set the area in `map.config.json`: `area` with its south, west, north and east edges in\n   degrees (right-click on [openstreetmap.org](https://www.openstreetmap.org) → \"Show address\"\n   gives you a point's latitude and longitude). 3-5 km across fits a 10K.\n2. Run `npm run map -- fetch`. It downloads the area from OpenStreetMap (one request, up to a\n   minute) and the heights from Open Topo Data (about a request a second, a few dozen requests),\n   builds the map's geometry in `src/data/map/` and draws the base map into `public/map/`, by day\n   and at night, saved as AVIF in every width the site uses (so building the site never re-encodes\n   it). A sea or a wide river mapped as a coastline is filled in.\n3. Give each race its start and its course. The course is found along the real streets when the\n   site builds, with its length, kilometre markers and profile.\n\nThe map's colours are in `map.config.json` → `colors`; after changing them, `npm run map --\nrender` redraws the maps without downloading again. `elevation.spacing` sets how densely the\nheights are sampled.\n\nMap data is © OpenStreetMap contributors, under the Open Database License; the heights come from\nOpen Topo Data. Every map shows the OpenStreetMap attribution and every profile its elevation\nsource; keep them. See the template's `LICENSES.md`.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Races, results, training, news, race day, 404 | Pages → the page's own entry |\n| Map labels (districts, the river) and attributions | Pages → The map |\n| Races (course, start, entries, aid stations) | Content → Races |\n| Results (one file per race and year) | Content → Results |\n| Coaches | Content → Coaches |\n| News posts | Content → News |\n| Name, description, city, email, address, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` turn to the accent on a volt bar, as in \"Ten kilometres\nof Lisbon, \\*every one of them real\\*\".\n\n## Races and results\n\n- A **race** has a start and a **course**: points with a latitude and longitude in the order the\n  runners pass them, the last one being the finish. Name the points worth a chapter (they become\n  the home page's chapters, labels and the race page's timeline); leave the rest unnamed to steer\n  the course into a particular street. The site finds the way between them along the runnable\n  streets (big roads included, steps only when there's no other way), measures it and draws it with\n  kilometre markers and the elevation profile. A race also has its distance as named (\"10K\"), date\n  and start time, time limit, surface, price, categories, aid stations by kilometre, what's\n  included, a picture and a colour.\n- **Entries**: turn on \"Sell entries with Stripe Checkout\" and put the one-time Price id of one\n  entry there; the race's page gets a working button. Runners come back to the races page, and the\n  Stripe webhook (`/api/stripe/webhook`) emails them where to pick up their bib.\n- **Results**: one file per race and year with every finisher (bib, name, category, club, time) in\n  any order. Places and pace per kilometre come from the times. The results page has a finder by\n  name, bib, club and category that works in the browser, without a server.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Entries | Each race's page | Stripe |\n| Call with a coach | Training page, loads on click; a preview of the calendar until connected | Cal.com |\n| Newsletter, volunteers | Home, news and race day pages, with a welcome email | Supabase |\n| Contact form | Race day page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your races (with their courses and climbs), results, training, race day and news | Claude |\n| Search | ⌘K or the search button: races, results, training, news, race day | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The course (it draws itself as you\nscroll; pick the race and the clock's pace), Races, What you get, Numbers, Latest results,\nTraining, Partners, From the news, Newsletter sign-up and Closing call (a picture from edge to\nedge).\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (volt: buttons and the course on the map), the **second** (signal: kilometre markers and\nwarm fills) and the **third** (lane: cool fills). Text on all three is dark; each is lightened until\nit reads, and links in the accent's hue are darkened by day; a color that can't get there stops the\nbuild with a message. Brand also sets the fonts (text, headings, and the mono font for numbers and\nsplits) and the corners.\n\n## Languages\n\nStride is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Negative split: ten kilometres with the hills of your first race's real course.\nBefore each kilometre pick Easy, Steady or Push (keys 1-3 or the buttons). Push is fast but costs\nenergy, climbs cost more, Easy gets some back; run out and you walk. Points come from the finish\ntime, and the best score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including how to draw your city and courses.\n- `/llms.txt` lists your pages, races, results and news for AI assistants, in every language.\n- Pages carry JSON-LD: the organiser as a `SportsOrganization`, races as `SportsEvent`s with\n  their start, the questions on the race day page, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm map                     # draw your city (map.config.json), with its heights\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/stroll/","url":"https://docs.wptom.com/templates/stroll/","title":"Stroll","description":"What's in the Stroll template, how to draw your own city and where to change things.","markdown":"Stroll is a site for guided walks in a real city: walking-tour companies, independent guides,\ntourist information centres and cities. Its maps are the real city, drawn from OpenStreetMap in an\nillustrated 3D style by a command in the template, and every walk's route follows the real streets.\nOn the home page the walk draws itself as you scroll, the camera flying from sight to sight. The\ndemo walks through Prague. It runs from edge to edge of the window.\n\nStroll comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Your city\n\nThe template draws the map itself, so it can be any city:\n\n1. Set the area in `map.config.json`: `area` with its south, west, north and east edges in\n   degrees (right-click on [openstreetmap.org](https://www.openstreetmap.org) → \"Show address\"\n   gives you a point's latitude and longitude). About 2-3 km across works best.\n2. Run `npm run map -- fetch`. It downloads the area from OpenStreetMap (one request, up to a\n   minute), builds the map's geometry in `src/data/map/` and draws four base maps into\n   `public/map/`: the city in 3D and as a plan, each by day and at dusk, saved as AVIF in every\n   width the site uses (so building the site never re-encodes them).\n3. Give your sights their latitude and longitude, your walks their route and meeting point. The\n   routes are found along the real streets when the site builds.\n\nThe map's colours are in `map.config.json` → `colors`; after changing them, `npm run map --\nrender` redraws the maps without downloading again. `camera` sets how steeply the 3D view looks\ndown, how tall buildings are drawn, and how many floors a building has when OpenStreetMap doesn't\nknow.\n\nMap data is © OpenStreetMap contributors, under the Open Database License. Every map shows the\nattribution; keep it. See the template's `LICENSES.md`.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Walks, sights, guides, journal, visit, 404 | Pages → the page's own entry |\n| Map labels (districts, the river) | Pages → The map |\n| Walks (route, meeting point, tickets) | Content → Walks |\n| Sights (with their latitude and longitude) | Content → Sights |\n| Guides | Content → Guides |\n| Journal posts | Content → Journal |\n| Name, description, city, email, address, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` are underlined with a dotted gold route, as in \"The\nwhole of old Prague, \\*one street at a time\\*\".\n\n## Walks and sights\n\n- A **walk** has a route: the sights in the order you visit them (by their file name), plus\n  waypoints with a latitude and longitude where you want it to take a particular street. The\n  site finds the way between them along the walkable streets, measures the distance and draws it\n  on the maps. It also has a meeting point, duration, times, languages, group size, price,\n  highlights, a picture and a colour.\n- **Tickets**: turn on \"Sell tickets with Stripe Checkout\" and put the one-time Price id of one\n  ticket there; the walk's page gets a working button. Walkers come back to the walks page, and\n  the Stripe webhook (`/api/stripe/webhook`) emails them the meeting point.\n- **Private walks** are booked with Cal.com on the walks pages.\n- A **sight** has its kind (the sights page filters by it), a summary, latitude and longitude,\n  when it was built, hours, entry and a picture. Sights are numbered on the maps by their order.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Tickets | Each walk's page | Stripe |\n| Private walks | Walks pages, loads on click | Cal.com |\n| Newsletter | Home and journal pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your walks (with their routes), sights, guides, visit page and journal | Claude |\n| Search | ⌘K or the search button: walks, sights, guides, journal, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The walk (the route draws itself as\nyou scroll; pick which walk), Walks, Sights, Guides, Numbers, From the journal, Newsletter sign-up\nand Closing call (a picture from edge to edge).\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (buttons and pins), the **second** (the river: cool fills) and the **third** (gold:\nhighlights and the walk's line on the maps). Buttons are darkened until their white text is\nreadable, the second and third are lightened until dark text on them is; a color that can't get\nthere stops the build with a message. Brand also sets the fonts (text, headings, and the mono font\nfor labels and distances) and the corners.\n\n## Languages\n\nStroll is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Postcard: a postcard of your city, cut from the 3D map around one of your sights, is\ncut into tiles and shuffled. Slide the tiles back into place (tap a tile next to the gap, or use\nthe arrow keys). Fewer moves, more points; three postcards, the last one cut finer, and the best\nscore is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including how to draw your city.\n- `/llms.txt` lists your pages, walks, sights and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the company as a `TravelAgency`, walks as `TouristTrip`s with their stops,\n  sights as `TouristAttraction`s with their coordinates, the questions on the visit page, and\n  each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm map                     # draw your city (map.config.json)\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/swell/","url":"https://docs.wptom.com/templates/swell/","title":"Swell","description":"What's in the Swell template and where to change it.","markdown":"Swell is a site for a mobile app (an indie app, a small studio's product, a subscription app\nwith a free tier or a beta), made of liquid glass over the water: panes that bend the photo\nbehind their rim, photos from edge to edge of the window, and phones whose screens are real\nmarkup filled from your content, so there are no screenshots to keep up to date. It has the\nfeatures (each next to its screen), pricing with subscriptions, what's new, a journal, support\nwith an AI chat and a beta sign-up.\n\nSwell comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Features, pricing, what's new, journal, support, beta, 404 | Pages → the page's own entry |\n| What the phones show | Pages → App screens |\n| Features | Content → Features |\n| Releases (what's new) | Content → What's new |\n| Journal posts | Content → Journal |\n| App name, description, made by, support email, store buttons, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` take the accent, as in \"Know the water \\*before\\* you\nget in\".\n\n## The phones and the app's screens\n\nEvery phone and the watch on the site show one of six screens: **Today** (the verdict, the\nwindow, four readings, the tide and the next hours), **Forecast** (hourly bars), **Spots**,\n**Check-in**, **Swim log** and **Watch**. They're built in markup and read Pages → App screens:\nthe clock, the spot, the verdict, the readings with their units, the tide heights (drawn as a\ncurve), the forecast hours, the spots and their status, the check-in, the log and the app's tab\nlabels. Put your own app's words and numbers there and every phone follows.\n\nPick which screen the hero shows (Home page → Hero → App screen) and which one each feature\nshows (Content → Features → App screen). To draw a screen of your own app, add a component in\n`src/components/screens/` (see `AGENTS.md` in the project).\n\n## Features and releases\n\nFeatures marked **Featured** come first, then by **Order**. Each has a name, a tagline, a\nsummary for cards and search, key points, an optional number (\"15 min between updates\") and a\n**color** for its dot; the body is the longer text on the features page, where every feature is\nits own section with its phone (`/features#<slug>`). The features page also lists smaller extras\n(offline, watch, widgets, privacy…) over a photo.\n\nReleases are sorted by date, newest first, each with its version, the platforms (iPhone,\nAndroid, Apple Watch), a summary and the list of changes as its body. The newest is marked\n**Latest**.\n\n## Plans and payments\n\nThe plans live on the pricing page (Pages → Pricing page → Plans): name, short text, price per\nmonth and per month billed yearly (0 for a free plan; empty for one you price per customer),\nwhat's included and whether it's **highlighted**. The pricing page has a monthly/yearly switch,\na comparison table, questions and a **Book a call** box (Cal.com) for the plan without a price,\nsuch as clubs or teams.\n\nA plan with **Stripe checkout** turned on gets a button that takes the subscription with Stripe\nCheckout, with the free trial set on the pricing page; add your Stripe key and the plan's monthly\nand yearly Price ids to make it work (until then the pricing page explains what's missing).\nAfter paying, customers land on `/pricing/welcome` (Pricing page → After paying), and the Stripe\nwebhook (`/api/stripe/webhook`) emails them; add your own steps there, such as unlocking the\nsubscription in your app.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Subscriptions | Pricing page (and the plans on the home page) | Stripe |\n| Book a call | Pricing page, loads on click | Cal.com |\n| Beta sign-up | Beta page, with a welcome email | Supabase |\n| Newsletter | Journal page, with a welcome email | Supabase |\n| Support form | Support page; messages go to the support email in Site settings | Resend |\n| AI chat | On every page, answers from your features, plans, questions, releases and journal | Claude |\n| Search | ⌘K or the search button: features, releases, journal, pricing, support, beta | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: Hero (the phone over a photo, with\nthe store buttons), Widgets (the app at a glance, glass widgets over a photo), Features, How it\nworks (numbered steps), Plans (from the pricing page), Numbers, Latest from the journal,\nNewsletter sign-up and Closing call (store buttons over a photo).\n\n## Colors, glass and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** tints the water and gives the buttons, links and the tide curve; the **second** is a\nwarm signal (the swim window, badges, the buoy); the **third** is a cool light fill. Buttons are\ndarkened until their white text is readable, the second and third are lightened until dark text\non them is; a color that can't get there stops the build with a message.\n\nThe glass is derived from the accent too, in both themes: white frosted glass over pale water,\nsmoked glass over deep water. Its text is checked against the glass over pure black and pure\nwhite, so it stays readable over any photo you put behind it; if it can't, the glass gets\ndenser. In Chromium browsers small panes (the header, the tab bar, the phones, the widgets) also\nbend what's behind their rim like a lens; other browsers show frosted glass. Visitors who ask\ntheir system for reduced transparency get solid panels.\n\nBrand also sets the fonts (text, headings, and the mono font for labels and units) and the\ncorners (round like the app, or softer).\n\n## Languages\n\nSwell is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Skim: skim a stone across a calm bay. Press anywhere on the sea and pull back like a slingshot (the\nfurther, the harder; pull down to throw higher) and let go, or set the angle and power with the\narrow keys and press Space; flat and fast skips furthest,\nand a skip right next to the buoy is worth a bonus. Five stones; the best score is kept in the\nvisitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including what makes the look.\n- `/llms.txt` lists your pages, features, releases and journal for AI assistants, in every\n  language.\n- Pages carry JSON-LD: the app as a `MobileApplication` with its plans as offers, the questions\n  on the pricing and support pages, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/tinytown/","url":"https://docs.wptom.com/templates/tinytown/","title":"Tiny Town","description":"What's in the Tiny Town template and where to change it.","markdown":"Tiny Town is a site for a place with many parts on one map: a new neighbourhood or regeneration\nproject, a coworking campus, a market hall or shopping street, a town or village, a university or\nholiday park. It's made like a building-block diorama: an isometric town, drawn from your own\nlayout in your brand's colours, builds itself as you scroll and flies to each district; places\nare pinned on the map; homes, workspace passes, what's on, news and a visit page complete it. It\nruns from edge to edge of the window.\n\nTiny Town comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Places, homes, workspace, what's on, news, visit, 404 | Pages → the page's own entry |\n| The map's districts | Pages → The town |\n| Places (with their pins) | Content → Places |\n| Homes | Content → Homes |\n| Events | Content → What's on |\n| News posts | Content → News |\n| Name, description, email, address, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` get a park-green highlight, as in \"A whole \\*quarter\\*\".\n\n## The map\n\nThe town is drawn by the template from `src/data/town.ts`: the baseboard's size, water, green\nand paving areas, and every building as a block (position and size in tiles, height, a paint:\nbrick, paper, canal, park or ink, rows of windows, a glass roof), with bridges, trees and boats.\nChange it to draw your own place; it's coloured from your brand, and windows light up in the dark\ntheme.\n\n**Pages → The town** lists the districts, each with a key, a name and its position on the map in\npercent. The home page's town section has chapters that each fly to a district; places and homes\nname their district too, and each place has a **pin** (percent of the map) for the places page\nand its own page. `townPoint(x, y, z)` in `town.ts` gives you the percentages of a tile.\n\nOn the home page the town builds itself block by block as you scroll, then the camera flies to\neach chapter's district while its card scrolls past. Visitors who prefer reduced motion see the\nfinished town.\n\n## Places, homes and passes\n\n- **Places** have a kind (the places page filters by it), a summary, address, hours, district,\n  pin, a square picture and a colour; featured ones come first on the home page.\n- **Homes** have a kind, size, price, status (available, reserved or sold), features and a\n  picture; each page offers a viewing with Cal.com. The homes page also has **register interest**\n  for new homes (a sign-up list in Supabase with a welcome email).\n- **Passes** (Pages → Workspace page): a pass with **Sell with Stripe Checkout** turned on and a\n  one-time Price id gets a working button; customers come back to the workspace page and get an\n  email from the Stripe webhook (`/api/stripe/webhook`), where you can add your own steps.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Passes | Workspace page | Stripe |\n| Viewings | Homes and home pages, loads on click | Cal.com |\n| Register interest | Homes page, with a welcome email | Supabase |\n| Newsletter | Home and news pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your places, homes, workspace, events, visit page and news | Claude |\n| Search | ⌘K or the search button: places, homes, events, news, workspace, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The town (builds itself, then flies to\neach chapter's district), Places, Homes, What's on, Numbers, Latest news, Newsletter sign-up and\nClosing call (a picture from edge to edge).\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** is brick (buttons, roofs, pins), the **second** the canal (water, cool fills), the\n**third** the park (grass, highlights, selected states). Buttons are darkened until their white\ntext is readable, the second and third are lightened until dark text on them is; a color that\ncan't get there stops the build with a message. The paper, ink and the dusk theme are derived\nfrom them. Brand also sets the fonts (text, headings, and the mono font for labels and\naddresses) and the corners (round like toy blocks, or softer).\n\n## Languages\n\nTiny Town is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Deliver it: three addresses light up on a grid of streets; drive the delivery van\ncorner by corner (arrow keys, the on-screen arrows, or tap the next corner) past all of them and\nback to the depot. The fewer streets you drive, the more points; five rounds, and the best score\nis kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including how to draw the map.\n- `/llms.txt` lists your pages, places, homes and news for AI assistants, in every language.\n- Pages carry JSON-LD: the quarter as a `Place`, places as `LocalBusiness`es, events, the\n  questions on the visit page, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/toon/","url":"https://docs.wptom.com/templates/toon/","title":"Toon","description":"What's in the Toon template and where to change it.","markdown":"Toon is a brand story site drawn like a cartoon: thick outlines, flat colors, halftone dots,\nspeech bubbles and a mascot who narrates. The home page tells your story chapter by chapter as\nvisitors scroll; then come a shop with product pages, your story, a journal and a visit page.\nHeadlines pop in word by word, buttons bounce and squish, a little honey jar fills as you read,\nand pages close and open on a circle like the end of an old cartoon.\n\nToon comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Shop, story, journal, visit, 404 | Pages → the page's own entry |\n| Products | Content → Products |\n| Journal posts | Content → Journal |\n| Brand name, mascot name, email, location, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nProducts marked **Featured** come first, then by **Order**. Each product has a price as shown,\na size, tasting notes, an optional sticker (**Badge**), a **color** for its card and an image.\nJournal posts pick a color too.\n\nIn headlines, a word wrapped in `*stars*` sits on a wobbly color blob, as in\n\"Honey with a \\*story\\* in every jar\".\n\n## The story\n\nThe **Story** section on the home page is a list of chapters (two to eight). Each chapter has a\nscene (an illustration, 4:3), a title, a few sentences, a speech bubble for the mascot and a\ncolor. While a chapter's text scrolls by, its scene stays on screen; in browsers with\nscroll-driven animations the scene pops in with a squash and stretch, the mascot flies in and\nthe text card swings into place. Other browsers, and visitors who prefer reduced motion, get\nthe same layout without the movement. It needs no JavaScript.\n\n## The mascot\n\nThe mascot (a bee in the demo) is drawn in your brand colors, so it matches any palette. Name\nit in Site settings: the chat answers as the mascot, and what it says in speech bubbles comes\nfrom the content. To use your own character, replace the drawing in\n`src/components/Mascot.astro`.\n\n## The map\n\nThe visit page shows an illustrated map (Pages → Visit page → Map) with a pin you place in\npercent from the left and top, its label and your address. Add your **latitude** and\n**longitude** and the page also offers directions (Google Maps, in a new tab) and a live\nOpenStreetMap, which loads only when the visitor opens it, so nothing is requested from a third\nparty before that. Without coordinates it says how to add them.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Shop | Product pages: a Buy button per product with a Stripe Price id (one-time) | Stripe |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| Book a visit | Visit page, loads on click | Cal.com |\n| Newsletter | Home and journal pages, with a welcome email | Supabase |\n| Ask the mascot | Chat on every page, answers from your products, shop questions, story and journal | Claude |\n| Search | ⌘K or the search button: products, journal, shop, story, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\nAfter a payment, the Stripe webhook (`/api/stripe/webhook`) emails the buyer; add your own steps\nthere, such as telling your shipping tool.\n\n## Sections\n\nThe home page is built from these sections, in any order: Hero (an opening scene with the\nmascot), Story (scrolling chapters), Products, Ribbon (words floating by on a band), Numbers,\nLatest journal posts, Newsletter sign-up and Closing call to action.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds four fills: the\n**accent** (buttons, highlights, the blobs behind words) and a **second**, **third** and\n**fourth** color for cards, stickers, bubbles and chapters; the third also tints the daytime\nsky and the fourth colors the hills in the footer. Text on a fill is always ink, so each fill\nis lightened until it is readable; a color that can't get there stops the build with a message.\nThe ground, sky, links and the night theme (a starry indigo sky) are derived from the fills.\nBrand also sets the fonts (text, headings, labels) and the outline weight (thin or bold).\n\n## Languages\n\nToon is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Honey run: honey drops and raindrops fall from the sky; move the jar to catch the\nhoney and dodge the rain (drag, arrow keys or A/D, or the Left and Right buttons). A raindrop in\nthe jar or honey on the grass costs a heart; after three the game ends. The best score is kept\nin the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`), including what makes the look.\n- `/llms.txt` lists your pages, products and journal posts for AI assistants, in every\n  language.\n- Pages carry JSON-LD: the brand, each product with its price, the shop's questions and each\n  post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/tuck/","url":"https://docs.wptom.com/templates/tuck/","title":"Tuck","description":"What's in the Tuck template and where to change it.","markdown":"Tuck is a site for a babysitting agency, a nanny or a childminder, an after-school or holiday\nclub: anyone who looks after other people's children. It's made like a family fridge door: the\npage is the door itself, full width, scribbled with faint crayon doodles, with the light of the\nday falling on it (afternoon at the top, night by the footer). Headlines spell a word in alphabet\nmagnets, photos and children's crayon drawings are held on with magnets, and handover notes,\nsticky notes and vouchers are pinned between them. On the home page a crayon window turns from\nafternoon to night as the evening scrolls past, while the handover note ticks itself off. Sitters\nwith their own pages, prices, a page for new sitters, a journal and a contact page complete it.\n\nTuck comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The\npages are the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Sitters, prices, join, journal, contact, 404 | Pages → the page's own entry |\n| Sitters (role, ages, experience, languages, good at, checks, free days, favourite game, portrait) | Content → Sitters |\n| Journal posts | Content → Journal |\n| Name, description, area you cover, email, phone, address, navigation, header button, profiles, social picture | Pages → Site settings |\n| Buttons, labels, chat, search, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, a word wrapped in `*stars*` is spelled in alphabet magnets, as in \"Grown-ups' night\nout, \\*sorted\\*\".\n\n## The evening\n\nThe evening section keeps a crayon window on screen while its chapters (three to six) scroll\npast, each one a line of the handover note with its time. The sky in the window turns from\nafternoon to sunset to night, the sun sinks, the moon and stars come out, the neighbours' lights\ngo on at dusk and off one by one, and the kitchen clock moves on to bedtime; each line ticks\nitself off as it passes. It is all drawn in code (`src/components/EveningWindow.astro`) in the\nbrand's colours, so it follows any palette. Visitors who prefer reduced motion see the night with\nevery line ticked.\n\n## Sitters, prices and vouchers\n\n- A **sitter** has a role, a tagline, a summary, who they look after, their experience,\n  languages, what they're good at, their checks and training, the days they're free (Monday\n  first), a favourite game, a portrait (4:5, a photo or a drawing) and a colour. Keep the checks\n  true: the sitter's page and the AI chat repeat them.\n- **The star chart** on the home page has a row for each check and a column for each sitter.\n- **Prices**: rates on sticky notes, how booking works, and **packs and vouchers**: turn on\n  \"Sell with Stripe Checkout\" on a pack and put a one-time Price id there; the button then works.\n  Buyers come back to the prices page, and the Stripe webhook (`/api/stripe/webhook`) emails them,\n  where you can add your own steps.\n- **Meet-and-greet**: a free call on Cal.com, on the prices page.\n- **Quotes** on the home page: only real quotes with their source.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Packs and vouchers | Prices page | Stripe |\n| Meet-and-greet calls | Prices page; a preview of the calendar until it's connected | Cal.com |\n| Newsletter and the sitter list | Home, journal and join pages, with a welcome email | Supabase |\n| Contact form | Contact page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers from your sitters, services, prices, the area you cover, questions and journal | Claude |\n| Search | ⌘K or the search button: sitters, journal, prices, join, contact | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: The opening, The evening, What we do,\nSitters, The star chart, Feature (a photo and a text, e.g. pick-ups or holidays), Numbers,\nQuotes, From the journal, Newsletter sign-up and Closing call.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (buttons, links and the first magnet colour), the **second** and the **third** (sticky\nnotes, magnets and fills). A fourth magnet colour is added for you (tomato red, or violet when\nyour accent is red). Buttons are darkened until their white text is readable, the fills are\nlightened until dark text on them is, and the magnet letters are kept readable as large text; a\ncolor that can't get there stops the build with a message. The site opens in the day theme\nwhatever the visitor's system is set to; the night theme, the kitchen with the night light on,\ncomes on with the toggle (paper stays white). Brand also sets the fonts (a round, chunky face for\nheadings, the text font and the handwriting for notes) and the corners.\n\n## Languages\n\nTuck is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Lights out: it is bedtime, but windows of the house are still lit. Tap a window to\nswitch its light and its neighbours' (above, below, left and right) until the whole house is\ndark; arrow keys and Enter work too. Three houses, each a size bigger; every house can be put to\nsleep, and points compare your taps with the fewest possible. Lit and dark windows also differ in\nwhat's drawn in them, so colour is never the only clue. The best score is kept in the visitor's\nbrowser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, sitters and journal for AI assistants, in every language.\n- Pages carry JSON-LD: the business as a `ChildCare`, sitters as `Person`s, prices as a\n  `Service`, the join page as a `JobPosting`, the contact questions as a `FAQPage`, and each post.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/wag/","url":"https://docs.wptom.com/templates/wag/","title":"Wag","description":"What's in the Wag template, how its pages and animations work, and where to change things.","markdown":"Wag is a site for a dog shelter or rescue: shelters, foster networks, breed rescues, municipal\nanimal shelters and dog sanctuaries. Its pages are kraft paper with the dogs cut out of their photos\nand set on top, each on a soft blob in its own colour, with name tags shaped like dog tags, speech\nbubbles in a marker hand and paw prints. On the home page the dogs looking for a home walk in one\nat a time as you scroll, stop to say hello and trot off as the next one arrives. The demo is a\nfictional rescue, Second Chance Dog Rescue.\n\nWag comes in two variants, chosen when you create the site: files/Sanity (static pages, any host)\nand [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages are\nthe same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Dogs, adopt (steps, fee, questions), help (ways, donations, wishlist), happy tails, visit (opening hours), 404 | Pages → the page's own entry |\n| Dogs (age, size, breed, good with, traits, story, photo, status, sponsorship) | Content → Dogs |\n| Happy tails | Content → Happy tails |\n| Name, charity details, address, navigation, header button, profiles | Pages → Site settings |\n| Buttons, labels, filters, chat, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` get a marker stroke underneath that draws itself as they\nscroll into view, as in \"Every dog deserves a \\*second chance\\*\".\n\n## Dogs and their photos\n\nEach dog is an entry with its age (as you'd say it, \"about 3 years\") and age group, size, breed,\nwhen they arrived, whether they get on with children, cats and other dogs (yes, no or ask us), a\nfew traits, a short summary, the line in their speech bubble and their story. The status is\nlooking for a home, reserved or adopted; only dogs looking for a home walk in on the home page,\nand adopted ones stay on the dogs page, marked, until you remove them. The dogs page filters by\nsize, age and who they get on with, with no JavaScript.\n\nThe photo is a **cut-out**: a PNG with a transparent background, the whole dog in frame, facing\nright. Remove the background with any tool (most phones' photo apps do it) and export a PNG; it\nis served as AVIF or WebP with its transparency. The peeking dogs on the dogs and adopt pages are\ncut-outs of a head and front paws.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Donate once, sponsor a dog monthly | Help page (one-time Price ids), each dog's page (a monthly Price id) | Stripe |\n| Book a meet-and-greet | Adopt page, loads on click; a preview of the calendar until connected | Cal.com |\n| Newsletter, volunteer sign-up | Home, happy tails and help pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers about the dogs, adopting, helping and visiting; never promises a dog | Claude |\n| Search | ⌘K or the search button: dogs, happy tails, adopt, help, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: Meet them one by one (the dogs walking\nin), Our dogs (cards whose dogs hop out as you point at them), How to adopt (steps along a trail of\npaw prints), Ways to help (tags that swing in on their strings), In numbers, Happy tails\n(polaroids), Newsletter sign-up and Closing call. With reduced motion, the dogs stand in a row\nwith their bubbles and nothing moves.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (collar red: buttons, paw prints and the marker underline), the **second** (tennis-ball\nyellow) and the **third** (sky blue), used for fills and the blobs under the dogs. Buttons are\ndarkened until their white text is readable, the second and third are lightened until ink text on\nthem is; a color that can't get there stops the build with a message. Brand also sets the fonts\n(text, headings, and the marker hand for bubbles, tags and captions) and the corners. The site\nopens in the day theme (kraft paper) even when the visitor's system is dark; the toggle switches\nto the night theme, a warm evening brown, and the browser remembers the choice.\n\n## Languages\n\nWag is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Which bowl?: a treat goes under one of three dog bowls, the bowls shuffle, faster\nevery round, and you pick the bowl with the treat (tap it, or press 1, 2 or 3). Five rounds; the\nbest score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, dogs and happy tails for AI assistants, in every language.\n- Pages carry JSON-LD: the rescue as an `AnimalShelter`, the dogs as an `ItemList`, adopting as a\n  `HowTo`, questions as a `FAQPage`, and each happy tail as a `BlogPosting`.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."},{"path":"/templates/whisker/","url":"https://docs.wptom.com/templates/whisker/","title":"Whisker","description":"What's in the Whisker template, how its pages and animations work, and where to change things.","markdown":"Whisker is a site for a cat shelter or rescue: shelters, foster networks, cat cafés with adoptions,\nTNR groups, municipal shelters and sanctuaries. It is set edge to edge in a sunlit room: a wall with\na soft wallpaper and sunbeams that sweep across it as you scroll, cardboard boxes, shipping labels and\npacking tape. On the home page a row of boxes stands on the floor; the first is open with a cat\npeeking out, and as you scroll the row slides along and each box opens in turn. The demo is a\nfictional rescue, Nine Lives Cat Rescue.\n\nWhisker comes in two variants, chosen when you create the site: files/Sanity (static pages, any\nhost) and [EmDash](/content/emdash/) (`--cms emdash`, rendered on request on Cloudflare). The pages\nare the same in both.\n\n## Pages and their content\n\n| Page | Edit it in |\n|---|---|\n| Home | Pages → Home page (a list of sections) |\n| Cats, adopt (steps, fee, questions), help (ways, donations, wishlist), happy tails, visit (opening hours), 404 | Pages → the page's own entry |\n| Cats (age, coat, home, gets on with, traits, care line, story, photo, status, sponsorship) | Content → Cats |\n| Happy tails | Content → Happy tails |\n| Name, charity details, address, navigation, header button, profiles, packing tape words | Pages → Site settings |\n| Buttons, labels, filters, chat, emails, game texts | Content → Interface texts |\n| Privacy, terms | Pages → Legal |\n\nIn headlines, words wrapped in `*stars*` get a strip of packing tape stuck underneath as they scroll\ninto view, as in \"Every box has \\*a cat in it\\*\".\n\n## Cats and their photos\n\nEach cat is an entry with its age (as you'd say it, \"about 13\") and age group, coat, the home it\nneeds (indoor only, a garden, or either), when it arrived, whether it gets on with children, other\ncats and dogs (yes, no or ask us), a few traits, a short summary, a \"handle with care\" line for its\nshipping label and its story. The status is looking for a home, reserved or adopted; only cats\nlooking for a home are in the boxes on the home page, and adopted ones stay on the cats page,\nmarked, until you remove them. The cats page filters by age, the home you can offer and who they get\non with, with no JavaScript.\n\nThe photo is a **cut-out** of the cat peeking over an edge: a PNG with a transparent background,\nhead and front paws in frame, the bottom cut straight just below the paws (4:3 works best). Remove\nthe background with any tool (most phones' photo apps do it) and export a PNG; it is served as AVIF\nor WebP with its transparency. The box draws the rim, so the paws rest on it.\n\n## Integrations\n\nEverything works without keys; each feature switches on with its variables in `.env.example`.\n\n| Feature | Where | Service |\n|---|---|---|\n| Donate once, sponsor a cat monthly | Help page (one-time Price ids), each cat's page (a monthly Price id) | Stripe |\n| Book a visit to meet a cat | Adopt page, loads on click; a preview of the calendar until connected | Cal.com |\n| Newsletter, fosters and volunteers | Home, happy tails and help pages, with a welcome email | Supabase |\n| Contact form | Visit page; messages go to the email in Site settings | Resend |\n| AI chat | On every page, answers about the cats, adopting, helping and visiting; never promises a cat | Claude |\n| Search | ⌘K or the search button: cats, happy tails, adopt, help, visit | built in, or Algolia |\n| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics |\n\n## Sections\n\nThe home page is built from these sections, in any order: Who's in the box? (the row of boxes),\nCats looking for a home (their boxes on shelves, each opening as it scrolls into view), How adoption\nworks (the steps tracked like a parcel, a little box travelling the route), Ways to help (stickers\npressed onto the side of a big box), Numbers (rubber stamps), Happy tails (photos taped to the wall),\nNewsletter sign-up and Closing call. Bands of packing tape run across the page between some of them,\nprinted with the words in Site settings → Packing tape. With reduced motion, the boxes stand open\nside by side and nothing moves.\n\n## Colors and brand\n\n`src/content/singletons/brand.json` (Pages → Brand in Keystatic) holds three colors: the\n**accent** (ginger: buttons and marks), the **second** (plum) and the **third** (sage), used for\nfills. Cardboard, packing tape and the inside of the boxes are materials, the same whatever your\ncolors. Buttons are darkened until their white text is readable, the second and third are lightened\nuntil ink text on them is; a color that can't get there stops the build with a message. Brand also\nsets the fonts (text, headings, and the typewriter face for labels and tape) and the corners. The\nsite opens in the day theme even when the visitor's system is dark; the toggle switches to the night\nroom, a dim blue lit by a lamp, and the browser remembers the choice.\n\n## Languages\n\nWhisker is ready for more languages: see [Languages](/content/languages/).\n\n## The 404 page\n\nA small game, Tiptoe: get across the room to the treat jar without waking the cat asleep on the rug.\nTake a step with the button, a tap or the space bar, at tiptoeing pace; when the cat's tail twitches\nan eye is about to open, and a step while it's open wakes her. Five rooms, the cat sleeping lighter\nin each; the best score is kept in the visitor's browser.\n\n## AI assistants and search engines\n\n- `AGENTS.md` describes the project for coding assistants (Claude Code reads it through\n  `CLAUDE.md`, Cursor through `.cursor/rules/`).\n- `/llms.txt` lists your pages, cats and happy tails for AI assistants, in every language.\n- Pages carry JSON-LD: the rescue as an `AnimalShelter`, the cats as an `ItemList`, adopting as a\n  `HowTo`, questions as a `FAQPage`, and each happy tail as a `BlogPosting`.\n\n## Commands\n\n```sh\npnpm dev                     # local site, editor at /keystatic\npnpm build                   # production build\npnpm cms sanity schema|seed  # move content to Sanity (see Content → Sanity)\n```\n\nIn the EmDash variant: `npm run dev`, `npm run deploy` (Cloudflare) and `npm run emdash:schema`\n(after changing the content model)."}]}