# Sticker Visibility Toggle — Design Spec ## Overview Add a 3-way segmented toggle to the Picker's Game Session screen that lets admins control sticker layer visibility on the downstream vote-app overlay. The three modes are **Auto** (default), **Show**, and **Hide**, matching the downstream API contract defined in `docs/external-downstream-stickers.md`. ## Motivation The vote-app overlay shows stickers that voters purchase during a session. By default, stickers follow poll state (visible during polls, hidden between). Admins need the ability to override this — force-showing stickers between rounds for a "sticker wall showcase" or force-hiding them during a busy poll for a cleaner view. ## Modes | Mode | Behavior | |------|----------| | `auto` | Default — stickers visible during polls, hidden between polls | | `show` | Force stickers visible regardless of poll state | | `hide` | Force stickers hidden regardless of poll state | When a new poll starts, the downstream vote-app auto-resets mode to `auto` and sends a `stickers.mode` event. ## Architecture ### State ownership - `stickerMode` state (`useState('auto')`) lives in the `Picker` component, where the toggle UI renders. - A `stickerSendRef` ref is created in `Picker` and passed to `SessionInfo`, which populates it with a send function once the WebSocket connection is authenticated. ### Props `SessionInfo` receives two new props (following the existing pattern for `setPollActive`, `setLeadingGame`, etc.): - `setStickerMode` — setter called when inbound `stickers.mode` WS events arrive. - `stickerSendRef` — ref that SessionInfo populates with `(mode) => ws.send(...)`. ### WebSocket integration All communication uses the existing `/api/sessions/live` WebSocket connection managed by `SessionInfo.connectWs`. **Receiving (`stickers.mode` events):** ```javascript if (message.type === 'stickers.mode') { setStickerMode(message.mode); return; } ``` Added alongside the existing `poll.*` and `game.*` handlers in the `ws.onmessage` callback. **Sending (`stickers.setMode` commands):** After successful auth and subscribe, SessionInfo populates the ref: ```javascript stickerSendRef.current = (mode) => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'stickers.setMode', data: { mode } })); } }; ``` The ref is cleared on disconnect/cleanup. **No HTTP fallback.** The default `auto` state is correct on fresh load, and the WS connection handles all sync from that point. ## UI Design ### Placement The sticker toggle renders in Picker's right column (`md:col-span-2`), **above** the poll leader indicator and poll control cards. It is always visible when an active session exists, regardless of poll state. ``` ┌─────────────────────────────────────────────┐ │ Stickers [Auto] [Show] [Hide] │ └─────────────────────────────────────────────┘ ↕ poll leader indicator (conditional) ↕ currently playing card (conditional) ↕ poll control card (conditional) ↕ SessionInfo games list ``` ### Control style A compact single-row card with: - **Label** ("Stickers") on the left. - **3-way segmented control** on the right, matching the Drawing/Family Friendly filter pattern (three adjacent buttons, active one highlighted). ### Segment colors | Segment | Active background | |---------|-------------------| | Auto | Neutral gray (`bg-gray-500`) | | Show | Green (`bg-green-500`) | | Hide | Red (`bg-red-500`) | Inactive segments use a muted/transparent style with border, same as the existing filter toggles. ### Auto-reset pulse When a `stickers.mode` event arrives with `mode: "auto"` and the current local state is not already `auto`, a brief CSS pulse animation plays on the toggle card: - A gentle border/background highlight flash that fades over ~2 seconds. - Implemented via a CSS `@keyframes` animation toggled by a transient state flag. - The flag is set on receiving an auto-reset event and cleared after the animation completes. ### Interaction behavior - Clicking a segment calls `stickerSendRef.current(mode)` and optimistically updates local state. - If the subsequent `stickers.mode` event returns a different value, state corrects to match. - The toggle is visually disabled (reduced opacity, no pointer events) if the WebSocket is not connected. ## Files changed | File | Change | |------|--------| | `frontend/src/pages/Picker.jsx` | Add `stickerMode` state, `stickerSendRef` ref, render sticker toggle card, pass new props to `SessionInfo` | | `frontend/src/pages/Picker.jsx` (`SessionInfo`) | Accept `setStickerMode` and `stickerSendRef` props, handle `stickers.mode` WS events, populate send ref | ## Scope boundaries - **In scope:** Frontend toggle UI + WS message send/receive in the game picker. - **Out of scope:** Backend relay of `stickers.setMode` messages to the vote-app (assumed handled by existing infrastructure or a separate task). Vote-app sticker rendering (already implemented downstream).