6.9 KiB
ZeroClock Motion System Design
Personality
Fluid & organic: spring-based easing with slight overshoot, 200-350ms durations for primary animations, 100-150ms for micro-interactions. The app should feel alive and responsive without being distracting.
Technology
@vueuse/motion for declarative spring-physics animations via v-motion directives and useMotion() composable. Vue's built-in <Transition> and <TransitionGroup> for enter/leave orchestration. CSS keyframes for ambient/looping animations (pulse, shimmer, float).
Spring Presets
Define reusable spring configs in a src/utils/motion.ts module:
- snappy:
{ damping: 20, stiffness: 300 }— buttons, toggles, small elements - smooth:
{ damping: 15, stiffness: 200 }— page transitions, modals, cards - popIn:
{ damping: 12, stiffness: 400 }— tag chips, badges, notifications
1. Page Transitions
Wrap <router-view> in App.vue with <router-view v-slot="{ Component }"> + <Transition>:
- Leave: opacity 1 -> 0, translateY 0 -> -8px, 150ms ease-out
- Enter: opacity 0 -> 1, translateY 8px -> 0, spring
smoothpreset - Mode:
out-into prevent layout overlap in the flex container
CSS classes: .page-enter-active, .page-leave-active, .page-enter-from, .page-leave-to
2. List Animations
Entry rows, project cards, client cards
Use <TransitionGroup> with staggered enter:
- Enter: opacity 0 -> 1, translateY 12px -> 0, spring
smooth, stagger 30ms per item (viatransition-delaycomputed from index) - Leave: opacity 1 -> 0, translateX 0 -> -20px, 150ms ease-in
- Move:
transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1)for reorder
CSS classes: .list-enter-active, .list-leave-active, .list-move, .list-enter-from, .list-leave-to
Tag chips
- Enter: scale 0.8 -> 1, opacity 0 -> 1, spring
popIn - Leave: scale 1 -> 0.8, opacity 1 -> 0, 100ms
Favorites strip pills
- Enter: translateX -10px -> 0, opacity 0 -> 1, stagger 50ms
- Leave: scale 1 -> 0, opacity 0, 100ms
Recent entries in Timer
- Enter: opacity 0 -> 1, translateX -8px -> 0, stagger 40ms
3. Button & Interactive Feedback
Primary buttons (Start/Stop, Generate, Save, Import)
:hover—scale(1.02), subtle shadow lift, springsnappy:active—scale(0.97), shadow compress, instant (no delay)- Release — spring back to
scale(1)viasnappypreset
Icon buttons (edit, delete, copy, star, repeat)
:active—scale(0.85), spring back- Delete icons:
rotate(-10deg)on active for a "shake" feel
Nav rail active indicator
Currently the left border appears instantly. Change to:
- Slide the 2px accent bar vertically to match the new active item position
- Use a dedicated
<div>withv-motionand absolute positioning keyed tocurrentPath - Spring
smoothpreset
Toggle switches
- Add
cubic-bezier(0.34, 1.56, 0.64, 1)(overshoot) to the thumb'stransition-transform - Duration: 200ms (up from 150ms)
Project/client cards
:hover—translateY(-1px), shadow elevation increase, 200ms:active—translateY(0), shadow compress
Accent color picker dots in Settings
- Selected dot:
scale(1.15)with spring - Hover:
scale(1.1)
4. Loading & Empty States
Skeleton shimmer
New CSS keyframe shimmer: a gradient highlight sweeping left-to-right on placeholder blocks. Apply via .skeleton utility class.
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
Content fade-in
When data loads (entries, projects, reports), wrap the content area in a <Transition>:
opacity: 0->1,translateY: 4px->0, springsmooth- Show skeleton while
loadingref is true, transition to real content
Empty state icons
Add gentle vertical float animation to the empty state icons (Timer, BarChart3):
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
Duration: 3s, ease-in-out, infinite.
5. Modal & Dropdown Polish
Modals
Replace animate-modal-enter keyframe with spring-based:
- Backdrop enter: opacity 0 -> 1, 200ms ease-out
- Backdrop leave: opacity 1 -> 0, 150ms ease-in
- Panel enter: scale 0.95 -> 1, opacity 0 -> 1, spring
smooth - Panel leave: scale 1 -> 0.97, opacity 1 -> 0, 150ms ease-in
Use Vue <Transition> on the modal's v-if to get proper leave animations (currently missing — modals just vanish).
Dropdowns (AppSelect, AppTagInput, AppDatePicker)
Replace animate-dropdown-enter with spring:
- Enter: scale 0.95 -> 1, opacity 0 -> 1, translateY -4px -> 0, spring
snappy - Leave: opacity 1 -> 0, 100ms (fast close)
Toast notifications
Keep existing enter/exit keyframes but add slight horizontal slide:
- Enter: translateY -20px -> 0, translateX 10px -> 0 (slide from top-right)
- Exit: opacity fade + translateY -10px
6. Timer-Specific Animations
Timer start
When the timer transitions from STOPPED to RUNNING:
- The time display pulses once (scale 1 -> 1.03 -> 1) via spring
- The Start button morphs to Stop (color transition already exists, add scale pulse)
Timer stop
When the timer stops:
- Brief "completed" flash — the time display gets a subtle glow/highlight that fades
Progress bars (goals, budgets)
- Animate width from 0 to target value on mount, 600ms with spring
smooth - Use CSS
transition: width 600ms cubic-bezier(0.22, 1, 0.36, 1)
Files to Modify
package.json— add@vueuse/motionsrc/main.ts— registerMotionPluginsrc/utils/motion.ts— new, spring preset definitionssrc/styles/main.css— new keyframes (shimmer, float), update existing keyframes, add transition utility classessrc/App.vue— page transition wrapper on<router-view>src/components/NavRail.vue— animated active indicatorsrc/components/AppSelect.vue— dropdown leave animationsrc/components/AppTagInput.vue— tag chip enter/leave transitionssrc/components/ToastNotification.vue— enhanced enter/exitsrc/views/Timer.vue— timer start/stop animations, list transition on recent entriessrc/views/Entries.vue— TransitionGroup on entry rowssrc/views/Projects.vue— TransitionGroup on project cards, modal transitionssrc/views/Clients.vue— TransitionGroup on client cards, modal transitionssrc/views/Dashboard.vue— content fade-in, progress bar animationsrc/views/Reports.vue— content fade-in on tab switchsrc/views/Settings.vue— modal leave transitionsrc/views/CalendarView.vue— entry block fade-in on week changesrc/views/TimesheetView.vue— row fade-in on data load
Verification
npm run buildpasses with no errors- Page transitions feel smooth, no layout flashing
- List add/remove animations don't cause scroll jumps
- Modals have proper enter AND leave animations
- No animation on
prefers-reduced-motion: reduce