Alveon Studio

Motion Design System

Spring-based animation rules for a professional production tool. Every transition serves the operator — physics-correct, interruptible, and respectful of accessibility preferences.

v2 — Spring-based · Apple HIG aligned
01
Physics, not curves
All animations use SwiftUI springs. Springs preserve velocity when interrupted — Bézier curves don't. The interface never jerks.
02
Always interruptible
Every animation can be reversed mid-flight. If the operator opens then immediately closes a panel, the spring catches the velocity and reverses smoothly.
03
One axis, one property
Elements enter on one axis with one transform. Y-translate + opacity. Scale + opacity. Never diagonal, never rotational, never theatrical.
04
Functional only
Animation communicates state change — what appeared, what moved, what completed. If it doesn't answer a question, remove it.
05
Reduce Motion first
Every animation has a cross-fade fallback. When the system Reduce Motion setting is on, slides become fades, scales become fades, durations shorten.
06
Faster than thought
Perceptual duration stays under 350ms. The operator should never wait for the interface to finish animating.
Two SwiftUI spring presets cover the entire app. No custom springs, no Bézier timing curves, no bouncy presets. Alveon uses only critically-damped and slightly-underdamped springs — professional tools don't bounce.
.smooth
duration: 0.5 · bounce: 0 · critically damped
The default for everything. Panels, content transitions, stage changes, modals. Settles without overshoot — direct and precise.
default
.snappy
duration: 0.5 · bounce: 0 · higher stiffness
For fast micro-interactions. Hovers, button presses, toggles, tooltip appear/dismiss. Reaches target faster than .smooth.
fast interactions
.bouncy
Not used in Alveon Studio
Bounce implies playfulness. Alveon is a precision production tool — every millimeter matters. Overshoot on a measurement readout or a slider value would undermine trust.
excluded
Why springs — velocity preservation
Drag the ball and release. The spring picks up whatever velocity you gave it. A Bézier curve ignores your gesture velocity and always plays the same fixed motion.
Spring (velocity preserved)
Bézier (velocity ignored)
Click a ball to animate it to the other side
// SwiftUI Spring Tokens — Alveon Studio extension Animation { // Default: panels, content, stage transitions, modals static let alveon = Animation.smooth(duration: 0.22) // Fast: hover, press, toggle, tooltip static let alveonFast = Animation.snappy(duration: 0.15) // Emphasis: modals, stage changes, full transitions static let alveonEmphasis = Animation.smooth(duration: 0.35) } // Usage withAnimation(.alveon) { showInspector = true } // Interruptible by default — just call again: withAnimation(.alveon) { showInspector = false // reverses mid-flight, preserving velocity }
Springs don't have a fixed duration — they settle naturally. These are the perceptual durations passed to the spring constructor. The actual settling time is slightly longer, but imperceptible.
80
Instant
Hover states, active presses, checkbox ticks, color changes
150
Quick
Tooltips, dropdowns, small state changes, toggle switches
220
Normal
Panel slides, content transitions, card enters, toasts, lists
350
Emphasis
Modals, full-screen transitions, stage changes, viewport shifts
Spring comparison — click any duration above
.smooth
.snappy
// Named duration tokens enum MotionDuration { static let instant: Double = 0.08 static let quick: Double = 0.15 static let normal: Double = 0.22 static let emphasis: Double = 0.35 }
Every animation in the app is one of these patterns. Click each card to replay.
Fade
opacity 0→1 · .snappy(0.15)
Tooltip content
Slide up + fade
translateY(8px)→0 + opacity · .smooth(0.22)
Card content
Scale + fade
scale(0.96)→1 + opacity · .smooth(0.35)
Release to Production?
Slide horizontal
translateX(12px)→0 + opacity · .smooth(0.22)
Scan
Last
Stagger list
Each item +40ms delay · slideUp + fade · .smooth(0.22)
How each pattern maps to real UI elements in Alveon Studio.
Toast notification
Boot released to production
Mesh validation failed
// Enter withAnimation(.alveon) { showToast = true } // translateY(-8)→0, opacity 0→1 // Exit (auto-dismiss after 3s) withAnimation(.alveonFast) { showToast = false } // opacity 1→0, translateY(0)→-8
Inspector panel
3D
Inspector
Length
267.4 mm
Width
98.1 mm
Arch
Normal
Quality
Good
// Panel width — spring handles interruption withAnimation(.alveon) { showInspector.toggle() } // Content fades in after panel opens withAnimation(.alveonFast.delay(0.1)) { contentVisible = true }
Pipeline stage transition
Stage progress
Last
// Stage change uses emphasis duration withAnimation(.alveonEmphasis) { currentStage = .sole }
Confirmation dialog
// Show withAnimation(.alveonEmphasis) { showModal = true } // scale(0.96)→1, opacity 0→1, backdrop fades in // Dismiss withAnimation(.alveonFast) { showModal = false } // Exits are always fast — operator made a decision
When the macOS "Reduce motion" accessibility setting is enabled, all spatial animations (slide, scale) collapse to simple cross-fades. This is not optional — it's a requirement.
Standard
Inspector panel slides in
Toast slides down
Modal scales up
Reduce Motion
Inspector cross-fades in
Toast cross-fades in
Modal cross-fades in
// Reduce Motion helper struct AlveonTransition { static func slideUp() -> AnyTransition { if UIAccessibility.isReduceMotionEnabled { return .opacity } return .move(edge: .bottom) .combined(with: .opacity) } static func scale() -> AnyTransition { if UIAccessibility.isReduceMotionEnabled { return .opacity } return .scale(scale: 0.96) .combined(with: .opacity) } } // Or use the environment value in SwiftUI @Environment(\.accessibilityReduceMotion) var reduceMotion
Element Pattern Spring Duration Reduce Motion
Hover state Background / border color .snappy 80ms Keep as-is
Button press scale(0.98) + opacity .snappy 80ms Opacity only
Tooltip Fade in .snappy 150ms Keep as-is
Dropdown Fade + slideUp(4px) .snappy 150ms Fade only
Toast SlideDown(8px) + fade .smooth 220ms Fade only
Inspector panel Width expand + content fade .smooth 220ms Fade only
Player list Stagger items (40ms) .smooth 220ms each Fade, no stagger
Stage change Dot color + bar width .smooth 350ms Keep as-is (color only)
Modal dialog Scale(0.96→1) + backdrop .smooth 350ms / 150ms Fade only
View transition Cross-fade .smooth 220ms Keep as-is
Scan line pulse Opacity 0.06→0.2→0.06 .smooth 350ms Keep as-is
Progress bar Width change .smooth 350ms Keep as-is
Sidebar expand Width 56→200px + fade .smooth 220ms Fade only
Connection bar SlideDown from top .smooth 220ms Fade only

Do

  • Use .smooth as the default spring
  • Use .snappy for micro-interactions
  • Keep perceptual durations ≤ 350ms
  • Stagger lists at 40ms intervals
  • Animate opacity + one transform only
  • Make exits faster than entrances
  • Fade content in after its container
  • Check @Environment(\.accessibilityReduceMotion)
  • Provide cross-fade fallback for all spatial motion
  • Test every animation with Reduce Motion on

Don't

  • Use .bouncy — no overshoot in production tools
  • Use Bézier timing curves (.easeIn, .easeOut)
  • Custom springs — only .smooth and .snappy
  • Blur transitions (performance, accessibility)
  • Diagonal or rotational movement
  • Chain more than 2 sequential animations
  • Delay anything more than 150ms
  • Animate layout properties (except panel width)
  • Assume motion is visible — always Reduce Motion
  • Auto-playing or looping animations