diff --git a/package-lock.json b/package-lock.json index b37d204..a94a783 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "ydo", "version": "0.0.0", "dependencies": { + "@fontsource/sacramento": "^5.2.6", + "embla-carousel-react": "^8.6.0", "lottie-react": "^2.4.1", "react": "^19.1.0", "react-dom": "^19.1.0", @@ -1132,6 +1134,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fontsource/sacramento": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@fontsource/sacramento/-/sacramento-5.2.6.tgz", + "integrity": "sha512-1AdcAwF5WV2/fQE9IGfCjsQy8PI/I7DhaeRv+Ug1XYfSmUjPo1zYaSQAO1O4NtBdp0x+aM+qEzRb4wI7EnWpAA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2601,6 +2612,34 @@ "integrity": "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==", "dev": true }, + "node_modules/embla-carousel": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "license": "MIT" + }, + "node_modules/embla-carousel-react": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz", + "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==", + "license": "MIT", + "dependencies": { + "embla-carousel": "8.6.0", + "embla-carousel-reactive-utils": "8.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz", + "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", diff --git a/package.json b/package.json index 431189b..86e4d4a 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ } }, "dependencies": { + "@fontsource/sacramento": "^5.2.6", + "embla-carousel-react": "^8.6.0", "lottie-react": "^2.4.1", "react": "^19.1.0", "react-dom": "^19.1.0", diff --git a/src/assets/Home/BG.png b/src/assets/Home/BG.png new file mode 100644 index 0000000..8486875 Binary files /dev/null and b/src/assets/Home/BG.png differ diff --git a/src/assets/Home/heartIcon.png b/src/assets/Home/heartIcon.png new file mode 100644 index 0000000..a6e53ed Binary files /dev/null and b/src/assets/Home/heartIcon.png differ diff --git a/src/assets/Home/pass.png b/src/assets/Home/pass.png new file mode 100644 index 0000000..d86c033 Binary files /dev/null and b/src/assets/Home/pass.png differ diff --git a/src/assets/Home/refreshIcon.png b/src/assets/Home/refreshIcon.png new file mode 100644 index 0000000..9f2450d Binary files /dev/null and b/src/assets/Home/refreshIcon.png differ diff --git a/src/assets/carousel/nextArrow.svg b/src/assets/carousel/nextArrow.svg new file mode 100644 index 0000000..e969b9f --- /dev/null +++ b/src/assets/carousel/nextArrow.svg @@ -0,0 +1,9 @@ + +
+ + + + + + +
diff --git a/src/assets/carousel/prevArrow.svg b/src/assets/carousel/prevArrow.svg new file mode 100644 index 0000000..36bb2cd --- /dev/null +++ b/src/assets/carousel/prevArrow.svg @@ -0,0 +1,9 @@ + +
+ + + + + + +
diff --git a/src/components/infinite-carousel/EmblaCarousel.css b/src/components/infinite-carousel/EmblaCarousel.css new file mode 100644 index 0000000..ece5c00 --- /dev/null +++ b/src/components/infinite-carousel/EmblaCarousel.css @@ -0,0 +1,354 @@ +*, +::before, +::after { + padding: 0; + margin: 0; + box-sizing: border-box; +} + +.embla { + position: relative; + max-width: 82vw; + margin: auto; +} + +.embla__viewport { + overflow: hidden; + position: relative; + margin: auto; + padding: 1px; +} + +.embla__container { + display: flex; + width: 100%; + touch-action: pan-y pinch-zoom; + position: relative; + margin: auto; +} + +.embla__slide { + flex: 0 0 35%; + position: relative; + height: 450px; +} + +.embla__slide:first-child:has(.card__container.selected), +.embla__slide:nth-child(2):has(.card__container.selected), +.embla__slide:last-child:has(.card__container.selected) { + z-index: 2; +} + +.card__container { + display: flex; + flex-direction: row; + align-items: center; + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(45px); + border: 1px solid white; + border-radius: 90px; + padding: 1rem; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); + overflow: hidden; + position: relative; + transform: scale(0.6); + opacity: 0.2; + z-index: -4; + transition: transform 1s ease, opacity 1s ease, z-index 1s ease; + pointer-events: none; + margin: 0 -120px; + height: 100%; + min-width: 800px; +} + +.card__container.selected { + transform: scale(1); + opacity: 1; + z-index: 11; + pointer-events: auto; +} + +.selected.fade-in { + opacity: 1; + transition: opacity 0.6s ease-in-out; +} + +.selected.fade-out { + opacity: 0; + transition: opacity 0.6s ease-in-out; +} + +.fade-in { + opacity: 0.2; + transition: opacity 0.6s ease-in-out; +} + +.fade-out { + opacity: 0; + transition: opacity 0.6s ease-in-out; +} + +.card__img-wrapper { + width: 54%; + margin-left: 25px; + margin-right: 8px; + height: 350px; + border-radius: 40px; + border: 1px solid white; +} + +.card__img { + display: inline-block; + width: 100%; + height: 100%; + border-radius: 40px; +} + +.card__data { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-evenly; + gap: 1rem; + padding: 0 1.5rem; + color: white; + font-family: "Poppins", sans-serif; + margin-left: 10px; + height: 95%; +} + +.card__data--heading { + font-family: "Poppins", sans-serif; + font-weight: 600; + font-size: 44px; + color: rgba(246, 204, 182, 1); + text-align: left; +} + +.card__data--subheading { + font-family: "Poppins", sans-serif; + font-weight: 400; + font-size: 44px; + color: rgba(246, 204, 182, 1); + text-align: left; +} +.card__tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 0.5rem 0; +} + +.tag { + font-family: "Poppins", sans-serif; + background-color: rgba(246, 204, 182, 0.2); + border: 1px solid white; + border-radius: 12px; + padding: 0.2rem 0.6rem; + font-size: 0.8rem; +} + +.card__description { + font-family: "Poppins", sans-serif; + background: rgba(246, 204, 182, 0.2); + border-radius: 20px; + padding: 8px; + border: 1px solid white; + max-width: 700px; + margin-bottom: 10px; +} + +.embla__next, +.embla__prev { + position: absolute; + top: 50%; + transform: translateY(-50%); + cursor: pointer; + z-index: 2; + transition: transform 0.6s ease; +} +.embla__next { + right: 280px; +} +.embla__prev { + left: 280px; +} + +.embla__next:hover { + transform: translateY(-50%) translateX(10px); +} + +.embla__prev:hover { + transform: translateY(-50%) translateX(-10px); +} + +/* +------------------------------Skeleton cards------------------------- +*/ + +.card__img-wrapper--skeleton { + width: 40%; + margin-left: 25px; + margin-right: 8px; + height: 350px; + border-radius: 40px; + border: 1px solid white; +} + +.card__data--heading--skeleton { + width: 100px; + height: 50px; + border-radius: 20px; +} + +.card__data--subheading--skeleton { + width: 150px; + height: 30px; + margin-top: 1rem; + border-radius: 20px; +} + +.tag--skeleton { + display: inline-block; + background-color: rgba(246, 204, 182, 0.2); + border: 1px solid white; + border-radius: 12px; + padding: 0.2rem 0.6rem; + width: 50px; + height: 25px; + margin: 5px; +} + +.card__description--skeleton { + display: inline-block; + background: rgba(246, 204, 182, 0.2); + border-radius: 20px; + padding: 8px; + border: 1px solid white; + width: 300px; + height: 30px; + margin-bottom: 10px; +} + +.skeleton { + position: relative; + overflow: hidden; + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0.1) 0%, + rgba(255, 255, 255, 0.15) 50%, + rgba(255, 255, 255, 0.1) 100% + ); + background-size: 200% 100%; + animation: shimmer 1s infinite ease-in-out; +} + +.skeleton::before { + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.4) 25%, + rgba(255, 255, 255, 0.6) 50%, + rgba(255, 255, 255, 0.4) 75%, + transparent 100% + ); + animation: shimmerOverlay 2s infinite ease-in-out; + z-index: 1; +} + +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + +@keyframes shimmerOverlay { + 0% { + left: -100%; + } + 100% { + left: 100%; + } +} + +.card__data--heading--skeleton { + animation-delay: 0.1s; +} + +.card__data--subheading--skeleton { + animation-delay: 0.2s; +} + +.tag--skeleton:nth-child(1) { + animation-delay: 0.3s; +} + +.tag--skeleton:nth-child(2) { + animation-delay: 0.4s; +} + +.tag--skeleton:nth-child(3) { + animation-delay: 0.5s; +} + +.tag--skeleton:nth-child(4) { + animation-delay: 0.6s; +} + +.card__description--skeleton { + animation-delay: 0.7s; +} + +@media (max-width: 1024px) { + .embla__slide { + flex: 0 0 50%; + height: 350px; + } + + .card__container { + border-radius: 60px; + transform: scale(0.5); + margin: 0 -100px; + min-width: 400px; + } + + .card__container.selected { + transform: scale(0.9); + } + + .card__img-wrapper { + width: 60%; + height: 250px; + } + + .card__data--heading, + .card__data--subheading { + font-size: 30px; + } + + .tag { + font-size: 0.6rem; + } + + .card__description { + max-width: 90%; + font-size: 0.67rem; + } + + .embla__next { + right: 80px; + } + + .embla__prev { + left: 80px; + } +} diff --git a/src/components/infinite-carousel/EmblaCarousel.jsx b/src/components/infinite-carousel/EmblaCarousel.jsx new file mode 100644 index 0000000..ddbee41 --- /dev/null +++ b/src/components/infinite-carousel/EmblaCarousel.jsx @@ -0,0 +1,206 @@ +import React, { useEffect, useState, useCallback } from "react"; +import useEmblaCarousel from "embla-carousel-react"; +import nextArrow from "../../assets/carousel/nextArrow.svg"; +import prevArrow from "../../assets/carousel/prevArrow.svg"; +import fetchUsers from "../../utils/fetchProfiles"; +import "./EmblaCarousel.css"; + +const ProfileCard = ({ + profile, + index, + selectedSlide, + isVisible, + fadeComplete, +}) => { + return ( + <> +
+
+
+ pic +
+
+
+

+ {profile.name} ({profile.age}) +

+

+ {profile.discipline} - {profile.year} +

+
+
+ {profile.tags.map((tag, idx) => ( + + {tag} + + ))} +
+
+

{profile.description}

+
+
+
+
+ + ); +}; + +const SkeletonCard = ({ index, selectedSlide }) => { + return ( + <> +
+
+
+
+
+
+
+
+
+ {Array.from({ length: 4 }).map((_, idx) => ( +
+ ))} +
+
+
+
+
+ + ); +}; + +const EmblaCarousel = () => { + const [emblaRef, emblaApi] = useEmblaCarousel({ + loop: true, + align: "center", + }); + + //Backend integration + + const [profiles, setProfiles] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [fadeComplete, setFadeComplete] = useState(false); + const [contentVisible, setContentVisible] = useState(false); + + useEffect(() => { + const controller = new AbortController(); + + const loadProfiles = async () => { + const { data, error } = await fetchUsers(controller.signal); + if (error) { + setError(error); + } else { + setProfiles(data); + } + setLoading(false); + setTimeout(() => setContentVisible(true), 100); + }; + + loadProfiles(); + + return () => controller.abort(); + }, []); + + useEffect(() => { + if (contentVisible && !fadeComplete) { + const timer = setTimeout(() => { + setFadeComplete(true); + }, 600); + + return () => clearTimeout(timer); + } + }, [contentVisible, fadeComplete]); + + //state control for the carousel + + const [selectedSlide, setSelectedSlide] = useState(0); + + const onSelect = useCallback(() => { + if (!emblaApi) return; + setSelectedSlide(emblaApi.selectedScrollSnap()); + }, [emblaApi, setSelectedSlide]); + + useEffect(() => { + if (!emblaApi) return; + + onSelect(); + emblaApi.on("select", onSelect); + emblaApi.on("reInit", onSelect); + + return () => { + emblaApi.off("select", onSelect); + emblaApi.off("reInit", onSelect); + }; + }, [emblaApi, onSelect]); + + const scrollNext = () => emblaApi && emblaApi.scrollNext(); + const scrollPrev = () => emblaApi && emblaApi.scrollPrev(); + + return ( + <> +
+ {loading && ( + <> +
+
+ {Array.from({ length: 4 }).map((_, index) => ( + + ))} +
+
+
+ next +
+
+ prev +
+ + )} + {error &&
Error: {error}
} + {!loading && !error && ( + <> +
+
+ {profiles?.length > 0 ? ( + profiles.map((profile, index) => ( + + )) + ) : ( +
No profiles found
+ )} +
+
+
+ next +
+
+ prev +
+ + )} +
+ + ); +}; + +export default EmblaCarousel; diff --git a/src/components/infinite-carousel/profiles.json b/src/components/infinite-carousel/profiles.json new file mode 100644 index 0000000..7922700 --- /dev/null +++ b/src/components/infinite-carousel/profiles.json @@ -0,0 +1,112 @@ +[ + { + "id": 1, + "name": "Alex", + "age": 21, + "discipline": "Ds", + "year": "3rd Year", + "tags": ["Creative", "Artist", "Dog lover", "Photography"], + "image": "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=300&h=400&fit=crop&crop=face", + "description": "Love capturing moments and creating art. Always up for coffee and deep conversations about life." + }, + { + "id": 2, + "name": "Sun", + "age": 20, + "discipline": "CSE", + "year": "2nd Year", + "tags": ["Smart", "Bird lover", "Coding", "Nature"], + "image": "https://images.unsplash.com/photo-1494790108755-2616b612b786?w=300&h=400&fit=crop&crop=face", + "description": "Tech enthusiast who loves solving problems and exploring nature. Yes, I would still love you if you were a frog 🐸" + }, + { + "id": 3, + "name": "Tom", + "age": 19, + "discipline": "ME", + "year": "1st Year", + "tags": ["Engineer", "Sporty", "Cat lover", "Gaming"], + "image": "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=300&h=400&fit=crop&crop=face", + "description": "Passionate about engineering and sports. Love playing video games and spending time with my cat Mr. Whiskers." + }, + { + "id": 4, + "name": "Emma", + "age": 22, + "discipline": "DS", + "year": "4th Year", + "tags": ["Creative", "Music", "Nature", "Yoga"], + "image": "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=300&h=400&fit=crop&crop=face", + "description": "Music producer and yoga instructor. Love hiking and finding inspiration in nature's beauty." + }, + { + "id": 5, + "name": "Jack", + "age": 20, + "discipline": "CS", + "year": "2nd Year", + "tags": ["Tech", "Gaming", "Coffee", "Anime"], + "image": "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=300&h=400&fit=crop&crop=face", + "description": "Full-stack developer who lives on coffee and dreams in code. Anime enthusiast and weekend gamer." + }, + { + "id": 6, + "name": "Lily", + "age": 23, + "discipline": "DS", + "year": "Final Year", + "tags": ["Bookworm", "Travel", "Foodie", "Languages"], + "image": "https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=300&h=400&fit=crop&crop=face", + "description": "Speaks 4 languages and has visited 15 countries. Love discovering new cuisines and hidden bookstores." + }, + { + "id": 7, + "name": "Ryan", + "age": 24, + "discipline": "CSE", + "year": "1st Year", + "tags": ["Fitness", "Cooking", "Adventure", "Motorcycle"], + "image": "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=300&h=400&fit=crop&crop=face", + "description": "Fitness enthusiast who loves cooking healthy meals. Weekend warrior on my motorcycle exploring new routes." + }, + { + "id": 8, + "name": "Zoe", + "age": 21, + "discipline": "ECE", + "year": "3rd Year", + "tags": ["Dancing", "Fashion", "Social", "Volunteering"], + "image": "https://images.unsplash.com/photo-1517841905240-472988babdf9?w=300&h=400&fit=crop&crop=face", + "description": "Professional dancer and fashion enthusiast. Love volunteering at local shelters and organizing events." + }, + { + "id": 9, + "name": "Marcus", + "age": 25, + "discipline": "ME", + "year": "PhD", + "tags": ["Music", "Guitar", "Philosophy", "Meditation"], + "image": "https://images.unsplash.com/photo-1507591064344-4c6ce005b128?w=300&h=400&fit=crop&crop=face", + "description": "Singer-songwriter who finds peace in meditation. Love deep conversations about existence and meaning." + }, + { + "id": 10, + "name": "Sophia", + "age": 22, + "discipline": "SM", + "year": "4th Year", + "tags": ["Science", "Research", "Hiking", "Astronomy"], + "image": "https://images.unsplash.com/photo-1508214751196-bcfd4ca60f91?w=300&h=400&fit=crop&crop=face", + "description": "Love stargazing and hiking under the night sky." + }, + { + "id": 11, + "name": "LucidSovereign", + "age": 19, + "discipline": "CSE", + "year": "2nd Year", + "tags": ["Anime", "Philosophy", "Gaming", "Tech"], + "image": "./src/assets/carousel/profilepic.jpg", + "description": "It took me a week to made this. In desperate need for someone to listen to me whining(i dont look like that btw)" + } +] diff --git a/src/pages/Home/Home.css b/src/pages/Home/Home.css index e69de29..f011217 100644 --- a/src/pages/Home/Home.css +++ b/src/pages/Home/Home.css @@ -0,0 +1,100 @@ +html, +body { + margin: 0; + padding: 0; + width: 100%; + min-height: 100vh; +} + +#root { + background: url("../../assets/Home/BG.png") no-repeat; + background-size: cover; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.main { + width: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; + gap: 1rem; + margin-top: 70px; + padding-bottom: 300px; +} + +.text-wrapper { + width: auto; + height: 150px; + margin: 3rem auto 3rem auto; + padding: 2rem; +} + +.text { + font-family: "Sacramento", cursive; + font-size: 6rem; + text-align: center; + width: 100%; + color: rgba(246, 204, 182, 1); +} + +.icons { + display: flex; + justify-content: space-evenly; + align-items: center; + width: 700px; + margin: 2rem auto; + padding: 1rem; +} + +.pass-icon, +.smash, +.refresh { + width: 60px; + height: 60px; +} + +.pass-icon__image, +.smash__image { + width: 65px; + height: 65px; + transition: all 0.4s ease; +} + +.refresh__image { + width: 70px; + height: 70px; + transition: all 0.4s ease; +} + +.pass-icon__image:hover, +.smash__image:hover, +.refresh__image:hover { + transform: scale(1.2); + transform: translateY(-5px); +} + +.pass-icon__image:active, +.smash__image:active, +.refresh__image:active { + transform: scale(1.2); + transform: translateY(5px); +} + +@media (max-width: 1024px) { + .text { + font-size: 4rem; + } + .pass-icon__image, + .smash__image { + width: 55px; + height: 55px; + transition: all 0.4s ease; + } + .refresh__image { + width: 60px; + height: 60px; + transition: all 0.4s ease; + } +} diff --git a/src/pages/Home/Home.jsx b/src/pages/Home/Home.jsx index c961722..9e1fdf1 100644 --- a/src/pages/Home/Home.jsx +++ b/src/pages/Home/Home.jsx @@ -1,51 +1,32 @@ -import React from "react"; -import { useNavigate } from "react-router-dom"; -import Button from "../../components/button/Button"; -import { fetchUserData } from "../../utils/fetchUserData"; +import Navbar from "../../components/Navbar/Navbar.jsx"; +import "./Home.css"; +import refreshIcon from "../../assets/Home/refreshIcon.png"; +import heartIcon from "../../assets/Home/heartIcon.png"; +import pass from "../../assets/Home/pass.png"; +import EmblaCarousel from "../../components/infinite-carousel/EmblaCarousel"; +import "@fontsource/sacramento"; const Home = () => { - const [userData, setUserData] = React.useState(null); - - const API_BASE_URL = import.meta.env.VITE_API_URL || ""; - - const nav = useNavigate(); - - const redirectToLogin = () => { - nav("/login"); - }; - - React.useEffect(() => { - const loadUser = async () => { - const data = await fetchUserData(API_BASE_URL); - if (data) { - setUserData(data); - } - }; - - loadUser(); - }, []); - - return ( <> -

YDO💜

- {userData && userData.avatar_url && ( -
- Profile -

- Welcome{" "} - - {userData?.name}! - -

+ +
+
+

Ready to make someone blush today?🥰

- )} - {!userData &&

Please login to continue.

} - - {/* */} - + +
+
+ pass +
+
+ smash +
+
+ refresh +
+
+
); }; diff --git a/src/utils/fetchProfiles.js b/src/utils/fetchProfiles.js new file mode 100644 index 0000000..bb115a9 --- /dev/null +++ b/src/utils/fetchProfiles.js @@ -0,0 +1,14 @@ +const API_BASE_URL = import.meta.env.VITE_API_URL; + +const fetchUsers = async (signal) => { + try { + const res = await fetch(`${API_BASE_URL}/api/profiles`, { signal }); + if (!res.ok) throw new Error("Failed to fetch profiles"); + const data = await res.json(); + return { data }; + } catch (error) { + return { error: error.message }; + } +}; + +export default fetchUsers;