···11+# Design: Mobile "Open Camera" Button
22+33+**Date:** 2026-05-31
44+**Status:** Approved, pending implementation plan
55+66+## Summary
77+88+Add a button to both the **profile screen** and the **event screen** that, on
99+mobile devices only, opens the device's rear camera when tapped. The button is
1010+a camera-launch affordance — it pops the native camera capture sheet and does
1111+not process the captured image.
1212+1313+## Motivation
1414+1515+atmo.quest's connect flow relies on scanning a QR code (each profile exposes a
1616+QR encoding `/c/{did}`). Today users scan with their phone's standalone camera
1717+app. This feature adds an in-page shortcut to open the camera from the profile
1818+and event screens on mobile.
1919+2020+## Scope and explicit non-goals
2121+2222+**In scope:** a mobile-only button that opens the native camera capture sheet.
2323+2424+**Explicit non-goal — no QR decode.** Per the product decision during
2525+brainstorming, the page does **not** process the captured photo. The button is
2626+a v1 stub / camera-launch affordance, **not a working scanner**. Tapping it
2727+opens the camera; whatever photo the user takes is discarded.
2828+2929+> Web reality that drove this decision: the only no-dependency way for a web
3030+> page to open the camera is `<input type="file" capture="environment">`, which
3131+> opens the **photo-capture sheet**. That sheet takes a still photo and returns
3232+> it to the input — it does **not** do the live QR detection of the standalone
3333+> Camera app, and there is no web API to launch that standalone app. Making the
3434+> button actually connect someone would require adding a decode step (client-
3535+> side JS QR decode, then redirect to the connect URL). That is a deliberate
3636+> future enhancement, not part of this work.
3737+3838+## Behavior
3939+4040+- Rendered as a `<label class="btn …">` wrapping a hidden
4141+ `<input type="file" accept="image/*" capture="environment">`.
4242+- `capture="environment"` requests the rear camera.
4343+- Tapping the label opens the native camera capture sheet.
4444+- The input has **no** change handler — the captured image is not read,
4545+ uploaded, or decoded.
4646+4747+## Mobile-only visibility
4848+4949+Pure CSS, mirroring the existing `.bottom-nav` convention:
5050+5151+- The button's container class defaults to `display: none`.
5252+- Inside `@media (max-width: 768px)` it becomes visible.
5353+5454+768px is the app's established mobile breakpoint (see `.bottom-nav` in
5555+`web/resources/static/css/terminal.css`). No JavaScript, no user-agent
5656+sniffing. The element is always present in the HTML and simply hidden on
5757+desktop.
5858+5959+## Reuse
6060+6161+A single shared templ component, `CameraScanButton`, lives in
6262+`features/common/layouts` alongside `TopNav` / `BottomNav`. Both
6363+`profile.templ` and `event.templ` invoke it so the markup is defined once.
6464+6565+## Placement
6666+6767+- **Profile** (`features/profile/pages/profile.templ`): directly under the QR
6868+ flip card (`.profile-flip`), near the top of `.profile-card`.
6969+- **Event** (`features/event/pages/event.templ`): near the top of
7070+ `.event-card`, just under the event meta lines.
7171+7272+## Label
7373+7474+`📷 open camera` — honest about what happens, since it does not decode a QR.
7575+7676+## Files touched
7777+7878+- `features/common/layouts/` — new `CameraScanButton` templ component
7979+ (+ regenerated `*_templ.go`).
8080+- `features/profile/pages/profile.templ` — invoke the component.
8181+- `features/event/pages/event.templ` — invoke the component.
8282+- `web/resources/static/css/terminal.css` — new mobile-only button class.
8383+- templ regeneration via the project's `templ generate` task.
8484+8585+No Go handler/route changes. No new dependencies.
8686+8787+## Testing
8888+8989+- Verify the button is hidden on desktop widths (> 768px) and visible at
9090+ ≤ 768px on both screens.
9191+- On a mobile device / emulated mobile, verify tapping opens the camera capture
9292+ sheet.
9393+- Verify existing profile flip and event check-in behavior are unaffected.
9494+- `templ generate` runs clean; `go build` / existing checks pass.