Add client outreach photos and Instagram profile link
Five optimized community images and outreach video on home hero, gallery, and events page; footer and SEO link to @miraclesinmotionfoundation on Instagram. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 346 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 356 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 496 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 396 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
Binary file not shown.
+20
-67
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useRef, useContext, createContext } from 'react'
|
||||
import { motion, useMotionValue, useSpring, useTransform, useScroll, AnimatePresence } from 'framer-motion'
|
||||
import { motion, useTransform, useScroll, AnimatePresence } from 'framer-motion'
|
||||
import {
|
||||
ArrowRight,
|
||||
Backpack,
|
||||
@@ -75,8 +75,9 @@ import {
|
||||
StoriesIntroBlock,
|
||||
WhatWeDoPage,
|
||||
} from './pages/foundation'
|
||||
import { CommunityGallery } from './components/home/CommunityGallery'
|
||||
import { WhoWeServe } from './components/home/WhoWeServe'
|
||||
import { SITE, WHAT_WE_DO } from './content/siteContent'
|
||||
import { COMMUNITY_PHOTOS, SITE, WHAT_WE_DO } from './content/siteContent'
|
||||
|
||||
/**
|
||||
* Miracles in Motion — Complete Non-Profit Website
|
||||
@@ -588,12 +589,6 @@ interface Translations {
|
||||
}
|
||||
}
|
||||
|
||||
interface TiltCardProps {
|
||||
icon: React.ComponentType<IconProps>
|
||||
title: string
|
||||
desc: string
|
||||
}
|
||||
|
||||
interface FeatureCardProps {
|
||||
icon: React.ComponentType<IconProps>
|
||||
title: string
|
||||
@@ -796,6 +791,7 @@ function HomePage() {
|
||||
<WhoWeServe />
|
||||
<PartnerMarquee />
|
||||
<Programs />
|
||||
<CommunityGallery />
|
||||
<Impact />
|
||||
<HowItWorks />
|
||||
<Testimonial />
|
||||
@@ -1032,74 +1028,31 @@ function Hero() {
|
||||
}
|
||||
|
||||
function HeroShowcase() {
|
||||
const featured = COMMUNITY_PHOTOS.slice(0, 4)
|
||||
return (
|
||||
<div className="relative mx-auto max-w-sm lg:max-w-md xl:max-w-lg">
|
||||
<div className="absolute -inset-6 -z-10 rounded-3xl bg-gradient-to-tr from-primary-500/15 via-secondary-500/15 to-secondary-600/15 blur-2xl" />
|
||||
<div className="grid grid-cols-2 gap-3 sm:gap-4">
|
||||
<TiltCard icon={Users} title="Community Outreach" desc="Direct support for families in need" />
|
||||
<TiltCard icon={Heart} title="Crisis & Relief" desc="Fire, disaster, and emergency assistance" />
|
||||
<TiltCard icon={Shield} title="Advocacy & Navigation" desc="Resources, referrals, and compassionate care" />
|
||||
<TiltCard icon={Sparkles} title="Hope & Restoration" desc="Faith-rooted support when it matters most" />
|
||||
{featured.map((photo, index) => (
|
||||
<img
|
||||
key={photo.src}
|
||||
src={photo.src}
|
||||
alt={photo.alt}
|
||||
width={600}
|
||||
height={800}
|
||||
fetchPriority={index === 0 ? 'high' : 'auto'}
|
||||
loading={index === 0 ? 'eager' : 'lazy'}
|
||||
decoding="async"
|
||||
className={`rounded-2xl border border-white/60 object-cover shadow-lg dark:border-white/10 ${
|
||||
index === 0 ? 'col-span-2 aspect-[16/10] w-full' : 'aspect-[4/5] w-full'
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TiltCard({ icon: Icon, title, desc }: TiltCardProps) {
|
||||
const x = useMotionValue(0)
|
||||
const y = useMotionValue(0)
|
||||
const rx = useTransform(y, [-50, 50], [12, -12])
|
||||
const ry = useTransform(x, [-50, 50], [-12, 12])
|
||||
const springX = useSpring(rx, { stiffness: 150, damping: 15 })
|
||||
const springY = useSpring(ry, { stiffness: 150, damping: 15 })
|
||||
const scale = useSpring(1, { stiffness: 300, damping: 25 })
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
onMouseMove={(e) => {
|
||||
const rect = e.currentTarget.getBoundingClientRect()
|
||||
x.set(e.clientX - rect.left - rect.width / 2)
|
||||
y.set(e.clientY - rect.top - rect.height / 2)
|
||||
scale.set(1.05)
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
x.set(0)
|
||||
y.set(0)
|
||||
scale.set(1)
|
||||
}}
|
||||
style={{
|
||||
rotateX: springX,
|
||||
rotateY: springY,
|
||||
scale: scale,
|
||||
transformStyle: "preserve-3d",
|
||||
boxShadow: useTransform(scale, [1, 1.05], [
|
||||
"0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
||||
"0 20px 25px -5px rgba(0, 0, 0, 0.15), 0 10px 10px -5px rgba(0, 0, 0, 0.04)"
|
||||
])
|
||||
}}
|
||||
className="group card card-hover will-change-transform cursor-pointer"
|
||||
whileHover={{
|
||||
z: 20
|
||||
}}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(150px_80px_at_var(--x)_var(--y),_rgba(255,255,255,0.35),_transparent)] opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
|
||||
<div className="flex items-start gap-4" style={{ transform: "translateZ(40px)" }}>
|
||||
<div className="grid h-12 w-12 place-items-center rounded-xl bg-gradient-to-br from-primary-500 to-secondary-600 text-white shadow-lg">
|
||||
<Icon className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold tracking-tight">{title}</div>
|
||||
<div className="text-sm text-neutral-600 dark:text-neutral-300">{desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center gap-2 text-sm text-primary-600 transition group-hover:translate-x-1 dark:text-primary-300">
|
||||
Learn more <ArrowRight className="h-4 w-4" />
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
function PartnerMarquee() {
|
||||
return (
|
||||
<section aria-label="Community partners" className="relative">
|
||||
|
||||
+12
-11
@@ -1,5 +1,5 @@
|
||||
import { Facebook, Globe, Instagram, Mail } from 'lucide-react'
|
||||
import { SITE } from '../content/siteContent'
|
||||
import { Instagram, Mail } from 'lucide-react'
|
||||
import { SITE, SOCIAL } from '../content/siteContent'
|
||||
import { LogoMark } from './ui/LogoMark'
|
||||
|
||||
export function Footer() {
|
||||
@@ -16,15 +16,16 @@ export function Footer() {
|
||||
{SITE.brandMessage}
|
||||
</p>
|
||||
<p className="mt-3 text-sm text-neutral-600 dark:text-neutral-400">{SITE.serviceArea}</p>
|
||||
<div className="mt-4 flex gap-4">
|
||||
<a href="#" className="text-neutral-600 hover:text-primary-600 dark:text-neutral-400" aria-label="Facebook">
|
||||
<Facebook className="h-5 w-5" />
|
||||
</a>
|
||||
<a href="#" className="text-neutral-600 hover:text-primary-600 dark:text-neutral-400" aria-label="Instagram">
|
||||
<Instagram className="h-5 w-5" />
|
||||
</a>
|
||||
<a href="#" className="text-neutral-600 hover:text-primary-600 dark:text-neutral-400" aria-label="Website">
|
||||
<Globe className="h-5 w-5" />
|
||||
<div className="mt-4">
|
||||
<a
|
||||
href={SOCIAL.instagram}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 text-neutral-700 transition hover:text-primary-600 dark:text-neutral-300 dark:hover:text-secondary-400"
|
||||
aria-label="Follow Miracles in Motion Foundation on Instagram"
|
||||
>
|
||||
<Instagram className="h-5 w-5" aria-hidden />
|
||||
@miraclesinmotionfoundation
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,9 +93,7 @@ export function SEOHead({
|
||||
"foundingDate": "2020",
|
||||
"areaServed": "United States",
|
||||
"sameAs": [
|
||||
"https://facebook.com/miraclesinmotion",
|
||||
"https://twitter.com/miraclesinmotion",
|
||||
"https://instagram.com/miraclesinmotion"
|
||||
"https://www.instagram.com/miraclesinmotionfoundation"
|
||||
],
|
||||
"contactPoint": {
|
||||
"@type": "ContactPoint",
|
||||
|
||||
@@ -18,9 +18,13 @@ describe('Footer Component', () => {
|
||||
expect(screen.getByText(SITE.brandMessage)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders service area', () => {
|
||||
it('renders service area and Instagram', () => {
|
||||
render(<Footer />)
|
||||
expect(screen.getByText(/Los Angeles County/)).toBeInTheDocument()
|
||||
expect(screen.getByLabelText(/Instagram/i)).toHaveAttribute(
|
||||
'href',
|
||||
'https://www.instagram.com/miraclesinmotionfoundation'
|
||||
)
|
||||
})
|
||||
|
||||
it('renders Get Involved section with correct links', () => {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Instagram } from 'lucide-react'
|
||||
import { COMMUNITY_PHOTOS, SOCIAL, SITE } from '../../content/siteContent'
|
||||
|
||||
type CommunityGalleryProps = {
|
||||
/** Show section heading and Instagram CTA */
|
||||
showHeader?: boolean
|
||||
/** Max photos to display (default: all) */
|
||||
limit?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function CommunityGallery({
|
||||
showHeader = true,
|
||||
limit,
|
||||
className = '',
|
||||
}: CommunityGalleryProps) {
|
||||
const photos = limit ? COMMUNITY_PHOTOS.slice(0, limit) : COMMUNITY_PHOTOS
|
||||
|
||||
return (
|
||||
<section
|
||||
className={`relative py-20 mim-warm-section ${className}`}
|
||||
aria-labelledby={showHeader ? 'community-gallery-heading' : undefined}
|
||||
>
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
{showHeader && (
|
||||
<div className="section-header mx-auto mb-12 max-w-3xl text-center">
|
||||
<div className="section-eyebrow">Real moments, real hope</div>
|
||||
<h2 id="community-gallery-heading" className="section-title">
|
||||
Community in action
|
||||
</h2>
|
||||
<p className="section-subtitle">
|
||||
Authentic photos from our outreach events and gatherings throughout {SITE.serviceArea.split('.')[0].replace('Serving ', '')}.
|
||||
</p>
|
||||
<a
|
||||
href={SOCIAL.instagram}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn-secondary mt-6 inline-flex"
|
||||
>
|
||||
<Instagram className="mr-2 h-4 w-4" aria-hidden />
|
||||
Follow @{SOCIAL.instagram.split('/').pop()}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ul className="grid grid-cols-2 gap-3 sm:gap-4 md:grid-cols-3 lg:grid-cols-5">
|
||||
{photos.map((photo, index) => (
|
||||
<li
|
||||
key={photo.src}
|
||||
className={
|
||||
index === 0
|
||||
? 'col-span-2 row-span-2 md:col-span-2 md:row-span-2'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<figure className="group relative h-full overflow-hidden rounded-2xl border border-secondary-200/40 bg-white shadow-md dark:border-white/10">
|
||||
<img
|
||||
src={photo.src}
|
||||
alt={photo.alt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className={`h-full w-full object-cover transition duration-500 group-hover:scale-[1.03] ${
|
||||
index === 0 ? 'min-h-[280px] sm:min-h-[360px]' : 'aspect-[3/4] min-h-[140px]'
|
||||
}`}
|
||||
/>
|
||||
</figure>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { COMMUNITY_VIDEO } from '../../content/siteContent'
|
||||
|
||||
export function CommunityVideo() {
|
||||
return (
|
||||
<div className="card overflow-hidden p-0">
|
||||
<video
|
||||
className="w-full rounded-xl"
|
||||
controls
|
||||
playsInline
|
||||
preload="metadata"
|
||||
poster={COMMUNITY_VIDEO.poster}
|
||||
aria-label={COMMUNITY_VIDEO.alt}
|
||||
>
|
||||
<source src={COMMUNITY_VIDEO.src} type="video/mp4" />
|
||||
Your browser does not support embedded video.
|
||||
</video>
|
||||
<p className="px-4 py-3 text-center text-xs text-neutral-600 dark:text-neutral-400">
|
||||
{COMMUNITY_VIDEO.alt}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -13,6 +13,42 @@ export const SITE = {
|
||||
closingLine: 'Because miracles still happen every day.',
|
||||
} as const
|
||||
|
||||
/** Official social profiles */
|
||||
export const SOCIAL = {
|
||||
instagram: 'https://www.instagram.com/miraclesinmotionfoundation',
|
||||
instagramQr: 'https://www.instagram.com/miraclesinmotionfoundation?utm_source=qr',
|
||||
} as const
|
||||
|
||||
/** Client-provided community outreach photos (public/photos/community/) */
|
||||
export const COMMUNITY_PHOTOS = [
|
||||
{
|
||||
src: '/photos/community/outreach-01.jpg',
|
||||
alt: 'Miracles in Motion Foundation community outreach — volunteers serving families with compassion',
|
||||
},
|
||||
{
|
||||
src: '/photos/community/outreach-02.jpg',
|
||||
alt: 'Community event supporting families in Los Angeles County',
|
||||
},
|
||||
{
|
||||
src: '/photos/community/outreach-03.jpg',
|
||||
alt: 'Faith-centered outreach and fellowship at a Miracles in Motion Foundation gathering',
|
||||
},
|
||||
{
|
||||
src: '/photos/community/outreach-04.jpg',
|
||||
alt: 'Families and volunteers connected through hope and community support',
|
||||
},
|
||||
{
|
||||
src: '/photos/community/outreach-05.jpg',
|
||||
alt: 'Miracles in Motion Foundation outreach — restoring dignity and hope in the community',
|
||||
},
|
||||
] as const
|
||||
|
||||
export const COMMUNITY_VIDEO = {
|
||||
src: '/photos/community/outreach-video.mp4',
|
||||
poster: '/photos/community/outreach-01.jpg',
|
||||
alt: 'Video from a Miracles in Motion Foundation community outreach event',
|
||||
} as const
|
||||
|
||||
export const NAV_LINKS = [
|
||||
{ href: '#/about', label: 'About', ariaLabel: 'About us' },
|
||||
{ href: '#/what-we-do', label: 'What We Do', ariaLabel: 'Our programs and services' },
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { ArrowRight, Calendar, Heart, Mail, MapPin, Users } from 'lucide-react'
|
||||
import { ArrowRight, Calendar, Heart, Instagram, Mail, MapPin, Users } from 'lucide-react'
|
||||
import { CommunityGallery } from '../../components/home/CommunityGallery'
|
||||
import { CommunityVideo } from '../../components/home/CommunityVideo'
|
||||
import {
|
||||
EVENTS,
|
||||
INTRO,
|
||||
MISSION,
|
||||
POPULATIONS_SERVED,
|
||||
SITE,
|
||||
SOCIAL,
|
||||
STORIES_OF_HOPE,
|
||||
VISION,
|
||||
WHAT_WE_DO,
|
||||
@@ -151,8 +154,9 @@ export function EventsPage() {
|
||||
<p>{EVENTS.body}</p>
|
||||
</PageIntro>
|
||||
|
||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="card flex flex-col items-center gap-4 py-12 text-center">
|
||||
<div className="mx-auto max-w-4xl space-y-8 px-4 sm:px-6 lg:px-8">
|
||||
<CommunityVideo />
|
||||
<div className="card flex flex-col items-center gap-4 py-8 text-center">
|
||||
<Calendar className="h-12 w-12 text-secondary-500" aria-hidden />
|
||||
<p className="max-w-md text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Event listings will be posted here as dates are confirmed. We welcome volunteers and
|
||||
@@ -165,9 +169,20 @@ export function EventsPage() {
|
||||
<a href={EVENTS.ctaContact} className="btn-secondary">
|
||||
Contact us
|
||||
</a>
|
||||
<a
|
||||
href={SOCIAL.instagram}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn-secondary inline-flex"
|
||||
>
|
||||
<Instagram className="mr-2 h-4 w-4" aria-hidden />
|
||||
Instagram
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CommunityGallery showHeader={false} className="pt-8" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user