locqr/extension/gui.md
2026-08-16 22:00:08 +02:00

12 KiB
Raw Blame History

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_1phase_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_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. 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 — 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.