feat: Build Skills section with SVG circular gauges
- Create Skills.tsx with three categories (Technical, Clinical, Strategic) - Add SkillGauge component with animated circular SVG progress - Create useScrollReveal.ts hook for IntersectionObserver-based animations - Staggered gauge fill animations (100ms delay per skill) - Exact 18 skills with percentages from concept.html - Hover effects matching concept.html styling
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { useEffect, useRef, useState, type RefObject } from 'react'
|
||||
|
||||
interface UseScrollRevealOptions {
|
||||
threshold?: number
|
||||
rootMargin?: string
|
||||
triggerOnce?: boolean
|
||||
}
|
||||
|
||||
export function useScrollReveal<T extends HTMLElement>(
|
||||
options: UseScrollRevealOptions = {}
|
||||
): [RefObject<T | null>, boolean] {
|
||||
const { threshold = 0.15, rootMargin = '0px', triggerOnce = true } = options
|
||||
const ref = useRef<T | null>(null)
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current
|
||||
if (!element) return
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsVisible(true)
|
||||
if (triggerOnce) {
|
||||
observer.unobserve(element)
|
||||
}
|
||||
} else if (!triggerOnce) {
|
||||
setIsVisible(false)
|
||||
}
|
||||
},
|
||||
{ threshold, rootMargin }
|
||||
)
|
||||
|
||||
observer.observe(element)
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [threshold, rootMargin, triggerOnce])
|
||||
|
||||
return [ref, isVisible]
|
||||
}
|
||||
Reference in New Issue
Block a user