--- name: design-vpn-client-ui description: "Use when changing or reviewing Harbor Connect and Gateway client UI. Apply the established visual and motion language; not for administrative Gateway UI unless explicitly requested." --- # Design VPN Client UI Preserve the repo's focused one-screen VPN client language: a centered primary action, quiet technical typography, mode-specific accents, and motion that makes live state and interaction legible without moving layout. ## Workflow 1. Inspect the affected client component, state owner, relevant styles, and nearest analogue. Read `PRODUCT.md` when product behavior or mode semantics change; expand context when shared invariants require it. 2. Inspect supplied evidence and trace the real DOM and state change that causes the visual issue. Follow repository testing policy; do not launch manual or interactive visual testing unless the user explicitly requests it in the current prompt. 3. Read [visual-language.md](references/visual-language.md) for layout, hierarchy, color, and typography work. 4. Read [motion-and-interaction.md](references/motion-and-interaction.md) for animation, hover, refresh, input, copy, or state-transition work. 5. Reuse existing React state, CSS variables, formatters, and API paths. Prefer a narrow CSS/markup change over a new abstraction or dependency. 6. Keep geometry stable across every state. Reserve space before animating content. 7. Implement `prefers-reduced-motion` alongside every new animation. 8. Run focused automated checks for affected behavior and `npm run build` for client code changes; use the full `npm test` suite when shared behavior changes or narrower evidence is insufficient. Documentation-only work uses structural/link checks and `git diff --check`. Manual visual inspection requires an explicit request in the current prompt. ## Communicating a proposed change For a local correction, report the result and focused evidence briefly. For a change spanning several owners or state lifecycles, use one compact table of affected modules plus the relevant states, accessibility, and motion decisions. Omit empty sections and unrelated modules. ## Non-negotiable decisions - Keep the power action on the screen's central vertical axis. Place subscription content to its right without shifting that axis. - Keep the power hit target generous while rendering only the icon, never a large enclosing accent circle. - Drive every active accent from the current mode token: Connect is blue-green; Gateway is orange. Keep inactive power gray, including hover, and preserve semantic warning/error colors. - Never let labels, timers, feedback, icons, progress, or server rows shift neighboring content. - Animate state, opacity, blur, glow, color, filter, and transform. Do not animate layout properties. - Make live behavior visibly alive: running processes, changing values, mode changes, and interactive affordances should communicate through restrained motion instead of abrupt static replacement. - Give every actionable icon a semantic hover/focus response; rotate cyclic actions, move the physical part of object-like controls, and keep their hit targets fixed. - Let every visible cycle finish and return to its resting coordinates before stopping. Never cancel a hover animation, spinner, or list exit at an arbitrary frame. - Animate dynamic rows through complete enter and exit phases; keep a departing row mounted until its exit finishes, with immediate removal under reduced motion. - Animate only what changed. Keep unchanged digits, labels, icons, and surrounding geometry stable. - Keep tooltips outside transformed, rotating, glowing, or filtered controls. Show them quickly above the control as independent translucent cloud surfaces. - Prefer one clear value over unsupported detail. Hide subscription fields the provider does not supply. - Keep client UI compact and calm. Do not introduce dashboard cards, decorative chrome, or admin-console density. - Do not use a modal, popup, or blocking backdrop unless the user explicitly asks for one. Prefer inline disclosure or a non-modal layer that preserves the main screen. - When the owner explicitly chooses modal treatment for critical confirmations, reuse one accessible full-screen confirmation popup: blur and block the background, reveal from center, then stage text and actions. - Avoid borders, divider lines, and framed regions by default. Build hierarchy with spacing, typography, subtle surface changes, light, and depth; use a line only when it communicates an essential state. - In client-side editors, prefer flat text controls and accessible custom pickers over browser-native menus when the native surface breaks the visual language. Do not append another blank row until the current row is complete. ## Acceptance pass Check the affected behaviors below using automated evidence where possible. Shared layout or motion changes require broader coverage; a local correction does not require exercising unrelated controls. Manual inspection remains limited to an explicit request in the current prompt: - Power on/off is unmistakable without reading the label. - Switching on/off preserves the exact positions of title, timer, and hint. - Switching Connect/Gateway crossfades status in a fixed slot, changes the full accent palette, and clearly de-emphasizes data irrelevant to the active route. - A timer tick animates only changed digits and reads as a soft flow, never a blink. - Hover motion completes its current cycle and settles before stopping; ambient affordance motion remains subtle and infrequent. - Tooltips remain upright, unfiltered, above adjacent content, and visually consistent across controls. - Refresh and copy feedback cannot change element width or alignment. - Server separators are compact and only slightly wider than their content. - Repeated polling does not replay decorative list animations. - Manual refresh has an obvious but non-jarring response. - Icon-only controls respond on hover and focus, active cyclic work spins, and durable states such as pinned remain legible at rest. - Keyboard focus remains visible even when the text caret is intentionally hidden. - Narrow screens return to a simple single-column layout.