# 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 260×260px — fills the card body's content width edge to edge (300px popup − 20px padding × 2), so the QR's outer edge lines up with the countdown bar below it. No margin beyond that shared 20px card padding; a smaller fixed size (190px, tried initially) left extra whitespace around the code and made it read as unnecessarily dense for its size. - 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_1–4) | `--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. Built for a `run_error` security-class reason that interrupts something the user started, the same way `cert_invalid`'s security variant interrupts before one starts. No concrete extension-side reason uses this template today — `claude.md`'s `run_error` note explains why `alpha_hash_mismatch`/`pin_mismatch` (this template's original motivating examples) turned out to be companion-only detections; `bundle_consumed` is the one real candidate, if the server is ever extended to signal it. The template stays ready either way: the popup code renders any `errorClass: 'security'` reason through it generically. 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`** — two variants depending on the site's advisory `sessionActive` hint (`claude.md`, `interfaces.md`'s `session_status` message); same badge treatment either way, copy and action differ: - No session reported (default): 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**. - Session reported active: green tinted badge, checkmark glyph (same glyph, badge color shifts to match `delivered`'s green — reads as "a settled, good state," not an action item). Heading "Signed in," body (`"You're already signed in to {origin}."`). One primary action: **Sign in again** — same underlying action as plain `idle`'s Sign in (starts a new run), just worded for a user who's already authenticated. No extra confirmation before it: starting a run doesn't affect the existing session at all, and the run's own multi-step confirmation (QR scan, PIN compare) is already real friction against an accidental click. - **`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.