hypercal 2.1.0

A new name, a from-source build, and a server that is markedly harder to attack.

**`WEBAUTHN_ORIGIN` is now required in production.** A server started with
`NODE_ENV=production` and no `WEBAUTHN_ORIGIN` refuses to boot. Set it to the
public origin people reach the instance on, scheme included — e.g.
`https://calendar.example.com`. It is not a convenience setting: without it a
passkey assertion is checked against the caller's own `Origin` header, which is
a check that can only pass. `WEBAUTHN_RP_ID` remains optional and defaults to
that origin's hostname.

`AUTH_DEV_AUTOLOGIN=1` together with `NODE_ENV=production` is now fatal rather
than a warning — it treats every session-less request as a seeded admin, which
is a complete authentication bypass. The Playwright suites run as the new
`NODE_ENV=e2e`, which is production behaviour in every respect that matters and
tolerates the bypass the harness needs.

Nothing else needs attention. The schema migrates itself and the Android package
identifiers are unchanged, so this is an ordinary in-place update.

The name is shorter everywhere a person reads it — launcher, PWA manifest,
browser tab, F-Droid listing — with a new icon and a refreshed set of
screenshots. `dev.hypercalendar.app` is untouched, and so is the CalDAV realm
string: clients cache credentials against (host, realm), so renaming it would
make every configured device re-prompt for its app password in exchange for a
name nobody ever sees.

The SQLite engine the on-device backend runs on is now compiled from source
during the build — emscripten, in CI — instead of arriving as the prebuilt
`.wasm` inside `@sqlite.org/sqlite-wasm`. F-Droid builds every APK from source
on their own infrastructure and their scanner rejects a prebuilt binary
outright, so this is what makes a from-source build possible at all.

Doing it in our pipeline too, rather than only inside the F-Droid recipe, is
the point: the published APK and the one F-Droid builds now run the same
engine, and the standalone test suite exercises it on every tag instead of
leaving it a path only their reviewer discovers is broken.

- **Per-username login throttling.** The existing per-IP limiter cannot help
  with the case that actually matters for a password: many hosts making a few
  attempts each against one account, which stays under every per-IP ceiling
  indefinitely. Failures are now counted against the account itself, wherever
  they come from. It is a throttle and not a lockout — each failure past a
  small free allowance imposes a doubling delay, so knowing a family member's
  username is not a way to lock them out of their own calendar. Failed CalDAV
  Basic auth feeds the same counter.
- **Origin checking on state-changing `/api` requests.** `SameSite=Lax` cookies
  already stop a cross-site POST, but Lax treats sibling subdomains as
  same-site, so an untrusted neighbour under the same registrable domain was
  not covered. Requests carrying an `Origin` must now match this instance's;
  requests with no `Origin` are unaffected, because they did not come from a
  browser being tricked. `CSRF_ALLOWED_ORIGINS` adds deliberate extras.
- **Passkey sign-in no longer skips 2FA when it proves only possession.** User
  verification is requested as "preferred", so an authenticator that performs
  none still returns a valid assertion. For an account that has deliberately
  enabled TOTP, that assertion now falls through to the same second step the
  password flow uses.
- **A real Content-Security-Policy for the SPA shell.** The one inline script —
  the pre-paint theme read — is allowed by a hash computed at startup from the
  file the server is about to serve, rather than by `'unsafe-inline'` or by a
  constant that drifts the next time someone edits the shell.
- **SSRF hardening** on the outbound fetches behind ICS subscriptions and remote
  CalDAV links, with the same guard shimmed into the on-device backend.
- **Signing out wipes the device.** The offline event cache, the pending write
  queue, the conflict list and the service worker's cached `/api/events`
  responses are all cleared, so the next account on a shared device starts
  clean.
- **A leaner, pinned container.** Base images are pinned by digest, and the
  native build toolchain `better-sqlite3` needs is confined to a build stage
  instead of shipping to production. `docker-compose.yml` is now a hardened
  reference deployment — read-only root filesystem, no new privileges.

The security model, security policy, deployment and self-hosting pages have all
been reworked around the above, including what the optional per-calendar
encryption does and does not defend against.