Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ projects in the DNS/deployment configuration. The `handbook.` subdomain is unaff
## Roadmap (v2+)

- `/confirm-aktionariat` — guided Aktionariat address confirmation (calls `api.dfx.swiss`)
- `/account-merge` — confirms adding a wallet address to the existing account (calls the public DFX API)
- Legal pages — rendered from the app's `assets/legal/*.md` (build-time fetch, single source)
- Universal Links / App Links (`/.well-known/*`)

Expand Down
152 changes: 152 additions & 0 deletions public/account-merge/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>RealUnit — Adresse hinzufügen</title>
<meta name="description" content="Bestätigung, dass eine Wallet-Adresse Ihrem Konto hinzugefügt wird." />
<meta name="theme-color" content="#1988C6" />
<meta name="robots" content="noindex" />
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg" />

<!-- deliberately blocking (no defer): sets html[data-platform]="ios"|"android"
before first paint so the confirmed / already-completed states can pick
their copy (mobile vs. desktop) without a flash. Desktop/unknown — and
no-JS — get no attribute. platform-core.js (the pure detection) loads
first; platform.js is the DOM glue. External files because the CSP blocks
inline scripts; not under /assets/ because _headers caches that path as
immutable. -->
<script src="/js/lib/platform-core.js"></script>
<script src="/platform.js"></script>

<style>
:root { --bg: #f2f6fa; --blue: #1988C6; --ink: #343233; --muted: #647488; --green: #4CAC36; --red: #E02523; }
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color: var(--ink);
background: var(--bg);
background-image: radial-gradient(120% 90% at 50% 0%, #ffffff 0%, var(--bg) 60%, #e7eef5 100%);
display: flex;
align-items: center;
justify-content: center;
min-height: 100%;
padding: clamp(16px, 4vw, 48px);
}
.card {
width: 100%;
max-width: 460px;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 18px;
box-shadow: 0 10px 40px rgba(3, 76, 129, 0.08);
padding: clamp(28px, 6vw, 44px) clamp(22px, 5vw, 40px);
text-align: center;
}
.logo { display: block; height: 40px; width: auto; max-width: 100%; margin: 0 auto 24px; }
.icon { width: 64px; height: 64px; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center; border-radius: 50%; }
.icon svg { width: 34px; height: 34px; }
.icon--ok { background: rgba(76, 172, 54, 0.12); color: var(--green); }
.icon--warn { background: rgba(224, 37, 35, 0.10); color: var(--red); }
.icon--wait { background: rgba(25, 136, 198, 0.10); color: var(--blue); }
h1 { font-size: clamp(1.25rem, 4.5vw, 1.6rem); margin: 0 0 10px; line-height: 1.25; }
p { font-size: 1rem; line-height: 1.55; color: var(--muted); margin: 0 0 22px; }
.spinner { width: 34px; height: 34px; border: 3px solid rgba(25,136,198,0.25); border-top-color: var(--blue); border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.btn {
display: inline-flex; align-items: center; justify-content: center;
width: 100%; padding: 14px 20px; border: 0; border-radius: 12px;
background: var(--blue); color: #fff; font-size: 1rem; font-weight: 600;
text-decoration: none; cursor: pointer;
transition: opacity 120ms ease, transform 120ms ease;
}
.btn:hover { opacity: 0.92; }
.btn:active { transform: scale(0.99); }
.btn:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
/* Context-sensitive success copy. platform.js sets html[data-platform] to
"ios"|"android" for phones before first paint; desktop/unknown get no
attribute. On a phone the return-to-app button is shown (the app-link host
re-opens the app on the device); a desktop can't act on that link, so it
is told to continue on its phone instead. */
.only-mobile { display: none; }
html[data-platform] .only-mobile { display: block; }
html[data-platform] .only-desktop { display: none; }
[hidden] { display: none !important; }
</style>
</head>
<body>
<main class="card">
<img class="logo" src="/assets/realunit-logo.png" width="900" height="267" alt="RealUnit Schweiz AG" />

<!-- live region: only the changing state is announced, main stays a landmark -->
<div class="live" role="status" aria-live="polite">
<!-- loading -->
<div id="state-loading">
<div class="icon icon--wait"><div class="spinner"></div></div>
<h1 data-i18n="loading.title">Adresse wird hinzugefügt…</h1>
<p data-i18n="loading.body">Einen Moment, wir fügen die Wallet-Adresse Ihrem Konto hinzu.</p>
</div>

<!-- confirmed -->
<div id="state-confirmed" hidden>
<div class="icon icon--ok">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
</div>
<h1 data-i18n="confirmed.title">Adresse hinzugefügt</h1>
<!-- Desktop / unknown: the custom URL scheme only resolves on the device
that has the app installed, so a desktop can't act on it here — it is
told to return to the app on the phone instead. -->
<p class="only-desktop" data-i18n="confirmed.desktop">Die Wallet-Adresse ist Ihrem Konto hinzugefügt. Kehren Sie auf Ihrem Smartphone zur RealUnit-App zurück.</p>
<!-- Phone: prominent hand-off back to the app via the fixed custom URL
scheme the app registers. realunit.app claims no Universal/App Link, so
this page always loads and confirms first; the button then re-opens the
app. -->
<p class="only-mobile" data-i18n="confirmed.mobile">Die Wallet-Adresse ist Ihrem Konto hinzugefügt.</p>
<div class="only-mobile">
<a class="btn" href="realunit-wallet://open" data-i18n="confirmed.cta">Zurück zur App</a>
</div>
</div>

<!-- already-completed: merge was already done (HTTP 409); same hand-off
pattern as confirmed. -->
<div id="state-already-completed" hidden>
<div class="icon icon--ok">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
</div>
<h1 data-i18n="already-completed.title">Adresse bereits hinzugefügt</h1>
<p class="only-desktop" data-i18n="already-completed.desktop">Diese Wallet-Adresse ist bereits auf Ihrem Konto. Kehren Sie auf Ihrem Smartphone zur RealUnit-App zurück.</p>
<p class="only-mobile" data-i18n="already-completed.mobile">Diese Wallet-Adresse ist bereits auf Ihrem Konto.</p>
<div class="only-mobile">
<a class="btn" href="realunit-wallet://open" data-i18n="already-completed.cta">Zurück zur App</a>
</div>
</div>

<!-- invalid -->
<div id="state-invalid" hidden>
<div class="icon icon--warn">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v5"/><path d="M12 16h.01"/></svg>
</div>
<h1 data-i18n="invalid.title">Link ungültig oder abgelaufen</h1>
<p data-i18n="invalid.body">Dieser Link ist ungültig oder bereits abgelaufen. Bitte fordern Sie in der App eine neue Bestätigung an.</p>
</div>

<!-- unavailable -->
<div id="state-unavailable" hidden>
<div class="icon icon--wait">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8v5"/><path d="M12 16h.01"/><circle cx="12" cy="12" r="9"/></svg>
</div>
<h1 data-i18n="unavailable.title">Dienst vorübergehend nicht erreichbar</h1>
<p data-i18n="unavailable.body">Wir konnten die Adresse gerade nicht hinzufügen. Bitte versuchen Sie es in ein paar Minuten erneut.</p>
<button id="retry" type="button" class="btn" data-i18n="unavailable.cta">Erneut versuchen</button>
</div>
</div>
</main>

<!-- merge-core.js (pure logic + i18n copy) loads first; merge.js is the DOM
and network glue. Both deferred so they run in order after parsing. -->
<script src="/js/lib/merge-core.js" defer></script>
<script src="/account-merge/merge.js" defer></script>
</body>
</html>
Loading
Loading