Refine VPN client design guidance for mode-aware motion
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 11s
Build and Deploy Gateway / deploy (push) Successful in 1s

This commit is contained in:
2026-07-11 16:51:41 +03:00
parent 0bf7d2ee30
commit 4b326c5e99
3 changed files with 56 additions and 15 deletions

View File

@@ -5,27 +5,30 @@ description: Design, implement, review, or refine the client-facing VPN interfac
# Design VPN Client UI # Design VPN Client UI
Preserve the repo's focused one-screen VPN client language: a centered primary action, quiet technical typography, restrained green state color, and motion that feels slow, fluid, and deliberate without moving layout. 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 ## Workflow
1. Read `PRODUCT.md` and the complete client component and styles before editing. 1. Read `PRODUCT.md` and the complete client component and styles before editing.
2. Inspect the supplied screenshot or live UI. Trace the real DOM and state change that causes the visual issue. 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. 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. 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. 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. 6. Keep geometry stable across every state. Reserve space before animating content.
7. Implement `prefers-reduced-motion` alongside every new animation. 7. Implement `prefers-reduced-motion` alongside every new animation.
8. Run `npm test`, `npm run build`, and `git diff --check`. Visually inspect when a runnable client is available. 8. Run `npm test`, `npm run build`, and `git diff --check`. Perform manual visual inspection only when explicitly requested.
## Non-negotiable decisions ## 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 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 green circle. - Keep the power hit target generous while rendering only the icon, never a large enclosing accent circle.
- Use green only for active, selected, successful, or refreshed states. Keep inactive power gray, including hover. - 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. - 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. - Animate state, opacity, blur, glow, color, filter, and transform. Do not animate layout properties.
- Let visible cycles finish. Never stop a spinner mid-turn or remount a list before its exit animation completes. - Make live behavior visibly alive: running processes, changing values, mode changes, and interactive affordances should communicate through restrained motion instead of abrupt static replacement.
- 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 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. - 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. - 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. - 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.
@@ -37,6 +40,10 @@ Before handing off, verify:
- Power on/off is unmistakable without reading the label. - Power on/off is unmistakable without reading the label.
- Switching on/off preserves the exact positions of title, timer, and hint. - 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. - Refresh and copy feedback cannot change element width or alignment.
- Server separators are compact and only slightly wider than their content. - Server separators are compact and only slightly wider than their content.
- Repeated polling does not replay decorative list animations. - Repeated polling does not replay decorative list animations.

View File

@@ -4,6 +4,8 @@
Aim for fluid, slightly viscous motion: noticeable, calm, and complete. Avoid bounce, elastic easing, abrupt unmounts, decorative page choreography, or tiny effects too weak to communicate feedback. Aim for fluid, slightly viscous motion: noticeable, calm, and complete. Avoid bounce, elastic easing, abrupt unmounts, decorative page choreography, or tiny effects too weak to communicate feedback.
Motion is functional feedback. If the system is running, refreshing, counting, switching route, or inviting interaction, show that activity with restrained movement. Do not animate every static decoration; animate the part that proves work, state, or affordance.
Use exponential ease-out curves such as `cubic-bezier(0.16, 1, 0.3, 1)` for arrivals. Typical timing: Use exponential ease-out curves such as `cubic-bezier(0.16, 1, 0.3, 1)` for arrivals. Typical timing:
- hover and press: 180-300 ms; - hover and press: 180-300 ms;
@@ -12,10 +14,19 @@ Use exponential ease-out curves such as `cubic-bezier(0.16, 1, 0.3, 1)` for arri
- numeric tween and progress: about 900 ms; - numeric tween and progress: about 900 ms;
- copy feedback: about 800 ms; - copy feedback: about 800 ms;
- server cascade: 620-760 ms per row with 90-110 ms stagger. - server cascade: 620-760 ms per row with 90-110 ms stagger.
- tooltip arrival: about 90-140 ms with almost no delay;
- ambient affordance hint: one small cycle roughly every 10 seconds.
## Cycle completion
- On pointer leave, do not snap an infinite hover animation or reverse it from the middle. Mark it for stopping, let the current iteration reach its original coordinates, then remove the animation.
- If the user re-enters before the iteration ends, clear the stop request and continue the same behavior.
- Separate state transforms from repeating motion when both affect one control. Animate a child for the cycle and its wrapper for durable state, or wait for `animationiteration` before clearing the animated class.
- Keep reduced-motion behavior immediate and static; never wait for an iteration event that will not fire.
## Power state ## Power state
- Transition gray to green slowly when connecting and green to gray when disconnecting. - Transition gray to the current mode accent slowly when connecting and back to gray when disconnecting.
- Animate icon color, localized light, and SVG shadow together. - Animate icon color, localized light, and SVG shadow together.
- Let the light expand and brighten on enable, then contract and fade on disable. - Let the light expand and brighten on enable, then contract and fade on disable.
- Keep the hit target and all surrounding geometry fixed. - Keep the hit target and all surrounding geometry fixed.
@@ -24,12 +35,22 @@ Use exponential ease-out curves such as `cubic-bezier(0.16, 1, 0.3, 1)` for arri
## Changing text and numbers ## Changing text and numbers
- Put alternate labels in fixed-size slots. - Put alternate labels in fixed-size slots.
- Reveal connection title, timer, and hint with opacity plus light blur, never vertical layout movement. - Reveal connection title, timer, and hint with overlapping fixed layers, opacity, and light blur, never vertical layout movement.
- Do not reanimate the timer every second; animate only its initial appearance. - Crossfade `VPN включён`, `Gateway подключён`, and disconnected copy in the same reserved slot when route state changes.
- Split changing numeric values into stable digits. On a tick such as `33 → 34`, keep the first `3` mounted and animate only `3 → 4` with a soft color/glow/blur flow; avoid low-opacity blinking or scaling the whole seconds value.
- Persist user-selected timer presentation locally and restore it on the next visit.
- Tween numeric traffic values from old to new with `requestAnimationFrame` or an equivalent stable counter. - Tween numeric traffic values from old to new with `requestAnimationFrame` or an equivalent stable counter.
- Animate progress width concurrently and add a brief glow that fully fades. - Animate progress width concurrently and add a brief glow that fully fades.
- Never translate changing numbers if the user asked for a fluid morph; use numerical interpolation, opacity, color, blur, and light. - Never translate changing numbers if the user asked for a fluid morph; use numerical interpolation, opacity, color, blur, and light.
## Mode switch affordance
- Treat the Connect/Gateway brand as one state control with a foreground label, a background label, and two independently colored direction arrows.
- On hover, move both labels continuously: let the foreground drift slightly down while the background rises toward it. Move the right arrow right and the left arrow left, then return; keep amplitudes small.
- When mode changes, swap the arrows' positions smoothly and bring the new label to the foreground without changing the brand's centered geometry.
- When hover ends, finish the current cycle at rest before stopping. Outside hover, replay one smaller cycle about every 10 seconds to hint that the control is clickable.
- Keep explanatory tooltip geometry tied to the mode-label-to-arrows span, not to the entire Harbor wordmark.
## Refresh ## Refresh
- Use a clean, symmetric SVG refresh icon aligned in the same flex row as its label. - Use a clean, symmetric SVG refresh icon aligned in the same flex row as its label.
@@ -37,7 +58,8 @@ Use exponential ease-out curves such as `cubic-bezier(0.16, 1, 0.3, 1)` for arri
- Update data immediately when it arrives; finishing the icon cycle must not delay the data. - Update data immediately when it arrives; finishing the icon cycle must not delay the data.
- Manual refresh may replay meaningful data and server transitions. - Manual refresh may replay meaningful data and server transitions.
- Background polling should update quietly and must not repeatedly replay the server cascade. - Background polling should update quietly and must not repeatedly replay the server cascade.
- On updated traffic, tween the number, advance the bar, and emit a visible but brief green flare. - On updated traffic, tween the number, advance the bar, and emit a visible but brief mode-accent flare.
- Keep refresh tooltip outside the rotating button so it remains upright and unfiltered.
## Server cascade ## Server cascade
@@ -52,9 +74,10 @@ Use exponential ease-out curves such as `cubic-bezier(0.16, 1, 0.3, 1)` for arri
- Show the public domain while retaining the full URL internally. - Show the public domain while retaining the full URL internally.
- Disable browser autocomplete suggestions and neutralize autofill backgrounds. - Disable browser autocomplete suggestions and neutralize autofill backgrounds.
- Hide the blinking caret when the paste-first interaction does not need it, while preserving keyboard input and focus outline. - Hide the blinking caret when the paste-first interaction does not need it, while preserving keyboard input and focus outline.
- When an existing subscription is being edited and the field is idle, use the green underline as a five-second timeout indicator: start bright, fade to quiet, then restore display mode. - When an existing subscription is being edited and the field is idle, use the mode-accent underline as a five-second timeout indicator: start bright, fade to quiet, then restore display mode.
- Pause the timeout once the user enters content. - Pause the timeout once the user enters content.
- Close and clear unfinished input on outside click or Escape. - Close and clear unfinished input on outside click or Escape.
- Animate the trash lid independently on hover. Open an inline, non-blocking confirmation surface instead of a browser-native confirm dialog.
## First-run initialization ## First-run initialization
@@ -68,7 +91,7 @@ Use exponential ease-out curves such as `cubic-bezier(0.16, 1, 0.3, 1)` for arri
- Keep protocol buttons fixed-size and centered. - Keep protocol buttons fixed-size and centered.
- Copy the complete protocol URL while showing a shared address separately. - Copy the complete protocol URL while showing a shared address separately.
- Overlay green `Copied` feedback in the same fixed box; do not append text or move the label. - Overlay mode-accent `Copied` feedback in the same fixed box; do not append text or move the label.
- Make feedback appear immediately, hold briefly, and fade fully before restoring the original label. Keep the whole cycle near 800 ms. - Make feedback appear immediately, hold briefly, and fade fully before restoring the original label. Keep the whole cycle near 800 ms.
## Reduced motion ## Reduced motion

View File

@@ -23,6 +23,8 @@ Design for a macOS user glancing at a small VPN control surface in a quiet deskt
- Preserve a generous invisible hit area around icon-only controls. - Preserve a generous invisible hit area around icon-only controls.
- Center proxy address and protocol actions with the power column. - Center proxy address and protocol actions with the power column.
- Treat one-pixel optical misalignment as a defect when controls sit beside uppercase labels. - Treat one-pixel optical misalignment as a defect when controls sit beside uppercase labels.
- Center the semantic brand or label independently from optional action icons. Place secondary icons beside it without letting their width move the centered content.
- Layer mutually exclusive status text in one fixed slot and crossfade between layers. Never replace text in normal flow when its length can move the interface.
## Typography ## Typography
@@ -35,10 +37,19 @@ Design for a macOS user glancing at a small VPN control surface in a quiet deskt
## Color and light ## Color and light
- Preserve green-tinted dark and light neutrals through the existing OKLCH variables. - Preserve green-tinted dark and light neutrals through the existing OKLCH variables.
- Inactive power stays neutral gray even on hover; active power becomes green. - Treat mode color as a system-wide state, not a logo-only decoration: Connect uses its blue-green token and Gateway uses its orange token for power, glow, selected rows, progress, copy/refresh feedback, focus, and mode-relevant labels.
- Use green for active VPN, selected server underline, progress, copy success, refresh success, and focus. - Inactive power stays neutral gray even on hover. Warning and destructive actions remain semantic red rather than inheriting the mode accent.
- Prefer localized `drop-shadow`, `text-shadow`, or a soft radial light layer over filled green containers. - Prefer localized `drop-shadow`, `text-shadow`, or a soft radial light layer over filled accent containers.
- Let glow support state recognition. Do not leave every element glowing continuously. - Let glow support state recognition. Do not leave every element glowing continuously.
- Give Connect and Gateway distinct favicons and brand marks using the same mode palette.
- When Gateway carries traffic, fade, desaturate, and disable the local subscription/server block: it remains understandable context but must not look active or actionable.
## Interactive surfaces
- Use one fast translucent cloud treatment for explanatory tooltips. Place the cloud above its target with strong enough contrast to survive busy content beneath it.
- Keep a tooltip as a sibling of the animated icon/button it describes. A tooltip must never rotate, glow, blur, scale, or move with the control.
- Use inline confirmation for destructive subscription removal. Explain that VPN stops and proxy-dependent applications lose connectivity until a new subscription is configured.
- Animate physical icon parts when their function suggests it, such as lifting a trash lid on hover, while keeping hit areas and nearby text fixed.
## Data presentation ## Data presentation