Language inference: - Walk navigator.languages (the full ordered preference list), not just navigator.language, and pick the first base language we support. A user who prefers e.g. Dutch then German now gets German instead of the English fallback. De-duplicates the default-language resolution into setDefaultText. Paper size (US Letter + A4): - Add A4 (210x297mm) alongside US Letter (8.5x11in). Sheet dimensions move to CSS custom properties (--sheet-w/--sheet-h) toggled by a paper-letter/paper-a4 class on <html>; the tear-off column and QR codes stay fixed physical sizes and the content column flexes. - @page size is injected by JS (id voxvera-page-size) since @page cannot read CSS variables across engines; the print block keeps margin:0 and a Letter default for the pre-JS state. - Default paper is chosen from the browser locale's region subtag (en-US -> Letter, en-GB/de-DE -> A4) — "by location" without geolocation, falling back to Letter when no region is present. A manual override selector (topbar + viewer controls) persists in localStorage and wins over the default. - Paper size is a local print/view preference and is never written into the Nostr event, so a flyer reprints on either size unchanged. - Add paper_size/paper_letter/paper_a4 labels for all 14 languages. Docs + tests: - Spec gains a "Print paper size" section; AGENTS.md records the not-in-payload invariant; README adds a feature bullet; the field-limits cheat sheet gains Letter/A4 columns (re-measured per paper size). - Playwright: add tests for navigator.languages inference, region-based paper default (A4 and Letter), and override persistence. 54 tests pass across Chromium, Firefox, WebKit, and Android/iOS mobile emulations. Co-Authored-By: Claude Opus 4.8 <[email protected]>
VoxVera
VoxVera is a static, Nostr-powered flyer client.
The active app lives at the site root (site/). It lets anyone author flyer content, publish it as a public Nostr event, and re-render or reprint the flyer later from a Nostr address or event ID — no application server. The frontend is intentionally self-contained: Nostr relays store the content, and any static host can serve the client.
The previous Tor/OnionShare CLI version is preserved on the legacy-tor branch.
Features
- Author and publish. Fill in the flyer fields and publish them as a Nostr
30078parameterized replaceable event. Republishing with the same flyer name replaces the previous version. - Anonymous by default. The client generates an anonymous browser-side key to sign events. A NIP-07 browser extension is detected and used if present, but is never required.
- Open from a link or ID. Load a flyer from an
naddr,nevent1,note1, raw 64-char event ID, ornostr:URL. The lookup field tolerates copy/paste whitespace and newlines, so an event ID copied off a printed tear-off (where it wraps across two lines) still resolves. - Short, scannable poster URLs. Each poster URL is a standard NIP-19
naddrcarrying a single relay hint, placed in the URL#fragment(no?addr=query). Shorter URLs make the tear-off QR code easier to scan. Older relay-listnaddrs and?addr=URLs still resolve. - Tear-off tabs. The printable flyer has ten tear-off tabs. Each tab is a single hyperlinked call to action (the URL is the link, not printed as text) and prints the full event ID so anyone can re-find the flyer by typing it.
- Loading state on shared links. Opening a poster/event link shows a localized "Loading content…" placeholder (and a "Could not load this flyer." state on failure) instead of the default flyer; the default sample content appears only on the bare site URL.
- 14 languages with RTL support. Flyer content and UI are localized; right-to-left scripts render correctly. Letter-spacing is applied per script so cursive (Arabic/Persian) and combining (Devanagari) scripts are not broken — see the spec.
- US Letter and A4. Print size defaults to the right paper for the visitor's country (from the browser locale's region —
en-US→ Letter,en-GB→ A4, no geolocation), with a manual override. Paper size is a local print preference and is never written into the event. See the spec. - Fits-the-page editing. Fields that would overflow the printable sheet are rejected as you type, with an inline error under the affected field. See the field length cheat sheet for how much text fits each field per language and paper size.
- QR codes. The tear-off QR points at the poster URL (re-open and reprint); the main flyer QR points at the creator's content URL.
- Self-contained. No build step and no CDNs — Nostr and QR libraries are vendored under
site/vendor/.
What Is In main
site/index.html,site/nostr-client.js,site/nostr-client.css,site/locales.js: static editor/viewer clientsite/vendor/: vendored browser dependencies for Nostr and QR generationsite/nostr/index.html: redirect stub so legacy/nostr/poster URLs and QR codes still resolvesite/CNAME: custom-domain targetvoxvera/nostr/: small Python schema/validation helpersdocs/nostr-static-client-spec.md: design notes and development plandocs/flyer-field-limits.md: per-language field length cheat sheet
Local Development
Run the static site from the repo root:
python3 -m http.server 8768 --directory site
Then open:
http://127.0.0.1:8768/
No build command is required for the frontend.
Vercel
Point Vercel at this repository on main.
- Framework preset: Other
- Root directory: repo root
- Build command: leave empty
- Output directory:
site
The client is served from the domain root (site/index.html). site/nostr/index.html is a redirect stub that forwards the legacy /nostr/ path to the root while preserving the query/fragment, so older poster URLs and printed QR codes still resolve.
Verification
node --check site/nostr-client.js # client syntax
pytest -q # Nostr source schema
Cross-browser end-to-end tests
Playwright smoke tests run the client on Chromium, Firefox, and WebKit (Safari's engine), plus Android/iOS mobile emulations — covering rendering, the @media print path, WebCrypto key generation, the /nostr/ redirect, and the loading state.
npm install
npx playwright install # download the browser engines
npm run test:e2e
See e2e/README.md for per-engine coverage and the one-time Linux step for WebKit (sudo npx playwright install-deps).
The Python tests only cover the Nostr source schema. The frontend is static; the Playwright suite covers engine behavior, but real Safari/iOS print and clipboard behavior is still worth a manual pass before a release.
License
MIT (c) 2026 thePR0M3TH3AN