Add in support for mobile browsers

This commit is contained in:
TheMaddax 2024-10-14 20:05:06 -05:00
parent 05ec2484af
commit e47efca847
11 changed files with 301 additions and 186 deletions

View file

@ -41,6 +41,8 @@
]
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"postcss-cli": "^8.3.1",
"@types/styled-components": "^5.1.29",
"autoprefixer": "^10.4.16",

View file

@ -16,7 +16,7 @@ const App: React.FC = () => {
<Router>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow">
<main className="flex-grow px-4 sm:px-6 lg:px-8 py-8">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/calendar" element={<Calendar />} />

View file

@ -4,25 +4,41 @@ import EventCard from './EventCard';
import { parseISO, startOfDay, isSameDay, isAfter, format, startOfMonth } from 'date-fns';
import { events, Event } from '../eventData';
const formatTime = (dateString: string, time: string): string => {
export const formatTime = (dateString: string, time: string): string => {
const date = parseISO(dateString);
const [hours, minutes] = time.split(':');
const eventDate = new Date(date.setHours(parseInt(hours, 10), parseInt(minutes, 10)));
return format(eventDate, 'MMMM d, yyyy h:mm a');
return format(eventDate, 'h:mm a');
};
const EventPopup: React.FC<{ event: Event; position: { top: number; left: number } }> = ({ event, position }) => (
export const formatDateRange = (startDate: string, startTime: string, endDate: string, endTime: string): string => {
const start = parseISO(`${startDate}T${startTime}`);
const end = parseISO(`${endDate}T${endTime}`);
if (isSameDay(start, end)) {
return `${format(start, 'MMMM d, yyyy')} ${formatTime(startDate, startTime)} - ${formatTime(endDate, endTime)}`;
} else {
return `${format(start, 'MMMM d, yyyy h:mm a')} - ${format(end, 'MMMM d, yyyy h:mm a')}`;
}
};
const EventPopup: React.FC<{ event: Event; position: { top: number; left: number }; onClose: () => void }> = ({ event, position, onClose }) => (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="absolute z-10 bg-white p-4 rounded-lg shadow-lg"
style={{ top: position.top, left: position.left }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4"
onClick={onClose}
>
<h3 className="font-bold text-lg">{event.title}</h3>
<p>{formatTime(event.date, event.startTime)} - {formatTime(event.date, event.endTime)}</p>
<p>{event.description}</p>
<p>Contact: <a href={`mailto:${event.email}`} className="text-blue-500 hover:underline">{event.pointOfContact}</a></p>
<motion.div
className="bg-white p-4 rounded-lg shadow-lg max-w-xs w-full"
onClick={(e) => e.stopPropagation()}
>
<h3 className="font-bold text-lg">{event.title}</h3>
<p className="text-sm">{formatDateRange(event.date, event.startTime, event.date, event.endTime)}</p>
<p className="text-sm mt-2">{event.description}</p>
<p className="text-sm mt-2">Contact: <a href={`mailto:${event.email}`} className="text-blue-500 hover:underline">{event.pointOfContact}</a></p>
</motion.div>
</motion.div>
);
@ -32,10 +48,23 @@ const Calendar: React.FC = () => {
return startOfMonth(now);
});
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0 });
const [isPopupClosing, setIsPopupClosing] = useState(false);
const calendarRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
if (selectedEvent && calendarRef.current && !calendarRef.current.contains(event.target as Node)) {
setSelectedEvent(null);
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchstart', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchstart', handleClickOutside);
};
}, [selectedEvent]);
const isEventUpcoming = (eventDate: string) => {
const today = startOfDay(new Date());
const parsedEventDate = startOfDay(parseISO(eventDate));
@ -60,53 +89,8 @@ const Calendar: React.FC = () => {
setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1));
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (calendarRef.current && !calendarRef.current.contains(event.target as Node)) {
setSelectedEvent(null);
} else if (selectedEvent) {
setSelectedEvent(null);
setIsPopupClosing(true);
setTimeout(() => setIsPopupClosing(false), 0);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [selectedEvent]);
useEffect(() => {
const now = new Date();
const currentMonthEvents = events.filter(event => {
const eventDate = parseISO(event.date);
return eventDate.getMonth() === now.getMonth() && eventDate.getFullYear() === now.getFullYear();
});
if (currentMonthEvents.length === 0) {
// If no events in the current month, find the next month with events
let nextMonth = now;
while (true) {
nextMonth = new Date(nextMonth.getFullYear(), nextMonth.getMonth() + 1, 1);
const nextMonthEvents = events.filter(event => {
const eventDate = parseISO(event.date);
return eventDate.getMonth() === nextMonth.getMonth() && eventDate.getFullYear() === nextMonth.getFullYear();
});
if (nextMonthEvents.length > 0) {
setCurrentMonth(startOfMonth(nextMonth));
break;
}
}
}
}, []);
const handleEventClick = (event: Event, e: React.MouseEvent) => {
if (!isPopupClosing) {
const rect = (e.target as HTMLElement).getBoundingClientRect();
setPopupPosition({ top: rect.bottom, left: rect.left });
setSelectedEvent(event);
}
const handleEventClick = (event: Event) => {
setSelectedEvent(event);
};
const upcomingEvents = events
@ -114,7 +98,7 @@ const Calendar: React.FC = () => {
.sort((a, b) => parseISO(a.date).getTime() - parseISO(b.date).getTime());
return (
<div className="max-w-4xl mx-auto p-6 relative" ref={calendarRef}>
<div className="max-w-4xl mx-auto p-4 sm:p-6 relative" ref={calendarRef}>
<div className="flex justify-between items-center mb-6">
<motion.button
whileHover={{ scale: 1.1 }}
@ -124,7 +108,7 @@ const Calendar: React.FC = () => {
>
</motion.button>
<h2 className="text-2xl font-bold text-[#8C1D40]">
<h2 className="text-xl sm:text-2xl font-bold text-[#8C1D40]">
{monthNames[currentMonth.getMonth()]} {currentMonth.getFullYear()}
</h2>
<motion.button
@ -137,13 +121,13 @@ const Calendar: React.FC = () => {
</motion.button>
</div>
<div className="grid grid-cols-7 gap-1 bg-gray-200 rounded-lg overflow-hidden">
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => (
<div key={day} className="bg-[#8C1D40] text-white font-bold p-2 text-center">
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(day => (
<div key={day} className="bg-[#8C1D40] text-white font-bold p-2 text-center text-xs sm:text-sm">
{day}
</div>
))}
{emptyDays.map(day => (
<div key={`empty-${day}`} className="bg-white p-2 h-24"></div>
<div key={`empty-${day}`} className="bg-white p-2 h-16 sm:h-24"></div>
))}
{days.map(day => {
const currentDate = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day);
@ -153,18 +137,20 @@ const Calendar: React.FC = () => {
return (
<motion.div
key={day}
className="bg-white p-2 h-24 cursor-pointer flex flex-col items-start justify-start overflow-hidden"
className="bg-white p-1 sm:p-2 h-16 sm:h-24 cursor-pointer flex flex-col items-start justify-start overflow-hidden"
whileHover={{ backgroundColor: '#FFCCCB' }}
>
<span className="font-semibold">{day}</span>
<span className="font-semibold text-xs sm:text-sm">{day}</span>
{dayEvents.map((event, index) => (
<div
key={index}
className="text-xs bg-[#8C1D40] text-white p-1 mt-1 rounded w-full"
onClick={(e) => handleEventClick(event, e)}
className="text-xs bg-[#8C1D40] text-white p-1 mt-1 rounded w-full truncate"
onClick={() => handleEventClick(event)}
>
<div>{event.title}</div>
<div>{`${formatTime(event.date, event.startTime).split(' ')[3]} - ${formatTime(event.date, event.endTime).split(' ')[3]}`}</div>
<div className="truncate">{event.title}</div>
<div className="hidden sm:block">
{formatTime(event.date, event.startTime)} - {formatTime(event.date, event.endTime)}
</div>
</div>
))}
</motion.div>
@ -175,27 +161,27 @@ const Calendar: React.FC = () => {
{selectedEvent && (
<EventPopup
event={selectedEvent}
position={popupPosition}
position={{ top: 0, left: 0 }}
onClose={() => setSelectedEvent(null)}
/>
)}
</AnimatePresence>
<div className="mt-12">
<h2 className="text-2xl font-bold text-[#8C1D40] mb-6">Upcoming Events</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 justify-items-center">
{upcomingEvents.map((event, index) => (
<EventCard
key={index}
event={{
...event,
formattedStartTime: formatTime(event.date, event.startTime),
formattedEndTime: formatTime(event.date, event.endTime)
}}
/>
))}
</div>
<h2 className="text-xl sm:text-2xl font-bold text-[#8C1D40] mb-6">Upcoming Events</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 justify-items-center">
{upcomingEvents.map((event, index) => (
<EventCard
key={index}
event={{
...event,
formattedDateRange: formatDateRange(event.date, event.startTime, event.date, event.endTime)
}}
/>
))}
</div>
</div>
</div>
);
};

View file

@ -1,38 +1,25 @@
import React from 'react';
import { motion } from 'framer-motion';
import { format } from 'date-fns';
import { Event } from '../eventData';
interface EventCardProps {
event: {
date: string;
title: string;
description: string;
formattedStartTime: string;
formattedEndTime: string;
pointOfContact: string;
email: string;
event: Event & {
formattedDateRange: string;
};
}
const EventCard: React.FC<EventCardProps> = ({ event }) => {
const startDate = new Date(event.formattedStartTime);
const endDate = new Date(event.formattedEndTime);
const formattedDate = format(startDate, 'MMMM d, yyyy');
const formattedTime = `${format(startDate, 'h:mm a')} - ${format(endDate, 'h:mm a')}`;
return (
<motion.div
whileHover={{ scale: 1.05 }}
className="bg-white rounded-lg shadow-md p-4 w-full max-w-xs"
>
<h3 className="font-bold text-lg text-[#8C1D40] mb-2">{event.title}</h3>
<p className="text-sm text-gray-600 mb-1">{formattedDate}</p>
<p className="text-sm mb-2">{formattedTime}</p>
<p className="text-sm text-gray-600 mb-2">{event.formattedDateRange}</p>
<p className="text-sm mb-2">{event.description}</p>
<p className="text-sm">Contact: <a href={`mailto:${event.email}`} className="text-blue-500 hover:underline">{event.pointOfContact}</a></p>
</motion.div>
);
};
export default EventCard;
export default EventCard;

View file

@ -7,7 +7,7 @@ const Footer: React.FC = () => {
email: '',
subject: '',
message: '',
honeypot: '', // Honeypot field
honeypot: '',
});
const [status, setStatus] = useState('');
const [errors, setErrors] = useState<Partial<typeof formData>>({});
@ -45,15 +45,13 @@ const Footer: React.FC = () => {
return;
}
// Check honeypot field
if (formData.honeypot) {
setStatus('Form submission rejected.');
return;
}
// Rate limiting
const currentTime = Date.now();
if (currentTime - lastSubmissionTime < 60000) { // 1 minute cooldown
if (currentTime - lastSubmissionTime < 60000) {
setStatus('Please wait a moment before submitting again.');
return;
}
@ -115,12 +113,12 @@ const Footer: React.FC = () => {
</div>
<div className="w-full md:w-1/3 mb-6 md:mb-0">
<h3 className="text-xl font-bold mb-4">Quick Links</h3>
<ul className="text-sm">
<li className="mb-2"><Link to="/" className="hover:text-yellow-200 transition-colors duration-200">Home</Link></li>
<li className="mb-2"><Link to="/calendar" className="hover:text-yellow-200 transition-colors duration-200">Calendar</Link></li>
<li className="mb-2"><Link to="/bylaws" className="hover:text-yellow-200 transition-colors duration-200">Bylaws</Link></li>
<li className="mb-2"><Link to="/sponsors" className="hover:text-yellow-200 transition-colors duration-200">Sponsors</Link></li>
<li className="mb-2"><Link to="/meet-board" className="hover:text-yellow-200 transition-colors duration-200">Meet MCDi Board</Link></li>
<ul className="text-sm grid grid-cols-2 md:grid-cols-1 gap-2">
<li><Link to="/" className="hover:text-yellow-200 transition-colors duration-200">Home</Link></li>
<li><Link to="/calendar" className="hover:text-yellow-200 transition-colors duration-200">Calendar</Link></li>
<li><Link to="/bylaws" className="hover:text-yellow-200 transition-colors duration-200">Bylaws</Link></li>
<li><Link to="/sponsors" className="hover:text-yellow-200 transition-colors duration-200">Sponsors</Link></li>
<li><Link to="/meet-board" className="hover:text-yellow-200 transition-colors duration-200">Meet MCDi Board</Link></li>
</ul>
</div>
<div className="w-full md:w-1/3">
@ -198,4 +196,4 @@ const Footer: React.FC = () => {
);
};
export default Footer;
export default Footer;

View file

@ -3,15 +3,17 @@ import { Link } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
const Header: React.FC = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
return (
<header className="bg-[#8C1D40] text-white shadow-md">
<div className="container mx-auto px-4 py-4">
<div className="flex justify-between items-center">
<div className="flex items-center">
<img src="/mcdi-logo-small.png" alt="MCDi Logo" className="w-20 h-20 mr-4" />
<h1 className="text-2xl font-bold">Missoula Council of the Deaf, Inc</h1>
<img src="/mcdi-logo-small.png" alt="MCDi Logo" className="w-12 h-12 md:w-20 md:h-20 mr-2 md:mr-4" />
<h1 className="text-lg md:text-2xl font-bold">Missoula Council of the Deaf, Inc</h1>
</div>
<nav>
<nav className="hidden md:block">
<ul className="flex space-x-6 items-center">
<li><NavItem href="/">Home</NavItem></li>
<li><AboutDropdown /></li>
@ -20,8 +22,34 @@ const Header: React.FC = () => {
<li><DonateButton /></li>
</ul>
</nav>
<button
className="md:hidden text-white"
onClick={() => setIsMenuOpen(!isMenuOpen)}
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16m-7 6h7" />
</svg>
</button>
</div>
</div>
<AnimatePresence>
{isMenuOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="md:hidden bg-[#8C1D40] px-4 py-2"
>
<ul className="space-y-2">
<li><NavItem href="/" onClick={() => setIsMenuOpen(false)}>Home</NavItem></li>
<li><NavItem href="/about" onClick={() => setIsMenuOpen(false)}>About MCDi</NavItem></li>
<li><NavItem href="/calendar" onClick={() => setIsMenuOpen(false)}>Calendar</NavItem></li>
<li><NavItem href="/joinmcdi" onClick={() => setIsMenuOpen(false)}>Join MCDi</NavItem></li>
<li><DonateButton onClick={() => setIsMenuOpen(false)} /></li>
</ul>
</motion.div>
)}
</AnimatePresence>
</header>
);
};
@ -29,15 +57,17 @@ const Header: React.FC = () => {
interface NavItemProps {
href: string;
children: React.ReactNode;
onClick?: () => void;
}
const NavItem: React.FC<NavItemProps> = ({ href, children }) => (
<Link to={href} className="hover:text-yellow-200 transition-colors duration-200">
const NavItem: React.FC<NavItemProps> = ({ href, children, onClick }) => (
<Link to={href} className="block hover:text-yellow-200 transition-colors duration-200" onClick={onClick}>
<motion.span whileHover={{ scale: 1.05 }} className="inline-block">
{children}
</motion.span>
</Link>
);
const AboutDropdown: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
@ -75,6 +105,7 @@ const AboutDropdown: React.FC = () => {
</div>
);
};
const DropdownItem: React.FC<NavItemProps> = ({ href, children }) => (
<Link to={href} className="block px-4 py-2 hover:bg-[#6B1631] transition-colors duration-200">
<motion.span whileHover={{ x: 5 }} className="inline-block">
@ -83,15 +114,15 @@ const DropdownItem: React.FC<NavItemProps> = ({ href, children }) => (
</Link>
);
const DonateButton: React.FC = () => (
const DonateButton: React.FC<{ onClick?: () => void }> = ({ onClick }) => (
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Link to="/donate" className="inline-block bg-yellow-400 text-[#8C1D40] font-bold py-2 px-4 rounded-full hover:bg-yellow-300 transition-colors duration-200 shadow-md">
<Link to="/donate" onClick={onClick} className="inline-block bg-yellow-400 text-[#8C1D40] font-bold py-2 px-4 rounded-full hover:bg-yellow-300 transition-colors duration-200 shadow-md">
Donate
</Link>
</motion.div>
);
export default Header;
export default Header;

View file

@ -2,15 +2,9 @@ import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import ImageCarousel from './ImageCarousel';
import EventCard from './EventCard';
import { parseISO, startOfDay, isSameDay, isAfter, format } from 'date-fns';
import { parseISO, startOfDay, isSameDay, isAfter } from 'date-fns';
import { events, Event } from '../eventData';
const formatTime = (dateString: string, time: string): string => {
const date = parseISO(dateString);
const [hours, minutes] = time.split(':');
const eventDate = new Date(date.setHours(parseInt(hours, 10), parseInt(minutes, 10)));
return format(eventDate, 'MMMM d, yyyy h:mm a');
};
import { formatDateRange } from './Calendar';
const isEventUpcoming = (eventDate: string) => {
const today = startOfDay(new Date());
@ -35,13 +29,14 @@ const Home: React.FC = () => {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="space-y-8"
>
<h1 className="text-4xl font-bold text-[#8C1D40] mb-8">Welcome to MCDi</h1>
<h1 className="text-3xl md:text-4xl font-bold text-[#8C1D40] mb-8 text-center">Welcome to MCDi</h1>
<ImageCarousel />
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
<div className="grid grid-rows-2 gap-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div className="space-y-8">
<InfoCard
title="Empowering Deaf Culture and Community"
content="We're proud to be the Missoula Council of the Deaf, Inc. (MCDi) in the heart of Missoula, Montana. Our organization is dedicated to promoting Deaf culture, community, and systematic change in our beautiful state."
@ -59,15 +54,14 @@ const Home: React.FC = () => {
key={index}
event={{
...event,
formattedStartTime: formatTime(event.date, event.startTime),
formattedEndTime: formatTime(event.date, event.endTime)
formattedDateRange: formatDateRange(event.date, event.startTime, event.date, event.endTime)
}}
/>
))}
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<InfoCard
title="Join Us: Support Change in Montana"
content="As a parent or ally, you can make a difference by joining MCDi. Together, we'll work toward creating a more inclusive environment for all Montanans, regardless of communication style. Your support will help shape the future of Deaf education, employment, and accessibility in our great state."
@ -92,8 +86,8 @@ const InfoCard: React.FC<{ title: string; content: string }> = ({ title, content
whileHover={{ scale: 1.03 }}
transition={{ duration: 0.2 }}
>
<h2 className="text-2xl font-semibold text-[#8C1D40] mb-4">{title}</h2>
<p className="text-gray-700 flex-grow">{content}</p>
<h2 className="text-xl md:text-2xl font-semibold text-[#8C1D40] mb-4">{title}</h2>
<p className="text-gray-700 flex-grow text-sm md:text-base">{content}</p>
</motion.div>
);

View file

@ -122,18 +122,13 @@ const MeetBoard: React.FC = () => {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<h1 className="text-4xl font-bold text-[#8C1D40] mb-8">Meet MCDi Board</h1>
<h1 className="text-3xl md:text-4xl font-bold text-[#8C1D40] mb-8">Meet MCDi Board</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{shuffledMembers.slice(0, 3).map((member, index) => (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8">
{shuffledMembers.map((member, index) => (
<BoardMemberCard key={index} member={member} onClick={() => setSelectedMember(member)} />
))}
</div>
<div className="mt-8 flex justify-center gap-8">
{shuffledMembers.slice(3, 5).map((member, index) => (
<BoardMemberCard key={index + 3} member={member} onClick={() => setSelectedMember(member)} />
))}
</div>
<div className="mt-12">
<div
@ -156,7 +151,7 @@ const MeetBoard: React.FC = () => {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 p-4"
className="absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 p-2 md:p-4"
>
<div className="flex items-center justify-between">
<button onClick={handleStop} className="text-white mr-2">
@ -187,20 +182,20 @@ const MeetBoard: React.FC = () => {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4"
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"
onClick={() => setSelectedMember(null)}
>
<motion.div
initial={{ y: -50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -50, opacity: 0 }}
className="bg-white rounded-lg p-8 max-w-md"
className="bg-white rounded-lg p-4 md:p-8 max-w-md w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<h2 className="text-2xl font-semibold text-[#8C1D40] mb-4">{selectedMember.name}</h2>
<p className="text-lg text-gray-700 mb-2">{selectedMember.position}</p>
<p className="text-gray-600 mb-4">{selectedMember.bio}</p>
<a href={`mailto:${selectedMember.email}`} className="text-blue-500 hover:underline">{selectedMember.email}</a>
<h2 className="text-xl md:text-2xl font-semibold text-[#8C1D40] mb-4">{selectedMember.name}</h2>
<p className="text-base md:text-lg text-gray-700 mb-2">{selectedMember.position}</p>
<p className="text-sm md:text-base text-gray-600 mb-4">{selectedMember.bio}</p>
<a href={`mailto:${selectedMember.email}`} className="text-blue-500 hover:underline text-sm md:text-base">{selectedMember.email}</a>
</motion.div>
</motion.div>
)}
@ -211,14 +206,14 @@ const MeetBoard: React.FC = () => {
const BoardMemberCard: React.FC<{ member: BoardMember; onClick: () => void }> = ({ member, onClick }) => (
<motion.div
className="bg-white rounded-lg shadow-md p-6 flex flex-col items-center cursor-pointer"
className="bg-white rounded-lg shadow-md p-4 md:p-6 flex flex-col items-center cursor-pointer"
whileHover={{ scale: 1.05 }}
transition={{ duration: 0.2 }}
onClick={onClick}
>
<h2 className="text-2xl font-semibold text-[#8C1D40] mb-4">{member.name}</h2>
<img src={member.image} alt={member.name} className="w-48 h-48 object-cover rounded-full mb-4" />
<p className="text-lg text-gray-700">{member.position}</p>
<h2 className="text-xl md:text-2xl font-semibold text-[#8C1D40] mb-2 md:mb-4">{member.name}</h2>
<img src={member.image} alt={member.name} className="w-32 h-32 md:w-48 md:h-48 object-cover rounded-full mb-2 md:mb-4" />
<p className="text-base md:text-lg text-gray-700">{member.position}</p>
</motion.div>
);

View file

@ -10,13 +10,13 @@ interface SponsorCardProps {
const SponsorCard: React.FC<SponsorCardProps> = ({ name, logoPath, onClick }) => (
<motion.div
className="bg-white rounded-lg shadow-md p-6 flex flex-col items-center h-full cursor-pointer"
className="bg-white rounded-lg shadow-md p-4 sm:p-6 flex flex-col items-center h-full cursor-pointer"
whileHover={{ scale: 1.03 }}
transition={{ duration: 0.2 }}
onClick={onClick}
>
<h2 className="text-2xl font-semibold text-[#8C1D40] mb-4">{name}</h2>
<div className="w-full h-48 flex items-center justify-center">
<h2 className="text-xl sm:text-2xl font-semibold text-[#8C1D40] mb-2 sm:mb-4 text-center">{name}</h2>
<div className="w-full h-32 sm:h-48 flex items-center justify-center">
<img src={logoPath} alt={`${name} logo`} className="max-w-full max-h-full object-contain" />
</div>
</motion.div>
@ -26,21 +26,21 @@ const Popup: React.FC<{ isOpen: boolean; onClose: () => void; children: React.Re
<AnimatePresence>
{isOpen && (
<motion.div
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4"
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
>
<motion.div
className="bg-white rounded-lg p-6 max-w-md"
className="bg-white rounded-lg p-4 sm:p-6 w-full max-w-md"
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
onClick={(e) => e.stopPropagation()}
>
{children}
<button className="mt-4 bg-[#8C1D40] text-white px-4 py-2 rounded" onClick={onClose}>Close</button>
<button className="mt-4 bg-[#8C1D40] text-white px-4 py-2 rounded w-full sm:w-auto" onClick={onClose}>Close</button>
</motion.div>
</motion.div>
)}
@ -63,9 +63,9 @@ const Sponsors: React.FC = () => {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<h1 className="text-4xl font-bold text-[#8C1D40] mb-8">Our Sponsors</h1>
<h1 className="text-3xl sm:text-4xl font-bold text-[#8C1D40] mb-6 sm:mb-8 text-center">Our Sponsors</h1>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-8">
<SponsorCard
name="Drum Coffee"
logoPath="/sponsors/drum-coffee-missoula.png"
@ -78,24 +78,20 @@ const Sponsors: React.FC = () => {
description={sponsorInfo["Imagine Nation Brewing"]}
onClick={() => setSelectedSponsor("Imagine Nation Brewing")}
/>
</div>
<div className="mt-8 flex justify-center">
<div className="w-full md:w-1/2">
<SponsorCard
name="Gild Brewing"
logoPath="/sponsors/gild-brewing.png"
description={sponsorInfo["Gild Brewing"]}
onClick={() => setSelectedSponsor("Gild Brewing")}
/>
</div>
<SponsorCard
name="Gild Brewing"
logoPath="/sponsors/gild-brewing.png"
description={sponsorInfo["Gild Brewing"]}
onClick={() => setSelectedSponsor("Gild Brewing")}
/>
</div>
</motion.div>
<Popup isOpen={!!selectedSponsor} onClose={() => setSelectedSponsor(null)}>
{selectedSponsor && (
<>
<h2 className="text-2xl font-bold mb-4">{selectedSponsor}</h2>
<p>{sponsorInfo[selectedSponsor as keyof typeof sponsorInfo]}</p>
<h2 className="text-xl sm:text-2xl font-bold mb-4">{selectedSponsor}</h2>
<p className="text-sm sm:text-base">{sponsorInfo[selectedSponsor as keyof typeof sponsorInfo]}</p>
</>
)}
</Popup>
@ -103,4 +99,4 @@ const Sponsors: React.FC = () => {
);
};
export default Sponsors;
export default Sponsors;

View file

@ -2,4 +2,108 @@
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
/* You can add any custom styles here */
/* Custom styles */
html, body {
@apply antialiased;
}
body {
@apply bg-gray-100;
}
/* Mobile-first approach */
.container {
@apply px-4 sm:px-6 lg:px-8;
}
/* Header styles */
.header-nav {
@apply flex flex-col sm:flex-row items-center justify-between;
}
.nav-item {
@apply block py-2 px-4 text-center sm:inline-block sm:py-0;
}
/* Footer styles */
.footer-content {
@apply flex flex-col sm:flex-row justify-between items-start sm:items-center;
}
.footer-section {
@apply w-full sm:w-1/3 mb-6 sm:mb-0;
}
/* Form styles */
.form-input {
@apply w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-[#8C1D40] focus:border-transparent;
}
/* Button styles */
.btn {
@apply px-4 py-2 rounded-md transition duration-300 ease-in-out;
}
.btn-primary {
@apply bg-[#8C1D40] text-white hover:bg-[#6B1631];
}
/* Responsive typography */
h1 {
@apply text-3xl sm:text-4xl font-bold;
}
h2 {
@apply text-2xl sm:text-3xl font-semibold;
}
/* Custom scrollbar for webkit browsers */
::-webkit-scrollbar {
@apply w-2;
}
::-webkit-scrollbar-track {
@apply bg-gray-200;
}
::-webkit-scrollbar-thumb {
@apply bg-[#8C1D40] rounded-full;
}
/* Smooth scrolling for the entire page */
html {
scroll-behavior: smooth;
}
/* Improved focus styles for accessibility */
*:focus {
@apply outline-none ring-2 ring-[#8C1D40] ring-opacity-50;
}
/* Custom utility classes */
.text-shadow {
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.hover-grow {
@apply transition-transform duration-300 ease-in-out;
}
.hover-grow:hover {
@apply transform scale-105;
}
/* Responsive grid layouts */
.grid-responsive {
@apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6;
}
/* Custom animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fadeIn {
animation: fadeIn 0.5s ease-in-out;
}

View file

@ -1,10 +1,32 @@
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
"./public/index.html",
],
theme: {
extend: {},
content: [
"./src/**/*.{js,jsx,ts,tsx}",
"./public/index.html",
],
theme: {
extend: {
colors: {
'mcdi-maroon': '#8C1D40',
'mcdi-gold': '#FFC627',
},
fontFamily: {
'sans': ['Roboto', 'Arial', 'sans-serif'],
'serif': ['Merriweather', 'Georgia', 'serif'],
},
screens: {
'xs': '475px',
},
spacing: {
'128': '32rem',
'144': '36rem',
},
minHeight: {
'1/2': '50vh',
},
},
plugins: [],
}
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
}