|
|
import { motion } from 'framer-motion'; |
|
|
import { FiGithub, FiTwitter, FiMail, FiHeart } from 'react-icons/fi'; |
|
|
import { HiSparkles } from 'react-icons/hi'; |
|
|
import { useMemo, useCallback } from 'react'; |
|
|
|
|
|
export default function Footer() { |
|
|
const currentYear = new Date().getFullYear(); |
|
|
|
|
|
|
|
|
const socialLinks = useMemo( |
|
|
() => [ |
|
|
{ icon: FiGithub, href: 'https://github.com/YoruAkio', label: 'GitHub' }, |
|
|
{ |
|
|
icon: FiTwitter, |
|
|
href: 'https://twitter.com/YoruAkio', |
|
|
label: 'Twitter', |
|
|
}, |
|
|
{ icon: FiMail, href: 'mailto:hello@akio.lol', label: 'Email' }, |
|
|
], |
|
|
[], |
|
|
); |
|
|
|
|
|
const quickLinks = useMemo( |
|
|
() => [ |
|
|
{ label: 'Features', href: '#features' }, |
|
|
{ label: 'File Converters', href: '#converters' }, |
|
|
{ label: 'Social Media Downloader', href: '#social-media' }, |
|
|
{ |
|
|
label: 'Documentation', |
|
|
|
|
|
href: '#hero', |
|
|
}, |
|
|
], |
|
|
[], |
|
|
); |
|
|
|
|
|
|
|
|
const easeInOutCubic = t => { |
|
|
return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; |
|
|
}; |
|
|
|
|
|
const smoothScrollTo = useCallback((targetPosition, duration = 1200) => { |
|
|
const startPosition = window.pageYOffset; |
|
|
const distance = targetPosition - startPosition; |
|
|
let startTime = null; |
|
|
|
|
|
const animation = currentTime => { |
|
|
if (startTime === null) startTime = currentTime; |
|
|
const timeElapsed = currentTime - startTime; |
|
|
const progress = Math.min(timeElapsed / duration, 1); |
|
|
const ease = easeInOutCubic(progress); |
|
|
|
|
|
window.scrollTo(0, startPosition + distance * ease); |
|
|
|
|
|
if (timeElapsed < duration) { |
|
|
requestAnimationFrame(animation); |
|
|
} |
|
|
}; |
|
|
|
|
|
requestAnimationFrame(animation); |
|
|
}, []); |
|
|
|
|
|
|
|
|
const handleSmoothScroll = useCallback( |
|
|
href => { |
|
|
if (href.startsWith('#')) { |
|
|
const element = document.querySelector(href); |
|
|
if (element) { |
|
|
const offset = 80; |
|
|
const elementPosition = element.getBoundingClientRect().top; |
|
|
const offsetPosition = elementPosition + window.pageYOffset - offset; |
|
|
|
|
|
smoothScrollTo(offsetPosition, 1200); |
|
|
} |
|
|
} |
|
|
}, |
|
|
[smoothScrollTo], |
|
|
); |
|
|
|
|
|
return ( |
|
|
<footer className="relative bg-[var(--background-secondary)] border-t border-[var(--border)]"> |
|
|
{/* Background decorative elements */} |
|
|
<div className="absolute inset-0 overflow-hidden pointer-events-none"> |
|
|
<div className="absolute -top-20 -right-20 w-40 h-40 sm:w-60 sm:h-60 lg:w-80 lg:h-80 bg-[var(--accent)]/5 rounded-full blur-3xl"></div> |
|
|
<div className="absolute -bottom-20 -left-20 w-40 h-40 sm:w-60 sm:h-60 lg:w-80 lg:h-80 bg-[var(--accent)]/5 rounded-full blur-3xl"></div> |
|
|
</div> |
|
|
|
|
|
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 sm:py-12"> |
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 sm:gap-8"> |
|
|
{/* Brand Section */} |
|
|
<div className="space-y-4"> |
|
|
<motion.div |
|
|
className="flex items-center space-x-2" |
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
whileInView={{ opacity: 1, y: 0 }} |
|
|
viewport={{ once: true, margin: '-50px' }} |
|
|
transition={{ duration: 0.6 }} |
|
|
> |
|
|
<div className="relative"> |
|
|
<HiSparkles className="text-xl sm:text-2xl text-[var(--accent)]" /> |
|
|
<div className="absolute inset-0 bg-[var(--accent)] blur-lg opacity-20"></div> |
|
|
</div> |
|
|
<span className="text-lg sm:text-xl font-bold gradient-text"> |
|
|
LumaKit |
|
|
</span> |
|
|
</motion.div> |
|
|
<motion.p |
|
|
className="text-[var(--foreground-secondary)] text-sm leading-relaxed max-w-sm" |
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
whileInView={{ opacity: 1, y: 0 }} |
|
|
viewport={{ once: true, margin: '-50px' }} |
|
|
transition={{ duration: 0.6, delay: 0.1 }} |
|
|
> |
|
|
A versatile suite of tools for social media downloading, file |
|
|
conversion, and AI-powered code transformation. Streamline your |
|
|
workflow with our modern, intuitive platform. |
|
|
</motion.p> |
|
|
</div> |
|
|
|
|
|
{/* Quick Links */} |
|
|
<div className="space-y-4"> |
|
|
<motion.h3 |
|
|
className="text-[var(--foreground)] font-semibold text-base sm:text-lg" |
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
whileInView={{ opacity: 1, y: 0 }} |
|
|
viewport={{ once: true, margin: '-50px' }} |
|
|
transition={{ duration: 0.6, delay: 0.2 }} |
|
|
> |
|
|
Quick Links |
|
|
</motion.h3> |
|
|
<motion.div |
|
|
className="space-y-2" |
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
whileInView={{ opacity: 1, y: 0 }} |
|
|
viewport={{ once: true, margin: '-50px' }} |
|
|
transition={{ duration: 0.6, delay: 0.3 }} |
|
|
> |
|
|
{quickLinks.map((link, index) => ( |
|
|
<motion.button |
|
|
key={index} |
|
|
onClick={() => |
|
|
link.href.startsWith('#') |
|
|
? handleSmoothScroll(link.href) |
|
|
: window.open(link.href, '_blank') |
|
|
} |
|
|
className="block text-[var(--foreground-muted)] hover:text-[var(--accent)] transition-colors duration-300 text-sm text-left" |
|
|
whileHover={{ x: 4 }} |
|
|
transition={{ duration: 0.2 }} |
|
|
> |
|
|
{link.label} |
|
|
</motion.button> |
|
|
))} |
|
|
</motion.div> |
|
|
</div> |
|
|
|
|
|
{/* Contact & Social */} |
|
|
<div className="space-y-4"> |
|
|
<motion.h3 |
|
|
className="text-[var(--foreground)] font-semibold text-base sm:text-lg" |
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
whileInView={{ opacity: 1, y: 0 }} |
|
|
viewport={{ once: true, margin: '-50px' }} |
|
|
transition={{ duration: 0.6, delay: 0.4 }} |
|
|
> |
|
|
Connect |
|
|
</motion.h3> |
|
|
<motion.div |
|
|
className="flex space-x-4" |
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
whileInView={{ opacity: 1, y: 0 }} |
|
|
viewport={{ once: true, margin: '-50px' }} |
|
|
transition={{ duration: 0.6, delay: 0.5 }} |
|
|
> |
|
|
{socialLinks.map((social, index) => ( |
|
|
<motion.a |
|
|
key={index} |
|
|
href={social.href} |
|
|
target="_blank" |
|
|
rel="noopener noreferrer" |
|
|
className="text-[var(--foreground-muted)] hover:text-[var(--accent)] transition-colors duration-300 p-2 rounded-lg hover:bg-[var(--background-tertiary)]" |
|
|
whileHover={{ scale: 1.1, y: -2 }} |
|
|
whileTap={{ scale: 0.95 }} |
|
|
aria-label={social.label} |
|
|
> |
|
|
<social.icon size={18} /> |
|
|
</motion.a> |
|
|
))} |
|
|
</motion.div> |
|
|
{/* <motion.p |
|
|
className="text-[var(--foreground-muted)] text-xs leading-relaxed" |
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
whileInView={{ opacity: 1, y: 0 }} |
|
|
viewport={{ once: true, margin: '-50px' }} |
|
|
transition={{ duration: 0.6, delay: 0.6 }} |
|
|
> |
|
|
Visit our{' '} |
|
|
<a |
|
|
href="https://huggingface.co/spaces/YoruAkio/LumaKit" |
|
|
target="_blank" |
|
|
rel="noopener noreferrer" |
|
|
className="text-[var(--accent)] hover:text-[var(--accent-hover)] transition-colors duration-300 underline decoration-1 underline-offset-2" |
|
|
> |
|
|
Hugging Face Space |
|
|
</a>{' '} |
|
|
to try LumaKit now. |
|
|
</motion.p> */} |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
{/* Bottom Section */} |
|
|
<motion.div |
|
|
className="mt-8 sm:mt-12 pt-6 sm:pt-8 border-t border-[var(--border)] flex flex-col sm:flex-row items-center justify-between space-y-4 sm:space-y-0" |
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
whileInView={{ opacity: 1, y: 0 }} |
|
|
viewport={{ once: true, margin: '-50px' }} |
|
|
transition={{ duration: 0.6, delay: 0.7 }} |
|
|
> |
|
|
<p className="text-[var(--foreground-muted)] text-xs sm:text-sm flex items-center space-x-1"> |
|
|
<span>Β© {currentYear} LumaKit. Made with</span> |
|
|
<FiHeart className="text-red-400 text-xs animate-pulse" /> |
|
|
<span>by the community</span> |
|
|
</p> |
|
|
<p className="text-[var(--foreground-muted)] text-xs sm:text-sm"> |
|
|
Powered by AI β’ Built for Creators |
|
|
</p> |
|
|
</motion.div> |
|
|
</div> |
|
|
</footer> |
|
|
); |
|
|
} |
|
|
|