diff --git a/src/components/Home/HomeLandingSections.tsx b/src/components/Home/HomeLandingSections.tsx
new file mode 100644
index 0000000..aa13cc7
--- /dev/null
+++ b/src/components/Home/HomeLandingSections.tsx
@@ -0,0 +1,386 @@
+import * as React from 'react';
+import {
+ IconBrandGoogle,
+ IconLock,
+ IconMapPin,
+ IconMapPinFilled,
+ IconUsers,
+} from '@tabler/icons-react';
+import Button from '../utils/components/Button';
+import imgHeroCalendar from '../utils/components/TutorialModal/DaySelect-dark.png';
+import heroVidCreate from '../utils/components/TutorialModal/ymeets-create-event-vid.mp4';
+import heroVidShare from '../utils/components/TutorialModal/ymeets-copy-link-vid.mp4';
+import heroVidImport from '../utils/components/TutorialModal/ymeets-import-cals-and-autofill-avails.mp4';
+import heroVidSchedule from '../utils/components/TutorialModal/ymeets-view-availabilities.mp4';
+import imgHowItWorksStep3 from './assets/how-it-works-step-3.png';
+import gcalLogo from '../SideBySideView/gcal-logo.png';
+
+type NavigateFn = (path: string) => void;
+
+const sectionShell = 'max-w-6xl mx-auto px-4 sm:px-6 lg:px-8';
+
+const heroTutorialClips: {
+ src: string;
+ chromeTitle: string;
+ videoAlt: string;
+}[] = [
+ {
+ src: heroVidCreate,
+ chromeTitle: 'ymeets — New event',
+ videoAlt: 'Screen recording: creating an event in ymeets',
+ },
+ {
+ src: heroVidShare,
+ chromeTitle: 'ymeets — Share invite',
+ videoAlt: 'Screen recording: sharing an event link',
+ },
+ {
+ src: heroVidImport,
+ chromeTitle: 'ymeets — Calendar sync',
+ videoAlt: 'Screen recording: importing Google Calendar in ymeets',
+ },
+ {
+ src: heroVidSchedule,
+ chromeTitle: 'ymeets — Availability',
+ videoAlt: 'Screen recording: viewing group availability',
+ },
+];
+
+export function LandingHero({
+ navigate,
+ onOpenTutorial,
+}: {
+ navigate: NavigateFn;
+ onOpenTutorial: () => void;
+}) {
+ const [heroClipIndex, setHeroClipIndex] = React.useState(0);
+ const [prefersReducedMotion, setPrefersReducedMotion] = React.useState(
+ () =>
+ typeof window !== 'undefined' &&
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches,
+ );
+
+ React.useEffect(() => {
+ const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
+ const onChange = () => setPrefersReducedMotion(mq.matches);
+ mq.addEventListener('change', onChange);
+ return () => mq.removeEventListener('change', onChange);
+ }, []);
+
+ const clip = heroTutorialClips[heroClipIndex];
+
+ return (
+
+
+
+
+ A cleaner, faster way to schedule meetings on Yale's campus.
+
+
+
+
+ GCal integration
+
+
+
+
+
+ Vote on a preferred campus meeting place
+
+
+ Vote on a preferred meeting place
+
+
+
+
+
+
+ Lock in the best time and place to gather with a selection
+
+
+
+
+ navigate('/dayselect')}
+ className="!px-10 !py-4 !text-lg"
+ >
+ Create Event
+
+
+
+
+ New to ymeets?
+ {' '}
+
+ Take a quick walkthrough
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {clip.chromeTitle}
+
+
+
+ {prefersReducedMotion ? (
+
+ ) : (
+
+
+
+ )}
+
+ {!prefersReducedMotion && (
+
+
+ Quick walkthrough{' '}
+
+ •
+ {' '}
+ {heroClipIndex + 1} of {heroTutorialClips.length}
+
+
+ {heroTutorialClips.map((_, index) => {
+ const isCurrent = index === heroClipIndex;
+ return (
+ setHeroClipIndex(index)}
+ className="relative p-1 rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-primary dark:focus-visible:ring-primary-dark"
+ aria-label={`Show tutorial clip ${index + 1} of ${heroTutorialClips.length}`}
+ aria-current={isCurrent ? 'true' : undefined}
+ >
+ {isCurrent && (
+
+ )}
+
+
+ );
+ })}
+
+
+ )}
+
+
+
+
+
+ );
+}
+
+const featureCardBase =
+ 'rounded-2xl border border-outline/25 dark:border-gray-600/70 bg-secondary_background dark:bg-secondary_background-dark p-6 sm:p-8 shadow-sm hover:shadow-md transition-shadow duration-300';
+
+export function LandingFeatureHighlights() {
+ const items = [
+ {
+ icon: IconBrandGoogle,
+ title: 'Google Calendar sync',
+ body: 'Auto-fill availability from Google Calendar to save time.',
+ },
+ {
+ icon: IconUsers,
+ title: 'Group scheduling',
+ body: 'Compare times across your group without endless messages.',
+ },
+ {
+ icon: IconMapPin,
+ title: 'Campus place voting',
+ body: 'Let everyone vote on the best place to meet on campus.',
+ },
+ ];
+
+ return (
+
+
+ {items.map(({ icon: Icon, title, body }) => (
+
+
+
+
+
+ {title}
+
+
+ {body}
+
+
+ ))}
+
+
+ );
+}
+
+/** Same frame for all steps: tight border, height from content (image or GCal panel). */
+const howItWorksVisualFrame =
+ 'w-full shrink-0 overflow-hidden rounded-xl border border-outline/20 dark:border-gray-600/50 bg-secondary_background dark:bg-gray-800/90 shadow-inner p-0';
+
+export function LandingHowItWorks() {
+ const steps = [
+ {
+ title: 'Create an event',
+ body: 'Name it, set your time window, then share the event—copy the link or invite people by email.',
+ visual: (
+
+ ),
+ },
+ {
+ title: 'Pull in availability with Google Calendar',
+ body: 'Connect GCal so your busy times show up instantly—and compare with the rest of the group.',
+ visual: (
+
+
+
+
+
+
+ Calendar-connected availability
+
+
+ Optional sync—or mark free times by hand. Your grid updates for
+ the group either way.
+
+
+
+ ),
+ },
+ {
+ title: 'Vote and finalize time & place',
+ body: 'See overlapping availability, vote on Yale campus locations, and lock the meeting details everyone agreed on.',
+ visual: (
+
+ ),
+ },
+ ];
+
+ return (
+
+
+ How it works
+
+
+ Three steps from “we should meet” to a booked time and place.
+
+
+ {steps.map((step, i) => (
+
+
+
+ {i + 1}
+
+
+ {step.title}
+
+
+
+ {step.body}
+
+
+ {step.visual}
+
+
+ ))}
+
+
+ );
+}
+
+export function LandingFinalCta({ navigate }: { navigate: NavigateFn }) {
+ return (
+
+
+
+ Ready to stop scheduling in the group chat?
+
+
+ Create your first event in under a minute—built for Yale, free to
+ use.
+
+
+ navigate('/dayselect')}
+ className="!px-10 !py-4 !text-lg"
+ >
+ Create Event
+
+
+
+
+ );
+}
diff --git a/src/components/Home/HomePage.tsx b/src/components/Home/HomePage.tsx
index c77c92b..7bf8f30 100644
--- a/src/components/Home/HomePage.tsx
+++ b/src/components/Home/HomePage.tsx
@@ -1,177 +1,37 @@
-/* eslint-disable */
import * as React from 'react';
-// import background from '../landingpage/landingbackground.jpg'
import { useNavigate } from 'react-router-dom';
-import { checkIfLoggedIn, getEventById } from '../../backend/events';
-import graphic from './calendargraphic.png';
-import LoginPopup from '../utils/components/LoginPopup';
import Footer from '../utils/components/Footer';
-import Button from '../utils/components/Button';
import TutorialModal from '../utils/components/TutorialModal/TutorialModal';
-
-// import { SiGooglecalendar } from 'react-icons/si';
-// import { FaLock } from 'react-icons/fa';
-// import { CiLocationOn } from 'react-icons/ci';
-
import {
- IconMapPinFilled,
- IconMapPin,
- IconBrandGoogle,
- IconLockAccessOff,
- IconLock,
-} from '@tabler/icons-react';
-import { sendAvailabilityUpdatedEmail } from '../../emails/sendEmailHelpers';
+ LandingHero,
+ LandingFeatureHighlights,
+ LandingHowItWorks,
+ LandingFinalCta,
+} from './HomeLandingSections';
export default function HomePage() {
const navigate = useNavigate();
- const [showInput, setShowInput] = React.useState(true);
- const [eventCode, setEventCode] = React.useState('');
- const [showLoginPopup, setShowLoginPopup] = React.useState(false);
- const [showFormValidation, setShowFormValidation] =
- React.useState(false);
- const [formErrorMessage, setFormErrorMessage] = React.useState('');
- const [emailStatus, setEmailStatus] = React.useState<
- 'idle' | 'sending' | 'sent' | 'error'
- >('idle');
-
- function formValidationPopup(message: string) {
- setShowFormValidation(true);
- setFormErrorMessage(message);
- }
-
- const showEventInput = () => {
- setShowInput(!showInput);
- };
- const updateEventCode = (event: React.BaseSyntheticEvent) => {
- setEventCode(event.target.value);
- };
- const handleKeyPress = (e: any) => {
- if (e.key == 'Enter') {
- validateAndGoToEvent();
- }
- };
-
- const goToEvent = () => {
- getEventById(eventCode)
- .then((result) => {
- navigate('/dashboard/' + eventCode);
- })
- .catch((err) => {
- formValidationPopup('Code is invalid.');
- });
- };
-
- const validateAndGoToEvent = () => {
- if (eventCode.length != 6) {
- formValidationPopup('Codes are 6 characters long.');
- } else {
- goToEvent();
- }
- };
-
- const handleLoginPopupClose = (successFlag?: boolean) => {
- setShowLoginPopup(false);
- if (successFlag) {
- // instead of checkIfLoggedIn because login is async
- goToEvent();
- }
- };
-
- const [showTutorial, setTutorial] = React.useState(false);
+ const [showTutorial, setShowTutorial] = React.useState(false);
return (
-
-
{showTutorial && (
setTutorial(false)}
+ isOpen={showTutorial}
+ onClose={() => setShowTutorial(false)}
/>
)}
-
-
-
-
-
- A cleaner, faster way to schedule meetings on Yale's campus.
-
-
-
-
-
-
-
- Vote on a preferred campus meeting place
-
-
- Vote on a preferred meeting place
-
-
-
-
-
- Lock in the best time and place to gather with a selection
-
-
-
-
-
-
-
- navigate('/dayselect')}
- >
- Create Event
-
-
- navigate('/useraccount')}
- bolded={false}
- >
- View My Events
-
-
-
-
- New to ymeets? →
- setTutorial(true)}>
- Click here for a quick walkthrough!
-
-
-
-
-
-
-
-
-
-
- {showLoginPopup && (
-
- )}
-
+
+ setShowTutorial(true)}
+ />
+
+
+
+
- {/* Footer always at bottom */}
);
diff --git a/src/components/Home/assets/how-it-works-step-3.png b/src/components/Home/assets/how-it-works-step-3.png
new file mode 100644
index 0000000..98d3a96
Binary files /dev/null and b/src/components/Home/assets/how-it-works-step-3.png differ
diff --git a/src/components/utils/components/TutorialModal/DaySelect-dark.png b/src/components/utils/components/TutorialModal/DaySelect-dark.png
index 449acfe..0d5168f 100644
Binary files a/src/components/utils/components/TutorialModal/DaySelect-dark.png and b/src/components/utils/components/TutorialModal/DaySelect-dark.png differ
diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
index 40e1f70..34c5719 100644
--- a/src/react-app-env.d.ts
+++ b/src/react-app-env.d.ts
@@ -10,4 +10,9 @@ declare global {
declare module '*.mp4' {
const src: string;
export default src;
+}
+
+declare module '*.png' {
+ const src: string;
+ export default src;
}
\ No newline at end of file