The website served at realunit.app. Public, static.
A minimal landing page — one hero image from the RealUnit app plus the store buttons —
plus invite/promo landings and the Aktionariat confirm / account-merge flows.
Deliberately without a build toolchain: plain HTML + assets in public/,
uploaded to Cloudflare Pages.
public/index.html— landing, showsassets/hero.jpgcentered on a light background with the store/download buttons below itpublic/invite/andpublic/promo/— referral and campaign landings; look upGET /v1/realunit/referral/code/:code(contract: JonnyLuca/dfx-referral-api), openrealunit-wallet://invite|promo/{code}, and pass the code as a Play install referrer. Path and query codes (including ads/Playutm_content/referrerand Facebook/Google/Outlooku=/q=/url=/ emaillink=wrappinginvite=or a landing URL, with or withouthttps://; a campaign name inutm_contentdoes not hide a later wrapper key; an empty or foreigncode=does not hide a laterinvite=/promo=; shortios-app://alternate links; Proofpoint URL Defense and Outlook Safe Links wrapping a RealUnit landing; a foreignhttps://URL is not a code) are uppercased, stripped of messenger zero-width/fullwidth characters and trailing sentence punct (!,?,/, …), and capped at 32 like the API lookup — including in the crawler HTML bytes Safari snapshots. The re-tap hint is iOS-only; Android keeps the code via the Play referrer. An App Store / Play / CTA tap also copies the code (user gesture) so an iOS badge install can still be pasted at registration. iOSformat-detectionistelephone=no, date=noso Safari does not turn the campaign code or Aktionstext date into a link.#ok-codeand#ok-bodyalso setx-apple-data-detectors="false"because those strings are written after load.www.realunit.app/invite|promois HTTP 200 (not a 301 to the apex) so Universal Links and the Smart App Banner keep the host. The Smart App Bannerapp-argument,og:url,rel=canonical,twitter:url,og:title,twitter:title,og:description,twitter:description,og:image:alt,twitter:image:alt,og:locale,og:site_name, Play install referrer, android-app / ios-app alternate links, Facebook App Links (al:ios:url/al:android:urlarerealunit-wallet://…;al:android:classisswiss.realunit.app.MainActivity;al:web:urlis the HTTPS landing), and Twitter App Cardtwitter:app:url:*(same custom scheme;twitter:app:countryis CH) are injected into the HTML bytes from the request URL (functions/_middleware.json Cloudflare Pages, and the local dev-server) so Safari, Play, WhatsApp, X, and share crawlers can snapshot them before JS. The Function also serves the landing for a code-bearing path:_routes.jsonclaims/invite,/invite/*,/promoand/promo/*, so the_redirects200-rewrites never run and, on a code-bearing path,context.next()answers with the site's 404 page. The platform is asked first and only its404is replaced — the shell is read from the asset binding by name, checked against the two landing marks, injected and answered200./invite/and/promo/are real files, rewritten in place for aGET; aHEADreads the shell from the binding like the404case, because its answer carries no body to recognise the shell in./inviteand/invite/index.htmlkeep their308.og:title,og:description, and image alt name the campaign code;?lang=ensets English copy andog:locale=en_GB; invitee names wait for lookup JS./js/invite-banner.jsin<head>is the CSP-safe JS fallback — Cloudflare Pages CSP blocks inline<script>.public/.well-known/apple-app-site-associationandassetlinks.json— Universal Links / App Links for/invite/*and/promo/*on apex and www (HTTP 200, no 301).sha256_cert_fingerprintslists the v2 signing cert of GitHub release APKrealunit-1.2.17.apk(O=DFX AG,CN=Konstantin Ullrich). If Play App Signing uses a different app-signing key, add that SHA-256 from Play Console beside them.public/platform.js— enlarges the store button matching the visitor's platform (iOS → App Store, Android → Play Store); without JS all buttons stay equal-sizepublic/assets/hero.jpg— hero (source: the app's splash background)public/assets/og.png— social sharing image (source: the app's Android feature graphic)public/assets/favicon.svg— app iconpublic/_headers— security headers + cache-control for Cloudflare Pages
Same develop → main flow as the other DFX Cloudflare-Pages sites (e.g. landing-page):
- Feature branches → PR into
develop(default branch) - Push to
develop→.github/workflows/dev.yamldeployspublic/to the DEV Pages projectrealunit-web-dev→ dev.realunit.app .github/workflows/auto-release-pr.yamlopens/updates adevelop → mainrelease PR- Merge to
main→.github/workflows/prd.yamldeployspublic/to the PRD Pages projectrealunit-web→ realunit.app
Both deploys are Direct Upload via wrangler pages deploy (no build step).
Required repo secrets (both environments share them):
CLOUDFLARE_API_TOKEN— scoped to Account → Cloudflare Pages: EditCLOUDFLARE_ACCOUNT_ID
The custom domains realunit.app / dev.realunit.app are attached to the Pages
projects in the DNS/deployment configuration. The handbook. subdomain is unaffected.
- Legal pages — rendered from the app's
assets/legal/*.md(build-time fetch, single source)
From v2 a build toolchain (Astro) is introduced; the plain-image landing stays the home page.
The site still ships verbatim — the tooling is dev-only. Pure browser logic lives
in public/js/lib/** and is unit-tested to 100% (Vitest + jsdom), as is
functions/_middleware.js, which decides which page a landing path is answered
with; functions/lib/** is measured and ratcheted at the level it reaches
today. The pages,
platform detection and the full confirm flow are covered by Playwright
(functional + screenshot regression). See CONTRIBUTING
for the gate list and commands (npm run check, npm run test:e2e,
npm run e2e:docker).