diff --git a/package.json b/package.json index 6f5c62145..abb31933a 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "embla-carousel-react": "^8.0.0", "framer-motion": "^11.3.28", "fuse.js": "^7.0.0", + "gsap": "^3.12.7", "hammerjs": "^2.0.8", "ioredis": "^5.4.1", "jose": "^5.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d6bb3dc4..40292640e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: fuse.js: specifier: ^7.0.0 version: 7.0.0 + gsap: + specifier: ^3.12.7 + version: 3.12.7 hammerjs: specifier: ^2.0.8 version: 2.0.8 @@ -2434,6 +2437,9 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + gsap@3.12.7: + resolution: {integrity: sha512-V4GsyVamhmKefvcAKaoy0h6si0xX7ogwBoBSs2CTJwt7luW0oZzC0LhdkyuKV8PJAXr7Yaj8pMjCKD4GJ+eEMg==} + hammerjs@2.0.8: resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} engines: {node: '>=0.8.0'} @@ -6657,6 +6663,8 @@ snapshots: graphemer@1.4.0: {} + gsap@3.12.7: {} + hammerjs@2.0.8: {} hamt_plus@1.0.2: {} diff --git a/public/fonts/font-head.woff2 b/public/fonts/font-head.woff2 new file mode 100644 index 000000000..2085440ff Binary files /dev/null and b/public/fonts/font-head.woff2 differ diff --git a/src/app/globals.css b/src/app/globals.css index 4db63a850..2ee6ee326 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -73,6 +73,10 @@ body { @apply bg-background text-foreground; } + @font-face { + font-family: "font-head"; + src: url("/fonts/font-head.woff2") format("woff2"); + } } .vjs-tech { @@ -406,4 +410,34 @@ .vjs-text-track-cue { display: none !important; -} \ No newline at end of file +} + +/* CSS styles for the floating navbar */ +/* Extra shadow for floating effect */ +.floating-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + width: 100%; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); +} + +/* Mobile adjustment */ +@media (max-width: 640px) { + .floating-nav { + left: 0; + right: 0; + } +} + +/* CSS styles for the font-head */ +.font-head { + font-family: font-head, sanf-serif; +} + +.special-font b { + font-family: "font-head"; + font-feature-settings: "ss01" on; +} + diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 14e7375b5..3155e5b37 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -3,7 +3,7 @@ import { motion, AnimatePresence } from 'framer-motion'; import { useSession } from 'next-auth/react'; import { usePathname, useRouter } from 'next/navigation'; -import React, { useState, useCallback, useMemo } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import Link from 'next/link'; import { ArrowLeft, Menu, Search, X } from 'lucide-react'; import { Button } from './ui/button'; @@ -13,122 +13,241 @@ import ProfileDropdown from './profile-menu/ProfileDropdown'; import { SearchBar } from './search/SearchBar'; export const Navbar = () => { + // Basic state for user session and navigation const { data: session } = useSession(); const router = useRouter(); + const pathname = usePathname(); + + // State for menu and search toggles const [isMenuOpen, setIsMenuOpen] = useState(false); const [isSearchOpen, setIsSearchOpen] = useState(false); - const pathname = usePathname(); + + // Create a reference to the navbar container element + const navbarRef = useRef(null); + + // State for controlling navbar visibility + const [showNavbar, setShowNavbar] = useState(true); + const [previousScrollPosition, setPreviousScrollPosition] = useState(0); + + // Function to handle opening/closing the menu + function toggleMenu() { + setIsMenuOpen(!isMenuOpen); + } + + // Function to handle opening/closing the search + function toggleSearch() { + setIsSearchOpen(!isSearchOpen); + } + + // This runs when the user scrolls + useEffect(() => { + function handleScroll() { + // Get current scroll position + const currentScrollPosition = window.scrollY; + + // At the top of the page + if (currentScrollPosition === 0) { + setShowNavbar(true); + // Remove floating style if navbar exists + if (navbarRef.current) { + navbarRef.current.classList.remove('floating-nav'); + } + } else if (currentScrollPosition > previousScrollPosition) { + setShowNavbar(false); + // Add floating style if navbar exists + if (navbarRef.current) { + navbarRef.current.classList.add('floating-nav'); + } + } else if (currentScrollPosition < previousScrollPosition) { + setShowNavbar(true); + // Add floating style if navbar exists + if (navbarRef.current) { + navbarRef.current.classList.add('floating-nav'); + } + } + + // Save current position for next comparison + setPreviousScrollPosition(currentScrollPosition); + } - // Memoizing the toggleMenu and toggleSearch functions - const toggleMenu = useCallback(() => setIsMenuOpen((prev) => !prev), []); - const toggleSearch = useCallback(() => setIsSearchOpen((prev) => !prev), []); + // Add scroll listener when component mounts + window.addEventListener('scroll', handleScroll); + + // Remove scroll listener when component unmounts + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, [previousScrollPosition]); - // Memoizing the navItemVariants object - const navItemVariants = useMemo( - () => ({ - hidden: { opacity: 0, y: -20 }, - visible: (i: number) => ({ - opacity: 1, - y: 0, - transition: { - delay: i * 0.1, - duration: 0.5, - ease: [0.43, 0.13, 0.23, 0.96], - }, - }), + // Show or hide navbar based on scroll direction + useEffect(() => { + if (navbarRef.current) { + if (showNavbar) { + // Show navbar + navbarRef.current.style.transform = 'translateY(0)'; + navbarRef.current.style.opacity = '1'; + } else { + // Hide navbar + navbarRef.current.style.transform = 'translateY(-100%)'; + navbarRef.current.style.opacity = '0'; + } + } + }, [showNavbar]); + + // Animation settings for navbar items + const navItemAnimations = { + hidden: { opacity: 0, y: -20 }, + visible: (i: number) => ({ + opacity: 1, + y: 0, + transition: { + delay: i * 0.1, + duration: 0.5, + ease: [0.43, 0.13, 0.23, 0.96], + }, }), - [], - ); + }; return ( <> - -
- - {session?.user && pathname !== '/home' && ( - - )} - - 100xDevs Logo -

- 100xDevs -

- -
+ {/* Navbar with animations */} + + {/* Navbar content */} +
+ {/* Left side - Logo and back button */} + + {/* Back button - only shows for logged in users not on home page */} + {session?.user && pathname !== '/home' && ( + + )} + + {/* Logo and site name */} + + 100xDevs Logo +

+ 100xDevs +

+ +
- - {/* Search Bar */} - {session?.user && ( - <> -
- -
-
+ {/* Right side - Search, theme, profile */} + + {/* Search Bar - only for logged in users */} + {session?.user && ( + <> + {/* Desktop search */} +
+ +
+ {/* Mobile search button */} +
+ +
+ + )} + + {/* Theme toggle button */} + + + {/* User profile dropdown - only for logged in users */} + {session?.user && } + + {/* Login/signup for logged out users */} + {!session?.user && ( + <> + {/* Mobile menu button */} -
- - )} - - - {session?.user && } + + {/* Desktop auth buttons */} +
+ + +
+ + )} +
+
- {!session?.user && ( - <> - -
- -
- - )} - -
- - {/* Mobile menu */} - - {!session?.user && isMenuOpen && ( - - - - + - - )} - -
+ )} + + + - {/* Mobile search overlay */} + {/* Mobile search overlay - shows when search is opened on mobile */} {isSearchOpen && ( { ); -}; +}; \ No newline at end of file diff --git a/src/components/landing/footer-cta.tsx b/src/components/landing/footer-cta.tsx index fbe814fcd..56de54fd8 100644 --- a/src/components/landing/footer-cta.tsx +++ b/src/components/landing/footer-cta.tsx @@ -27,7 +27,7 @@ const FooterCTA = () => { damping: 10, stiffness: 100, }} - className="relative flex h-[75vh] w-full flex-col overflow-hidden rounded-3xl bg-gradient-to-b from-blue-400 to-blue-700 p-8 md:h-[45vh] md:flex-col" + className="relative flex h-[75vh] w-full flex-col overflow-hidden rounded-3xl bg-gradient-to-b from-blue-400 to-black p-8 md:h-[45vh] md:flex-col" >
diff --git a/src/components/landing/landing-page.tsx b/src/components/landing/landing-page.tsx index 3e19ad170..63410ef14 100644 --- a/src/components/landing/landing-page.tsx +++ b/src/components/landing/landing-page.tsx @@ -1,10 +1,18 @@ 'use client'; +import { useRef, useEffect } from 'react'; import Link from 'next/link'; import { Button } from '../ui/button'; import { InfiniteMovingCards } from '../ui/infinite-moving-cards'; import FooterCTA from './footer-cta'; import Footer from './footer'; import { motion } from 'framer-motion'; +import gsap from 'gsap'; +import { ScrollTrigger } from 'gsap/all'; + +// Register GSAP plugins +if (typeof window !== 'undefined') { + gsap.registerPlugin(ScrollTrigger); +} const heroItems = [ { @@ -21,7 +29,6 @@ const heroItems = [ { imageUrl: 'https://100x-b-mcdn.akamai.net.in/images/ds.jpeg', }, - { imageUrl: 'https://appxcontent.kaxa.in/paid_course3/2024-07-09-0.6125162399767927.png', @@ -29,88 +36,141 @@ const heroItems = [ ]; export default function LandingPage() { + const heroFrameRef = useRef(null); + + useEffect(() => { + if (!heroFrameRef.current) return; + + // Initial clip path setup + gsap.set(heroFrameRef.current, { + clipPath: 'polygon(14% 0%, 72% 0%, 90% 90%, 0% 100%)', + borderRadius: '0 0 40% 10%', + }); + + // Animation on scroll + const animation = gsap.from(heroFrameRef.current, { + clipPath: 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)', + borderRadius: '0 0 0 0', + ease: 'power1.inOut', + scrollTrigger: { + trigger: heroFrameRef.current, + start: 'center center', + end: 'bottom center', + scrub: true, + }, + }); + + return () => { + animation.kill(); + }; + }, []); + return (
-
- {/* Hero */} - +
-

- - 100xDevs, - {' '} - - because 10x ain't enough! - -

+ {/* Hero content */} +
+ +

+ + 100xDevs, + {' '} + + because 10x ain't enough! + +

-

- A beginner-friendly platform for mastering programming skills. -

-
- {/* CTA Buttons */} - - - - - - - - -
+ + + +
+ + {/* Decorative text elements with dark mode support */} +

+ 100xdevs +

+
+

+ 100xdevs +

+
+ + {/* Moving Cards Section */} + + + + + {/* Background gradient */} + + + {/* Footer sections */}