diff --git a/services/frontend/src/shared/hooks/useFramerStagger.ts b/services/frontend/src/shared/hooks/useFramerStagger.ts new file mode 100644 index 0000000..85f000f --- /dev/null +++ b/services/frontend/src/shared/hooks/useFramerStagger.ts @@ -0,0 +1,73 @@ +import { type Variants } from "framer-motion"; + +/** + * Parent container variant for staggerChildren. + * Use on motion.div wrapping a list of cardItem children. + */ +export const cardStagger: Variants = { + initial: { opacity: 0 }, + animate: { + opacity: 1, + transition: { staggerChildren: 0.08, delayChildren: 0.1 }, + }, +}; + +/** + * Child item variant for fade + slide up. + * Intended as a child of cardStagger. + */ +export const cardItem: Variants = { + initial: { opacity: 0, y: 20 }, + animate: { + opacity: 1, + y: 0, + transition: { duration: 0.4, ease: "easeOut" }, + }, +}; + +/** + * Single element variant: fade + slide up with a cubic-bezier ease. + * Supports exit animation (fade out + slide up). + */ +export const fadeSlideUp: Variants = { + initial: { opacity: 0, y: 24 }, + animate: { + opacity: 1, + y: 0, + transition: { duration: 0.5, ease: [0.25, 0.46, 0.45, 0.94] }, + }, + exit: { opacity: 0, y: -12, transition: { duration: 0.2 } }, +}; + +/** + * Simple fade variant for generic element transitions. + */ +export const fadeIn: Variants = { + initial: { opacity: 0 }, + animate: { opacity: 1, transition: { duration: 0.3 } }, + exit: { opacity: 0, transition: { duration: 0.2 } }, +}; + +/** + * Scale + fade variant for badges, pills, or small decorative elements. + */ +export const scaleIn: Variants = { + initial: { opacity: 0, scale: 0.8 }, + animate: { + opacity: 1, + scale: 1, + transition: { duration: 0.3, ease: "backOut" }, + }, +}; + +/** + * Spring-based entrance for important elements. + */ +export const springUp: Variants = { + initial: { opacity: 0, y: 30 }, + animate: { + opacity: 1, + y: 0, + transition: { type: "spring", stiffness: 200, damping: 20 }, + }, +}; diff --git a/services/frontend/src/styles.css b/services/frontend/src/styles.css index 244b99a..fb167f3 100644 --- a/services/frontend/src/styles.css +++ b/services/frontend/src/styles.css @@ -3,26 +3,28 @@ @layer base { :root { - --background: 222 47% 4%; - --foreground: 210 40% 98%; - --card: 222 47% 8%; - --card-glass: 222 47% 12% / 0.6; - --card-foreground: 210 40% 98%; - --primary: 199 89% 52%; - --primary-foreground: 210 40% 98%; - --secondary: 217 33% 15%; - --secondary-foreground: 210 40% 98%; - --muted: 217 33% 15%; - --muted-foreground: 215 20% 65%; - --accent: 217 33% 20%; - --accent-foreground: 210 40% 98%; + --background: 0 0% 98%; + --foreground: 222 20% 12%; + --card: 0 0% 100%; + --card-foreground: 222 20% 12%; + --primary: 199 80% 50%; + --primary-soft: 199 80% 92%; + --primary-foreground: 0 0% 100%; + --secondary: 210 20% 94%; + --secondary-foreground: 222 20% 12%; + --muted: 210 20% 94%; + --muted-foreground: 215 16% 47%; + --accent: 340 80% 65%; + --accent-foreground: 0 0% 100%; --destructive: 0 72% 55%; - --destructive-foreground: 210 40% 98%; - --border: 217 33% 20% / 0.5; - --input: 217 33% 20%; - --ring: 199 89% 52%; - --radius: 0.85rem; - --glow: 199 89% 52% / 0.15; + --destructive-foreground: 0 0% 100%; + --border: 199 30% 85%; + --input: 199 30% 85%; + --ring: 199 80% 50%; + --radius: 1rem; + --primary-glow: 199 80% 50% / 0.15; + --accent-glow: 340 80% 65% / 0.15; + --card-shadow: 199 30% 70% / 0.12; } * { @@ -35,12 +37,6 @@ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - - /* Mesh gradient background */ - background-image: - radial-gradient(circle at 15% 50%, hsl(260 50% 15% / 0.15) 0%, transparent 50%), - radial-gradient(circle at 85% 30%, hsl(var(--primary) / 0.1) 0%, transparent 50%); - background-attachment: fixed; } html, @@ -51,16 +47,18 @@ } @layer utilities { - .glass { - background-color: hsl(var(--card-glass)); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border: 1px solid hsl(var(--border)); - box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + .card-shadow { + box-shadow: 0 1px 3px hsl(var(--card-shadow)), 0 1px 2px -1px hsl(var(--card-shadow)); } - .glow { - box-shadow: 0 0 20px hsl(var(--glow)); + .shine { + background: linear-gradient( + 135deg, + hsl(var(--primary-soft)) 0%, + hsl(var(--background)) 50%, + hsl(var(--primary-soft)) 100% + ); + background-size: 200% 200%; } } @@ -73,7 +71,37 @@ } } +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + .animate-bar-pulse { animation: bar-pulse 0.4s ease-in-out infinite; transform-origin: bottom; } + +.animate-shimmer { + background: linear-gradient( + 90deg, + hsl(199 30% 90%) 0%, + hsl(199 30% 95%) 40%, + hsl(199 30% 90%) 80%, + hsl(199 30% 85%) 100% + ); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +}