# W1: Website **Status:** phases 1 to 3 (home, Install and Downloads; the user guide; how-tos and the FAQ) and the devlog are live at roro9stack.net; phase 4 (the developer docs) is in a pull request. Issue #12. **Goal:** a public home for the project at **roro9stack.net**, separate from the blog (stories) and from Gitea (developers): what it is, how to install it, how to use each App, and the docs. The home page was designed on a canvas in a Claude chat (a dark and a light theme, built on the device's own 256-colour palette, pixel-notched corners, DM Mono and Hanken Grotesk). It is the starting point, not the final copy: it has to say only what the firmware does today. ## What was found while planning (2026-10-06) - `roro9stack.net` already points at the server that hosts Gitea. Plain HTTP redirects to HTTPS; HTTPS has no certificate yet, which is the server's side to set up. - **Release downloads from Gitea carry no CORS header,** so a browser can't fetch the factory image from another origin as things are. Gitea is behind Caddy, which can add the header (below). - Zola can read JSON from a URL at build time (`load_data`), so the home page's "latest version" can come from the Gitea API. - There is no Gitea wiki: the design's "Wiki" links would 404. - The blog is published by pulling its repository on the web server and running `zola build`. The site does the same. ## Decisions (design round 2026-10-06) | # | Decision | |---|---| | Q175 | The site lives in this repository, in `site/`, so the documentation is built from `docs/`, `CONTEXT.md` and the README instead of being copied. | | Q176 | **Zola,** like the blog. The design becomes a template, its tokens CSS custom properties. Dark and light follow the visitor's setting, with a visible switch. No JavaScript except the flasher's. | | Q177 | Domain: **roro9stack.net.** The blog stays at experiments.twis.la. | | Q178 | **Publishing is the blog's way:** the web server pulls `main` and runs `zola build`; that part is the maintainer's. Changes reach `main` through pull requests as everywhere. **CI is split:** a dedicated `site` job builds the site (`zola build`) when `site/`, `docs/`, `README.md` or `CONTEXT.md` change, and the firmware tests and builds skip a change that touches nothing else. A change that touches both runs both. | | Q179 | Phases, each its own pull request: **1.** the CI split, the home page, an Install page with the browser flasher, downloads and the changelog. **2.** a user guide page per App. **3.** how-tos and the FAQ. **4.** developer docs generated from the repository. | | Q180 | **A browser flasher** (ESP Web Tools), **without copying the firmware.** Caddy, in front of Gitea, adds `Access-Control-Allow-Origin: https://roro9stack.net` (and `Vary: Origin`) to GET and HEAD on `/twisla/roro9stack/releases/download/*` and `/api/v1/repos/twisla/roro9stack/releases*`: both are public already. The Install page asks the API for the latest release in the browser, finds the asset ending `-factory.bin`, and gives ESP Web Tools a manifest built on the spot, so it offers a new release as soon as it exists, with no rebuild. The library is **vendored** into `site/static/` (Apache-2.0), not loaded from a CDN. The file's SHA-256 is shown on the page. Chrome or Edge on a desktop only; other browsers, and visitors without JavaScript, get the `esptool` steps on the same page. | | Q181 | Docs for the latest version only. The changelog is the Gitea releases, read at build time. | | Q182 | English only. | | Q183 | The FAQ starts from real questions: the README, and issues labelled `kind/docs`. | | Q184 | Fonts are **self-hosted** (no request to a third party). The hero keeps the design's illustrations, labelled as illustrations, and a section of **real device screenshots** is added. | | Q185 | **The site says only what the firmware does today.** Planned features are marked as planned, with their milestone. The mesh messenger is **planned**: the LoRa Scanner listens, nothing is sent. | | Q186 | Left out, each with its issue: a Gemini capsule mirror (#57), French (#58), docs per version (#59), search (#60). | | Q187 | The site has no version of its own. Contact is **contact@roro9stack.net,** and the issue tracker. | ## The design, reviewed Kept as designed: the layout, the tokens, the nine App cards (their facts check out against the code: Probation 3 minutes, Safe Mode after 3 crashes, 60 seconds of typing before an update restarts the device). Changed before it ships: - **Install, not Download, is the first action.** Downloads are for developers; a visitor wants to try it. - **A "what you need" strip:** Cardputer ADV, the Cap LoRa-1262 (only the radio needs it), a microSD card, Wi-Fi. And a plain status line: the version, and what isn't there yet. - **The mesh card and the hero** no longer promise sending and reading mesh messages. - **The latest version is read from the API,** not typed. - **"Wiki" is replaced by Docs.** The updates section gains what v0.11.0 added: the device installs releases from the project's server itself. - **An independence line:** not affiliated with or endorsed by M5Stack or Meshtastic. - **No cookies, no analytics, no third-party requests,** said on the page (fonts self-hosted). - **The keyboard focus ring** is invisible on the notched buttons: `clip-path` clips an outline. Another way to show focus is needed. - **The wordmark SVGs** carry an embedded C2PA content-credentials block: stripped from the site's copies. ## Done when (phase 1) - Pushing a change under `site/` runs the `site` job and not the firmware tests; a firmware change runs the firmware jobs and not the site's. - The home page renders in both themes, at phone width, with the keyboard, and says nothing the firmware doesn't do. - The latest version on the page is the latest release. - The browser flasher installs the latest release on a Cardputer ADV from Chrome (tried by hand), the page shows the file's SHA-256, and the `esptool` steps are on the same page. - A release published after the site was built is the one the Install page offers. - The home and Downloads pages make no request to another origin, and the Install page only asks git.twis.la. ## Work breakdown 1. **CI split:** a `site` workflow, path filters on the firmware workflow. 2. **The skeleton:** `site/` with the tokens, fonts, base template and the theme switch. 3. **The home page,** from the design, with the changes above. 4. **Install and downloads:** the flasher with its manifest built in the page, the `esptool` steps, the changelog. Needs the Caddy headers on the Gitea host (the maintainer's side); the page is tested against them once they're in. 5. **Checks,** recorded here. ## As built (phase 1) - **CI is split.** `ci.yml` (the firmware) has `paths-ignore: site/**, docs/**, README.md, CONTEXT.md` on pushes to `main` and on pull requests. `site.yml` runs `zola check` and `zola build` with a Zola pinned by its checksum, then `site/tools/check_site.py`, when those files change. Gitea's own source (v1.24, read, not run against the 1.27 server) shows that path filters count as matched for tag pushes, so a tag still releases. A change that touches both runs both. - **The build output** goes to `public/` at the root of the repository, not into `site/`: `output_dir = "../public"` in `site/config.toml`, so `zola build` in `site/` and `zola --root site build` from the root agree, and git ignores `/public/`. - **The site** is in `site/`: `config.toml`, templates (base, home, install, downloads, 404), `data/` for the App cards and the screenshots' captions, `static/` (stylesheet, theme switch, fonts, wordmark, icons, real screenshots, the vendored flasher). `site/README.md` says how to build it and what the server needs. - **The home page** follows the design. Changed from it: the hero and the mesh card promise nothing that isn't built (the mesh messenger is a "planned" card), an Install button first, a status box, a "what you need" row, a section of real screenshots, the updates section says the device installs releases itself, an independence line and a statement about cookies and third-party requests in the footer, the latest version read from the Gitea API at build time, and the nav's Wiki replaced. The two hero drawings are generated by `site/tools/make_illustrations.py` (a port of the design's scripted shapes) as inline SVG. - **The focus ring.** `clip-path` clips outlines, so a focused notched control drops its notches and shows square corners and its ring. - **The Install page** asks the API for the latest release in the browser, builds the ESP Web Tools manifest as a blob, shows the version, size and SHA-256, and only ever hands the flasher a download from the project's own server for this repository. The flasher library (ESP Web Tools 10.4.0, Apache-2.0) is vendored, trimmed to the ESP32-S3. Fonts (DM Mono, Hanken Grotesk, SIL OFL) are self-hosted. - **The Downloads page** lists the last 30 releases with their files, read at build time. - **The wordmark SVGs** from the design carried an embedded C2PA content-credentials block; it is removed from the site's copies. ## As built (phase 2, the user guide) - **`/guide/`** is a section of 11 pages, `site/content/guide/`, each with `template` from the section's `page_template` and an order from `weight`: the basics (keys, Launcher, Status Bar, first start, the card), then one page per App (LoRa Scanner, GNSS, Gemini, IRC, Wi-Fi tools, Notes, Storage, System), Settings and Updates. Pages with real screenshots list them in `extra.screens`, looked up in `data/screens.toml`. - **Facts come from the README, the milestone documents and the Apps' own source** (key handlers, labels, the Status Bar's drawing code), not from memory. Some wording was corrected against the source while writing: the Track folder is `/gnss/tracks`, the reasons a Track won't start, what the Status Bar shows. - **Not covered:** the mesh messenger (planned), the debug console and Debug Builds beyond a pointer to the README. How-tos and the FAQ are phase 3. ## As built (the devlog) Not one of the planned phases: the blog's seven roro9stack posts, imported into `site/content/devlog/` and shown in the site's own style, with the **Blog** link in the navigation and the footer replaced by **Devlog**. The posts' text, tone and structure are unchanged; what changed: - **Links:** the posts' links to each other point to `/devlog//`, and one link to an unpublished work-in-progress post became plain text. Each post keeps its old directory name, so the old URL `//` maps to `/devlog//`. - **The posts' parts** (the sign, the cast, the steps, asides, folded sections, diagrams, captions) are shortcodes in `site/templates/shortcodes/`, restyled in `static/css/devlog.css`: the site's palette, notched boxes, DM Mono and Hanken Grotesk. The two older posts about other subjects (a vinyl remote, a ZFS rescue) stay on the blog. - **The 17 diagrams are inline SVG**, and carried `