diff --git a/src/App.jsx b/src/App.jsx
index 2587f7c..a47bc51 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -5,6 +5,7 @@ import { useCallback, useEffect } from 'react'
import { Outlet, useNavigate } from 'react-router-dom'
import { useRegisterSW } from 'virtual:pwa-register/react'
import { registerCapacitorListeners } from './CapacitorListener'
+import PageTransition from './components/animations/PageTransition'
import { ImpersonateUserProvider } from './contexts/ImpersonateUserContext'
import { AuthenticationProvider } from './service/AuthenticationService'
import { useNotification } from './service/NotificationProvider'
@@ -74,7 +75,9 @@ const AppContent = () => {
<>
-
+
+
+
>
)
diff --git a/src/components/animations/AnimatedList.jsx b/src/components/animations/AnimatedList.jsx
new file mode 100644
index 0000000..d18fff6
--- /dev/null
+++ b/src/components/animations/AnimatedList.jsx
@@ -0,0 +1,88 @@
+import React from 'react'
+import { Box } from '@mui/joy'
+import { CSSTransition, TransitionGroup } from 'react-transition-group'
+import { useStaggeredAnimation, useReducedMotion } from '../../hooks/useAnimations'
+import './PageTransition.css'
+
+const AnimatedList = ({
+ children,
+ staggerDelay = 50,
+ animationType = 'stagger', // 'stagger', 'fade', 'slide'
+ direction = 'up', // 'up', 'down', 'left', 'right'
+ renderItem,
+ keyExtractor,
+ items,
+ ...boxProps
+}) => {
+ // Handle both children and items patterns
+ let childrenArray
+ if (items && renderItem) {
+ childrenArray = items.map((item, index) =>
+ React.cloneElement(renderItem(item, index), {
+ key: keyExtractor ? keyExtractor(item, index) : index
+ })
+ )
+ } else {
+ childrenArray = React.Children.toArray(children)
+ }
+
+ const visibleItems = useStaggeredAnimation(childrenArray.length, staggerDelay)
+ const prefersReducedMotion = useReducedMotion()
+
+ // If user prefers reduced motion, render without animations
+ if (prefersReducedMotion) {
+ return (
+
+ {items && renderItem ? childrenArray : children}
+
+ )
+ }
+
+ const getAnimationClass = () => {
+ switch (animationType) {
+ case 'fade':
+ return 'fade'
+ case 'slide':
+ return direction === 'up' ? 'slide-up' : 'page'
+ case 'stagger':
+ default:
+ return 'stagger'
+ }
+ }
+
+ return (
+
+
+ {childrenArray.map((child, index) => {
+ const isVisible = visibleItems.has(index)
+
+ return (
+
+
+ {child}
+
+
+ )
+ })}
+
+
+ )
+}
+
+export default AnimatedList
diff --git a/src/components/animations/LoadingScreen.jsx b/src/components/animations/LoadingScreen.jsx
new file mode 100644
index 0000000..6ee18c9
--- /dev/null
+++ b/src/components/animations/LoadingScreen.jsx
@@ -0,0 +1,105 @@
+import { Box, CircularProgress, Typography } from '@mui/joy'
+import { styled } from '@mui/joy/styles'
+
+// Styled components with keyframe animations
+const LoadingContainer = styled(Box)({
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
+ backdropFilter: 'blur(8px)',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ zIndex: 9999,
+ animation: 'fadeIn 0.3s ease-out',
+ '@keyframes fadeIn': {
+ from: {
+ opacity: 0,
+ },
+ to: {
+ opacity: 1,
+ },
+ },
+})
+
+const LoadingContent = styled(Box)({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ gap: '24px',
+ animation: 'slideUp 0.5s ease-out 0.2s both',
+ '@keyframes slideUp': {
+ from: {
+ opacity: 0,
+ transform: 'translateY(20px)',
+ },
+ to: {
+ opacity: 1,
+ transform: 'translateY(0)',
+ },
+ },
+})
+
+const PulsingText = styled(Typography)({
+ animation: 'pulse 2s ease-in-out infinite',
+ '@keyframes pulse': {
+ '0%, 100%': {
+ opacity: 1,
+ },
+ '50%': {
+ opacity: 0.5,
+ },
+ },
+})
+
+const LogoContainer = styled(Box)({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ marginBottom: '24px',
+})
+
+const LoadingScreen = ({
+ message = 'Loading...',
+ showLogo = true,
+ size = 'lg',
+}) => {
+ return (
+
+
+ {showLogo && (
+
+
+ Done
+ tick
+
+
+ )}
+
+
+
+ {message}
+
+
+ )
+}
+
+export default LoadingScreen
diff --git a/src/components/animations/PageTransition.css b/src/components/animations/PageTransition.css
new file mode 100644
index 0000000..afc073b
--- /dev/null
+++ b/src/components/animations/PageTransition.css
@@ -0,0 +1,208 @@
+/* Page Transition Animations */
+.page-wrapper {
+ position: relative;
+ width: 100%;
+ min-height: 100vh;
+}
+
+/* Enter animations */
+.page-enter {
+ opacity: 0;
+ transform: translateX(20px);
+}
+
+.page-enter-active {
+ opacity: 1;
+ transform: translateX(0);
+ transition: opacity 300ms ease-out, transform 300ms ease-out;
+}
+
+/* Exit animations */
+.page-exit {
+ opacity: 1;
+ transform: translateX(0);
+}
+
+.page-exit-active {
+ opacity: 0;
+ transform: translateX(-20px);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+}
+
+/* Specific animation for back navigation */
+.page-back-enter {
+ opacity: 0;
+ transform: translateX(-20px);
+}
+
+.page-back-enter-active {
+ opacity: 1;
+ transform: translateX(0);
+ transition: opacity 300ms ease-out, transform 300ms ease-out;
+}
+
+.page-back-exit {
+ opacity: 1;
+ transform: translateX(0);
+}
+
+.page-back-exit-active {
+ opacity: 0;
+ transform: translateX(20px);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+}
+
+/* Modal/overlay animations */
+.modal-enter {
+ opacity: 0;
+ transform: scale(0.95) translateY(10px);
+}
+
+.modal-enter-active {
+ opacity: 1;
+ transform: scale(1) translateY(0);
+ transition: opacity 250ms ease-out, transform 250ms ease-out;
+}
+
+.modal-exit {
+ opacity: 1;
+ transform: scale(1) translateY(0);
+}
+
+.modal-exit-active {
+ opacity: 0;
+ transform: scale(0.95) translateY(10px);
+ transition: opacity 200ms ease-in, transform 200ms ease-in;
+}
+
+/* Fade animation for simple transitions */
+.fade-enter {
+ opacity: 0;
+}
+
+.fade-enter-active {
+ opacity: 1;
+ transition: opacity 250ms ease-out;
+}
+
+.fade-exit {
+ opacity: 1;
+}
+
+.fade-exit-active {
+ opacity: 0;
+ transition: opacity 200ms ease-in;
+}
+
+/* Slide up animation for bottom sheets/panels */
+.slide-up-enter {
+ opacity: 0;
+ transform: translateY(100%);
+}
+
+.slide-up-enter-active {
+ opacity: 1;
+ transform: translateY(0);
+ transition: opacity 300ms ease-out, transform 300ms ease-out;
+}
+
+.slide-up-exit {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.slide-up-exit-active {
+ opacity: 0;
+ transform: translateY(100%);
+ transition: opacity 250ms ease-in, transform 250ms ease-in;
+}
+
+/* Loading spinner animation */
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.loading-spinner {
+ animation: spin 1s linear infinite;
+}
+
+/* Skeleton loading animations */
+@keyframes skeleton-loading {
+ 0% {
+ background-position: -200px 0;
+ }
+ 100% {
+ background-position: calc(200px + 100%) 0;
+ }
+}
+
+.skeleton {
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
+ background-size: 200px 100%;
+ animation: skeleton-loading 1.5s infinite;
+}
+
+/* Micro-interactions */
+.interactive-element {
+ transition: all 0.2s ease-out;
+ transform: translateZ(0); /* Enable GPU acceleration */
+}
+
+.interactive-element:hover {
+ transform: translateY(-2px) translateZ(0);
+}
+
+.interactive-element:active {
+ transform: translateY(0) translateZ(0);
+ transition: all 0.1s ease-out;
+}
+
+/* Stagger animation for lists */
+.stagger-enter {
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+.stagger-enter-active {
+ opacity: 1;
+ transform: translateY(0);
+ transition: opacity 300ms ease-out, transform 300ms ease-out;
+}
+
+/* Add animation delays for staggered effects */
+.stagger-enter-active:nth-child(1) { transition-delay: 0ms; }
+.stagger-enter-active:nth-child(2) { transition-delay: 50ms; }
+.stagger-enter-active:nth-child(3) { transition-delay: 100ms; }
+.stagger-enter-active:nth-child(4) { transition-delay: 150ms; }
+.stagger-enter-active:nth-child(5) { transition-delay: 200ms; }
+.stagger-enter-active:nth-child(6) { transition-delay: 250ms; }
+.stagger-enter-active:nth-child(7) { transition-delay: 300ms; }
+.stagger-enter-active:nth-child(8) { transition-delay: 350ms; }
+
+/* Reduced motion for accessibility */
+@media (prefers-reduced-motion: reduce) {
+ .page-enter,
+ .page-enter-active,
+ .page-exit,
+ .page-exit-active,
+ .modal-enter,
+ .modal-enter-active,
+ .modal-exit,
+ .modal-exit-active,
+ .fade-enter,
+ .fade-enter-active,
+ .fade-exit,
+ .fade-exit-active,
+ .interactive-element,
+ .stagger-enter,
+ .stagger-enter-active {
+ transition: none !important;
+ animation: none !important;
+ transform: none !important;
+ }
+}
diff --git a/src/components/animations/PageTransition.jsx b/src/components/animations/PageTransition.jsx
new file mode 100644
index 0000000..b2f714f
--- /dev/null
+++ b/src/components/animations/PageTransition.jsx
@@ -0,0 +1,88 @@
+import React, { useEffect, useRef } from 'react'
+import { CSSTransition, TransitionGroup } from 'react-transition-group'
+import { useLocation } from 'react-router-dom'
+import './PageTransition.css'
+
+// Route hierarchy for determining navigation direction
+const routeHierarchy = {
+ '/': 0,
+ '/my/chores': 0,
+ '/chores': 1,
+ '/chores/create': 2,
+ '/settings': 1,
+ '/things': 1,
+ '/activities': 1,
+ '/points': 1,
+ '/labels': 1,
+ '/login': 0,
+ '/signup': 1,
+ '/landing': 0,
+}
+
+const getRouteLevel = pathname => {
+ // Check for exact matches first
+ if (routeHierarchy[pathname] !== undefined) {
+ return routeHierarchy[pathname]
+ }
+
+ // Check for dynamic routes (e.g., /chores/123/edit)
+ if (pathname.includes('/chores/') && pathname.includes('/edit')) {
+ return 3
+ }
+ if (pathname.includes('/chores/') && pathname.includes('/history')) {
+ return 3
+ }
+ if (pathname.includes('/chores/') && !pathname.includes('/edit') && !pathname.includes('/history')) {
+ return 2
+ }
+ if (pathname.includes('/things/')) {
+ return 2
+ }
+
+ // Default level
+ return 1
+}
+
+const PageTransition = ({ children }) => {
+ const location = useLocation()
+ const prevLocation = useRef(location)
+ const isNavigatingBack = useRef(false)
+
+ useEffect(() => {
+ const currentLevel = getRouteLevel(location.pathname)
+ const previousLevel = getRouteLevel(prevLocation.current.pathname)
+
+ // Determine if we're navigating back (to a higher level in hierarchy)
+ isNavigatingBack.current = currentLevel < previousLevel
+
+ prevLocation.current = location
+ }, [location])
+
+ const getTransitionClasses = () => {
+ if (location.pathname.includes('/login') || location.pathname.includes('/signup') || location.pathname.includes('/landing')) {
+ return 'fade' // Use fade for auth pages
+ }
+
+ return isNavigatingBack.current ? 'page-back' : 'page'
+ }
+
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
+
+export default PageTransition
diff --git a/src/components/animations/SkeletonLoader.jsx b/src/components/animations/SkeletonLoader.jsx
new file mode 100644
index 0000000..244bddf
--- /dev/null
+++ b/src/components/animations/SkeletonLoader.jsx
@@ -0,0 +1,138 @@
+import { Box, Skeleton } from '@mui/joy'
+
+const SkeletonLoader = ({
+ type = 'card',
+ count = 1,
+ height = 100,
+ width = '100%',
+ variant = 'rectangular',
+ ...props
+}) => {
+ const renderSkeleton = () => {
+ switch (type) {
+ case 'card':
+ return (
+
+
+
+
+
+
+
+
+
+ )
+
+ case 'list':
+ return (
+
+
+
+
+
+
+
+
+ )
+
+ case 'chore':
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+
+ case 'profile':
+ return (
+
+
+
+
+
+ )
+
+ default:
+ return (
+
+ )
+ }
+ }
+
+ return (
+ <>
+ {Array.from({ length: count }, (_, index) => (
+
+ {renderSkeleton()}
+
+ ))}
+ >
+ )
+}
+
+export default SkeletonLoader
diff --git a/src/components/animations/SmoothButton.jsx b/src/components/animations/SmoothButton.jsx
new file mode 100644
index 0000000..25d82fb
--- /dev/null
+++ b/src/components/animations/SmoothButton.jsx
@@ -0,0 +1,107 @@
+import { Button } from '@mui/joy'
+import { styled } from '@mui/joy/styles'
+
+const AnimatedButton = styled(Button)(({ theme }) => ({
+ transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
+ transform: 'translateZ(0)', // Enable GPU acceleration
+ position: 'relative',
+ overflow: 'hidden',
+
+ '&:hover:not(:disabled)': {
+ transform: 'translateY(-2px) translateZ(0)',
+ boxShadow: theme.shadow.lg,
+ },
+
+ '&:active:not(:disabled)': {
+ transform: 'translateY(0) translateZ(0)',
+ transition: 'all 0.1s cubic-bezier(0.4, 0, 0.2, 1)',
+ },
+
+ '&:focus-visible': {
+ outline: '2px solid',
+ outlineColor: theme.palette.primary[500],
+ outlineOffset: '2px',
+ },
+
+ // Ripple effect
+ '&::before': {
+ content: '""',
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ width: '0',
+ height: '0',
+ borderRadius: '50%',
+ background: 'currentColor',
+ opacity: 0.1,
+ transform: 'translate(-50%, -50%)',
+ transition: 'width 0.6s, height 0.6s',
+ },
+
+ '&:active::before': {
+ width: '300px',
+ height: '300px',
+ },
+
+ // Loading state
+ '&[data-loading="true"]': {
+ pointerEvents: 'none',
+ position: 'relative',
+
+ '& > *': {
+ opacity: 0.6,
+ },
+
+ '&::after': {
+ content: '""',
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ width: '16px',
+ height: '16px',
+ border: '2px solid currentColor',
+ borderTop: '2px solid transparent',
+ borderRadius: '50%',
+ transform: 'translate(-50%, -50%)',
+ animation: 'spin 1s linear infinite',
+ },
+ },
+
+ // Reduced motion support
+ '@media (prefers-reduced-motion: reduce)': {
+ transition: 'none',
+ transform: 'none !important',
+
+ '&:hover:not(:disabled)': {
+ transform: 'none',
+ },
+
+ '&:active:not(:disabled)': {
+ transform: 'none',
+ },
+
+ '&::before': {
+ display: 'none',
+ },
+ },
+}))
+
+const SmoothButton = ({ children, loading = false, onClick, ...props }) => {
+ const handleClick = event => {
+ if (loading) return
+ onClick?.(event)
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+export default SmoothButton
diff --git a/src/components/animations/SmoothCard.jsx b/src/components/animations/SmoothCard.jsx
new file mode 100644
index 0000000..9d26f1e
--- /dev/null
+++ b/src/components/animations/SmoothCard.jsx
@@ -0,0 +1,88 @@
+import React from 'react'
+import { Card } from '@mui/joy'
+import { styled } from '@mui/joy/styles'
+
+const AnimatedCard = styled(Card)(({ theme }) => ({
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
+ transform: 'translateZ(0)', // Enable GPU acceleration
+ cursor: 'pointer',
+ position: 'relative',
+
+ '&:hover': {
+ transform: 'translateY(-4px) translateZ(0)',
+ boxShadow: theme.shadow.lg,
+ },
+
+ '&:active': {
+ transform: 'translateY(-2px) translateZ(0)',
+ transition: 'all 0.1s cubic-bezier(0.4, 0, 0.2, 1)',
+ },
+
+ // Subtle background animation on hover
+ '&::before': {
+ content: '""',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ background: 'linear-gradient(45deg, transparent, rgba(255,255,255,0.1), transparent)',
+ opacity: 0,
+ transition: 'opacity 0.3s ease',
+ pointerEvents: 'none',
+ borderRadius: 'inherit',
+ },
+
+ '&:hover::before': {
+ opacity: 1,
+ },
+
+ // Focus states for accessibility
+ '&:focus-visible': {
+ outline: '2px solid',
+ outlineColor: theme.palette.primary[500],
+ outlineOffset: '2px',
+ },
+
+ // Reduced motion support
+ '@media (prefers-reduced-motion: reduce)': {
+ transition: 'none',
+ transform: 'none !important',
+
+ '&:hover': {
+ transform: 'none',
+ boxShadow: theme.shadow.md, // Still provide visual feedback
+ },
+
+ '&:active': {
+ transform: 'none',
+ },
+
+ '&::before': {
+ display: 'none',
+ },
+ },
+}))
+
+const SmoothCard = ({
+ children,
+ onClick,
+ animationDisabled = false,
+ ...props
+}) => {
+ if (animationDisabled) {
+ return (
+
+ {children}
+
+ )
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+export default SmoothCard
diff --git a/src/components/animations/StaggeredList.jsx b/src/components/animations/StaggeredList.jsx
new file mode 100644
index 0000000..32970c5
--- /dev/null
+++ b/src/components/animations/StaggeredList.jsx
@@ -0,0 +1,54 @@
+import { Box } from '@mui/joy'
+import React, { useEffect, useState } from 'react'
+import { CSSTransition, TransitionGroup } from 'react-transition-group'
+import './PageTransition.css'
+
+const StaggeredList = ({
+ children,
+ staggerDelay = 50,
+ initialDelay = 0,
+ animate = true,
+}) => {
+ const [isVisible, setIsVisible] = useState(!animate)
+
+ useEffect(() => {
+ if (animate) {
+ const timer = setTimeout(() => {
+ setIsVisible(true)
+ }, initialDelay)
+
+ return () => clearTimeout(timer)
+ }
+ }, [animate, initialDelay])
+
+ if (!animate) {
+ return {children}
+ }
+
+ const childrenArray = React.Children.toArray(children)
+
+ return (
+
+
+ {isVisible &&
+ childrenArray.map((child, index) => (
+
+ {child}
+
+ ))}
+
+
+ )
+}
+
+export default StaggeredList
diff --git a/src/components/animations/index.js b/src/components/animations/index.js
new file mode 100644
index 0000000..54af463
--- /dev/null
+++ b/src/components/animations/index.js
@@ -0,0 +1,10 @@
+// Animation Components
+export { default as AnimatedList } from './AnimatedList'
+export { default as LoadingScreen } from './LoadingScreen'
+export { default as PageTransition } from './PageTransition'
+export { default as SmoothButton } from './SmoothButton'
+export { default as SmoothCard } from './SmoothCard'
+export { default as StaggeredList } from './StaggeredList'
+
+// Animation Styles
+import './PageTransition.css'
diff --git a/src/hooks/useAnimations.js b/src/hooks/useAnimations.js
new file mode 100644
index 0000000..7ecc5b2
--- /dev/null
+++ b/src/hooks/useAnimations.js
@@ -0,0 +1,90 @@
+import { useEffect, useState } from 'react'
+
+// Hook to detect user's motion preferences
+export const useReducedMotion = () => {
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(false)
+
+ useEffect(() => {
+ const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
+ setPrefersReducedMotion(mediaQuery.matches)
+
+ const handleChange = (event) => {
+ setPrefersReducedMotion(event.matches)
+ }
+
+ mediaQuery.addEventListener('change', handleChange)
+ return () => mediaQuery.removeEventListener('change', handleChange)
+ }, [])
+
+ return prefersReducedMotion
+}
+
+// Hook for staggered animations
+export const useStaggeredAnimation = (itemCount, delay = 50) => {
+ const [visibleItems, setVisibleItems] = useState(new Set())
+ const prefersReducedMotion = useReducedMotion()
+
+ useEffect(() => {
+ if (prefersReducedMotion) {
+ // Show all items immediately if reduced motion is preferred
+ setVisibleItems(new Set(Array.from({ length: itemCount }, (_, i) => i)))
+ return
+ }
+
+ const timeouts = []
+
+ // Stagger the appearance of items
+ for (let i = 0; i < itemCount; i++) {
+ const timeout = setTimeout(() => {
+ setVisibleItems(prev => new Set([...prev, i]))
+ }, i * delay)
+
+ timeouts.push(timeout)
+ }
+
+ return () => {
+ timeouts.forEach(clearTimeout)
+ }
+ }, [itemCount, delay, prefersReducedMotion])
+
+ return visibleItems
+}
+
+// Hook for intersection observer animations
+export const useInViewAnimation = (threshold = 0.1) => {
+ const [isInView, setIsInView] = useState(false)
+ const [element, setElement] = useState(null)
+
+ useEffect(() => {
+ if (!element) return
+
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ setIsInView(entry.isIntersecting)
+ },
+ { threshold }
+ )
+
+ observer.observe(element)
+
+ return () => {
+ observer.unobserve(element)
+ }
+ }, [element, threshold])
+
+ return [setElement, isInView]
+}
+
+// Hook for page transition context
+export const usePageTransition = () => {
+ const [isTransitioning, setIsTransitioning] = useState(false)
+
+ const startTransition = () => setIsTransitioning(true)
+ const endTransition = () => setIsTransitioning(false)
+
+ return {
+ isTransitioning,
+ startTransition,
+ endTransition,
+ }
+}
diff --git a/src/index.css b/src/index.css
index b5c61c9..3586c73 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,3 +1,51 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+/* Global animations for smooth interactions */
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* Smooth scrolling for better UX */
+html {
+ scroll-behavior: smooth;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+}
+
+/* Global focus styles for better accessibility */
+*:focus-visible {
+ outline: 2px solid #0ea5e9;
+ outline-offset: 2px;
+ border-radius: 4px;
+}
+
+/* Improve button and interactive element performance */
+button, a, [role="button"] {
+ transform: translateZ(0);
+ backface-visibility: hidden;
+}
+
+/* Ensure smooth transitions for dynamic content */
+* {
+ box-sizing: border-box;
+}
+
+/* Optimize animations for performance */
+.animate-optimized {
+ will-change: transform, opacity;
+}
+
+.animate-optimized.animation-complete {
+ will-change: auto;
+}
diff --git a/src/views/History/ChoreHistory.jsx b/src/views/History/ChoreHistory.jsx
index 195b744..3259a30 100644
--- a/src/views/History/ChoreHistory.jsx
+++ b/src/views/History/ChoreHistory.jsx
@@ -14,13 +14,13 @@ import {
import moment from 'moment'
import { useEffect, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
+import { LoadingScreen, SmoothCard } from '../../components/animations'
import {
DeleteChoreHistory,
GetAllCircleMembers,
GetChoreHistory,
UpdateChoreHistory,
} from '../../utils/Fetcher'
-import LoadingComponent from '../components/Loading'
import EditHistoryModal from '../Modals/EditHistoryModal'
import HistoryCard from './HistoryCard'
@@ -143,7 +143,7 @@ const ChoreHistory = () => {
}
if (isLoading) {
- return
+ return
}
if (!choreHistory.length) {
return (