# DAMM Onboarding — Design Brief

A grounded brief for visual + interaction designers picking up the in-flight onboarding wizard at `damm.raindesk.dev/get`. Everything below is pinned to what the code actually does as of this writing — when the code shifts, this brief drifts; treat it as a snapshot, not a contract.

## What DAMM is, in one paragraph

DAMM is a small, honest VPN. A user installs WireGuard from their app store, points it at our gateway, and their internet traffic exits from a server we run in Germany. The encryption is WireGuard's (ChaCha20-Poly1305, Curve25519). We never see the user's private key — it's generated in their browser via WebCrypto and gets baked straight into a config file they import into WireGuard. The whole product is a thin friendly wrapper around a thing WireGuard already does well; the design job is to make that wrapper feel like a calm thirty-second errand, not an IT chore.

## What the user is trying to accomplish at `/get`

Land. Understand the deal in roughly two sentences. Do four small specific things in sequence (install WireGuard, generate keys, get a tunnel, import it). End up with a working VPN connection they can verify is actually working. Have a way back later when they want to revoke or diagnose.

The user is **not** trying to: read a privacy policy, set up an account, choose a plan, or be sold to. The brand voice is "calm friend with infrastructure" — not "enterprise SaaS".

## The eight-step state machine, with what data exists at each step

This is the canonical flow. Designers should treat each step as a screen / view; transitions should feel light (push/slide), and back-navigation should preserve in-progress state so a user who realises they missed something can step back without losing their generated keys.

### Step 0 — Hello

**Data on screen**: nothing dynamic — just product framing.
**The user knows**: nothing yet.
**What we communicate**:
- What DAMM is (one sentence)
- What we route (one sentence: "everything you send through the tunnel")
- What we never see (one sentence: "your private key, your DNS lookups when you choose your own resolver")
- Time estimate ("about two minutes")
- The promise of no email / no signup
- A single primary CTA: "Let's get you connected"

**Edge state**: none (always available).

### Step 1 — Pick your device

**Data on screen**: detected platform (from `navigator.userAgent` parse), six explicit choices.
**Auto-detection**: iPhone, iPad, Android, macOS, Windows, Linux. Treat ChromeOS / BSD / others as "Linux" by default; allow "Other" override.
**What we communicate**: "Looks like you're on iPhone — that right?" with the six choices below in case it's wrong.
**What changes downstream**: which app-store link, which import-method, whether QR is the primary path or download is.

**Edge state**: detection inconclusive → no auto-pick, user picks from the menu cold.

### Step 2 — Install WireGuard

**Data on screen**: a platform-specific large primary action button, brand-correct WireGuard logo (designers: WireGuard's logo is permitted for unmodified use), short reassurance copy ("the official app, free, no ads, made by the people who made WireGuard"), a tiny secondary "I have it installed already" link that skips the install confirmation.

**Action**: deep link to App Store / Play Store / wireguard.com download page / `apt install wireguard` instructions.
**Confirmation**: a single checkbox or button click — "OK, it's installed" — advances. We don't try to detect; we trust them.

**Edge state**: user comes back to the page after installing and the wizard has lost state → resumable via the URL query string carrying their install pass token.

### Step 3 — Generate your keys

**Data on screen before action**: explainer ("Your phone will create a private key right now, here in this browser. We never see it.")
**Action**: `await window.crypto.subtle.generateKey({ name: "X25519" }, true, ["deriveBits"])`. The promise resolves with a public + private JWK. We export both as base64 and hold them only in memory.
**Data on screen after action**:
- Truncated public-key fingerprint (first 8 chars + "…")
- Status pill: "✓ keys generated, private key held in browser memory"
- Disclosure (collapsible): "Public key fingerprint: …, full key: … (this is fine to share). The private key was just made and will be written into your WireGuard config a moment from now. We never receive it."

**Edge state**: WebCrypto X25519 not available (older Safari, old Chromium) → graceful fallback message: "Your browser doesn't support the modern key format yet. Try Safari 17+, Chrome 110+, or Firefox 130+, or download a config we'll prepare server-side." (Server-side fallback is not yet built — for the first ship, we say "this browser isn't supported, here are the ones that are" and stop.)

### Step 4 — Get your tunnel

**Data on screen before action**: explainer ("Now we ask the DAMM control plane for an address on the tunnel and the gateway's public key. This is the only network call we make to our server. We send your public key, region, and your install pass — nothing else.").
**Action**: `POST https://cp.damm.raindesk.dev/v1/devices/enroll` with `{ publicKey, enrollmentToken, region: "eu-central" }`.
**Data on screen after action** (from the JSON response):
- Tunnel address (e.g. `10.44.0.10/32`)
- Gateway endpoint (`vpn.damm.raindesk.dev:51820`)
- Egress: country, IP, gateway name
- Tier: e.g. "early-adopter"
- A status pill: "✓ control plane responded — your tunnel is ready"

**Network-call inspector** (collapsed by default, valuable for trust): show the actual request body (with public key visible, since it's public), the response body, the latency. Shows we don't send anything we shouldn't.

**Edge state**: `429 rate_limited` (more than three install passes from this IP this hour) → show a friendly "we limit how many fresh passes one network can pull, try again at HH:MM" with the actual reset time from the response.
**Edge state**: `503 no_active_gateways` → "Looks like our gateway isn't healthy right now. We get notified automatically; if you give it a couple of minutes and try again, it usually clears." (rare; matters most under live failover).
**Edge state**: network failure → retryable, the keys are kept.

### Step 5 — Move it into WireGuard

**Data on screen**: the rendered `.conf` (in a syntax-highlighted box, with `PrivateKey` and `PresharedKey` visually redacted to `••••••• [tap to reveal]` so the user isn't unnerved by seeing them).

**Three on-ramps, in priority order** (the design challenge: which one is biggest depends on platform):

1. **Mobile / iOS / Android** — primary is QR. WireGuard mobile apps have "+ → Create from QR code" as a first-class flow. The QR encodes the entire `.conf` text. Render it large (≥240px, ideally 320px on phone landing). Below it: small line "open WireGuard, tap +, choose 'Create from QR code', and point it here."

2. **iOS specifically** — secondary is the deep link. iOS WireGuard registers a `wireguard://` URL scheme that **only** works for hosted configs (`wireguard://import?url=https://example.com/config.conf`), not inline configs. We can host a one-time-use signed URL for the config; the link button reads "Open in WireGuard". Designers: assume this is available, but plan a graceful fallback if hosting isn't ready ship-day.

3. **Desktop** — primary is the download. A `wg.conf` file the user double-clicks (macOS) or imports via WireGuard's "Add Tunnel → from file". QR is secondary on desktop because users lack a camera flow.

A small permanent footer link: "Show the raw config" → reveals the full `.conf` text including private key for users who want to copy-paste manually. This is for advanced users and Linux users who need to edit it before importing.

### Step 6 — Flip the switch

**Data on screen**: instructions specific to the platform, starting from where they are now ("Open WireGuard now. You'll see a tunnel called `phase0-test`. Toggle it on. iOS will ask permission once — that's normal."). For each platform, one to three sentences max.

**Action**: a button "Test my connection" (or auto-trigger after a 4–5s grace period to let the user actually flip the switch).
**Network call**: `GET https://cp.damm.raindesk.dev/v1/whoami` — returns `{ ip, gatewayMatch, egress, observedAt }`. If `gatewayMatch === true`, the user's request came in from our gateway's egress IP; they're connected.

**Polling shape**: try every 2 seconds for up to 30 seconds. While polling, animate the status: "looking for you on our gateway…". On success: green confirmation with the actual IP we see and the country.

**Edge state — connected**: "✓ Connected. We see you coming from 149.102.137.139 (Germany), via gw-eu-hub2." Show their tunnel address (`10.44.0.10`) for symmetry.
**Edge state — not yet**: "We don't see you through the tunnel yet. Common causes: the toggle isn't on, or iOS hasn't allowed the VPN profile (Settings → General → VPN). Want me to check again?" — retry button.
**Edge state — never connects** (after 30s): "Hmm. Let's troubleshoot. [link to Step 7 troubleshoot panel]." Don't trap the user in a loop.

### Step 7 — You're in

**Data on screen** — the posture panel:
- Encryption: ChaCha20-Poly1305 (WireGuard's only cipher; no negotiation; this is a feature, but say it plainly)
- Key age: e.g. "47s (rotates every ~2 min automatically)"
- Gateway: gw-eu-hub2, Germany
- Egress: 149.102.137.139, AS39351 (or whatever the actual ASN is)
- Tier: early-adopter
- Obfuscation: T0 bare WireGuard (T1–T4 placeholder cards, "coming soon", with the tradeoff matrix from the architecture)

**Permanent CTAs**:
- "Show config" — reveals the full `.conf` again
- "Re-download .conf"
- "Revoke this device" — POSTs to `/v1/devices/{id}/revoke` (admin route — for the wizard, this needs a per-device self-revoke token issued at enrollment time; design assumes that exists, the engineering follows.)
- "Diagnose" — runs the diagnostics flow

**Future placeholders to leave room for** (Phase 2 / Phase 3):
- A "Sequester this" toggle (Phase 3): "send specific traffic out a different IP pool, slower but isolated reputation"
- An obfuscation-tier picker: cards showing T0/T1/T2/T3/T4 with measured tradeoffs (throughput hit, latency hit, blocks-defeated, app-compat)
- A per-flow rule editor: domain/CIDR rules → which pool routes them

## Visual and tonal direction

### Palette already in use

The existing client surface (`damm-client.html`) sets:

```
--paper:   #f4efe6   (warm off-white background)
--panel:   #fffdfa   (panel against paper)
--ink:     #191714   (primary text)
--muted:   #5f5952   (secondary text)
--line:    #d6cec2   (1-px borders, dividers)
--accent:  #245861   (deep teal — primary CTAs, links)
--accent-2:#8a5d28   (warm tan — emphasis, warnings, callouts)
--wash:    #fbf8f2   (faint inset)
--success: #2f6a50
--warn:    #8a5d28
--danger:  #8b3c31
--shadow:  0 18px 40px rgba(29, 24, 18, 0.08)
```

The ethos is **paper, ink, and a single deep accent**. Not a tech-bro sleek dark mode. Designers are free to evolve, but the existing client surface is internally consistent — the wizard should feel like a sibling, not a different product.

### Typography in use

`ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif` for body. `"IBM Plex Mono", "SFMono-Regular", Consolas, monospace` for code, fingerprints, status pills, eyebrows. Hierarchy lives in weight + spacing, not in many sizes.

### Tone of voice

- **Plain English first**, technical accuracy second, marketing copy never.
- Address the user as "you", talk in second person.
- Name the things ("WireGuard", "control plane", "gateway", "egress") because they'll see them in error messages — but introduce each in passing the first time it appears.
- Honesty about limits. "T0 is the default because it's the fastest. If your network blocks it, we have heavier options coming." not "Best-in-class privacy unlocked."
- Favor short sentences for instructions, longer for explainers.

### Sample copy snippets to test against

> **Step 0 hero**
>
> Set up a private connection in about two minutes. We'll walk you through it. No signup, no email, no app to install beyond WireGuard from your app store.

> **Step 3 explainer**
>
> Your phone will make a private key right now, in this browser. We never see it. You don't need to remember it — it'll go straight into your WireGuard config and live there.

> **Step 5 ramp copy**
>
> Three ways in, pick the easiest:
>
> 1. **Scan the QR** — open WireGuard, tap +, choose "Create from QR code", point your phone at the screen.
> 2. **Open in WireGuard** — tap the button, iOS will offer to add the tunnel.
> 3. **Save the file** — for desktop or if the others don't work.

> **Step 6 success**
>
> Connected. We see you coming from 149.102.137.139 (Germany), through gateway gw-eu-hub2. Your tunnel address is 10.44.0.10. You can leave this page now — the connection is on your phone.

## Hand-off shape — what designers should produce

For the engineer to consume cleanly:

1. **Eight screens** at iPhone width (390 × 844), full bleed, with the safe-area top/bottom respected.
2. **Three desktop screens** for the screens that diverge (steps 0, 5 download path, 7 posture panel).
3. **A flow diagram** showing the eight steps, the back-arrow possibilities, and the seven edge-state variants called out above.
4. **Component library**:
   - status pills (success, warn, danger, neutral)
   - disclosure / collapse with the specific shape used for "what's actually happening"
   - QR card with caption
   - posture panel card
   - error / retry banner
   - rate-limit banner with countdown
5. **Copy doc** — every word the user can see, including all error states, in a doc that the engineer can paste.
6. **Acceptance criteria** for each screen — what state must be visible, what affordances must be reachable, what the keyboard nav is.

## What's open / undecided

- The Phase 2 obfuscation-tier picker UI shape is unspecified. We have a tradeoff matrix; we don't have a UI for picking. Designers should propose two or three takes.
- The Phase 3 "Sequester this" surface — is it a session-wide toggle in the wizard, or a per-flow rules editor, or both? Expect to iterate.
- iOS WireGuard's `wireguard://` deep-link reliability across iOS versions — engineering will confirm. Design should assume it works, with the QR + download path as graceful fallback.
- We've named "early-adopter" as the public tier. We do not yet have a paid tier or a "friend tier". Copy should not promise pricing certainty.

## What is fixed and won't change

- Browser-side X25519 keygen with `crypto.subtle.generateKey({ name: "X25519" })`. Private key never leaves.
- WireGuard is the transport, not "DAMM's own VPN protocol".
- Public install pass is rate-limited per IP. Three per hour.
- The iOS user must install WireGuard from the App Store. We will never pretend we can be the tunnel ourselves in Safari.
- The whole flow must work on a saved/offline page (the page is a PWA). Designers should account for: page works after first load with no network, until the user actually needs to call `enroll` or `whoami`.
