Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
92 lines
2.5 KiB
TypeScript
92 lines
2.5 KiB
TypeScript
/**
|
|
* useMagneticEffect Hook
|
|
*
|
|
* Calculates proximity-based magnetic effect for button animations.
|
|
* Returns scale and position transforms based on mouse distance.
|
|
*/
|
|
|
|
import { useEffect, useState, RefObject } from 'react'
|
|
|
|
export interface MagneticEffectConfig {
|
|
/** Maximum distance to trigger effect (px) */
|
|
maxDistance?: number
|
|
/** Maximum scale multiplier */
|
|
maxScale?: number
|
|
/** Animation stiffness (lower = smoother) */
|
|
stiffness?: number
|
|
/** Enable magnetic effect */
|
|
enabled?: boolean
|
|
}
|
|
|
|
export interface MagneticEffectState {
|
|
/** Scale transform (1 = normal, >1 = enlarged) */
|
|
scale: number
|
|
/** X offset from mouse proximity (px) */
|
|
offsetX: number
|
|
/** Y offset from mouse proximity (px) */
|
|
offsetY: number
|
|
/** Whether mouse is within max distance */
|
|
isNear: boolean
|
|
}
|
|
|
|
const defaultConfig: Required<MagneticEffectConfig> = {
|
|
maxDistance: 120,
|
|
maxScale: 1.3,
|
|
stiffness: 0.3,
|
|
enabled: true,
|
|
}
|
|
|
|
export function useMagneticEffect(
|
|
ref: RefObject<HTMLElement>,
|
|
mousePosition: { x: number; y: number } | null,
|
|
config: MagneticEffectConfig = {}
|
|
): MagneticEffectState {
|
|
const cfg = { ...defaultConfig, ...config }
|
|
const [state, setState] = useState<MagneticEffectState>({
|
|
scale: 1,
|
|
offsetX: 0,
|
|
offsetY: 0,
|
|
isNear: false,
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (!cfg.enabled || !mousePosition || !ref.current) {
|
|
setState({ scale: 1, offsetX: 0, offsetY: 0, isNear: false })
|
|
return
|
|
}
|
|
|
|
const element = ref.current
|
|
const rect = element.getBoundingClientRect()
|
|
const centerX = rect.left + rect.width / 2
|
|
const centerY = rect.top + rect.height / 2
|
|
|
|
const deltaX = mousePosition.x - centerX
|
|
const deltaY = mousePosition.y - centerY
|
|
const distance = Math.sqrt(deltaX ** 2 + deltaY ** 2)
|
|
|
|
if (distance > cfg.maxDistance) {
|
|
setState({ scale: 1, offsetX: 0, offsetY: 0, isNear: false })
|
|
return
|
|
}
|
|
|
|
// Calculate proximity ratio (1 = at center, 0 = at max distance)
|
|
const proximity = 1 - distance / cfg.maxDistance
|
|
|
|
// Calculate scale (grows as mouse approaches)
|
|
const scale = 1 + (cfg.maxScale - 1) * proximity
|
|
|
|
// Calculate magnetic pull (subtle movement toward mouse)
|
|
const pullStrength = proximity * cfg.stiffness
|
|
const offsetX = deltaX * pullStrength
|
|
const offsetY = deltaY * pullStrength
|
|
|
|
setState({
|
|
scale,
|
|
offsetX,
|
|
offsetY,
|
|
isNear: true,
|
|
})
|
|
}, [mousePosition, cfg.enabled, cfg.maxDistance, cfg.maxScale, cfg.stiffness])
|
|
|
|
return state
|
|
}
|