Design System
Motion
Motion that means something, with no bounce and no spring.
On this page
Motion is functional. Every animation either signals a state change, draws the operator’s eye to something new, or indicates that the system is live. Motion is never decorative - there are no flourishes, no overshoots, no reveals for their own sake.
Tempo
Different categories of change earn different durations. The scale is tight on purpose - fast enough that an operator doesn’t wait, slow enough that the change registers.
| Change class | Duration | Examples |
|---|---|---|
| UI state change | 150–250ms | button hover, toggle flip, focus ring, tab switch |
| Panel entrance / exit | 400–600ms | drawer open, sheet appear, modal in, banner dismiss |
| Ambient signal | 1–2s | radar sweep rotation, live pulse, sonar ping |
Easing
Two curves cover the system.
- UI motion uses
cubic-bezier(0.2, 0.8, 0.2, 1)- fast out, gentle settle. Applies to everything in the 150–600ms range. - Tactical simulations use linear easing. Radar sweeps, clock ticks, countdown bars - these represent physical-world processes that don’t ease in and out. Linear is the honest choice.
Signature motions
Four motions appear often enough across the system to earn their own names. Learn them; reuse them; don’t invent new ones without a design review.
| Name | Duration | Behavior |
|---|---|---|
| Acquire | 200ms, ease-out | Electric-blue brackets [ ] snap inward around a target. --fdt-glow-md blooms at the end of the snap and settles to --fdt-glow-sm over the following 300ms. Used when a track is locked, a unit is selected, or a decision is committed. |
| Sweep | 2s, linear, loop | Radar sweep in electric-500 rotates continuously. A 30° trail follows at 30% opacity, peaking at --fdt-glow-md. The trail never fully clears - the sweep is the system telling the operator it’s awake. |
| Panel slide | 350ms, ease-out | Translucent panel translates in from the viewport edge while fading in. Exit reverses. No overshoot. Drawers, sheets, and mobile HUD collapses all use this. |
| Live pulse | 1.2s, sinusoidal | Opacity-only pulse on --fdt-glow-sm. Never touches scale or translate. Applied to elements actively receiving data - contacts refreshing, incoming feeds, live telemetry. |
Hard rules
- No bounce, no overshoot, no spring physics. The system is not playful. Elements arrive at their target position and stop.
- Opacity is the default channel for “live” signals. Scale and translate are reserved for state changes, not for signalling activity.
- Motion respects
prefers-reduced-motion: reduce. See Accessibility for the full fallback behavior - radar sweep freezes, live pulse holds at 100%, panel slides become 150ms crossfades.