feat: Refactor UI components by extracting Navigation, Footer, LogoMark, and Card; implement responsive design and improve code organization

This commit is contained in:
defiQUG
2025-10-05 09:31:54 -07:00
parent c997f33d62
commit 93bcf4d560
9 changed files with 344 additions and 233 deletions

View File

@@ -4,19 +4,14 @@ import {
ArrowRight,
Backpack,
CheckCircle2,
Facebook,
Globe,
Heart,
Instagram,
Mail,
MapPin,
Menu,
Moon,
Phone,
Shirt,
Sparkles,
Star,
SunMedium,
Users,
Building2,
BookOpenText,
@@ -67,6 +62,10 @@ import AdvancedAnalyticsDashboard from './components/AdvancedAnalyticsDashboard'
import MobileVolunteerApp from './components/MobileVolunteerApp'
import StaffTrainingDashboard from './components/StaffTrainingDashboard'
// Phase 4: Extracted Components
import { Navigation } from './components/Navigation'
import { Footer } from './components/Footer'
/**
* Miracles in Motion — Complete Non-Profit Website
* A comprehensive 501(c)3 organization website with modern design,
@@ -778,175 +777,9 @@ function SkipToContent() {
)
}
interface NavProps {
darkMode: boolean
setDarkMode: (value: boolean) => void
mobileMenuOpen: boolean
setMobileMenuOpen: (value: boolean) => void
}
// Nav component has been extracted to ./components/Navigation.tsx
function Nav({ darkMode, setDarkMode, mobileMenuOpen, setMobileMenuOpen }: NavProps) {
// Close mobile menu when route changes
useEffect(() => {
setMobileMenuOpen(false)
}, [window.location.hash])
// Handle keyboard navigation
const handleKeyDown = (e: React.KeyboardEvent, action: () => void) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
action()
}
}
return (
<>
<nav className="mx-auto flex w-full max-w-7xl items-center justify-between px-4 py-3 sm:px-6 lg:px-8" role="navigation" aria-label="Main navigation">
<div className="flex items-center gap-3">
<a
href="#/"
className="flex items-center gap-3 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded-lg p-1"
aria-label="Miracles in Motion - Home"
>
<LogoMark />
<div className="-space-y-1">
<div className="font-semibold tracking-tight">Miracles in Motion</div>
<div className="text-xs text-neutral-600 dark:text-neutral-400">Essentials for every student</div>
</div>
</a>
</div>
{/* Desktop Navigation */}
<div className="hidden items-center gap-6 md:flex">
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/stories" aria-label="Read success stories">Stories</a>
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/testimonies" aria-label="View testimonies">Testimonies</a>
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/volunteers" aria-label="Volunteer opportunities">Volunteers</a>
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/sponsors" aria-label="Corporate partnerships">Corporate</a>
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/request-assistance" aria-label="Request assistance">Get Help</a>
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/portals" aria-label="Portal login">Portals</a>
</div>
{/* Desktop Actions */}
<div className="hidden md:flex items-center gap-3">
<Magnetic>
<a
href="#/donate"
className="btn-primary focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
aria-label="Make a donation"
>
<Heart className="mr-2 h-4 w-4" aria-hidden="true" /> Donate
</a>
</Magnetic>
<button
aria-label={darkMode ? 'Switch to light mode' : 'Switch to dark mode'}
onClick={() => setDarkMode(!darkMode)}
onKeyDown={(e) => handleKeyDown(e, () => setDarkMode(!darkMode))}
className="group rounded-full border border-neutral-200/70 bg-white/70 p-2 shadow-sm transition hover:scale-105 hover:bg-white dark:border-white/10 dark:bg-white/10 dark:hover:bg-white/15 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
>
{darkMode ? (
<SunMedium className="h-5 w-5 transition group-hover:rotate-12" aria-hidden="true" />
) : (
<Moon className="h-5 w-5 transition group-hover:-rotate-12" aria-hidden="true" />
)}
</button>
</div>
{/* Mobile Actions */}
<div className="flex md:hidden items-center gap-2">
<Magnetic>
<a
href="#/donate"
className="btn-primary text-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
aria-label="Make a donation"
>
<Heart className="h-4 w-4" aria-hidden="true" />
</a>
</Magnetic>
<button
aria-label={darkMode ? 'Switch to light mode' : 'Switch to dark mode'}
onClick={() => setDarkMode(!darkMode)}
className="rounded-full border border-neutral-200/70 bg-white/70 p-2 shadow-sm transition hover:scale-105 hover:bg-white dark:border-white/10 dark:bg-white/10 dark:hover:bg-white/15 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
>
{darkMode ? (
<SunMedium className="h-4 w-4" aria-hidden="true" />
) : (
<Moon className="h-4 w-4" aria-hidden="true" />
)}
</button>
<button
aria-label={mobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu'}
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
onKeyDown={(e) => handleKeyDown(e, () => setMobileMenuOpen(!mobileMenuOpen))}
className="rounded-full border border-neutral-200/70 bg-white/70 p-2 shadow-sm transition hover:scale-105 hover:bg-white dark:border-white/10 dark:bg-white/10 dark:hover:bg-white/15 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
aria-expanded={mobileMenuOpen}
aria-controls="mobile-menu"
>
{mobileMenuOpen ? (
<X className="h-5 w-5" aria-hidden="true" />
) : (
<Menu className="h-5 w-5" aria-hidden="true" />
)}
</button>
</div>
</nav>
{/* Mobile Menu */}
<motion.div
id="mobile-menu"
className="md:hidden"
initial={false}
animate={{
height: mobileMenuOpen ? 'auto' : 0,
opacity: mobileMenuOpen ? 1 : 0
}}
transition={{ duration: 0.3, ease: 'easeInOut' }}
style={{ overflow: 'hidden' }}
role="region"
aria-label="Mobile navigation menu"
>
<div className="border-t border-neutral-200/50 dark:border-white/10 bg-white/95 dark:bg-gray-900/95 backdrop-blur">
<div className="max-w-7xl mx-auto px-4 py-4 space-y-3">
<a className="block py-3 px-4 text-lg font-medium text-neutral-900 dark:text-white hover:bg-primary-50 dark:hover:bg-primary-900/20 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" href="#/stories">Success Stories</a>
<a className="block py-3 px-4 text-lg font-medium text-neutral-900 dark:text-white hover:bg-primary-50 dark:hover:bg-primary-900/20 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" href="#/testimonies">Testimonies</a>
<a className="block py-3 px-4 text-lg font-medium text-neutral-900 dark:text-white hover:bg-primary-50 dark:hover:bg-primary-900/20 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" href="#/volunteers">Volunteer</a>
<a className="block py-3 px-4 text-lg font-medium text-neutral-900 dark:text-white hover:bg-primary-50 dark:hover:bg-primary-900/20 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" href="#/sponsors">Corporate Partners</a>
<a className="block py-3 px-4 text-lg font-medium text-emerald-900 dark:text-emerald-100 bg-emerald-50 dark:bg-emerald-900/20 hover:bg-emerald-100 dark:hover:bg-emerald-900/30 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" href="#/request-assistance">
<ClipboardList className="inline mr-2 h-5 w-5" aria-hidden="true" />
Request Assistance
</a>
<a className="block py-3 px-4 text-lg font-medium text-neutral-900 dark:text-white hover:bg-primary-50 dark:hover:bg-primary-900/20 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" href="#/portals">Staff & Partner Portals</a>
<a className="block py-3 px-4 text-lg font-medium text-neutral-900 dark:text-white hover:bg-primary-50 dark:hover:bg-primary-900/20 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" href="#/legal">Legal & Privacy</a>
<div className="pt-2 border-t border-neutral-200/50 dark:border-white/10">
<a className="block py-3 px-4 bg-primary-600 text-white font-semibold rounded-lg hover:bg-primary-700 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" href="#/donate">
<Heart className="inline mr-2 h-5 w-5" aria-hidden="true" />
Donate Now
</a>
</div>
</div>
</div>
</motion.div>
</>
)
}
function LogoMark() {
return (
<div className="relative grid h-10 w-10 place-items-center overflow-hidden rounded-2xl bg-gradient-to-br from-primary-500 via-secondary-500 to-secondary-600 shadow-lg shadow-primary-500/20">
<motion.div
className="absolute inset-0 opacity-60"
animate={{
background: [
"radial-gradient(120px 80px at 20% 20%, rgba(255,255,255,0.4), transparent)",
"radial-gradient(120px 80px at 80% 30%, rgba(255,255,255,0.4), transparent)",
"radial-gradient(120px 80px at 50% 80%, rgba(255,255,255,0.4), transparent)",
]
}}
transition={{ duration: 6, repeat: Infinity, ease: "easeInOut" }}
/>
<Sparkles className="relative h-6 w-6 text-white drop-shadow" />
</div>
)
}
// LogoMark component has been extracted to ./components/ui/LogoMark.tsx
/* ===================== Home Page ===================== */
function HomePage() {
@@ -4374,61 +4207,7 @@ function BackgroundDecor() {
)
}
function Footer() {
return (
<footer className="relative mt-24 border-t border-white/30 bg-white/50 backdrop-blur dark:border-white/10 dark:bg-white/5">
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
<div className="grid gap-8 lg:grid-cols-4">
<div className="lg:col-span-2">
<div className="flex items-center gap-3">
<LogoMark />
<div>
<div className="font-semibold">Miracles in Motion</div>
<div className="text-sm text-neutral-600 dark:text-neutral-400">Essentials for every student</div>
</div>
</div>
<p className="mt-4 max-w-md text-sm text-neutral-600 dark:text-neutral-400">
A 501(c)(3) nonprofit providing students with school supplies, clothing, and emergency support to help them succeed.
</p>
<div className="mt-4 flex gap-4">
<a href="#" className="text-neutral-600 hover:text-primary-600 dark:text-neutral-400">
<Facebook className="h-5 w-5" />
</a>
<a href="#" className="text-neutral-600 hover:text-primary-600 dark:text-neutral-400">
<Instagram className="h-5 w-5" />
</a>
<a href="#" className="text-neutral-600 hover:text-primary-600 dark:text-neutral-400">
<Globe className="h-5 w-5" />
</a>
</div>
</div>
<div>
<h3 className="font-semibold">Get Involved</h3>
<ul className="mt-4 space-y-2 text-sm">
<li><a href="#/donate" className="navlink">Donate</a></li>
<li><a href="#/volunteers" className="navlink">Volunteer</a></li>
<li><a href="#/sponsors" className="navlink">Corporate Partnerships</a></li>
<li><a href="#/stories" className="navlink">Success Stories</a></li>
</ul>
</div>
<div>
<h3 className="font-semibold">Organization</h3>
<ul className="mt-4 space-y-2 text-sm">
<li><a href="#/testimonies" className="navlink">Testimonials</a></li>
<li><a href="#/legal" className="navlink">Legal & Policies</a></li>
<li><a href="mailto:contact@miraclesinmotion.org" className="navlink">Contact Us</a></li>
<li><a href="tel:+15551234567" className="navlink">(555) 123-4567</a></li>
</ul>
</div>
</div>
<div className="mt-8 border-t border-white/30 pt-8 text-center text-xs text-neutral-500 dark:border-white/10 dark:text-neutral-400">
<p>© 2024 Miracles in Motion. All rights reserved. EIN: 12-3456789</p>
<p className="mt-1">501(c)(3) nonprofit organization. Donations are tax-deductible to the extent allowed by law.</p>
</div>
</div>
</footer>
)
}
// Footer component has been extracted to ./components/Footer.tsx
function StickyDonate() {
return (
@@ -4615,7 +4394,7 @@ function AppContent() {
)}
<header className="sticky top-0 z-50 backdrop-blur supports-[backdrop-filter]:bg-white/50 dark:supports-[backdrop-filter]:bg-black/40 border-b border-white/30 dark:border-white/10">
<Nav
<Navigation
darkMode={darkMode}
setDarkMode={setDarkMode}
mobileMenuOpen={isMobileMenuOpen}

64
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,64 @@
import {
Facebook,
Globe,
Instagram,
} from 'lucide-react'
import { LogoMark } from './ui/LogoMark'
export function Footer() {
return (
<footer className="relative mt-24 border-t border-white/30 bg-white/50 backdrop-blur dark:border-white/10 dark:bg-white/5">
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
<div className="grid gap-8 lg:grid-cols-4">
<div className="lg:col-span-2">
<div className="flex items-center gap-3">
<LogoMark />
<div>
<div className="font-semibold">Miracles in Motion</div>
<div className="text-sm text-neutral-600 dark:text-neutral-400">Essentials for every student</div>
</div>
</div>
<p className="mt-4 max-w-md text-sm text-neutral-600 dark:text-neutral-400">
A 501(c)(3) nonprofit providing students with school supplies, clothing, and emergency support to help them succeed.
</p>
<div className="mt-4 flex gap-4">
<a href="#" className="text-neutral-600 hover:text-primary-600 dark:text-neutral-400">
<Facebook className="h-5 w-5" />
</a>
<a href="#" className="text-neutral-600 hover:text-primary-600 dark:text-neutral-400">
<Instagram className="h-5 w-5" />
</a>
<a href="#" className="text-neutral-600 hover:text-primary-600 dark:text-neutral-400">
<Globe className="h-5 w-5" />
</a>
</div>
</div>
<div>
<h3 className="font-semibold">Get Involved</h3>
<ul className="mt-4 space-y-2 text-sm">
<li><a href="#/donate" className="navlink">Donate</a></li>
<li><a href="#/volunteers" className="navlink">Volunteer</a></li>
<li><a href="#/sponsors" className="navlink">Corporate Partnerships</a></li>
<li><a href="#/stories" className="navlink">Success Stories</a></li>
</ul>
</div>
<div>
<h3 className="font-semibold">Organization</h3>
<ul className="mt-4 space-y-2 text-sm">
<li><a href="#/testimonies" className="navlink">Testimonials</a></li>
<li><a href="#/legal" className="navlink">Legal & Policies</a></li>
<li><a href="mailto:contact@miraclesinmotion.org" className="navlink">Contact Us</a></li>
<li><a href="tel:+15551234567" className="navlink">(555) 123-4567</a></li>
</ul>
</div>
</div>
<div className="mt-8 border-t border-white/30 pt-8 text-center text-xs text-neutral-500 dark:border-white/10 dark:text-neutral-400">
<p>© 2024 Miracles in Motion. All rights reserved. EIN: 12-3456789</p>
<p className="mt-1">501(c)(3) nonprofit organization. Donations are tax-deductible to the extent allowed by law.</p>
</div>
</div>
</footer>
)
}
export default Footer

View File

@@ -0,0 +1,154 @@
import React, { useEffect } from 'react'
import { motion } from 'framer-motion'
import {
Heart,
Menu,
Moon,
SunMedium,
X,
} from 'lucide-react'
// Import UI components
import { Magnetic, LogoMark } from './ui'
interface NavProps {
darkMode: boolean
setDarkMode: (value: boolean) => void
mobileMenuOpen: boolean
setMobileMenuOpen: (value: boolean) => void
}
export function Navigation({ darkMode, setDarkMode, mobileMenuOpen, setMobileMenuOpen }: NavProps) {
// Close mobile menu when route changes
useEffect(() => {
setMobileMenuOpen(false)
}, [window.location.hash])
// Handle keyboard navigation
const handleKeyDown = (e: React.KeyboardEvent, action: () => void) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
action()
}
}
return (
<>
<nav className="mx-auto flex w-full max-w-7xl items-center justify-between px-4 py-3 sm:px-6 lg:px-8" role="navigation" aria-label="Main navigation">
<div className="flex items-center gap-3">
<a
href="#/"
className="flex items-center gap-3 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded-lg p-1"
aria-label="Miracles in Motion - Home"
>
<LogoMark />
<div className="-space-y-1">
<div className="font-semibold tracking-tight">Miracles in Motion</div>
<div className="text-xs text-neutral-600 dark:text-neutral-400">Essentials for every student</div>
</div>
</a>
</div>
{/* Desktop Navigation */}
<div className="hidden items-center gap-6 md:flex">
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/stories" aria-label="Read success stories">Stories</a>
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/testimonies" aria-label="View testimonies">Testimonies</a>
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/volunteers" aria-label="Volunteer opportunities">Volunteers</a>
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/sponsors" aria-label="Corporate partnerships">Corporate</a>
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/request-assistance" aria-label="Request assistance">Get Help</a>
<a className="navlink focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded" href="#/portals" aria-label="Portal login">Portals</a>
</div>
{/* Desktop Actions */}
<div className="hidden md:flex items-center gap-3">
<Magnetic>
<a
href="#/donate"
className="btn-primary focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
aria-label="Make a donation"
>
<Heart className="mr-2 h-4 w-4" aria-hidden="true" /> Donate
</a>
</Magnetic>
<button
aria-label={darkMode ? 'Switch to light mode' : 'Switch to dark mode'}
onClick={() => setDarkMode(!darkMode)}
onKeyDown={(e) => handleKeyDown(e, () => setDarkMode(!darkMode))}
className="group rounded-full border border-neutral-200/70 bg-white/70 p-2 shadow-sm transition hover:scale-105 hover:bg-white dark:border-white/10 dark:bg-white/10 dark:hover:bg-white/15 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
>
{darkMode ? (
<SunMedium className="h-5 w-5 transition group-hover:rotate-12" aria-hidden="true" />
) : (
<Moon className="h-5 w-5 transition group-hover:-rotate-12" aria-hidden="true" />
)}
</button>
</div>
{/* Mobile Actions */}
<div className="flex md:hidden items-center gap-2">
<Magnetic>
<a
href="#/donate"
className="btn-primary text-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
aria-label="Make a donation"
>
<Heart className="h-4 w-4" aria-hidden="true" />
</a>
</Magnetic>
<button
aria-label={darkMode ? 'Switch to light mode' : 'Switch to dark mode'}
onClick={() => setDarkMode(!darkMode)}
className="rounded-full border border-neutral-200/70 bg-white/70 p-2 shadow-sm transition hover:scale-105 hover:bg-white dark:border-white/10 dark:bg-white/10 dark:hover:bg-white/15 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
>
{darkMode ? (
<SunMedium className="h-4 w-4" aria-hidden="true" />
) : (
<Moon className="h-4 w-4" aria-hidden="true" />
)}
</button>
<button
aria-label={mobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu'}
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
onKeyDown={(e) => handleKeyDown(e, () => setMobileMenuOpen(!mobileMenuOpen))}
className="rounded-full border border-neutral-200/70 bg-white/70 p-2 shadow-sm transition hover:scale-105 hover:bg-white dark:border-white/10 dark:bg-white/10 dark:hover:bg-white/15 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
aria-expanded={mobileMenuOpen}
aria-controls="mobile-menu"
>
{mobileMenuOpen ? (
<X className="h-5 w-5" aria-hidden="true" />
) : (
<Menu className="h-5 w-5" aria-hidden="true" />
)}
</button>
</div>
</nav>
{/* Mobile Menu */}
<motion.div
id="mobile-menu"
className="md:hidden"
initial={false}
animate={{
height: mobileMenuOpen ? 'auto' : 0,
opacity: mobileMenuOpen ? 1 : 0
}}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
style={{ overflow: 'hidden' }}
>
<div className="border-t border-neutral-200/50 bg-white/95 px-4 py-3 backdrop-blur dark:border-white/10 dark:bg-black/90">
<div className="space-y-3">
<a className="block py-2 text-sm font-medium" href="#/stories">Stories</a>
<a className="block py-2 text-sm font-medium" href="#/testimonies">Testimonies</a>
<a className="block py-2 text-sm font-medium" href="#/volunteers">Volunteers</a>
<a className="block py-2 text-sm font-medium" href="#/sponsors">Corporate</a>
<a className="block py-2 text-sm font-medium" href="#/request-assistance">Get Help</a>
<a className="block py-2 text-sm font-medium" href="#/portals">Portals</a>
</div>
</div>
</motion.div>
</>
)
}
export default Navigation

View File

@@ -0,0 +1,23 @@
import { LucideIcon } from 'lucide-react'
interface CardProps {
title: string
icon: LucideIcon
children: React.ReactNode
}
export function Card({ title, icon: Icon, children }: CardProps) {
return (
<div className="card">
<div className="flex items-center gap-3">
<div className="grid h-10 w-10 place-items-center rounded-xl bg-gradient-to-br from-primary-500 to-secondary-600 text-white shadow">
<Icon className="h-5 w-5" />
</div>
<div className="font-semibold tracking-tight">{title}</div>
</div>
<div className="mt-3">{children}</div>
</div>
)
}
export default Card

View File

@@ -0,0 +1,24 @@
// LogoMark component
import { motion } from 'framer-motion'
import { Sparkles } from 'lucide-react'
export function LogoMark() {
return (
<div className="relative grid h-10 w-10 place-items-center overflow-hidden rounded-2xl bg-gradient-to-br from-primary-500 via-secondary-500 to-secondary-600 shadow-lg shadow-primary-500/20">
<motion.div
className="absolute inset-0 opacity-60"
animate={{
background: [
"radial-gradient(120px 80px at 20% 20%, rgba(255,255,255,0.4), transparent)",
"radial-gradient(120px 80px at 80% 30%, rgba(255,255,255,0.4), transparent)",
"radial-gradient(120px 80px at 50% 80%, rgba(255,255,255,0.4), transparent)",
]
}}
transition={{ duration: 6, repeat: Infinity, ease: "easeInOut" }}
/>
<Sparkles className="relative h-6 w-6 text-white drop-shadow" />
</div>
)
}
export default LogoMark

View File

@@ -0,0 +1,15 @@
import React from 'react'
interface MagneticProps {
children: React.ReactNode
}
export function Magnetic({ children }: MagneticProps) {
return (
<div className="relative">
{children}
</div>
)
}
export default Magnetic

View File

@@ -0,0 +1,17 @@
interface SectionHeaderProps {
eyebrow?: string
title: string
subtitle?: string
}
export function SectionHeader({ eyebrow, title, subtitle }: SectionHeaderProps) {
return (
<div className="section-header">
{eyebrow && <div className="section-eyebrow">{eyebrow}</div>}
<h2 className="section-title">{title}</h2>
{subtitle && <p className="section-subtitle">{subtitle}</p>}
</div>
)
}
export default SectionHeader

View File

@@ -0,0 +1,11 @@
// UI Component Exports
export { LogoMark } from './LogoMark'
export { Magnetic } from './Magnetic'
export { SectionHeader } from './SectionHeader'
export { Card } from './Card'
// Re-export everything
export * from './LogoMark'
export * from './Magnetic'
export * from './SectionHeader'
export * from './Card'

View File

@@ -1,10 +1,14 @@
import React, { ReactNode } from 'react'
import React, { ReactNode, useState } from 'react'
import { motion } from 'framer-motion'
import { LucideIcon } from 'lucide-react'
import { Navigation } from '../components/Navigation'
import { Footer } from '../components/Footer'
interface MainLayoutProps {
children: ReactNode
className?: string
darkMode?: boolean
setDarkMode?: (value: boolean) => void
}
interface PageShellProps {
@@ -17,11 +21,31 @@ interface PageShellProps {
className?: string
}
// Main layout wrapper
export const MainLayout: React.FC<MainLayoutProps> = ({ children, className = '' }) => {
// Main layout wrapper with Navigation and Footer
export const MainLayout: React.FC<MainLayoutProps> = ({
children,
className = '',
darkMode = false,
setDarkMode = () => {}
}) => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return (
<div className={`min-h-screen bg-gradient-to-br from-purple-50 via-white to-pink-50 dark:from-gray-900 dark:via-gray-800 dark:to-purple-900 ${className}`}>
{children}
<header className="sticky top-0 z-40 bg-white/80 backdrop-blur dark:bg-black/80">
<Navigation
darkMode={darkMode}
setDarkMode={setDarkMode}
mobileMenuOpen={mobileMenuOpen}
setMobileMenuOpen={setMobileMenuOpen}
/>
</header>
<main id="content">
{children}
</main>
<Footer />
</div>
)
}