diff --git a/src/pages/LandingPageV3/CelebrateOurWins/index.jsx b/src/pages/LandingPageV3/CelebrateOurWins/index.jsx deleted file mode 100644 index 07bad9e4..00000000 --- a/src/pages/LandingPageV3/CelebrateOurWins/index.jsx +++ /dev/null @@ -1,151 +0,0 @@ -import React, { useState, useEffect } from 'react' -import celebrate from '../../../assets/images/aboutpage/spirited.jpg' -import LeftArrow from '../../../assets/images/landingpage-v3/LeftTriangleArrow.svg?react' -import RightArrow from '../../../assets/images/landingpage-v3/RightTriangleArrow.svg?react' -import LargeStars from '../../../assets/images/landingpage-v3/LargeShowcaseStars.svg?react' -import SmallStars from '../../../assets/images/landingpage-v3/SmallShowcaseStars.svg?react' -import PrevArrow from '../../../assets/icons/arrows/prevArrowLeft.svg?react' -import NextArrow from '../../../assets/icons/arrows/nextArrowRight.svg?react' -import { useSanity } from '../../../services/useSanity' -import { urlFor } from '../../../client' - -export default function CelebrateOurWins() { - const copyQuery = `*[_type == "copy" && key == "celebrate-our-wins"]{header, content}` - const copy = useSanity(copyQuery) - const showcaseQuery = `*[_type == "showcase"] {year, image, semester} | order(year asc)` - const showcases = useSanity(showcaseQuery, {}, (data) => - data - ? data.map((showcase) => ({ - ...showcase, - semester: showcase.semester.toUpperCase(), - image: urlFor(showcase.image) - })).sort((a, b) => { - if (a.year === b.year){ - return a.semester === "SPRING" ? -1 : 1 - } - return a.year - b.year - }) - : [] - ) - - const [selectedShowcase, setSelectedShowcase] = useState( - showcases && showcases.length !== 0 ? showcases[showcases.length - 1] : {} - ) - - useEffect(() => { - setSelectedShowcase( - showcases && showcases.length !== 0 ? showcases[showcases.length - 1] : {} - ) - }, [showcases]) - - const handleLeftButtonClick = () => { - const currentIndex = showcases.findIndex( - (showcase) => - showcase.year === selectedShowcase.year && - showcase.semester === selectedShowcase.semester - ) - if (currentIndex > 0) { - setSelectedShowcase(showcases[currentIndex - 1]) - } else { - setSelectedShowcase(showcases.at(-1)) - } - } - - const handleRightButtonClick = () => { - const currentIndex = showcases.findIndex( - (showcase) => - showcase.year === selectedShowcase.year && - showcase.semester === selectedShowcase.semester - ) - if (currentIndex < showcases.length - 1) { - setSelectedShowcase(showcases[currentIndex + 1]) - } else { - setSelectedShowcase(showcases.at(0)) - } - } - - return ( -
-
- - -
- {copy && copy[0] && copy[0].header} -
-
- Placeholder -
-
- {copy && copy[0] && copy[0].content[0]} -
- -
-
- - -
-
- ) -} diff --git a/src/pages/LandingPageV3/CelebrateOurWins/index.tsx b/src/pages/LandingPageV3/CelebrateOurWins/index.tsx new file mode 100644 index 00000000..da52a0b1 --- /dev/null +++ b/src/pages/LandingPageV3/CelebrateOurWins/index.tsx @@ -0,0 +1,132 @@ +import React, { useState, useEffect } from 'react'; + +import NextArrow from '../../../assets/icons/arrows/nextArrowRight.svg?react'; +import PrevArrow from '../../../assets/icons/arrows/prevArrowLeft.svg?react'; +import celebrate from '../../../assets/images/aboutpage/spirited.jpg'; +import LargeStars from '../../../assets/images/landingpage-v3/LargeShowcaseStars.svg?react'; +import LeftArrow from '../../../assets/images/landingpage-v3/LeftTriangleArrow.svg?react'; +import RightArrow from '../../../assets/images/landingpage-v3/RightTriangleArrow.svg?react'; +import SmallStars from '../../../assets/images/landingpage-v3/SmallShowcaseStars.svg?react'; +import { urlFor } from '../../../client'; +import { useSanity } from '../../../services/useSanity'; +import type { Copy, SanityShowcase, Showcase } from '../types'; + +export default function CelebrateOurWins() { + const copyQuery = `*[_type == "copy" && key == "celebrate-our-wins"]{header, content}`; + const copy = useSanity(copyQuery); + const showcaseQuery = `*[_type == "showcase"] {year, image, semester} | order(year asc)`; + + const showcases = useSanity(showcaseQuery, {}, data => + data + ? data + .map(showcase => ({ + ...showcase, + semester: showcase.semester.toUpperCase(), + image: urlFor(showcase.image).url(), + })) + .sort((a, b) => { + if (a.year === b.year) { + return a.semester === 'SPRING' ? -1 : 1; + } + + return Number(a.year) - Number(b.year); + }) + : [] + ); + + const [selectedShowcase, setSelectedShowcase] = useState>( + showcases && showcases.length !== 0 ? showcases[showcases.length - 1] : {} + ); + + useEffect(() => { + setSelectedShowcase(showcases && showcases.length !== 0 ? showcases[showcases.length - 1] : {}); + }, [showcases]); + + const handleLeftButtonClick = () => { + const currentIndex = showcases.findIndex( + showcase => showcase.year === selectedShowcase.year && showcase.semester === selectedShowcase.semester + ); + + if (currentIndex > 0) { + setSelectedShowcase(showcases[currentIndex - 1]); + } else { + setSelectedShowcase(showcases.at(-1) ?? {}); + } + }; + + const handleRightButtonClick = () => { + const currentIndex = showcases.findIndex( + showcase => showcase.year === selectedShowcase.year && showcase.semester === selectedShowcase.semester + ); + + if (currentIndex < showcases.length - 1) { + setSelectedShowcase(showcases[currentIndex + 1]); + } else { + setSelectedShowcase(showcases.at(0) ?? {}); + } + }; + + return ( +
+
+ + +
+ {copy && copy[0] && copy[0].header} +
+
+ Placeholder +
+
{copy && copy[0] && copy[0].content[0]}
+ +
+
+ + +
+
+ ); +} diff --git a/src/pages/LandingPageV3/ChooseYourCharacter/Mascot.jsx b/src/pages/LandingPageV3/ChooseYourCharacter/Mascot.tsx similarity index 96% rename from src/pages/LandingPageV3/ChooseYourCharacter/Mascot.jsx rename to src/pages/LandingPageV3/ChooseYourCharacter/Mascot.tsx index 7f2bff24..0c722f72 100644 --- a/src/pages/LandingPageV3/ChooseYourCharacter/Mascot.jsx +++ b/src/pages/LandingPageV3/ChooseYourCharacter/Mascot.tsx @@ -5,16 +5,15 @@ * template). Everything else (hand, legs, shadow) keeps its original styling. * Any other svg props (width, height, className, etc.) are forwarded. */ -function Mascot({ color = '#FFFFFF', ...props }) { +import type { SVGProps } from 'react'; + +type MascotProps = SVGProps & { + color?: string; +}; + +function Mascot({ color = '#FFFFFF', ...props }: MascotProps) { return ( - + {/* Body */} - ) + ); } -export default Mascot +export default Mascot; diff --git a/src/pages/LandingPageV3/ChooseYourCharacter/index.jsx b/src/pages/LandingPageV3/ChooseYourCharacter/index.tsx similarity index 78% rename from src/pages/LandingPageV3/ChooseYourCharacter/index.jsx rename to src/pages/LandingPageV3/ChooseYourCharacter/index.tsx index 29bd0f8b..a4b82ac2 100644 --- a/src/pages/LandingPageV3/ChooseYourCharacter/index.jsx +++ b/src/pages/LandingPageV3/ChooseYourCharacter/index.tsx @@ -4,9 +4,29 @@ import ArcadeMachine from '../../../assets/images/landingpage-v3/DynamicArcadeMa import ArcadeText from '../../../assets/images/landingpage-v3/DynamicArcadeText'; import { urlFor } from '../../../client'; import { useSanity } from '../../../services/useSanity'; -import Mascot from './Mascot.jsx'; +import type { Branch, Director, SanityBranch, SanityDirector } from '../types'; +import Mascot from './Mascot'; -const MascotRadioButton = ({ color, index, isFullOpacity, handleMouseEnter }) => { +type MascotRadioButtonProps = { + color: string; + index: number; + isFullOpacity: boolean; + handleMouseEnter: (index: number) => void; +}; + +type DirectorsListProps = { + directors: Director[]; + coloredIndex: number; + handleSelect: (index: number) => void; +}; + +type ArcadeMachineWrapperProps = { + directors: Director[]; + directorToTeam: Map; + coloredIndex: number; +}; + +const MascotRadioButton = ({ color, index, isFullOpacity, handleMouseEnter }: MascotRadioButtonProps) => { return (
handleMouseEnter(index)}> ); }; -const DirectorsList = ({ directors, coloredIndex, handleSelect }) => { +const DirectorsList = ({ directors, coloredIndex, handleSelect }: DirectorsListProps) => { return (
{directors && @@ -37,11 +57,13 @@ const DirectorsList = ({ directors, coloredIndex, handleSelect }) => { ); }; -const ArcadeMachineWrapper = ({ directors, directorToTeam, coloredIndex }) => { +const ArcadeMachineWrapper = ({ directors, directorToTeam, coloredIndex }: ArcadeMachineWrapperProps) => { if (!directors[coloredIndex]?.name) { return <>; } + const currentTeam = directorToTeam.get(directors[coloredIndex].name); + return ( <> {directors && directorToTeam && directors[coloredIndex] && ( @@ -59,17 +81,17 @@ export default function ChooseYourCharacter() { const directorsQuery = `*[_type == "director"] | order(zIndex)`; const branchQuery = `*[_type == "team"] {team,team_abbreviation,color,zIndex} | order(zIndex)`; - const directors = useSanity(directorsQuery, {}, data => + const directors = useSanity(directorsQuery, {}, data => data ? data.map(director => ({ ...director, color: director.color.hex, - image: urlFor(director.image), + image: urlFor(director.image).url(), })) : [] ); - const branches = useSanity(branchQuery, {}, data => + const branches = useSanity(branchQuery, {}, data => data ? data.map(branch => ({ ...branch, @@ -86,6 +108,7 @@ export default function ChooseYourCharacter() { const newDirectorMapping = new Map( directors.map(director => { const branch = branches.find(branch => branch.team.toUpperCase() === director.team.toUpperCase()); + return [director.name, branch]; }) ); @@ -95,9 +118,9 @@ export default function ChooseYourCharacter() { }, [branches, directors]); const [coloredIndex, setSelected] = useState(0); - let [directorToTeam, setDirectorToTeam] = useState(new Map()); + let [directorToTeam, setDirectorToTeam] = useState(new Map()); - const handleSelect = index => { + const handleSelect = (index: number) => { setSelected(index); }; diff --git a/src/pages/LandingPageV3/Footer/MobileFooter.jsx b/src/pages/LandingPageV3/Footer/MobileFooter.jsx deleted file mode 100644 index d6b635df..00000000 --- a/src/pages/LandingPageV3/Footer/MobileFooter.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react' -import { FooterLink } from '.' -import { useNavigate } from 'react-router-dom' -import { pages } from '.' -import ShadowedButton from '../../../component/ShadowedButton' -import Instagram from '../../../assets/icons/socials/Instagram.svg?react' -import Email from '../../../assets/icons/socials/Email.svg?react' -import LinkedIn from '../../../assets/icons/socials/LinkedIn.svg?react' -import UpArrowMobile from '../../../assets/icons/arrows/upArrowMobile.svg' - -function MobileFooter() { - const navigate = useNavigate() - const currentPageUrl = window.location.href - const handleOnClick = () => { - window.scrollTo(0, 0) - navigate('/') - } - - const icons = [ - { - name: 'Instagram', - href: 'https://www.instagram.com/generatenu/', - symbol: - }, - { - name: 'LinkedIn', - href: 'https://www.linkedin.com/company/generate-product-development/mycompany/', - symbol: - }, - { - name: 'Email', - href: 'mailto:generate@northeastern.edu', - symbol: - } - ] - - return ( -
-
-
- {pages.map((page, index) => ( - - ))} -
-
- - } - onClick={handleOnClick} - /> -
-
- -
- {icons.map((si, index) => ( - - {si.symbol} - - ))} -
-
- ) -} - -export default MobileFooter diff --git a/src/pages/LandingPageV3/Footer/MobileFooter.tsx b/src/pages/LandingPageV3/Footer/MobileFooter.tsx new file mode 100644 index 00000000..2ec38e6b --- /dev/null +++ b/src/pages/LandingPageV3/Footer/MobileFooter.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { FooterLink } from '.'; +import { pages } from '.'; +import UpArrowMobile from '../../../assets/icons/arrows/upArrowMobile.svg'; +import Email from '../../../assets/icons/socials/Email.svg?react'; +import Instagram from '../../../assets/icons/socials/Instagram.svg?react'; +import LinkedIn from '../../../assets/icons/socials/LinkedIn.svg?react'; +import ShadowedButton from '../../../component/ShadowedButton'; + +function MobileFooter() { + const navigate = useNavigate(); + const currentPageUrl = window.location.href; + + const handleOnClick = () => { + window.scrollTo(0, 0); + navigate('/'); + }; + + const icons = [ + { + name: 'Instagram', + href: 'https://www.instagram.com/generatenu/', + symbol: , + }, + { + name: 'LinkedIn', + href: 'https://www.linkedin.com/company/generate-product-development/mycompany/', + symbol: , + }, + { + name: 'Email', + href: 'mailto:generate@northeastern.edu', + symbol: , + }, + ]; + + return ( +
+
+
+ {pages.map((page, index) => ( + + ))} +
+
+ } + onClick={handleOnClick} + /> +
+
+ +
+ {icons.map((si, index) => ( + + {si.symbol} + + ))} +
+
+ ); +} + +export default MobileFooter; diff --git a/src/pages/LandingPageV3/Footer/WebFooter.jsx b/src/pages/LandingPageV3/Footer/WebFooter.jsx deleted file mode 100644 index e4a2175a..00000000 --- a/src/pages/LandingPageV3/Footer/WebFooter.jsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from 'react' -import GenerateLogo from '../../../assets/images/landingpage-v2/footerlogo.svg' //"../../assets/images/landingpage-v2/footerlogo.svg"; -import { FooterLink } from '.' -import { pages } from '.' -import ShadowedButton from '../../../component/ShadowedButton' -import Instagram from '../../../assets/icons/socials/Instagram.svg?react' -import Email from '../../../assets/icons/socials/Email.svg?react' -import LinkedIn from '../../../assets/icons/socials/LinkedIn.svg?react' -import UpArrow from '../../../assets/icons/arrows/upArrow.svg' - -function WebFooter() { - const currentPageUrl = window.location.href - const handleOnClick = () => { - window.scrollTo(0, 0) - } - - const icons = [ - { - name: 'Instagram', - href: 'https://www.instagram.com/generatenu/', - symbol: - }, - { - name: 'LinkedIn', - href: 'https://www.linkedin.com/company/generate-product-development/mycompany/', - symbol: - }, - { - name: 'Email', - href: 'mailto:generate@northeastern.edu', - symbol: - } - ] - - return ( -
- - } - onClick={handleOnClick} - /> -
-
- {pages.map((page, index) => ( - - ))} -
-
-
- {icons.map((si, index) => ( - - {si.symbol} - - ))} -
- - } - onClick={handleOnClick} - /> -
-
-
- ) -} - -export default WebFooter diff --git a/src/pages/LandingPageV3/Footer/WebFooter.tsx b/src/pages/LandingPageV3/Footer/WebFooter.tsx new file mode 100644 index 00000000..b77ad792 --- /dev/null +++ b/src/pages/LandingPageV3/Footer/WebFooter.tsx @@ -0,0 +1,97 @@ +import React from 'react'; + +import { FooterLink } from '.'; +import { pages } from '.'; +import UpArrow from '../../../assets/icons/arrows/upArrow.svg'; +import Email from '../../../assets/icons/socials/Email.svg?react'; +import Instagram from '../../../assets/icons/socials/Instagram.svg?react'; +import LinkedIn from '../../../assets/icons/socials/LinkedIn.svg?react'; +import GenerateLogo from '../../../assets/images/landingpage-v2/footerlogo.svg'; //"../../assets/images/landingpage-v2/footerlogo.svg"; +import ShadowedButton from '../../../component/ShadowedButton'; + +function WebFooter() { + const currentPageUrl = window.location.href; + + const handleOnClick = () => { + window.scrollTo(0, 0); + }; + + const icons = [ + { + name: 'Instagram', + href: 'https://www.instagram.com/generatenu/', + symbol: , + }, + { + name: 'LinkedIn', + href: 'https://www.linkedin.com/company/generate-product-development/mycompany/', + symbol: , + }, + { + name: 'Email', + href: 'mailto:generate@northeastern.edu', + symbol: , + }, + ]; + + return ( +
+ + } + onClick={handleOnClick} + /> +
+
+ {pages.map((page, index) => ( + + ))} +
+
+
+ {icons.map((si, index) => ( + + {si.symbol} + + ))} +
+ + } + onClick={handleOnClick} + /> +
+
+
+ ); +} + +export default WebFooter; diff --git a/src/pages/LandingPageV3/Footer/index.jsx b/src/pages/LandingPageV3/Footer/index.jsx deleted file mode 100644 index b9bfa984..00000000 --- a/src/pages/LandingPageV3/Footer/index.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' -import WebFooter from './WebFooter' -import useWebsite from '../../../shared/useWebsite' -import MobileFooter from './MobileFooter' - -export const pages = [ - { name: 'About', link: '/about' }, - { name: 'Projects', link: '/projects' }, - { name: 'Apply', link: '/apply' }, -] - -export const FooterLink = ({ page, currentPage }) => { - const { name, link, disabled } = page - const isCurrentPage = currentPage === link - - return ( -
- {disabled ? ( - - {name} - - ) : isCurrentPage ? ( - - - {name} {''} - - - ) : ( - - {name} - - )} -
- ) -} - -function Footer() { - const website = useWebsite() - const isBigScreen = !window.matchMedia('(max-device-width: 650px)').matches - - return isBigScreen && website ? : -} - -export default Footer diff --git a/src/pages/LandingPageV3/Footer/index.tsx b/src/pages/LandingPageV3/Footer/index.tsx new file mode 100644 index 00000000..aa674b05 --- /dev/null +++ b/src/pages/LandingPageV3/Footer/index.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +import useWebsite from '../../../shared/useWebsite'; +import MobileFooter from './MobileFooter'; +import WebFooter from './WebFooter'; + +type FooterPage = { + name: string; + link: string; + disabled?: boolean; +}; + +type FooterLinkProps = { + page: FooterPage; + currentPage: string; +}; + +export const pages: FooterPage[] = [ + { name: 'About', link: '/about' }, + { name: 'Projects', link: '/projects' }, + { name: 'Apply', link: '/apply' }, +]; + +export const FooterLink = ({ page, currentPage }: FooterLinkProps) => { + const { name, link, disabled } = page; + const isCurrentPage = currentPage === link; + + return ( +
+ {disabled ? ( + + {name} + + ) : isCurrentPage ? ( + + + {name} {''} + + + ) : ( + + {name} + + )} +
+ ); +}; + +function Footer() { + const website = useWebsite(); + const isBigScreen = !window.matchMedia('(max-device-width: 650px)').matches; + + return isBigScreen && website ? : ; +} + +export default Footer; diff --git a/src/pages/LandingPageV3/HowWereStructured/index.jsx b/src/pages/LandingPageV3/HowWereStructured/index.jsx deleted file mode 100644 index a54cbf25..00000000 --- a/src/pages/LandingPageV3/HowWereStructured/index.jsx +++ /dev/null @@ -1,178 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { useSanity } from '../../../services/useSanity' -import { urlFor } from '../../../client' -import './style.css' - -const ARC_STEPS = 16 -const OUTER_RADIUS = 50 -const INNER_RADIUS = 20 -const IMAGE_RADIAL_DISTANCE = (OUTER_RADIUS + INNER_RADIUS) / 2 -const IMAGE_SIZE = 15 -const SELECTED_SHIFT = 7 - -/** - * Dynamically generates a circular "slice" shaped path (an annular sector / - * donut segment) based on the index of the element and the total number of - * elements that will be present in the circle. The inner edge follows a - * smaller arc so the center of the wheel reads as a true cutout. - */ -function sliceClipPath(index, total) { - const sliceAngle = 360 / total - // Rotate the path by the index - const startAngle = (index - 0.5) * sliceAngle - const endAngle = (index + 0.5) * sliceAngle - - const pointAt = (angle, radius) => { - const rad = (angle * Math.PI) / 180 - const x = 50 + radius * Math.sin(rad) - const y = 50 - radius * Math.cos(rad) - return `${x.toFixed(3)}% ${y.toFixed(3)}%` - } - - const points = [] - // Outer arc: sweep from the start angle to the end angle. - for (let s = 0; s <= ARC_STEPS; s++) { - const angle = startAngle + (endAngle - startAngle) * (s / ARC_STEPS) - points.push(pointAt(angle, OUTER_RADIUS)) - } - // Inner arc: sweep back from the end angle to the start angle to close the - // shape around the center cutout. - for (let s = ARC_STEPS; s >= 0; s--) { - const angle = startAngle + (endAngle - startAngle) * (s / ARC_STEPS) - points.push(pointAt(angle, INNER_RADIUS)) - } - - return `polygon(${points.join(', ')})` -} - -/** - * Computes the center of a sliceClipPath based on the index of the element - * and the number of slices in the circle` - */ -function imagePosition(index, total) { - const sliceAngle = 360 / total - const angle = index * sliceAngle - const rad = (angle * Math.PI) / 180 - const cx = 50 + IMAGE_RADIAL_DISTANCE * Math.sin(rad) - const cy = 50 - IMAGE_RADIAL_DISTANCE * Math.cos(rad) - - // Offset by half the logo's size so (cx, cy) lands at the logo's center - // rather than its top-left corner. - return { - left: `${(cx - IMAGE_SIZE / 2).toFixed(3)}%`, - top: `${(cy - IMAGE_SIZE / 2).toFixed(3)}%` - } -} - -/** - * Computes the radial "slide out" offset (as a CSS translate string) for the - * selected slice, pushing it away from the wheel center along its own angle. - */ -function sliceOffset(index, total) { - const angle = index * (360 / total) - const rad = (angle * Math.PI) / 180 - const x = (SELECTED_SHIFT * Math.sin(rad)).toFixed(3) - const y = (-SELECTED_SHIFT * Math.cos(rad)).toFixed(3) - return `translate(${x}%, ${y}%)` -} - -export default function HowWereStrctured() { - const copyQuery = `*[_type == "copy" && key == "how-were-structured"]{header, content}` - const copy = useSanity(copyQuery) - - const teamQuery = `*[_type == "team" && team != "Clients"] {team, image, teamDescription, zIndex, color} | order(zIndex)` - const teams = useSanity(teamQuery, {}, (data) => - data - ? data.map((value) => ({ - ...value, - image: urlFor(value.image) - })) - : [] - ) - - const [selected, setSelected] = useState({}) - - useEffect(() => { - if (teams.length > 0) { - const randomIndex = Math.floor(Math.random() * teams.length) - setSelected(teams[randomIndex]) - } - }, [teams]) - - const handleSelect = (teamData) => { - setSelected(teamData) - } - - return ( -
-
-
- {copy && copy[0] && copy[0].header} -
-
- {copy && copy[0] && copy[0].content[0]} -
-
-
-
- {teams && - teams[0] && - teams.map((team, index) => ( -
- ))} - {teams && - teams[0] && - teams.map((team, index) => { - const isSelected = selected.team === team.team - return ( - - ) - })} -
-
-
-
{selected.team}
-
{selected.teamDescription}
-
-
-
-
-
- ) -} diff --git a/src/pages/LandingPageV3/HowWereStructured/index.tsx b/src/pages/LandingPageV3/HowWereStructured/index.tsx new file mode 100644 index 00000000..37854781 --- /dev/null +++ b/src/pages/LandingPageV3/HowWereStructured/index.tsx @@ -0,0 +1,179 @@ +import React, { useEffect, useState } from 'react'; + +import { urlFor } from '../../../client'; +import { useSanity } from '../../../services/useSanity'; +import './style.css'; +import type { Copy, SanityStructuredTeam, StructuredTeam } from '../types'; + +const ARC_STEPS = 16; + +const OUTER_RADIUS = 50; + +const INNER_RADIUS = 20; + +const IMAGE_RADIAL_DISTANCE = (OUTER_RADIUS + INNER_RADIUS) / 2; + +const IMAGE_SIZE = 15; + +const SELECTED_SHIFT = 7; + +/** + * Dynamically generates a circular "slice" shaped path (an annular sector / + * donut segment) based on the index of the element and the total number of + * elements that will be present in the circle. The inner edge follows a + * smaller arc so the center of the wheel reads as a true cutout. + */ +function sliceClipPath(index: number, total: number) { + const sliceAngle = 360 / total; + // Rotate the path by the index + const startAngle = (index - 0.5) * sliceAngle; + const endAngle = (index + 0.5) * sliceAngle; + + const pointAt = (angle: number, radius: number) => { + const rad = (angle * Math.PI) / 180; + const x = 50 + radius * Math.sin(rad); + const y = 50 - radius * Math.cos(rad); + + return `${x.toFixed(3)}% ${y.toFixed(3)}%`; + }; + + const points: string[] = []; + // Outer arc: sweep from the start angle to the end angle. + + for (let s = 0; s <= ARC_STEPS; s++) { + const angle = startAngle + (endAngle - startAngle) * (s / ARC_STEPS); + points.push(pointAt(angle, OUTER_RADIUS)); + } + // Inner arc: sweep back from the end angle to the start angle to close the + // shape around the center cutout. + + for (let s = ARC_STEPS; s >= 0; s--) { + const angle = startAngle + (endAngle - startAngle) * (s / ARC_STEPS); + points.push(pointAt(angle, INNER_RADIUS)); + } + + return `polygon(${points.join(', ')})`; +} + +/** + * Computes the center of a sliceClipPath based on the index of the element + * and the number of slices in the circle` + */ +function imagePosition(index: number, total: number) { + const sliceAngle = 360 / total; + const angle = index * sliceAngle; + const rad = (angle * Math.PI) / 180; + const cx = 50 + IMAGE_RADIAL_DISTANCE * Math.sin(rad); + const cy = 50 - IMAGE_RADIAL_DISTANCE * Math.cos(rad); + + // Offset by half the logo's size so (cx, cy) lands at the logo's center + // rather than its top-left corner. + return { + left: `${(cx - IMAGE_SIZE / 2).toFixed(3)}%`, + top: `${(cy - IMAGE_SIZE / 2).toFixed(3)}%`, + }; +} + +/** + * Computes the radial "slide out" offset (as a CSS translate string) for the + * selected slice, pushing it away from the wheel center along its own angle. + */ +function sliceOffset(index: number, total: number) { + const angle = index * (360 / total); + const rad = (angle * Math.PI) / 180; + const x = (SELECTED_SHIFT * Math.sin(rad)).toFixed(3); + const y = (-SELECTED_SHIFT * Math.cos(rad)).toFixed(3); + + return `translate(${x}%, ${y}%)`; +} + +export default function HowWereStrctured() { + const copyQuery = `*[_type == "copy" && key == "how-were-structured"]{header, content}`; + const copy = useSanity(copyQuery); + + const teamQuery = `*[_type == "team" && team != "Clients"] {team, image, teamDescription, zIndex, color} | order(zIndex)`; + + const teams = useSanity(teamQuery, {}, data => + data + ? data.map(value => ({ + ...value, + image: urlFor(value.image).url(), + })) + : [] + ); + + const [selected, setSelected] = useState>({}); + + useEffect(() => { + if (teams.length > 0) { + const randomIndex = Math.floor(Math.random() * teams.length); + setSelected(teams[randomIndex]); + } + }, [teams]); + + const handleSelect = (teamData: StructuredTeam) => { + setSelected(teamData); + }; + + return ( +
+
+
{copy && copy[0] && copy[0].header}
+
{copy && copy[0] && copy[0].content[0]}
+
+
+
+ {teams && + teams[0] && + teams.map((team, index) => ( +
+ ))} + {teams && + teams[0] && + teams.map((team, index) => { + const isSelected = selected.team === team.team; + + return ( + + ); + })} +
+
+
+
{selected.team}
+
{selected.teamDescription}
+
+
+
+
+
+ ); +} diff --git a/src/pages/LandingPageV3/Navigation/index.jsx b/src/pages/LandingPageV3/Navigation/index.jsx deleted file mode 100644 index f84bf941..00000000 --- a/src/pages/LandingPageV3/Navigation/index.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import GenerateLogo from '../../../assets/images/landingpage-v3/GenerateLogo.svg?react' -import DownArrow from '../../../assets/images/landingpage-v3/DownArrow.svg?react' -import Instagram from '../../../assets/icons/socials/Instagram.svg?react' -import Email from '../../../assets/icons/socials/Email.svg?react' -import LinkedIn from '../../../assets/icons/socials/LinkedIn.svg?react' - -export default function Navigation({ scrollToWhatIsGenerate }) { - const icons = [ - { - name: 'Instagram', - href: 'https://www.instagram.com/generatenu/', - symbol: - }, - { - name: 'LinkedIn', - href: 'https://www.linkedin.com/company/generate-product-development/mycompany/', - symbol: - }, - { - name: 'Email', - href: 'mailto:generate@northeastern.edu', - symbol: - } - ] - - return ( -
-
- - -
- - APPLY - - - PROJECTS - - {/* - CLIENTS - */} - - -
- {icons.map((si, index) => ( - - {si.symbol} - - ))} -
-
-
-
-
-
-
- ) -} diff --git a/src/pages/LandingPageV3/Navigation/index.tsx b/src/pages/LandingPageV3/Navigation/index.tsx new file mode 100644 index 00000000..3bc7d568 --- /dev/null +++ b/src/pages/LandingPageV3/Navigation/index.tsx @@ -0,0 +1,72 @@ +import Email from '../../../assets/icons/socials/Email.svg?react'; +import Instagram from '../../../assets/icons/socials/Instagram.svg?react'; +import LinkedIn from '../../../assets/icons/socials/LinkedIn.svg?react'; +import DownArrow from '../../../assets/images/landingpage-v3/DownArrow.svg?react'; +import GenerateLogo from '../../../assets/images/landingpage-v3/GenerateLogo.svg?react'; + +type NavigationProps = { + scrollToWhatIsGenerate: () => void; +}; + +export default function Navigation({ scrollToWhatIsGenerate }: NavigationProps) { + const icons = [ + { + name: 'Instagram', + href: 'https://www.instagram.com/generatenu/', + symbol: , + }, + { + name: 'LinkedIn', + href: 'https://www.linkedin.com/company/generate-product-development/mycompany/', + symbol: , + }, + { + name: 'Email', + href: 'mailto:generate@northeastern.edu', + symbol: , + }, + ]; + + return ( +
+
+ + +
+ + APPLY + + + PROJECTS + + {/* + CLIENTS + */} + + +
+ {icons.map((si, index) => ( + + {si.symbol} + + ))} +
+
+
+
+
+
+
+ ); +} diff --git a/src/pages/LandingPageV3/ParentOrgs/index.jsx b/src/pages/LandingPageV3/ParentOrgs/index.jsx deleted file mode 100644 index e18f8daa..00000000 --- a/src/pages/LandingPageV3/ParentOrgs/index.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import ShermLogo from '../../../assets/images/landingpage-v3/ShermanLogo.svg?react' -import MosaicLogo from '../../../assets/images/landingpage-v3/MosaicLogo.svg?react' - -export default function ParentOrgs() { - return ( -
-
-
- Parent Orgs -
-
-
- - Sherman Center - - - window - .open('https://sherman.center.northeastern.edu/', '_blank') - .focus() - } - /> -
-
- - Mosaic - - { - window - .open( - 'https://mosaic.entrepreneurship.northeastern.edu/', - '_blank' - ) - .focus() - }} - /> -
-
-
-
- ) -} diff --git a/src/pages/LandingPageV3/ParentOrgs/index.tsx b/src/pages/LandingPageV3/ParentOrgs/index.tsx new file mode 100644 index 00000000..daa02372 --- /dev/null +++ b/src/pages/LandingPageV3/ParentOrgs/index.tsx @@ -0,0 +1,46 @@ +import MosaicLogo from '../../../assets/images/landingpage-v3/MosaicLogo.svg?react'; +import ShermLogo from '../../../assets/images/landingpage-v3/ShermanLogo.svg?react'; + +export default function ParentOrgs() { + return ( +
+
+
+ Parent Orgs +
+
+
+ + Sherman Center + + window.open('https://sherman.center.northeastern.edu/', '_blank')?.focus()} + /> +
+
+ + Mosaic + + { + window.open('https://mosaic.entrepreneurship.northeastern.edu/', '_blank')?.focus(); + }} + /> +
+
+
+
+ ); +} diff --git a/src/pages/LandingPageV3/Sponsors/index.jsx b/src/pages/LandingPageV3/Sponsors/index.tsx similarity index 55% rename from src/pages/LandingPageV3/Sponsors/index.jsx rename to src/pages/LandingPageV3/Sponsors/index.tsx index ca72bc7d..f4e25f12 100644 --- a/src/pages/LandingPageV3/Sponsors/index.jsx +++ b/src/pages/LandingPageV3/Sponsors/index.tsx @@ -1,20 +1,24 @@ export default function Sponsors() { return ( -
+
-
+
Sponsors
-
+
{/* To add sponsors, add their image to the public/images/LandingPageImages folder as a .png. Give the Image tag a class of sponsor-logo and the site should respond & resize to accomodate! */} - Logo for the company Altium. + Logo for the company Altium.
- ) + ); } diff --git a/src/pages/LandingPageV3/WeAre/index.jsx b/src/pages/LandingPageV3/WeAre/index.jsx deleted file mode 100644 index f7be0448..00000000 --- a/src/pages/LandingPageV3/WeAre/index.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import weare from '../../../assets/images/landingpage/weare/weare.jpg' -import { useSanity } from '../../../services/useSanity' - -export default function WeAre() { - const query = `*[_type == "value"] {value, index} | order(index asc)` - const values = useSanity(query) - - return ( -
-
-
We Are...
- Three Generate members working together. -
-
-
-
- {values && values[0] && values[0].value} -
-
- {values && values[1] && values[1].value} -
-
- {values && values[2] && values[2].value} -
-
- {values && values[3] && values[3].value} -
-
- {values && values[4] && values[4].value} -
-
-
-
-
- ) -} diff --git a/src/pages/LandingPageV3/WeAre/index.tsx b/src/pages/LandingPageV3/WeAre/index.tsx new file mode 100644 index 00000000..2525ef2e --- /dev/null +++ b/src/pages/LandingPageV3/WeAre/index.tsx @@ -0,0 +1,39 @@ +import weare from '../../../assets/images/landingpage/weare/weare.jpg'; +import { useSanity } from '../../../services/useSanity'; +import type { Value } from '../types'; + +export default function WeAre() { + const query = `*[_type == "value"] {value, index} | order(index asc)`; + const values = useSanity(query); + + return ( +
+
+
+ We Are... +
+ Three Generate members working together. +
+
+
+
+ {values && values[0] && values[0].value} +
+
+ {values && values[1] && values[1].value} +
+
+ {values && values[2] && values[2].value} +
+
+ {values && values[3] && values[3].value} +
+
+ {values && values[4] && values[4].value} +
+
+
+
+
+ ); +} diff --git a/src/pages/LandingPageV3/WhatIsGenerate/index.jsx b/src/pages/LandingPageV3/WhatIsGenerate/index.jsx deleted file mode 100644 index 52e8f9f2..00000000 --- a/src/pages/LandingPageV3/WhatIsGenerate/index.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react' -import { useSanity } from '../../../services/useSanity' -import workshop from '../../../assets/images/landingpage/weare/workshop.jpg' -import showcase from '../../../assets/images/landingpage/weare/showcase.jpg' - -function WhatIsGenerate(props, ref) { - const query = `*[_type == "copy" && key == "what-is-generate-landing"]{header, content}` - const headerCopy = useSanity(query) - - return ( -
-
-
- {headerCopy && headerCopy[0] && headerCopy[0].header} -
- {headerCopy && - headerCopy[0] && - headerCopy[0].content.map((item, index) => ( -

- {item}

-

- ))} -
-
-
- generate studnets having fun -
-
- generate studnets having fun -
-
-
-
-
-
- ) -} - -export default React.forwardRef(WhatIsGenerate) diff --git a/src/pages/LandingPageV3/WhatIsGenerate/index.tsx b/src/pages/LandingPageV3/WhatIsGenerate/index.tsx new file mode 100644 index 00000000..8e0e52fe --- /dev/null +++ b/src/pages/LandingPageV3/WhatIsGenerate/index.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import type { ForwardedRef } from 'react'; + +import showcase from '../../../assets/images/landingpage/weare/showcase.jpg'; +import workshop from '../../../assets/images/landingpage/weare/workshop.jpg'; +import { useSanity } from '../../../services/useSanity'; +import type { Copy } from '../types'; + +function WhatIsGenerate(_props: object, ref: ForwardedRef) { + const query = `*[_type == "copy" && key == "what-is-generate-landing"]{header, content}`; + const headerCopy = useSanity(query); + + return ( +
+
+
{headerCopy && headerCopy[0] && headerCopy[0].header}
+ {headerCopy && + headerCopy[0] && + headerCopy[0].content.map((item, index) => ( +

+ {item} +
+
+

+ ))} +
+
+
+ generate studnets having fun +
+
+ generate studnets having fun +
+
+
+
+
+
+ ); +} + +export default React.forwardRef(WhatIsGenerate); diff --git a/src/pages/LandingPageV3/WhyGenerate/index.jsx b/src/pages/LandingPageV3/WhyGenerate/index.jsx deleted file mode 100644 index 46347210..00000000 --- a/src/pages/LandingPageV3/WhyGenerate/index.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import members from '../../../assets/images/landingpage/weare/members.JPG' -import clients from '../../../assets/images/landingpage/weare/cstar.jpg' -import partners from '../../../assets/images/landingpage/weare/overhead.jpg' -import { useSanity } from '../../../services/useSanity' - -export default function WhyGenerate() { - const query1 = `*[_type == "copy" && key == "why-generate-1"]{header, content}` - const whyGenerate1 = useSanity(query1) - const query2 = `*[_type == "copy" && key == "why-generate-2"]{header, content}` - const whyGenerate2 = useSanity(query2) - const query3 = `*[_type == "copy" && key == "why-generate-3"]{header, content}` - const whyGenerate3 = useSanity(query3) - - return ( -
-
-
Why Generate?
-
-
-
- {whyGenerate1 && whyGenerate1[0] && whyGenerate1[0].header} -
-
-

- {whyGenerate1 && whyGenerate1[0] && whyGenerate1[0].content[0]} -

- Placeholder -
-
-
-
- {whyGenerate2 && whyGenerate2[0] && whyGenerate2[0].header} -
-
- Placeholder -

- {whyGenerate2 && whyGenerate2[0] && whyGenerate2[0].content[0]} -

-
-
-
-
- {whyGenerate3 && whyGenerate3[0] && whyGenerate3[0].header} -
-
-

- {whyGenerate3 && whyGenerate3[0] && whyGenerate3[0].content[0]} -

- Placeholder -
-
-
-
-
-
-
-
- ) -} diff --git a/src/pages/LandingPageV3/WhyGenerate/index.tsx b/src/pages/LandingPageV3/WhyGenerate/index.tsx new file mode 100644 index 00000000..9ee90342 --- /dev/null +++ b/src/pages/LandingPageV3/WhyGenerate/index.tsx @@ -0,0 +1,48 @@ +import clients from '../../../assets/images/landingpage/weare/cstar.jpg'; +import members from '../../../assets/images/landingpage/weare/members.JPG'; +import partners from '../../../assets/images/landingpage/weare/overhead.jpg'; +import { useSanity } from '../../../services/useSanity'; +import type { Copy } from '../types'; + +export default function WhyGenerate() { + const query1 = `*[_type == "copy" && key == "why-generate-1"]{header, content}`; + const whyGenerate1 = useSanity(query1); + const query2 = `*[_type == "copy" && key == "why-generate-2"]{header, content}`; + const whyGenerate2 = useSanity(query2); + const query3 = `*[_type == "copy" && key == "why-generate-3"]{header, content}`; + const whyGenerate3 = useSanity(query3); + + return ( +
+
+
Why Generate?
+
+
+
{whyGenerate1 && whyGenerate1[0] && whyGenerate1[0].header}
+
+

{whyGenerate1 && whyGenerate1[0] && whyGenerate1[0].content[0]}

+ Placeholder +
+
+
+
{whyGenerate2 && whyGenerate2[0] && whyGenerate2[0].header}
+
+ Placeholder +

{whyGenerate2 && whyGenerate2[0] && whyGenerate2[0].content[0]}

+
+
+
+
{whyGenerate3 && whyGenerate3[0] && whyGenerate3[0].header}
+
+

{whyGenerate3 && whyGenerate3[0] && whyGenerate3[0].content[0]}

+ Placeholder +
+
+
+
+
+
+
+
+ ); +} diff --git a/src/pages/LandingPageV3/index.jsx b/src/pages/LandingPageV3/index.jsx deleted file mode 100644 index b1361d60..00000000 --- a/src/pages/LandingPageV3/index.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useRef } from 'react' -import Navigation from './Navigation' -import WhatIsGenerate from './WhatIsGenerate' -import WeAre from './WeAre' -import WhyGenerate from './WhyGenerate' -import HowWereStrctured from './HowWereStructured' -import ChooseYourCharacter from './ChooseYourCharacter' -import CelebrateOurWins from './CelebrateOurWins' -import ParentOrgs from './ParentOrgs' -import Footer from './Footer' -import Sponsors from './Sponsors' -import { Dialog } from '@headlessui/react' -import { useState } from 'react' - -import './style.css' -import ApplyTodayPopup from '../../component/LandingPage/MemberInfoSession' - -export default function LandingPageV3() { - const whatIsGenerateRef = useRef(null) - const [isOpen, setIsOpen] = useState(false) // <-- Set to true to enable the popup and change the link below - - return ( -
- setIsOpen(false)} className='dialog'> - - - - - {isOpen &&
} - - whatIsGenerateRef.current.scrollIntoView({ behavior: 'smooth' }) - } - /> - - - - - - - - -
-
- ) -} diff --git a/src/pages/LandingPageV3/index.tsx b/src/pages/LandingPageV3/index.tsx new file mode 100644 index 00000000..f5dffd2e --- /dev/null +++ b/src/pages/LandingPageV3/index.tsx @@ -0,0 +1,54 @@ +import { Dialog } from '@headlessui/react'; +import React, { useRef } from 'react'; +import { useState } from 'react'; + +import ApplyTodayPopup from '../../component/LandingPage/MemberInfoSession'; +import CelebrateOurWins from './CelebrateOurWins'; +import ChooseYourCharacter from './ChooseYourCharacter'; +import Footer from './Footer'; +import HowWereStrctured from './HowWereStructured'; +import Navigation from './Navigation'; +import ParentOrgs from './ParentOrgs'; +import Sponsors from './Sponsors'; +import WeAre from './WeAre'; +import WhatIsGenerate from './WhatIsGenerate'; +import './style.css'; +import WhyGenerate from './WhyGenerate'; + +export default function LandingPageV3() { + const whatIsGenerateRef = useRef(null); + const [isOpen, setIsOpen] = useState(false); // <-- Set to true to enable the popup and change the link below + + return ( +
+ setIsOpen(false)} className="dialog"> + + + + + {isOpen &&
} + whatIsGenerateRef.current?.scrollIntoView({ behavior: 'smooth' })} /> + + + + + + + + +
+
+ ); +} diff --git a/src/pages/LandingPageV3/types.ts b/src/pages/LandingPageV3/types.ts new file mode 100644 index 00000000..1e8a78ef --- /dev/null +++ b/src/pages/LandingPageV3/types.ts @@ -0,0 +1,63 @@ +import type { SanityImageSource } from '@sanity/image-url'; + +type SanityColor = { + hex: string; +}; + +export type Copy = { + header: string; + content: string[]; +}; + +export type Value = { + value: string; + index: number; +}; + +export type SanityShowcase = { + year: string; + semester: string; + image: SanityImageSource; +}; + +export type Showcase = Omit & { + image: string; +}; + +export type SanityDirector = { + team: string; + title: string; + name: string; + email: string; + color: SanityColor; + image: SanityImageSource; + zIndex: number; +}; + +export type Director = Omit & { + color: string; + image: string; +}; + +export type SanityBranch = { + team: string; + team_abbreviation?: string; + color: SanityColor; + zIndex: number; +}; + +export type Branch = SanityBranch & { + teamAbbreviation: string; +}; + +export type SanityStructuredTeam = { + team: string; + image: SanityImageSource; + teamDescription: string; + zIndex: number; + color: SanityColor; +}; + +export type StructuredTeam = Omit & { + image: string; +}; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 0ac0fe04..b1ae25ec 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -5,3 +5,8 @@ type ImportMetaEnv = { }; type ImportMeta = { readonly env: ImportMetaEnv }; + +declare module '*.JPG' { + const src: string; + export default src; +}