Changes für lighthouse Branch
This commit is contained in:
210
src/utils/animations.ts
Normal file
210
src/utils/animations.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { Variants } from 'framer-motion';
|
||||
|
||||
// Smooth easing curves
|
||||
export const easing = {
|
||||
smooth: [0.6, 0.01, 0.05, 0.95],
|
||||
snappy: [0.25, 0.46, 0.45, 0.94],
|
||||
bouncy: [0.68, -0.55, 0.265, 1.55],
|
||||
elegant: [0.43, 0.13, 0.23, 0.96],
|
||||
};
|
||||
|
||||
// Hero section animations
|
||||
export const heroVariants: Variants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.15,
|
||||
delayChildren: 0.3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const heroItemVariants: Variants = {
|
||||
hidden: { opacity: 0, y: 30 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.8,
|
||||
ease: easing.elegant,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Fade in up animation
|
||||
export const fadeInUp: Variants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
y: 60,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.7,
|
||||
ease: easing.elegant,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Scale in animation
|
||||
export const scaleIn: Variants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
scale: 0.8,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: easing.smooth,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Slide in from left
|
||||
export const slideInLeft: Variants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
x: -60,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: {
|
||||
duration: 0.7,
|
||||
ease: easing.elegant,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Slide in from right
|
||||
export const slideInRight: Variants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
x: 60,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: {
|
||||
duration: 0.7,
|
||||
ease: easing.elegant,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Stagger container
|
||||
export const staggerContainer: Variants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.12,
|
||||
delayChildren: 0.1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Stagger item
|
||||
export const staggerItem: Variants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.5,
|
||||
ease: easing.elegant,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Button hover animation
|
||||
export const buttonHover = {
|
||||
scale: 1.02,
|
||||
transition: {
|
||||
duration: 0.2,
|
||||
ease: easing.snappy,
|
||||
},
|
||||
};
|
||||
|
||||
export const buttonTap = {
|
||||
scale: 0.98,
|
||||
};
|
||||
|
||||
// Card hover animation
|
||||
export const cardHover = {
|
||||
y: -8,
|
||||
transition: {
|
||||
duration: 0.3,
|
||||
ease: easing.smooth,
|
||||
},
|
||||
};
|
||||
|
||||
// Glow effect
|
||||
export const glowHover = {
|
||||
boxShadow: '0 0 30px rgba(51, 102, 255, 0.6)',
|
||||
transition: {
|
||||
duration: 0.3,
|
||||
},
|
||||
};
|
||||
|
||||
// Navigation animation
|
||||
export const navVariants: Variants = {
|
||||
hidden: {
|
||||
y: -100,
|
||||
opacity: 0,
|
||||
},
|
||||
visible: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: easing.elegant,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Page transition
|
||||
export const pageTransition = {
|
||||
initial: { opacity: 0, y: 20 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
exit: { opacity: 0, y: -20 },
|
||||
transition: { duration: 0.4, ease: easing.elegant },
|
||||
};
|
||||
|
||||
// Parallax scroll effect
|
||||
export const parallaxScroll = (scrollY: number, factor: number = 0.5) => ({
|
||||
y: scrollY * factor,
|
||||
transition: { type: 'tween', ease: 'linear', duration: 0 },
|
||||
});
|
||||
|
||||
// Scroll reveal with intersection observer
|
||||
export const scrollRevealVariants: Variants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
y: 50,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.7,
|
||||
ease: easing.elegant,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Magnetic button effect (advanced)
|
||||
export const magneticEffect = (x: number, y: number, strength: number = 0.3) => ({
|
||||
x: x * strength,
|
||||
y: y * strength,
|
||||
transition: {
|
||||
type: 'spring',
|
||||
stiffness: 150,
|
||||
damping: 15,
|
||||
mass: 0.1,
|
||||
},
|
||||
});
|
||||
100
src/utils/reportWebVitals.ts
Normal file
100
src/utils/reportWebVitals.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { onCLS, onFCP, onLCP, onTTFB, onINP, type Metric } from 'web-vitals';
|
||||
|
||||
function sendToAnalytics(metric: Metric) {
|
||||
// Log to console in development
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('Web Vitals:', {
|
||||
name: metric.name,
|
||||
value: metric.value,
|
||||
rating: metric.rating,
|
||||
delta: metric.delta,
|
||||
id: metric.id,
|
||||
});
|
||||
}
|
||||
|
||||
// Send to Google Analytics if available
|
||||
if (typeof window !== 'undefined' && (window as any).gtag) {
|
||||
(window as any).gtag('event', metric.name, {
|
||||
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
|
||||
event_category: 'Web Vitals',
|
||||
event_label: metric.id,
|
||||
non_interaction: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Send to custom analytics endpoint if needed
|
||||
if (import.meta.env.PROD && import.meta.env.VITE_ANALYTICS_ENDPOINT) {
|
||||
fetch(import.meta.env.VITE_ANALYTICS_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
metric: metric.name,
|
||||
value: metric.value,
|
||||
rating: metric.rating,
|
||||
id: metric.id,
|
||||
timestamp: Date.now(),
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
}),
|
||||
keepalive: true,
|
||||
}).catch((error) => {
|
||||
console.error('Failed to send web vitals:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function reportWebVitals() {
|
||||
// Core Web Vitals
|
||||
onCLS(sendToAnalytics);
|
||||
onLCP(sendToAnalytics);
|
||||
onINP(sendToAnalytics); // Replaces deprecated FID
|
||||
|
||||
// Other important metrics
|
||||
onFCP(sendToAnalytics);
|
||||
onTTFB(sendToAnalytics);
|
||||
}
|
||||
|
||||
// Performance observer for additional metrics
|
||||
export function observePerformance() {
|
||||
if (typeof window === 'undefined' || !('PerformanceObserver' in window)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Observe long tasks (blocking the main thread)
|
||||
try {
|
||||
const longTaskObserver = new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (entry.duration > 50) {
|
||||
console.warn('Long Task detected:', {
|
||||
duration: entry.duration,
|
||||
startTime: entry.startTime,
|
||||
name: entry.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
longTaskObserver.observe({ entryTypes: ['longtask'] });
|
||||
} catch (e) {
|
||||
// Long task API not supported
|
||||
}
|
||||
|
||||
// Observe layout shifts
|
||||
try {
|
||||
const layoutShiftObserver = new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if ((entry as any).hadRecentInput) continue;
|
||||
const value = (entry as any).value;
|
||||
if (value > 0.1) {
|
||||
console.warn('Large Layout Shift:', {
|
||||
value,
|
||||
startTime: entry.startTime,
|
||||
sources: (entry as any).sources,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
layoutShiftObserver.observe({ entryTypes: ['layout-shift'] });
|
||||
} catch (e) {
|
||||
// Layout shift API not supported
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user