Einladung wird geladen…
+Einen Moment bitte.
+Dein Code
+ + + + ++ +
+ + +Du bist eingeladen
+Die App übernimmt den Code automatisch.
+Link ungültig oder abgelaufen
+Dieser Einladungs- oder Promo-Link ist ungültig oder bereits abgelaufen.
+From b76f5bdc668b8659b168c7f1e1e0d0ca660d2277 Mon Sep 17 00:00:00 2001 From: Jonny Luca <320529100+JonnyLuca@users.noreply.github.com> Date: Sat, 29 Aug 2026 01:33:13 +0000 Subject: [PATCH 01/22] Add invite and promo landing pages. --- .github/workflows/dev.yaml | 2 + .github/workflows/prd.yaml | 2 + CONTRIBUTING.md | 24 + README.md | 52 +- functions/_middleware.js | 32 + functions/lib/itunes-banner.js | 618 ++++++ playwright.config.mjs | 3 + public/.well-known/apple-app-site-association | 18 + public/.well-known/assetlinks.json | 13 + public/_headers | 46 + public/_redirects | 18 + public/_routes.json | 5 + public/apple-app-site-association | 18 + public/invite/index.html | 202 ++ public/invite/invite.js | 770 ++++++++ public/js/invite-banner.js | 15 + public/js/lib/invite-core.js | 1426 ++++++++++++++ public/promo/index.html | 202 ++ scripts/check-site.mjs | 564 ++++++ scripts/dev-server.mjs | 49 +- scripts/e2e-docker.sh | 2 +- test/invite-core.test.mjs | 1708 +++++++++++++++++ test/itunes-banner-function.test.mjs | 497 +++++ .../desktop-chromium/invite-invalid.png | Bin 0 -> 31130 bytes .../desktop-chromium/invite-ok-en.png | Bin 0 -> 46681 bytes .../desktop-chromium/invite-ok.png | Bin 0 -> 50616 bytes .../desktop-chromium/invite-unavailable.png | Bin 0 -> 66755 bytes .../desktop-chromium/promo-invalid.png | Bin 0 -> 31130 bytes .../desktop-chromium/promo-ok-en.png | Bin 0 -> 76332 bytes .../desktop-chromium/promo-ok.png | Bin 0 -> 87708 bytes .../desktop-chromium/promo-unavailable.png | Bin 0 -> 63355 bytes .../mobile-safari/invite-invalid.png | Bin 0 -> 27046 bytes .../mobile-safari/invite-ok-android.png | Bin 0 -> 37394 bytes .../mobile-safari/invite-ok-ios.png | Bin 0 -> 50073 bytes .../mobile-safari/invite-unavailable.png | Bin 0 -> 58958 bytes .../mobile-safari/promo-invalid.png | Bin 0 -> 27046 bytes .../mobile-safari/promo-ok-android.png | Bin 0 -> 63960 bytes .../mobile-safari/promo-ok-ios.png | Bin 0 -> 72036 bytes .../mobile-safari/promo-unavailable.png | Bin 0 -> 57080 bytes .../tablet-chromium/invite-invalid.png | Bin 0 -> 32009 bytes .../tablet-chromium/invite-ok.png | Bin 0 -> 51026 bytes .../tablet-chromium/invite-unavailable.png | Bin 0 -> 67199 bytes .../tablet-chromium/promo-invalid.png | Bin 0 -> 32009 bytes .../tablet-chromium/promo-ok.png | Bin 0 -> 87618 bytes .../tablet-chromium/promo-unavailable.png | Bin 0 -> 63843 bytes tests/behavior.spec.mjs | 1335 +++++++++++++ tests/pages.mjs | 94 +- tests/visual.spec.mjs | 45 +- 48 files changed, 7736 insertions(+), 24 deletions(-) create mode 100644 functions/_middleware.js create mode 100644 functions/lib/itunes-banner.js create mode 100644 public/.well-known/apple-app-site-association create mode 100644 public/.well-known/assetlinks.json create mode 100644 public/_redirects create mode 100644 public/_routes.json create mode 100644 public/apple-app-site-association create mode 100644 public/invite/index.html create mode 100644 public/invite/invite.js create mode 100644 public/js/invite-banner.js create mode 100644 public/js/lib/invite-core.js create mode 100644 public/promo/index.html create mode 100644 test/invite-core.test.mjs create mode 100644 test/itunes-banner-function.test.mjs create mode 100644 tests/__screenshots__/desktop-chromium/invite-invalid.png create mode 100644 tests/__screenshots__/desktop-chromium/invite-ok-en.png create mode 100644 tests/__screenshots__/desktop-chromium/invite-ok.png create mode 100644 tests/__screenshots__/desktop-chromium/invite-unavailable.png create mode 100644 tests/__screenshots__/desktop-chromium/promo-invalid.png create mode 100644 tests/__screenshots__/desktop-chromium/promo-ok-en.png create mode 100644 tests/__screenshots__/desktop-chromium/promo-ok.png create mode 100644 tests/__screenshots__/desktop-chromium/promo-unavailable.png create mode 100644 tests/__screenshots__/mobile-safari/invite-invalid.png create mode 100644 tests/__screenshots__/mobile-safari/invite-ok-android.png create mode 100644 tests/__screenshots__/mobile-safari/invite-ok-ios.png create mode 100644 tests/__screenshots__/mobile-safari/invite-unavailable.png create mode 100644 tests/__screenshots__/mobile-safari/promo-invalid.png create mode 100644 tests/__screenshots__/mobile-safari/promo-ok-android.png create mode 100644 tests/__screenshots__/mobile-safari/promo-ok-ios.png create mode 100644 tests/__screenshots__/mobile-safari/promo-unavailable.png create mode 100644 tests/__screenshots__/tablet-chromium/invite-invalid.png create mode 100644 tests/__screenshots__/tablet-chromium/invite-ok.png create mode 100644 tests/__screenshots__/tablet-chromium/invite-unavailable.png create mode 100644 tests/__screenshots__/tablet-chromium/promo-invalid.png create mode 100644 tests/__screenshots__/tablet-chromium/promo-ok.png create mode 100644 tests/__screenshots__/tablet-chromium/promo-unavailable.png diff --git a/.github/workflows/dev.yaml b/.github/workflows/dev.yaml index 50fa6eb..b6baa6f 100644 --- a/.github/workflows/dev.yaml +++ b/.github/workflows/dev.yaml @@ -36,6 +36,8 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} COMMIT_MSG: ${{ github.event.head_commit.message }} + # wrangler looks for ./functions in the cwd (repo root), sibling to + # public/. That middleware injects Smart App Banner app-argument. run: | wrangler pages deploy public \ --project-name=realunit-web-dev \ diff --git a/.github/workflows/prd.yaml b/.github/workflows/prd.yaml index bf9cdae..f2706bb 100644 --- a/.github/workflows/prd.yaml +++ b/.github/workflows/prd.yaml @@ -36,6 +36,8 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} COMMIT_MSG: ${{ github.event.head_commit.message }} + # wrangler looks for ./functions in the cwd (repo root), sibling to + # public/. That middleware injects Smart App Banner app-argument. run: | wrangler pages deploy public \ --project-name=realunit-web \ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a94f6ac..ef09961 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,30 @@ This repo is the **realunit.app** website — public, static. See the served. The dev dependencies exist **only** for the quality gates below (formatting, HTML validation, unit tests, screenshots); nothing compiles or bundles the site. +- **Invite/promo HTML rewrite is banner, canonical, and store handoff.** Safari, + Play, and share crawlers snapshot `apple-itunes-app`, `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`, html `lang`, the Play + referrer, android-app / ios-app alternate links, Facebook App Links + (`al:ios:url` / `al:android:url` are `realunit-wallet://…`; `al:android:class` + is `swiss.realunit.app.MainActivity`; `al:web:url` + is the HTTPS landing), and Twitter App Card `twitter:app:url:iphone` / + `twitter:app:url:ipad` / `twitter:app:url:googleplay` (same custom scheme) + from the HTML bytes before + `/js/invite-banner.js` / `invite.js` run, so `functions/_middleware.js` + (repo-root `functions/`, picked up by `wrangler pages deploy public`) and + `scripts/dev-server.mjs` inject those from the request URL (www folded onto + the apex). The campaign code is in `og:title` / `twitter:title` / + `og:description` / `og:image:alt` / `twitter:image:alt`; `?lang=en` sets English title/description/alt and `og:locale`; + invitee names wait for lookup JS. The committed + `public/invite` and `public/promo` HTML stay generic (`app-id` only, og:url / + twitter:url `/invite/` or `/promo/`, `og:site_name` RealUnit, generic titles and descriptions, Play + href without referrer, no `al:*`, no `twitter:app:*`, `format-detection` + `telephone=no, date=no` so iOS does not turn the code or Aktionstext date + into a link, `x-apple-data-detectors="false"` on `#ok-code` / `#ok-body` + because Safari re-scans JS-inserted text, no inline + ` + + + + + +
+Einen Moment bitte.
+Dein Code
+ + + + ++ +
+ + +Die App übernimmt den Code automatisch.
+Dieser Einladungs- oder Promo-Link ist ungültig oder bereits abgelaufen.
+