locqr/extension/gui.md

211 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Popup Visual Design
Layout, color, and asset specs for the popup UI. State transitions, data, and
which screen maps to which state are defined in `claude.md`; this document
defines what each screen looks like. The popup itself is the browser's native
extension popup window — no custom window chrome is drawn; everything below
starts at the popup's content area.
## Shared chrome
Every run-phase screen (`phase_1``phase_4`, `delivered`) is built from the
same structure:
- **Header** — brand mark (20×20px, rounded 6px, solid accent color) + `locqr`
wordmark (bold, 14.5px) on the left; site origin (e.g. `localhost`) on the
right, secondary text color. Bottom border divider.
- **Body** — 28px top / 22px bottom / 20px horizontal padding, content
centered.
- **Countdown bar** — 4px tall, full width, rounded-full track and fill.
Bound to whichever TTL governs the current phase (`qr_ttl` in phase_2,
`pin_ttl` in phase_3). Same component throughout the run; only the value
driving it changes.
- **Caption** — 12.5px, secondary text color, centered, below the primary
content.
### Color tokens
| Token | Light | Dark |
|---|---|---|
| `--bg` (page, outside popup) | `#eef1f6` | `#0a0d13` |
| `--card-bg` | `#ffffff` | `#12161f` |
| `--card-border` | `rgba(15,23,42,.08)` | `rgba(255,255,255,.08)` |
| `--text-primary` | `#0f172a` | `#f1f5f9` |
| `--text-secondary` | `#667085` | `#94a3b8` |
| `--text-tertiary` | `#94a3b8` | `#64748b` |
| `--accent` | `#2f6fed` | `#5b8bff` |
| `--accent-track` | `#e2e8f0` | `#232a38` |
| `--box-border` (PIN digit boxes) | `#d7dce4` | `#2a3140` |
| `--box-bg` (PIN digit boxes) | `#f8fafc` | `#171c26` |
| `--divider` | `rgba(15,23,42,.07)` | `rgba(255,255,255,.08)` |
Dark mode follows the user's OS/browser theme (`prefers-color-scheme`).
## Phase 2 — QR
- Square QR image, no `border-radius` — rounding clips the finder-pattern
squares, which sit flush in each corner.
- Two colorways, selected by theme:
**Light (default):** plain black (`#1a1a1a`-ish, i.e. the QR library's
standard module color) on white. Quiet zone is baked into the raster: ~4
modules per the QR spec's minimum, measured at the reference asset's actual
module pitch (~3.14px/module → ~13px margin at this asset's resolution).
This one needs a real margin because its background is plain white, not
guaranteed to match whatever it's rendered on.
**Dark (inverted):** modules white (`#ffffff`), background set to the exact
dark `--card-bg` value (`#12161f`) rather than plain black. Cropped tight
to the module grid with **no** baked-in margin — because the raster
background is identical to the card background, the card's own padding
serves as the quiet zone, and no raster pixels are spent on margin.
Confirmed scannable on an older Android device during design review;
treat companion-app support for inverted QR decoding as a real
requirement, not a nice-to-have, since older scanners are the ones most
likely to reject reversed polarity.
- Display size 190×190px in the card body.
- Caption: "Scan with the companion app."
- Countdown bound to `qr_ttl`.
## Phase 3 — PIN confirmation
- 6-digit code, grouped 3+3 (wider gap between groups than within a group —
reads as one code, not two numbers).
- Each digit in its own box: 42×50px, 1.5px border, 10px corner radius,
monospace, 21px / weight 650.
- **Display-only.** The user does not type into these boxes — they compare
the shown code against the companion app's independently-derived value and
tap Confirm. (Confirmation semantics — what mismatch means, what the
underlying value is derived from — are specified in `../crypto.md` and
`claude.md`; this document only covers presentation.)
- Countdown bound to `pin_ttl`, same bar component as phase 2.
- Caption: "Confirm this code matches your companion app."
- Actions: **Confirm** (primary, filled, full width) above **Abort** (ghost /
text button, subdued). Confirm is the expected action and gets the visual
weight; Abort stays available without competing for attention.
## Toolbar icon set
Seven states, all built on the same rounded-square badge used for the header
brand mark (10px corner radius at 36px toolbar size) — the icon system reads
as one family rather than a separate alert-icon library bolted on. Severity
is never carried by color alone: color and glyph change together, so the
distinction holds under color blindness and under a glance too quick to read
text.
| State | Fill | Glyph |
|---|---|---|
| `neutral` (unverified) | `--neutral-badge` (`#94a3b8` / `#475569`) | small dot |
| `inactive` (not registered) | `--inactive-badge` (`#cbd5e1` / `#334155`) | dash |
| `ready` (idle) | `--accent` | checkmark |
| `animated` (phase_14) | `--accent` | rotating dashed ring |
| `success` (delivered) | `--green` (`#16a34a` / `#22c55e`) | bold checkmark |
| `error` (operational, retry-able) | `--amber` (`#f59e0b` / `#fbbf24`) | "!" |
| `security warning` (blocked, reported) | `--red` (`#dc2626` / `#ef4444`) | shield with "!" |
Color pairing is deliberate, not decorative: amber tracks every row in the
`claude.md` state table that offers Retry or needs no action; red tracks
every row that is Dismiss-only and already reported to the backend. The
shield glyph specifically for security warnings borrows the vocabulary
browsers already use for HTTPS/security indicators, so it doesn't depend on
the user reading anything.
## Error and security-warning popups
Three body variants, all using the shared chrome (header + card), differing
in the `card-body` content:
**Generic / operational error** — 48px amber badge (tinted background, not
solid), heading in normal text color, reason text, then full actions: primary
filled **Retry** above a plain-text **Dismiss**. Covers `cert_invalid`
(`insecure_origin`, operational reasons), `account_error`, and `run_error`
(timeout/user, network) — the icon is identical across these; only the body
copy and whether Retry is offered differ per `claude.md`'s table.
**Security warning — ambient** — 48px red badge (shield glyph), heading text
itself reads "Security warning" (explicit label, not just the reason —
required by `claude.md`: "user sees an explicit security warning, not a
generic error"), reason text, **no action buttons**. Used for `cert_invalid`
security-class reasons (`domain_mismatch`, `signature_invalid`, `malformed`),
which are ambient page state rather than part of an active run — nothing to
retry or dismiss; it persists until the cert or page changes.
**Security warning — mid-run** — same red badge and "Security warning"
heading, but with a single **Dismiss** button. Used for `run_error`
security-class reasons (`alpha_hash_mismatch`, `pin_mismatch`), which
interrupt something the user started. Dismiss uses the outlined/ghost button
style, never the filled accent button — nothing about a blocked state should
visually read as "the expected path," the way Confirm does in phase 3.
**Placeholder copy — needs a plain-language pass.** The reason strings used
while designing this ("relay timed out," "run has been stopped and
reported") are internal/protocol vocabulary — run, relay, bundle, etc. — and
were only good enough to validate the visual treatment. The end user has no
reason to know what a "run" or a "relay" is. Before this ships, every
user-facing string in this section needs to be rewritten in plain terms (what
happened, what it means for them, what to do), independent of the visual
design above, which is settled.
## Status screens — idle, phase_1, phase_4, delivered
Same chrome, no countdown bar (none of these four are TTL-governed). A 48px
badge + heading + short body, centered, shared across all four; only the
badge color/glyph, heading, body, and action differ.
- **`idle`** — accent-blue tinted badge, checkmark glyph (matches the `ready`
toolbar icon). Heading "Ready to sign in," body naming the site
(`"{origin} supports secure sign-in with LOCQR."`). One primary action:
**Sign in**.
- **`phase_1`** and **`phase_4`** share one template: accent-blue tinted
badge containing the rotating-ring spinner (real CSS rotation, not a
static frame; honors `prefers-reduced-motion` by holding still instead of
spinning), heading, short body, single **Cancel** action (plain-text
button, not filled). Only copy differs between the two:
- `phase_1`: "Connecting…" / "Setting up a secure connection to your
phone."
- `phase_4`: "Almost done…" / "Waiting for your phone to finish sending
your sign-in details."
- **`delivered`** — green tinted badge, bold checkmark (matches the
`success` toolbar icon). Heading "Signed in," body confirming the site
(`"You're securely signed in to {origin}."`), no action buttons, small
"Closing automatically…" note instead — matches `claude.md`'s "brief
display then auto-transition to `idle`."
## Ambient screens — unverified, not_registered
Two low-key states, neither an error nor part of a run, and deliberately
distinct from each other: `unverified` is transient (resolves quickly on any
page that does have LOCQR); `not_registered` is a settled fact about the
page. Neither uses the run-phase spinner — that motion means "a multi-step
exchange is actively in progress," and reusing it here would blur that
meaning. Neither has an action button, matching `claude.md`'s table (both
rows currently show `—`); `not_registered`'s table row notes a possible
future "register site" action, but no button is speculated here since that
capability is undecided, not just uncopywritten.
- **`unverified`** — neutral-gray tinted badge, small dot glyph, with a soft
opacity pulse (1 → 0.35 → 1, ~1.6s) rather than the rotating spinner — reads
as "still looking" without borrowing the run-phase meaning. Heading
"Checking…," body "Looking for LOCQR sign-in support on this page."
Respects `prefers-reduced-motion` (pulse disabled).
- **`not_registered`** — inactive-gray tinted badge (lighter than neutral),
static dash glyph, no animation. Heading "Not available here," body naming
the site (`"{origin} hasn't set up sign-in with LOCQR."`).
- Even the header brand mark picks up the neutral/inactive tint on these two
screens instead of the usual accent blue, so the header itself signals
"nothing active" before any text is read.
## Open / not yet specified
- Icons: deliberately deferred until the rest of the popup design is
finalized — polish, not blocking.
- A full plain-language copy pass across every state, not just the error
family — see the caveat above. Most copy drafted in this document made a
real attempt at plain language already, but none of it has had a dedicated
review pass.
- `mobile/gui.md` — the companion app's screens (scan, key exchange, PIN
display) are entirely undesigned. Some of what's settled here — especially
the PIN/code display concept — should visually correspond to whatever the
companion shows, since the user compares the two side by side.