feat(brand): official logo, forest green/gold theme, and favicon set
Wire public/logo.png into nav/footer LogoMark with responsive sizing, remap primary/secondary Tailwind scales to brand colors, and refresh CTAs, PWA manifest, and favicon assets for mim4u.org. Co-authored-by: Cursor <[email protected]>
|
After Width: | Height: | Size: 74 KiB |
@@ -4,6 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<!-- CSP is applied at nginx/reverse-proxy in production (see MIM4U_502_ERROR_RESOLUTION.md). Meta CSP omitted to avoid breaking dev/build. -->
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Miracles In Motion | 501(c)3 Non-Profit Organization</title>
|
||||
<meta name="description" content="Miracles In Motion is a 501(c)3 non-profit organization dedicated to creating positive change in our community through compassionate action and support." />
|
||||
@@ -24,12 +26,10 @@
|
||||
|
||||
<!-- Favicon and Web App Manifest -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<!-- Theme Color -->
|
||||
<meta name="theme-color" content="#ec4899" />
|
||||
<meta name="theme-color" content="#1a3c34" />
|
||||
|
||||
<!-- Fonts: self-hosted via @fontsource/inter (see src/index.css); no external font requests for LCP -->
|
||||
</head>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@fontsource/playfair-display": "^5.2.8",
|
||||
"@react-three/fiber": "^8.16.8",
|
||||
"@stripe/react-stripe-js": "^2.8.1",
|
||||
"@stripe/stripe-js": "^4.7.0",
|
||||
@@ -2417,6 +2418,15 @@
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/playfair-display": {
|
||||
"version": "5.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/playfair-display/-/playfair-display-5.2.8.tgz",
|
||||
"integrity": "sha512-fUEhib70SszNhQVsGbUMSsWJhr7Je0rdeuZdtGpDNu0GKF1xJM8QhpI/y0pckU25GcChXm9TLOmeZupkvvZo2g==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"homepage": "https://miraclesinmotion.org",
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@fontsource/playfair-display": "^5.2.8",
|
||||
"@react-three/fiber": "^8.16.8",
|
||||
"@stripe/react-stripe-js": "^2.8.1",
|
||||
"@stripe/stripe-js": "^4.7.0",
|
||||
|
||||
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 595 B |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 204 KiB |
@@ -1,13 +1,14 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ec4899;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#3b82f6;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="32" height="32" rx="8" fill="url(#grad1)"/>
|
||||
<path d="M16 8L20.5 14H11.5L16 8Z" fill="white" opacity="0.9"/>
|
||||
<circle cx="16" cy="18" r="3" fill="white" opacity="0.9"/>
|
||||
<path d="M10 22L16 20L22 22L20 26H12L10 22Z" fill="white" opacity="0.9"/>
|
||||
</svg>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Miracles in Motion Foundation">
|
||||
<rect width="32" height="32" rx="6" fill="#1a3c34"/>
|
||||
<!-- Stylized figure + starburst (brand mark simplification) -->
|
||||
<circle cx="16" cy="7.5" r="2.2" fill="#d4af37"/>
|
||||
<path d="M11 22c2-5 4.5-7.5 5-12 1.2 4 3.2 6.5 5 12" stroke="#c5a059" stroke-width="1.8" stroke-linecap="round" fill="none"/>
|
||||
<path d="M13.5 11.5l2.5-3 2.5 3" stroke="#d4af37" stroke-width="1.2" stroke-linecap="round" fill="none"/>
|
||||
<g stroke="#d4af37" stroke-width="0.7" stroke-linecap="round">
|
||||
<line x1="20" y1="6" x2="20" y2="3.5"/>
|
||||
<line x1="22.5" y1="7" x2="24.5" y2="5.5"/>
|
||||
<line x1="23.5" y1="9.5" x2="26" y2="9.5"/>
|
||||
<line x1="17.5" y1="7" x2="15.5" y2="5.5"/>
|
||||
<line x1="16.5" y1="9.5" x2="14" y2="9.5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 889 B |
|
After Width: | Height: | Size: 74 KiB |
@@ -1,21 +1,29 @@
|
||||
{
|
||||
"name": "Miracles In Motion",
|
||||
"short_name": "MiraclesInMotion",
|
||||
"name": "Miracles In Motion Foundation",
|
||||
"short_name": "MiM",
|
||||
"description": "A 501(c)3 non-profit providing essentials for student success",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#ec4899",
|
||||
"background_color": "#1a3c34",
|
||||
"theme_color": "#1a3c34",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/favicon.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
}
|
||||
],
|
||||
"categories": ["education", "social", "non-profit"],
|
||||
@@ -25,4 +33,4 @@
|
||||
"scope": "/",
|
||||
"related_applications": [],
|
||||
"prefer_related_applications": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1493,7 +1493,7 @@ function Testimonial() {
|
||||
|
||||
function GetInvolved() {
|
||||
const options = [
|
||||
{ title: "Donate", body: "Fuel fast responses for urgent student needs.", href: "#/donate", accent: "from-rose-500 to-primary-500", icon: Heart },
|
||||
{ title: "Donate", body: "Fuel fast responses for urgent student needs.", href: "#/donate", accent: "from-secondary-500 to-primary-500", icon: Heart },
|
||||
{ title: "Volunteer", body: "Assemble kits, deliver items, host a drive.", href: "#/volunteers", accent: "from-sky-500 to-secondary-500", icon: Users },
|
||||
{ title: "Corporate sponsor", body: "Schools and community orgs: let's team up.", href: "#/sponsors", accent: "from-emerald-500 to-lime-500", icon: Globe },
|
||||
]
|
||||
@@ -2939,7 +2939,7 @@ function PortalsPage() {
|
||||
<div className="grid gap-8 md:grid-cols-2 xl:grid-cols-4">
|
||||
{/* Admin Portal */}
|
||||
<motion.div
|
||||
className="card bg-gradient-to-br from-red-50 to-rose-50 dark:from-red-900/20 dark:to-rose-900/20 border-red-200 dark:border-red-800"
|
||||
className="card bg-gradient-to-br from-red-50 to-secondary-50 dark:from-red-900/20 dark:to-secondary-900/20 border-red-200 dark:border-red-800"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
@@ -2979,7 +2979,7 @@ function PortalsPage() {
|
||||
|
||||
{/* Volunteer Portal */}
|
||||
<motion.div
|
||||
className="card bg-gradient-to-br from-blue-50 to-sky-50 dark:from-blue-900/20 dark:to-sky-900/20 border-blue-200 dark:border-blue-800"
|
||||
className="card bg-gradient-to-br from-primary-50 to-sky-50 dark:from-primary-900/20 dark:to-sky-900/20 border-blue-200 dark:border-blue-800"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
@@ -3059,22 +3059,22 @@ function PortalsPage() {
|
||||
|
||||
{/* AI Assistance Portal - Phase 3 */}
|
||||
<motion.div
|
||||
className="card bg-gradient-to-br from-purple-50 to-violet-50 dark:from-purple-900/20 dark:to-violet-900/20 border-purple-200 dark:border-purple-800"
|
||||
className="card bg-gradient-to-br from-primary-50 to-primary-50 dark:from-primary-900/20 dark:to-primary-900/20 border-primary-200 dark:border-primary-800"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
>
|
||||
<div className="text-center mb-6">
|
||||
<div className="mx-auto w-16 h-16 bg-purple-600 text-white rounded-xl flex items-center justify-center mb-4">
|
||||
<div className="mx-auto w-16 h-16 bg-primary-600 text-white rounded-xl flex items-center justify-center mb-4">
|
||||
<Brain className="h-8 w-8" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-purple-900 dark:text-purple-100">AI Assistance Portal</h3>
|
||||
<p className="text-sm text-purple-700 dark:text-purple-300 mt-2">
|
||||
<h3 className="text-xl font-semibold text-primary-900 dark:text-primary-100">AI Assistance Portal</h3>
|
||||
<p className="text-sm text-primary-700 dark:text-primary-300 mt-2">
|
||||
AI-powered request matching and insights
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-sm text-purple-800 dark:text-purple-200 mb-6">
|
||||
<div className="space-y-2 text-sm text-primary-800 dark:text-primary-200 mb-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Cpu className="h-4 w-4" />
|
||||
<span>Real-time AI request processing</span>
|
||||
@@ -3091,7 +3091,7 @@ function PortalsPage() {
|
||||
|
||||
<a
|
||||
href="#/ai-portal"
|
||||
className="block w-full text-center bg-purple-600 text-white py-3 px-4 rounded-lg hover:bg-purple-700 transition-colors font-medium focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
|
||||
className="block w-full text-center bg-primary-600 text-white py-3 px-4 rounded-lg hover:bg-primary-700 transition-colors font-medium focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
|
||||
>
|
||||
Access AI Portal
|
||||
</a>
|
||||
@@ -3108,7 +3108,7 @@ function PortalsPage() {
|
||||
<div className="grid gap-8 md:grid-cols-2 xl:grid-cols-3">
|
||||
{/* Advanced Analytics */}
|
||||
<motion.div
|
||||
className="card bg-gradient-to-br from-indigo-50 to-blue-50 dark:from-indigo-900/20 dark:to-blue-900/20 border-indigo-200 dark:border-indigo-800"
|
||||
className="card bg-gradient-to-br from-indigo-50 to-primary-50 dark:from-indigo-900/20 dark:to-primary-900/20 border-indigo-200 dark:border-indigo-800"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
@@ -4115,7 +4115,7 @@ function AnalyticsDashboard() {
|
||||
{/* Key Metrics */}
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<motion.div
|
||||
className="card bg-gradient-to-br from-blue-50 to-blue-100 dark:from-blue-900/20 dark:to-blue-800/20 border-blue-200 dark:border-blue-800"
|
||||
className="card bg-gradient-to-br from-primary-50 to-primary-100 dark:from-primary-900/20 dark:to-primary-800/20 border-blue-200 dark:border-blue-800"
|
||||
whileHover={{ scale: 1.02, y: -2 }}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -4151,19 +4151,19 @@ function AnalyticsDashboard() {
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="card bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/20 border-purple-200 dark:border-purple-800"
|
||||
className="card bg-gradient-to-br from-primary-50 to-primary-100 dark:from-primary-900/20 dark:to-primary-800/20 border-primary-200 dark:border-primary-800"
|
||||
whileHover={{ scale: 1.02, y: -2 }}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-purple-600 dark:text-purple-400">Students Helped</p>
|
||||
<p className="text-2xl font-bold text-purple-900 dark:text-purple-100">1,847</p>
|
||||
<p className="text-sm text-primary-600 dark:text-primary-400">Students Helped</p>
|
||||
<p className="text-2xl font-bold text-primary-900 dark:text-primary-100">1,847</p>
|
||||
</div>
|
||||
<Target className="h-8 w-8 text-purple-500" />
|
||||
<Target className="h-8 w-8 text-primary-500" />
|
||||
</div>
|
||||
<div className="mt-2 flex items-center text-sm">
|
||||
<span className="text-green-600 dark:text-green-400 font-medium">+15.2%</span>
|
||||
<span className="text-purple-600 dark:text-purple-400 ml-1">vs last month</span>
|
||||
<span className="text-primary-600 dark:text-primary-400 ml-1">vs last month</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -4273,12 +4273,12 @@ function AnalyticsDashboard() {
|
||||
</div>
|
||||
<span className="text-2xl font-bold text-blue-600">789</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
|
||||
<div className="flex items-center justify-between p-4 bg-primary-50 dark:bg-primary-900/20 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertCircle className="h-6 w-6 text-purple-600" />
|
||||
<AlertCircle className="h-6 w-6 text-primary-600" />
|
||||
<span className="font-medium">Emergency Responses</span>
|
||||
</div>
|
||||
<span className="text-2xl font-bold text-purple-600">156</span>
|
||||
<span className="text-2xl font-bold text-primary-600">156</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -77,7 +77,7 @@ const App: React.FC = () => {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<NotificationProvider>
|
||||
<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">
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 via-white to-secondary-50 dark:from-gray-900 dark:via-gray-800 dark:to-primary-900">
|
||||
{/* Navigation */}
|
||||
<Navigation
|
||||
darkMode={darkMode}
|
||||
|
||||
@@ -192,7 +192,7 @@ export function AIAssistancePortal({ userRole }: AIAssistancePortalProps) {
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
>
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2 text-gray-900 dark:text-gray-100">
|
||||
<Brain className="w-5 h-5 text-purple-500" />
|
||||
<Brain className="w-5 h-5 text-primary-500" />
|
||||
AI Insights
|
||||
</h3>
|
||||
|
||||
@@ -200,7 +200,7 @@ export function AIAssistancePortal({ userRole }: AIAssistancePortalProps) {
|
||||
{aiInsights.map((insight) => (
|
||||
<motion.div
|
||||
key={insight.id}
|
||||
className="insight-card p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg mb-3 border border-purple-100 dark:border-purple-800"
|
||||
className="insight-card p-3 bg-primary-50 dark:bg-primary-900/20 rounded-lg mb-3 border border-primary-100 dark:border-primary-800"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
@@ -217,7 +217,7 @@ export function AIAssistancePortal({ userRole }: AIAssistancePortalProps) {
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<div className="w-16 bg-gray-200 dark:bg-gray-600 rounded-full h-1">
|
||||
<div
|
||||
className="bg-purple-500 h-1 rounded-full transition-all duration-300"
|
||||
className="bg-primary-500 h-1 rounded-full transition-all duration-300"
|
||||
style={{ width: `${insight.confidence * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
@@ -324,7 +324,7 @@ function RequestCard({ request, onApprove, onModify, showAIRecommendations, isSe
|
||||
|
||||
{showAIRecommendations && request.aiRecommendations && (
|
||||
<motion.div
|
||||
className="ai-recommendations bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 p-4 rounded-xl mb-4 border border-blue-200 dark:border-blue-800"
|
||||
className="ai-recommendations bg-gradient-to-r from-primary-50 to-indigo-50 dark:from-primary-900/20 dark:to-indigo-900/20 p-4 rounded-xl mb-4 border border-blue-200 dark:border-blue-800"
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
transition={{ duration: 0.3 }}
|
||||
@@ -542,14 +542,14 @@ function MetricCard({ title, value, trend, color, icon }: MetricCardProps) {
|
||||
const colorClasses = {
|
||||
green: 'bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800',
|
||||
blue: 'bg-blue-50 border-blue-200 dark:bg-blue-900/20 dark:border-blue-800',
|
||||
purple: 'bg-purple-50 border-purple-200 dark:bg-purple-900/20 dark:border-purple-800',
|
||||
purple: 'bg-primary-50 border-primary-200 dark:bg-primary-900/20 dark:border-primary-800',
|
||||
orange: 'bg-orange-50 border-orange-200 dark:bg-orange-900/20 dark:border-orange-800'
|
||||
}
|
||||
|
||||
const iconColors = {
|
||||
green: 'text-green-500',
|
||||
blue: 'text-blue-500',
|
||||
purple: 'text-purple-500',
|
||||
purple: 'text-primary-500',
|
||||
orange: 'text-orange-500'
|
||||
}
|
||||
|
||||
@@ -712,7 +712,7 @@ function getInsightColor(type: string): string {
|
||||
case 'anomaly': return 'bg-red-400'
|
||||
case 'optimization': return 'bg-green-400'
|
||||
case 'trend': return 'bg-blue-400'
|
||||
case 'prediction': return 'bg-purple-400'
|
||||
case 'prediction': return 'bg-primary-400'
|
||||
case 'recommendation': return 'bg-yellow-400'
|
||||
default: return 'bg-gray-400'
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ const AdvancedAnalyticsDashboard: React.FC = () => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-indigo-100 flex items-center justify-center">
|
||||
<motion.div
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 2, repeat: Infinity, ease: 'linear' }}
|
||||
@@ -152,7 +152,7 @@ const AdvancedAnalyticsDashboard: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 p-6">
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-indigo-100 p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
@@ -212,11 +212,11 @@ const AdvancedAnalyticsDashboard: React.FC = () => {
|
||||
<div className="bg-white rounded-xl p-6 shadow-lg">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-700">Donations Processed</h3>
|
||||
<div className="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||
<span className="text-purple-600 font-bold">💝</span>
|
||||
<div className="w-10 h-10 bg-primary-100 rounded-lg flex items-center justify-center">
|
||||
<span className="text-primary-600 font-bold">💝</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-purple-600 mb-2">{formatCurrency(metrics?.totalDonationsProcessed || 0)}</div>
|
||||
<div className="text-3xl font-bold text-primary-600 mb-2">{formatCurrency(metrics?.totalDonationsProcessed || 0)}</div>
|
||||
<div className="text-sm text-gray-500">+15% from last {selectedTimeframe}</div>
|
||||
</div>
|
||||
|
||||
@@ -247,7 +247,7 @@ const AdvancedAnalyticsDashboard: React.FC = () => {
|
||||
initial={{ height: 0 }}
|
||||
animate={{ height: `${(trend.studentsServed / 400) * 100}%` }}
|
||||
transition={{ delay: 0.6 + index * 0.1, duration: 0.8 }}
|
||||
className="bg-gradient-to-t from-blue-500 to-blue-300 rounded-t-lg w-full mb-2 min-h-[20px]"
|
||||
className="bg-gradient-to-t from-primary-500 to-primary-300 rounded-t-lg w-full mb-2 min-h-[20px]"
|
||||
/>
|
||||
<div className="text-sm font-medium text-gray-700">{trend.month}</div>
|
||||
<div className="text-xs text-gray-500">{trend.studentsServed}</div>
|
||||
|
||||
@@ -11,12 +11,9 @@ export function Footer() {
|
||||
<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 className="footer-brand-block">
|
||||
<LogoMark variant="footer" />
|
||||
<p className="footer-tagline">Essentials for every student</p>
|
||||
</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.
|
||||
|
||||
@@ -155,10 +155,10 @@ export default function MimChatAgent({ currentPath }: MimChatAgentProps) {
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 12, scale: 0.96 }}
|
||||
transition={{ duration: 0.18 }}
|
||||
className="w-[min(24rem,calc(100vw-2rem))] overflow-hidden rounded-[1.75rem] border border-white/60 bg-white/92 shadow-[0_24px_80px_rgba(219,39,119,0.22)] backdrop-blur-xl dark:border-white/10 dark:bg-neutral-950/92"
|
||||
className="w-[min(24rem,calc(100vw-2rem))] overflow-hidden rounded-[1.75rem] border border-white/60 bg-white/92 shadow-[0_24px_80px_rgba(26,60,52,0.22)] backdrop-blur-xl dark:border-white/10 dark:bg-neutral-950/92"
|
||||
aria-label="Miracles in Motion AI chat assistant"
|
||||
>
|
||||
<div className="bg-gradient-to-r from-primary-600 via-pink-500 to-fuchsia-500 px-4 py-4 text-white">
|
||||
<div className="bg-gradient-to-r from-primary-700 via-primary-600 to-secondary-500 px-4 py-4 text-white">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold uppercase tracking-[0.2em] text-white/80">
|
||||
@@ -191,7 +191,7 @@ export default function MimChatAgent({ currentPath }: MimChatAgentProps) {
|
||||
className={`max-w-[85%] rounded-2xl px-4 py-3 text-sm leading-6 shadow-sm ${
|
||||
message.role === 'user'
|
||||
? 'rounded-br-md bg-neutral-900 text-white dark:bg-white dark:text-neutral-900'
|
||||
: 'rounded-bl-md bg-pink-50 text-neutral-800 dark:bg-neutral-900 dark:text-neutral-100'
|
||||
: 'rounded-bl-md bg-primary-50 text-neutral-800 dark:bg-neutral-900 dark:text-neutral-100'
|
||||
}`}
|
||||
>
|
||||
{message.content}
|
||||
@@ -201,7 +201,7 @@ export default function MimChatAgent({ currentPath }: MimChatAgentProps) {
|
||||
|
||||
{isSending && (
|
||||
<div className="flex justify-start">
|
||||
<div className="rounded-2xl rounded-bl-md bg-pink-50 px-4 py-3 text-sm text-neutral-700 dark:bg-neutral-900 dark:text-neutral-200">
|
||||
<div className="rounded-2xl rounded-bl-md bg-primary-50 px-4 py-3 text-sm text-neutral-700 dark:bg-neutral-900 dark:text-neutral-200">
|
||||
Thinking...
|
||||
</div>
|
||||
</div>
|
||||
@@ -214,7 +214,7 @@ export default function MimChatAgent({ currentPath }: MimChatAgentProps) {
|
||||
key={suggestion}
|
||||
type="button"
|
||||
onClick={() => void handleSubmit(undefined, suggestion)}
|
||||
className="rounded-full border border-pink-200 bg-white px-3 py-2 text-left text-xs font-medium text-neutral-700 transition hover:border-pink-300 hover:bg-pink-50 dark:border-white/10 dark:bg-neutral-950 dark:text-neutral-200 dark:hover:bg-neutral-900"
|
||||
className="rounded-full border border-primary-200 bg-white px-3 py-2 text-left text-xs font-medium text-neutral-700 transition hover:border-secondary-400 hover:bg-primary-50 dark:border-white/10 dark:bg-neutral-950 dark:text-neutral-200 dark:hover:bg-neutral-900"
|
||||
>
|
||||
{suggestion}
|
||||
</button>
|
||||
@@ -231,12 +231,12 @@ export default function MimChatAgent({ currentPath }: MimChatAgentProps) {
|
||||
onChange={event => setInput(event.target.value)}
|
||||
rows={2}
|
||||
placeholder="Ask about programs, requests, donations, or volunteering..."
|
||||
className="min-h-[3.25rem] flex-1 resize-none rounded-2xl border border-neutral-200 bg-white px-4 py-3 text-sm text-neutral-900 outline-none transition focus:border-pink-400 focus:ring-2 focus:ring-pink-200 dark:border-white/10 dark:bg-neutral-950 dark:text-white dark:focus:border-pink-500 dark:focus:ring-pink-500/20"
|
||||
className="min-h-[3.25rem] flex-1 resize-none rounded-2xl border border-neutral-200 bg-white px-4 py-3 text-sm text-neutral-900 outline-none transition focus:border-primary-500 focus:ring-2 focus:ring-primary-200 dark:border-white/10 dark:bg-neutral-950 dark:text-white dark:focus:border-secondary-500 dark:focus:ring-secondary-500/20"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!input.trim() || isSending}
|
||||
className="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-r from-primary-600 to-pink-500 text-white shadow-lg transition hover:scale-[1.02] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
className="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-r from-primary-600 to-secondary-500 text-white shadow-lg transition hover:scale-[1.02] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
aria-label="Send message"
|
||||
>
|
||||
<Send className="h-4 w-4" />
|
||||
@@ -250,7 +250,7 @@ export default function MimChatAgent({ currentPath }: MimChatAgentProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetConversation}
|
||||
className="text-xs font-medium text-pink-600 transition hover:text-pink-700 dark:text-pink-400 dark:hover:text-pink-300"
|
||||
className="text-xs font-medium text-primary-600 transition hover:text-primary-700 dark:text-secondary-400 dark:hover:text-secondary-300"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
@@ -276,7 +276,7 @@ export default function MimChatAgent({ currentPath }: MimChatAgentProps) {
|
||||
aria-expanded={isOpen}
|
||||
aria-controls="mim-chat-agent-panel"
|
||||
>
|
||||
<span className="inline-flex h-11 w-11 items-center justify-center rounded-full bg-gradient-to-r from-primary-600 to-pink-500 text-white shadow-inner">
|
||||
<span className="inline-flex h-11 w-11 items-center justify-center rounded-full bg-gradient-to-r from-primary-600 to-secondary-500 text-white shadow-inner">
|
||||
{isOpen ? <X className="h-5 w-5" /> : <MessageCircle className="h-5 w-5" />}
|
||||
</span>
|
||||
<span className="hidden sm:flex sm:flex-col sm:items-start sm:leading-tight">
|
||||
|
||||
@@ -217,7 +217,7 @@ const MobileVolunteerApp: React.FC = () => {
|
||||
switch (status) {
|
||||
case 'pending': return 'text-orange-600 bg-orange-100'
|
||||
case 'accepted': return 'text-blue-600 bg-blue-100'
|
||||
case 'in-progress': return 'text-purple-600 bg-purple-100'
|
||||
case 'in-progress': return 'text-primary-600 bg-primary-100'
|
||||
case 'completed': return 'text-green-600 bg-green-100'
|
||||
default: return 'text-gray-600 bg-gray-100'
|
||||
}
|
||||
@@ -288,7 +288,7 @@ const MobileVolunteerApp: React.FC = () => {
|
||||
{assignment.status === 'in-progress' && (
|
||||
<button
|
||||
onClick={() => completeAssignment(assignment.id)}
|
||||
className="flex-1 bg-purple-500 text-white py-2 px-4 rounded-lg text-sm font-medium hover:bg-purple-600 transition-colors"
|
||||
className="flex-1 bg-primary-500 text-white py-2 px-4 rounded-lg text-sm font-medium hover:bg-primary-600 transition-colors"
|
||||
>
|
||||
Mark Complete
|
||||
</button>
|
||||
|
||||
@@ -37,16 +37,13 @@ export function Navigation({ darkMode, setDarkMode, mobileMenuOpen, setMobileMen
|
||||
<>
|
||||
<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"
|
||||
<a
|
||||
href="#/"
|
||||
className="nav-brand-link focus:outline-none"
|
||||
aria-label="Miracles in Motion Foundation — 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>
|
||||
<LogoMark variant="nav" />
|
||||
<span className="sr-only">Essentials for every student</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ const StaffTrainingDashboard: React.FC = () => {
|
||||
|
||||
const getProficiencyColor = (level: string) => {
|
||||
switch (level) {
|
||||
case 'expert': return 'text-purple-600 bg-purple-100'
|
||||
case 'expert': return 'text-primary-600 bg-primary-100'
|
||||
case 'proficient': return 'text-blue-600 bg-blue-100'
|
||||
case 'competent': return 'text-green-600 bg-green-100'
|
||||
case 'novice': return 'text-orange-600 bg-orange-100'
|
||||
@@ -279,19 +279,19 @@ const StaffTrainingDashboard: React.FC = () => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-purple-50 to-pink-100 flex items-center justify-center">
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-secondary-100 flex items-center justify-center">
|
||||
<motion.div
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 2, repeat: Infinity, ease: 'linear' }}
|
||||
className="w-12 h-12 border-4 border-purple-500 border-t-transparent rounded-full"
|
||||
className="w-12 h-12 border-4 border-primary-500 border-t-transparent rounded-full"
|
||||
/>
|
||||
<span className="ml-4 text-lg text-purple-700">Loading Training System...</span>
|
||||
<span className="ml-4 text-lg text-primary-700">Loading Training System...</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-purple-50 to-pink-100 p-6">
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary-50 to-secondary-100 p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
@@ -309,8 +309,8 @@ const StaffTrainingDashboard: React.FC = () => {
|
||||
onClick={() => setCurrentView(view)}
|
||||
className={`px-4 py-2 rounded-lg font-medium transition-all ${
|
||||
currentView === view
|
||||
? 'bg-purple-500 text-white shadow-lg'
|
||||
: 'bg-white text-gray-700 hover:bg-purple-50'
|
||||
? 'bg-primary-500 text-white shadow-lg'
|
||||
: 'bg-white text-gray-700 hover:bg-primary-50'
|
||||
}`}
|
||||
>
|
||||
{view.charAt(0).toUpperCase() + view.slice(1)}
|
||||
@@ -331,7 +331,7 @@ const StaffTrainingDashboard: React.FC = () => {
|
||||
<h3 className="text-lg font-semibold text-gray-700">Total Staff</h3>
|
||||
<span className="text-2xl">👥</span>
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-purple-600 mb-2">{staff.length}</div>
|
||||
<div className="text-3xl font-bold text-primary-600 mb-2">{staff.length}</div>
|
||||
<div className="text-sm text-gray-500">Across all departments</div>
|
||||
</div>
|
||||
|
||||
@@ -415,7 +415,7 @@ const StaffTrainingDashboard: React.FC = () => {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSelectedModule(module)}
|
||||
className="px-4 py-2 bg-purple-500 text-white rounded-lg font-medium hover:bg-purple-600 transition-colors"
|
||||
className="px-4 py-2 bg-primary-500 text-white rounded-lg font-medium hover:bg-primary-600 transition-colors"
|
||||
>
|
||||
View Details
|
||||
</button>
|
||||
@@ -461,7 +461,7 @@ const StaffTrainingDashboard: React.FC = () => {
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${member.trainingProgress}%` }}
|
||||
transition={{ delay: 0.2, duration: 1 }}
|
||||
className="bg-purple-500 h-2 rounded-full"
|
||||
className="bg-primary-500 h-2 rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -521,7 +521,7 @@ const StaffTrainingDashboard: React.FC = () => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-2xl font-bold text-purple-600">
|
||||
<div className="text-2xl font-bold text-primary-600">
|
||||
{checklist.completedItems}/{checklist.totalItems}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">Items Complete</div>
|
||||
@@ -538,7 +538,7 @@ const StaffTrainingDashboard: React.FC = () => {
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${(checklist.completedItems / checklist.totalItems) * 100}%` }}
|
||||
transition={{ delay: 0.3, duration: 1.2 }}
|
||||
className="bg-gradient-to-r from-purple-500 to-pink-500 h-3 rounded-full"
|
||||
className="bg-gradient-to-r from-primary-500 to-secondary-500 h-3 rounded-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -576,7 +576,7 @@ const StaffTrainingDashboard: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
item.category === 'certification' ? 'bg-purple-100 text-purple-600' :
|
||||
item.category === 'certification' ? 'bg-primary-100 text-primary-600' :
|
||||
item.category === 'training' ? 'bg-blue-100 text-blue-600' :
|
||||
item.category === 'practice' ? 'bg-green-100 text-green-600' :
|
||||
item.category === 'mentoring' ? 'bg-orange-100 text-orange-600' :
|
||||
@@ -659,7 +659,7 @@ const StaffTrainingDashboard: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<button className="flex-1 bg-purple-500 text-white py-3 px-4 rounded-lg font-medium hover:bg-purple-600 transition-colors">
|
||||
<button className="flex-1 bg-primary-500 text-white py-3 px-4 rounded-lg font-medium hover:bg-primary-600 transition-colors">
|
||||
Start Training
|
||||
</button>
|
||||
<button className="px-4 py-3 border border-gray-300 rounded-lg font-medium text-gray-700 hover:bg-gray-50 transition-colors">
|
||||
|
||||
@@ -22,12 +22,14 @@ describe('Navigation Component', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('renders navigation with logo and brand name', () => {
|
||||
it('renders navigation with logo and accessible brand label', () => {
|
||||
render(<Navigation {...mockProps} />)
|
||||
|
||||
|
||||
expect(screen.getByTestId('logo-mark')).toBeInTheDocument()
|
||||
expect(screen.getByText('Miracles in Motion')).toBeInTheDocument()
|
||||
expect(screen.getByText('Essentials for every student')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByLabelText('Miracles in Motion Foundation — Home')
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText('Essentials for every student')).toHaveClass('sr-only')
|
||||
})
|
||||
|
||||
it('renders desktop navigation links', () => {
|
||||
|
||||
@@ -140,8 +140,8 @@ function PaymentForm({ amount, isRecurring = false, onSuccess, onError }: Paymen
|
||||
<p className="text-gray-600 mb-6">
|
||||
Your donation of {formatCurrency(amount)} will help students in need.
|
||||
</p>
|
||||
<div className="bg-gradient-to-r from-purple-50 to-blue-50 rounded-lg p-6">
|
||||
<div className="flex items-center justify-center gap-2 text-purple-600 mb-2">
|
||||
<div className="bg-gradient-to-r from-primary-50 to-primary-50 rounded-lg p-6">
|
||||
<div className="flex items-center justify-center gap-2 text-primary-600 mb-2">
|
||||
<Sparkles className="w-5 h-5" />
|
||||
<span className="font-semibold">Impact Preview</span>
|
||||
</div>
|
||||
@@ -172,7 +172,7 @@ function PaymentForm({ amount, isRecurring = false, onSuccess, onError }: Paymen
|
||||
required
|
||||
value={customerInfo.name}
|
||||
onChange={(e) => setCustomerInfo(prev => ({ ...prev, name: e.target.value }))}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="John Doe"
|
||||
/>
|
||||
</div>
|
||||
@@ -186,7 +186,7 @@ function PaymentForm({ amount, isRecurring = false, onSuccess, onError }: Paymen
|
||||
required
|
||||
value={customerInfo.email}
|
||||
onChange={(e) => setCustomerInfo(prev => ({ ...prev, email: e.target.value }))}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="[email protected]"
|
||||
/>
|
||||
</div>
|
||||
@@ -200,7 +200,7 @@ function PaymentForm({ amount, isRecurring = false, onSuccess, onError }: Paymen
|
||||
type="tel"
|
||||
value={customerInfo.phone}
|
||||
onChange={(e) => setCustomerInfo(prev => ({ ...prev, phone: e.target.value }))}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||
placeholder="(555) 123-4567"
|
||||
/>
|
||||
</div>
|
||||
@@ -219,12 +219,12 @@ function PaymentForm({ amount, isRecurring = false, onSuccess, onError }: Paymen
|
||||
</div>
|
||||
|
||||
{/* Donation Summary */}
|
||||
<div className="bg-gradient-to-r from-purple-50 to-blue-50 rounded-lg p-6">
|
||||
<div className="bg-gradient-to-r from-primary-50 to-primary-50 rounded-lg p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-lg font-semibold text-gray-900">
|
||||
{isRecurring ? 'Monthly' : 'One-time'} Donation
|
||||
</span>
|
||||
<span className="text-2xl font-bold text-purple-600">
|
||||
<span className="text-2xl font-bold text-primary-600">
|
||||
{formatCurrency(amount)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -254,7 +254,7 @@ function PaymentForm({ amount, isRecurring = false, onSuccess, onError }: Paymen
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!stripe || isLoading || paymentStatus === 'processing'}
|
||||
className="w-full bg-gradient-to-r from-purple-600 to-blue-600 text-white py-4 px-6 rounded-lg font-semibold text-lg hover:from-purple-700 hover:to-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all flex items-center justify-center gap-2"
|
||||
className="w-full bg-gradient-to-r from-primary-600 to-primary-600 text-white py-4 px-6 rounded-lg font-semibold text-lg hover:from-primary-700 hover:to-primary-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
|
||||
@@ -17,7 +17,7 @@ export function HeroSection() {
|
||||
<section ref={containerRef} className="relative min-h-screen flex items-center justify-center overflow-hidden">
|
||||
{/* Animated Background */}
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-gradient-to-br from-primary-50 via-white to-secondary-50 dark:from-gray-900 dark:to-purple-900"
|
||||
className="absolute inset-0 bg-gradient-to-br from-primary-50 via-white to-secondary-50 dark:from-gray-900 dark:to-primary-900"
|
||||
style={{ y }}
|
||||
/>
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ export function ImpactSection() {
|
||||
label: "Students Helped",
|
||||
description: "Individual students who received direct support",
|
||||
trend: "+23% this year",
|
||||
color: "from-blue-500 to-blue-600"
|
||||
color: "from-primary-500 to-primary-600"
|
||||
},
|
||||
{
|
||||
icon: Heart,
|
||||
@@ -67,7 +67,7 @@ export function ImpactSection() {
|
||||
label: "Success Rate",
|
||||
description: "Students showing improved academic performance",
|
||||
trend: "Consistent excellence",
|
||||
color: "from-purple-500 to-purple-600"
|
||||
color: "from-primary-500 to-primary-600"
|
||||
},
|
||||
{
|
||||
icon: Award,
|
||||
|
||||
@@ -9,7 +9,7 @@ export function ProgramsSection() {
|
||||
title: "School Supplies",
|
||||
description: "Essential learning materials including notebooks, pens, calculators, and art supplies",
|
||||
impact: "2,847 students equipped",
|
||||
color: "from-blue-500 to-blue-600"
|
||||
color: "from-primary-500 to-primary-600"
|
||||
},
|
||||
{
|
||||
icon: Shirt,
|
||||
@@ -30,14 +30,14 @@ export function ProgramsSection() {
|
||||
title: "Educational Technology",
|
||||
description: "Laptops, tablets, and internet access for remote learning success",
|
||||
impact: "645 devices provided",
|
||||
color: "from-purple-500 to-purple-600"
|
||||
color: "from-primary-500 to-primary-600"
|
||||
},
|
||||
{
|
||||
icon: Heart,
|
||||
title: "Mentorship Programs",
|
||||
description: "One-on-one support and guidance for academic and personal growth",
|
||||
impact: "432 mentor relationships",
|
||||
color: "from-pink-500 to-pink-600"
|
||||
color: "from-secondary-500 to-secondary-600"
|
||||
},
|
||||
{
|
||||
icon: Home,
|
||||
|
||||
@@ -83,7 +83,7 @@ export function LanguageSwitcher({
|
||||
key={code}
|
||||
onClick={() => handleLanguageChange(code)}
|
||||
className={`w-full px-3 py-2 text-left hover:bg-gray-50 transition-colors flex items-center gap-2 ${
|
||||
code === currentLang ? 'bg-purple-50 text-purple-600' : 'text-gray-700'
|
||||
code === currentLang ? 'bg-primary-50 text-primary-600' : 'text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<span className="text-base">{lang.flag}</span>
|
||||
@@ -102,7 +102,7 @@ export function LanguageSwitcher({
|
||||
<div className={`w-full ${className}`}>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="w-full flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200 hover:border-purple-300 transition-colors"
|
||||
className="w-full flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200 hover:border-primary-300 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Globe className="w-5 h-5 text-gray-500" />
|
||||
@@ -134,7 +134,7 @@ export function LanguageSwitcher({
|
||||
key={code}
|
||||
onClick={() => handleLanguageChange(code)}
|
||||
className={`w-full px-4 py-3 text-left hover:bg-gray-50 transition-colors border-b border-gray-100 last:border-b-0 ${
|
||||
code === currentLang ? 'bg-purple-50 text-purple-600' : 'text-gray-700'
|
||||
code === currentLang ? 'bg-primary-50 text-primary-600' : 'text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -144,7 +144,7 @@ export function LanguageSwitcher({
|
||||
<div className="text-sm text-gray-500">{code.toUpperCase()}</div>
|
||||
</div>
|
||||
{code === currentLang && (
|
||||
<div className="ml-auto w-2 h-2 bg-purple-600 rounded-full" />
|
||||
<div className="ml-auto w-2 h-2 bg-primary-600 rounded-full" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
@@ -161,7 +161,7 @@ export function LanguageSwitcher({
|
||||
<div className={`relative ${className}`}>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="flex items-center gap-2 px-3 py-2 bg-white rounded-lg border border-gray-200 hover:border-purple-300 transition-colors shadow-sm"
|
||||
className="flex items-center gap-2 px-3 py-2 bg-white rounded-lg border border-gray-200 hover:border-primary-300 transition-colors shadow-sm"
|
||||
aria-label={t('accessibility.toggleLanguage')}
|
||||
>
|
||||
<Globe className="w-4 h-4 text-gray-500" />
|
||||
@@ -202,7 +202,7 @@ export function LanguageSwitcher({
|
||||
key={code}
|
||||
onClick={() => handleLanguageChange(code)}
|
||||
className={`w-full px-4 py-2 text-left hover:bg-gray-50 transition-colors flex items-center gap-3 ${
|
||||
code === currentLang ? 'bg-purple-50 text-purple-600' : 'text-gray-700'
|
||||
code === currentLang ? 'bg-primary-50 text-primary-600' : 'text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<span className="text-xl">{lang.flag}</span>
|
||||
@@ -211,7 +211,7 @@ export function LanguageSwitcher({
|
||||
<div className="text-xs text-gray-500">{code.toUpperCase()}</div>
|
||||
</div>
|
||||
{code === currentLang && (
|
||||
<div className="w-2 h-2 bg-purple-600 rounded-full" />
|
||||
<div className="w-2 h-2 bg-primary-600 rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
// LogoMark component
|
||||
import { motion } from 'framer-motion'
|
||||
import { Sparkles } from 'lucide-react'
|
||||
export type LogoMarkVariant = 'nav' | 'footer' | 'square'
|
||||
|
||||
export interface LogoMarkProps {
|
||||
/** nav/footer: full wordmark; square: compact icon tile */
|
||||
variant?: LogoMarkVariant
|
||||
className?: string
|
||||
}
|
||||
|
||||
const LOGO_SRC = '/logo.png'
|
||||
|
||||
export function LogoMark({ variant = 'nav', className = '' }: LogoMarkProps) {
|
||||
const isDecorative = variant === 'nav' || variant === 'footer'
|
||||
|
||||
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" }}
|
||||
<span
|
||||
className={['logo-mark', `logo-mark--${variant}`, className].filter(Boolean).join(' ')}
|
||||
aria-hidden={isDecorative || undefined}
|
||||
>
|
||||
<img
|
||||
src={LOGO_SRC}
|
||||
alt={isDecorative ? '' : 'Miracles in Motion Foundation'}
|
||||
className="logo-mark__img"
|
||||
width={1024}
|
||||
height={1024}
|
||||
decoding="async"
|
||||
fetchPriority={variant === 'nav' ? 'high' : undefined}
|
||||
/>
|
||||
<Sparkles className="relative h-6 w-6 text-white drop-shadow" />
|
||||
</div>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default LogoMark
|
||||
export default LogoMark
|
||||
|
||||
@@ -184,7 +184,7 @@ export function PerformanceMonitor({
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="flex items-center gap-1">
|
||||
<Image className="w-3 h-3 text-purple-500" />
|
||||
<Image className="w-3 h-3 text-primary-500" />
|
||||
Images
|
||||
</span>
|
||||
<span className="font-mono">{bundleMetrics.imageSize}KB</span>
|
||||
|
||||
@@ -1,27 +1,40 @@
|
||||
/* Self-hosted Inter (LCP-friendly; no external font requests) */
|
||||
/* Self-hosted fonts (LCP-friendly; no external font requests) */
|
||||
@import "@fontsource/inter/300.css";
|
||||
@import "@fontsource/inter/400.css";
|
||||
@import "@fontsource/inter/500.css";
|
||||
@import "@fontsource/inter/600.css";
|
||||
@import "@fontsource/inter/700.css";
|
||||
@import "@fontsource/playfair-display/600.css";
|
||||
@import "@fontsource/playfair-display/700.css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Official MIM brand palette (logo: forest green + gold) */
|
||||
:root {
|
||||
--mim-brand-green: #1a3c34;
|
||||
--mim-brand-green-deep: #0f2922;
|
||||
--mim-brand-green-mid: #2d6b5c;
|
||||
--mim-brand-gold: #c5a059;
|
||||
--mim-brand-gold-bright: #d4af37;
|
||||
--mim-focus-ring: var(--mim-brand-gold);
|
||||
--mim-shadow-brand: rgba(26, 60, 52, 0.28);
|
||||
--mim-shadow-gold: rgba(197, 160, 89, 0.32);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Skip to content: visible on focus for keyboard/screen reader (a11y) */
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: -100px;
|
||||
left: 0.5rem;
|
||||
z-index: 100;
|
||||
padding: 0.75rem 1rem;
|
||||
background: #db2777;
|
||||
background: var(--mim-brand-green);
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
border-radius: 0.5rem;
|
||||
@@ -29,43 +42,166 @@
|
||||
}
|
||||
.skip-link:focus {
|
||||
top: 0.5rem;
|
||||
outline: 2px solid #ec4899;
|
||||
outline: 2px solid var(--mim-brand-gold);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
|
||||
font-feature-settings: 'rlig' 1, 'calt' 1;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(135deg, #ec4899, #8b5cf6);
|
||||
background: linear-gradient(135deg, var(--mim-brand-green), var(--mim-brand-gold));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(135deg, #db2777, #7c3aed);
|
||||
background: linear-gradient(135deg, var(--mim-brand-green-deep), var(--mim-brand-gold-bright));
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Button Components */
|
||||
/* Official logo (public/logo.png) */
|
||||
.logo-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.logo-mark__img {
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Mobile-first; larger on tablet/desktop */
|
||||
.logo-mark--nav {
|
||||
height: 2.125rem;
|
||||
}
|
||||
|
||||
.logo-mark--nav .logo-mark__img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
max-width: 8.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.logo-mark--nav {
|
||||
height: 2.75rem;
|
||||
}
|
||||
.logo-mark--nav .logo-mark__img {
|
||||
max-width: 10.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.logo-mark--nav {
|
||||
height: 3.25rem;
|
||||
}
|
||||
.logo-mark--nav .logo-mark__img {
|
||||
max-width: 12rem;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-mark--footer {
|
||||
height: 2.75rem;
|
||||
}
|
||||
|
||||
.logo-mark--footer .logo-mark__img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
max-width: 10rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.logo-mark--footer {
|
||||
height: 3.25rem;
|
||||
}
|
||||
.logo-mark--footer .logo-mark__img {
|
||||
max-width: 12rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.logo-mark--footer {
|
||||
height: 3.75rem;
|
||||
}
|
||||
.logo-mark--footer .logo-mark__img {
|
||||
max-width: 14rem;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-mark--square {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 0.375rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgb(15 41 34 / 0.25);
|
||||
}
|
||||
|
||||
.logo-mark--square .logo-mark__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.nav-brand-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-brand-link:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.nav-brand-link:focus-visible {
|
||||
outline: 2px solid var(--mim-brand-gold);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.footer-brand-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.footer-brand-block .footer-tagline {
|
||||
font-size: 0.875rem;
|
||||
color: #52525b;
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.footer-brand-block .footer-tagline {
|
||||
color: #d4d4d8;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 9999px;
|
||||
background-image: linear-gradient(to right, #db2777, #7c3aed); /* Replace with your primary-600 and secondary-600 colors */
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
var(--mim-brand-green),
|
||||
var(--mim-brand-green-mid),
|
||||
var(--mim-brand-gold)
|
||||
);
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
padding-top: 0.75rem;
|
||||
@@ -73,64 +209,66 @@
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
box-shadow: 0 10px 15px -3px rgba(236, 72, 153, 0.25), 0 4px 6px -4px rgba(236, 72, 153, 0.25);
|
||||
box-shadow: 0 10px 15px -3px var(--mim-shadow-brand), 0 4px 6px -4px var(--mim-shadow-gold);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 20px 25px -5px rgba(236, 72, 153, 0.25), 0 8px 10px -6px rgba(236, 72, 153, 0.25);
|
||||
box-shadow: 0 20px 25px -5px var(--mim-shadow-brand), 0 8px 10px -6px var(--mim-shadow-gold);
|
||||
}
|
||||
.btn-primary:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #ec4899, 0 10px 15px -3px rgba(236, 72, 153, 0.25), 0 4px 6px -4px rgba(236, 72, 153, 0.25);
|
||||
box-shadow: 0 0 0 2px var(--mim-focus-ring), 0 10px 15px -3px var(--mim-shadow-brand);
|
||||
}
|
||||
.btn-primary:focus-visible {
|
||||
outline: 2px solid #ec4899;
|
||||
outline: 2px solid var(--mim-focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
|
||||
.btn-secondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 9999px;
|
||||
border: 1px solid #d4d4d8; /* border-neutral-300 */
|
||||
background-color: rgba(255,255,255,0.7); /* bg-white/70 */
|
||||
padding-left: 1.5rem; /* px-6 */
|
||||
border: 1px solid #d4d4d8;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
padding-top: 0.75rem; /* py-3 */
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
font-size: 0.875rem; /* text-sm */
|
||||
font-weight: 500; /* font-medium */
|
||||
color: #52525b; /* text-neutral-700 */
|
||||
backdrop-filter: blur(8px); /* backdrop-blur */
|
||||
transition: background 0.2s, box-shadow 0.2s;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #52525b;
|
||||
backdrop-filter: blur(8px);
|
||||
transition: background 0.2s, box-shadow 0.2s, border-color 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);
|
||||
border-color: var(--mim-brand-gold);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.btn-secondary:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #737373, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 0 0 2px var(--mim-brand-green), 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.btn-secondary:focus-visible {
|
||||
outline: 2px solid #737373; /* focus:ring-neutral-500 */
|
||||
outline: 2px solid var(--mim-brand-green);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.btn-secondary {
|
||||
border: 1px solid rgba(255,255,255,0.2); /* dark:border-white/20 */
|
||||
background-color: rgba(255,255,255,0.1); /* dark:bg-white/10 */
|
||||
color: #e5e5e5; /* dark:text-neutral-200 */
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #e5e5e5;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background-color: rgba(255,255,255,0.2); /* dark:hover:bg-white/20 */
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-color: var(--mim-brand-gold);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.btn-white {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -143,84 +281,72 @@
|
||||
padding-bottom: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #18181b;
|
||||
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);
|
||||
color: var(--mim-brand-green-deep);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
.btn-white:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1);
|
||||
}
|
||||
.btn-white:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #fff, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.btn-white:focus-visible {
|
||||
outline: 2px solid #fff;
|
||||
outline: 2px solid var(--mim-brand-gold);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
|
||||
.navlink {
|
||||
font-size: 0.875rem; /* text-sm */
|
||||
font-weight: 500; /* font-medium */
|
||||
color: #52525b; /* text-neutral-600 */
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #52525b;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.navlink:hover {
|
||||
color: #ec4899; /* text-primary-600 */
|
||||
color: var(--mim-brand-green);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.navlink {
|
||||
color: #d4d4d8; /* dark:text-neutral-300 */
|
||||
color: #d4d4d8;
|
||||
}
|
||||
.navlink:hover {
|
||||
color: #a78bfa; /* dark:hover:text-primary-400 */
|
||||
color: var(--mim-brand-gold-bright);
|
||||
}
|
||||
}
|
||||
|
||||
/* Form Components */
|
||||
|
||||
.input-field {
|
||||
width: 100%;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid #d1d5db; /* gray-300 */
|
||||
border: 1px solid #d1d5db;
|
||||
background-color: #fff;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: #111827; /* gray-900 */
|
||||
color: #111827;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.input-field::placeholder {
|
||||
color: #6b7280; /* gray-500 */
|
||||
color: #6b7280;
|
||||
opacity: 1;
|
||||
}
|
||||
.input-field:focus {
|
||||
border-color: #ec4899; /* primary-500 */
|
||||
border-color: var(--mim-brand-green);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #ec4899, 0 0 0 2px rgba(236, 72, 153, 0.2);
|
||||
box-shadow: 0 0 0 2px var(--mim-brand-green), 0 0 0 4px rgba(26, 60, 52, 0.15);
|
||||
}
|
||||
.input-field:focus-visible {
|
||||
outline: 2px solid #ec4899;
|
||||
outline: 2px solid var(--mim-brand-green);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.input-field {
|
||||
border: 1px solid #4b5563; /* gray-600 */
|
||||
background-color: #1f2937; /* gray-800 */
|
||||
color: #f3f4f6; /* gray-100 */
|
||||
}
|
||||
.input-field::placeholder {
|
||||
color: #9ca3af; /* gray-400 */
|
||||
border: 1px solid #4b5563;
|
||||
background-color: #1f2937;
|
||||
color: #f3f4f6;
|
||||
}
|
||||
.input-field:focus {
|
||||
border-color: #a21caf; /* primary-400 */
|
||||
border-color: var(--mim-brand-gold);
|
||||
}
|
||||
}
|
||||
|
||||
/* Text Utilities */
|
||||
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
@@ -228,7 +354,7 @@
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
@@ -236,140 +362,144 @@
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
border-radius: 0.75rem; /* rounded-xl */
|
||||
border: 1px solid rgba(255,255,255,0.3); /* border-white/30 */
|
||||
background-color: rgba(255,255,255,0.7); /* bg-white/70 */
|
||||
padding-left: 0.75rem; /* px-3 */
|
||||
padding-right: 0.75rem;
|
||||
padding-top: 0.5rem; /* py-2 */
|
||||
padding-bottom: 0.5rem;
|
||||
font-size: 0.875rem; /* text-sm */
|
||||
backdrop-filter: blur(8px); /* backdrop-blur */
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
backdrop-filter: blur(8px);
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
.input:focus {
|
||||
border-color: #ec4899; /* primary-500 */
|
||||
border-color: var(--mim-brand-green);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #ec4899, 0 0 0 2px rgba(236, 72, 153, 0.2);
|
||||
box-shadow: 0 0 0 2px var(--mim-brand-green), 0 0 0 4px rgba(26, 60, 52, 0.15);
|
||||
}
|
||||
.input:focus-visible {
|
||||
outline: 2px solid #ec4899;
|
||||
outline: 2px solid var(--mim-brand-green);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.input {
|
||||
border: 1px solid rgba(255,255,255,0.1); /* dark:border-white/10 */
|
||||
background-color: rgba(255,255,255,0.1); /* dark:bg-white/10 */
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.input:focus {
|
||||
border-color: #a21caf; /* dark:focus:border-primary-400 */
|
||||
border-color: var(--mim-brand-gold);
|
||||
}
|
||||
}
|
||||
|
||||
/* Card Components */
|
||||
|
||||
.card {
|
||||
border-radius: 1rem; /* rounded-2xl */
|
||||
border: 1px solid rgba(255,255,255,0.3); /* border-white/30 */
|
||||
background-color: rgba(255,255,255,0.7); /* bg-white/70 */
|
||||
padding: 1.5rem; /* p-6 */
|
||||
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1); /* shadow-xl */
|
||||
backdrop-filter: blur(8px); /* backdrop-blur */
|
||||
border-radius: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.card {
|
||||
border: 1px solid rgba(255,255,255,0.1); /* dark:border-white/10 */
|
||||
background-color: rgba(255,255,255,0.05); /* dark:bg-white/5 */
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.card-hover {
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.card-hover:hover {
|
||||
transform: translateY(-0.25rem); /* -translate-y-1 */
|
||||
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25), 0 8px 10px -6px rgba(0,0,0,0.1); /* shadow-2xl */
|
||||
transform: translateY(-0.25rem);
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Section Header */
|
||||
|
||||
.section-header {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 48rem; /* 3xl = 48rem */
|
||||
max-width: 48rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.section-eyebrow {
|
||||
font-size: 0.875rem; /* text-sm */
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em; /* tracking-wider */
|
||||
color: #db2777; /* text-primary-600 */
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--mim-brand-gold);
|
||||
font-weight: 600;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.section-eyebrow {
|
||||
color: #a78bfa; /* dark:text-primary-400 */
|
||||
color: var(--mim-brand-gold-bright);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.section-title {
|
||||
margin-top: 0.5rem; /* mt-2 */
|
||||
font-size: 1.875rem; /* text-3xl */
|
||||
font-weight: 700; /* font-bold */
|
||||
letter-spacing: -0.025em; /* tracking-tight */
|
||||
margin-top: 0.5rem;
|
||||
font-family: "Playfair Display", Georgia, "Times New Roman", serif;
|
||||
font-size: 1.875rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 2.25rem;
|
||||
color: var(--mim-brand-green-deep);
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.section-title {
|
||||
font-size: 2.25rem; /* sm:text-4xl */
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.section-title {
|
||||
color: #f4f4f5;
|
||||
}
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
margin-top: 1rem; /* mt-4 */
|
||||
font-size: 1.125rem; /* text-lg */
|
||||
color: #52525b; /* text-neutral-600 */
|
||||
margin-top: 1rem;
|
||||
font-size: 1.125rem;
|
||||
color: #52525b;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.section-subtitle {
|
||||
color: #d4d4d8; /* dark:text-neutral-300 */
|
||||
color: #d4d4d8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Gradient Text */
|
||||
.gradient-text {
|
||||
background-image: linear-gradient(to right, #ec4899, #a21caf, #7c3aed); /* Replace with your Tailwind color values */
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
var(--mim-brand-green),
|
||||
var(--mim-brand-green-mid),
|
||||
var(--mim-brand-gold-bright)
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
/* Glass Effect */
|
||||
|
||||
.glass {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
|
||||
.glass-dark {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
/* Text Balance */
|
||||
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* Animation Utilities */
|
||||
|
||||
.animate-in {
|
||||
animation: animate-in 0.6s ease-out;
|
||||
}
|
||||
|
||||
|
||||
@keyframes animate-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
@@ -380,29 +510,17 @@
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Focus States for Accessibility */
|
||||
|
||||
.focus-visible\:ring-2:focus-visible {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
box-shadow: 0 0 0 2px rgba(236, 72, 153, 0.5);
|
||||
box-shadow: 0 0 0 2px rgba(197, 160, 89, 0.55);
|
||||
}
|
||||
}
|
||||
|
||||
/* High Contrast Mode Support */
|
||||
@media (prefers-contrast: high) {
|
||||
.btn-primary {
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-secondary,
|
||||
.card {
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
@@ -410,7 +528,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Reduced Motion Support */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
@@ -420,34 +537,27 @@
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
|
||||
.animate-marquee {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
|
||||
.animate-marquee,
|
||||
.animate-float,
|
||||
.animate-pulse-slow {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print Styles */
|
||||
@media print {
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
color: black !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
|
||||
.gradient-text {
|
||||
color: black !important;
|
||||
background: none !important;
|
||||
-webkit-text-fill-color: initial !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const MainLayout: React.FC<MainLayoutProps> = ({
|
||||
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}`}>
|
||||
<div className={`min-h-screen bg-gradient-to-br from-primary-50 via-white to-secondary-50 dark:from-gray-900 dark:via-gray-800 dark:to-primary-900 ${className}`}>
|
||||
<a href="#content" className="skip-link">Skip to content</a>
|
||||
<header className="sticky top-0 z-40 bg-white/80 backdrop-blur dark:bg-black/80">
|
||||
<Navigation
|
||||
|
||||
@@ -38,7 +38,7 @@ export const HomePage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl md:text-7xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
Miracles in <span className="text-transparent bg-clip-text bg-gradient-to-r from-primary-600 to-pink-600">Motion</span>
|
||||
Miracles in <span className="text-transparent bg-clip-text bg-gradient-to-r from-primary-600 to-secondary-600">Motion</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-xl md:text-2xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto mb-12 leading-relaxed">
|
||||
@@ -70,7 +70,7 @@ export const HomePage: React.FC = () => {
|
||||
{/* Background Elements */}
|
||||
<div className="absolute inset-0 -z-10">
|
||||
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-primary-200/30 rounded-full blur-3xl" />
|
||||
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-pink-200/30 rounded-full blur-3xl" />
|
||||
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-secondary-200/30 rounded-full blur-3xl" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -124,7 +124,7 @@ export const HomePage: React.FC = () => {
|
||||
</section>
|
||||
|
||||
{/* Call to Action */}
|
||||
<section className="py-20 bg-gradient-to-r from-primary-600 to-pink-600">
|
||||
<section className="py-20 bg-gradient-to-r from-primary-600 to-secondary-600">
|
||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 9999px;
|
||||
background: linear-gradient(to right, #c026d3, #4f46e5);
|
||||
background: linear-gradient(to right, #1a3c34, #2d6b5c, #c5a059);
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
box-shadow: 0 10px 15px -3px rgba(236, 72, 153, 0.25);
|
||||
box-shadow: 0 10px 15px -3px rgba(26, 60, 52, 0.28);
|
||||
transition: all 0.3s;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -8,33 +8,42 @@ module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#fdf4ff',
|
||||
100: '#fae8ff',
|
||||
200: '#f5d0fe',
|
||||
300: '#f0abfc',
|
||||
400: '#e879f9',
|
||||
500: '#d946ef',
|
||||
600: '#c026d3',
|
||||
700: '#a21caf',
|
||||
800: '#86198f',
|
||||
900: '#701a75',
|
||||
brand: {
|
||||
green: '#1a3c34',
|
||||
'green-deep': '#0f2922',
|
||||
gold: '#c5a059',
|
||||
'gold-bright': '#d4af37',
|
||||
},
|
||||
/* Forest green — primary UI */
|
||||
primary: {
|
||||
50: '#f0f7f5',
|
||||
100: '#d9ebe6',
|
||||
200: '#b3d7cd',
|
||||
300: '#7db8a8',
|
||||
400: '#4d9482',
|
||||
500: '#2d6b5c',
|
||||
600: '#1a3c34',
|
||||
700: '#153029',
|
||||
800: '#0f2922',
|
||||
900: '#0a1f1a',
|
||||
},
|
||||
/* Gold — accents, CTAs, highlights */
|
||||
secondary: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
200: '#bfdbfe',
|
||||
300: '#93c5fd',
|
||||
400: '#60a5fa',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
800: '#1e40af',
|
||||
900: '#1e3a8a',
|
||||
50: '#faf6ef',
|
||||
100: '#f3ebda',
|
||||
200: '#e6d5b0',
|
||||
300: '#d4af37',
|
||||
400: '#c5a059',
|
||||
500: '#b8924a',
|
||||
600: '#9a7a3d',
|
||||
700: '#7d6232',
|
||||
800: '#604b27',
|
||||
900: '#45361c',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
serif: ['"Playfair Display"', 'Georgia', 'Times New Roman', 'serif'],
|
||||
},
|
||||
animation: {
|
||||
'marquee': 'marquee 30s linear infinite',
|
||||
@@ -56,8 +65,8 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
boxShadow: {
|
||||
'glow': '0 0 20px rgba(236, 72, 153, 0.3)',
|
||||
'glow-lg': '0 0 40px rgba(236, 72, 153, 0.4)',
|
||||
'glow': '0 0 20px rgba(26, 60, 52, 0.35)',
|
||||
'glow-lg': '0 0 40px rgba(197, 160, 89, 0.35)',
|
||||
},
|
||||
backdropBlur: {
|
||||
xs: '2px',
|
||||
@@ -85,4 +94,4 @@ module.exports = {
|
||||
})
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@ export default defineConfig({
|
||||
react(),
|
||||
VitePWA({
|
||||
registerType: 'prompt',
|
||||
includeAssets: ['favicon.svg', 'robots.txt', 'site.webmanifest'],
|
||||
includeAssets: ['favicon.svg', 'logo.png', 'robots.txt', 'site.webmanifest'],
|
||||
manifest: {
|
||||
name: 'Miracles in Motion',
|
||||
name: 'Miracles in Motion Foundation',
|
||||
short_name: 'MiM',
|
||||
description: 'A 501(c)(3) nonprofit providing students with essential support',
|
||||
theme_color: '#7c3aed',
|
||||
background_color: '#ffffff',
|
||||
theme_color: '#1a3c34',
|
||||
background_color: '#1a3c34',
|
||||
display: 'standalone',
|
||||
scope: '/',
|
||||
start_url: '/',
|
||||
@@ -24,6 +24,18 @@ export default defineConfig({
|
||||
src: '/favicon.svg',
|
||||
sizes: 'any',
|
||||
type: 'image/svg+xml',
|
||||
purpose: 'any'
|
||||
},
|
||||
{
|
||||
src: '/favicon-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable'
|
||||
},
|
||||
{
|
||||
src: '/favicon-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable'
|
||||
}
|
||||
]
|
||||
|
||||