├── public ├── _redirects └── index.html ├── src ├── components │ ├── Home.js │ ├── Footer.js │ ├── About.js │ ├── BlogItem.js │ ├── GalleryItem.js │ ├── Navbar.js │ ├── Gallery.js │ ├── NotFound.js │ ├── Featured.js │ ├── Hero.js │ └── Blog.js ├── index.js ├── hooks │ ├── useSmoothScroll.js │ └── gsap.js ├── App.js └── index.css ├── .gitignore ├── package.json └── README.md /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Immemorial 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /src/components/Home.js: -------------------------------------------------------------------------------- 1 | import About from "./About"; 2 | import Featured from "./Featured"; 3 | import Gallery from "./Gallery"; 4 | import Hero from "./Hero"; 5 | 6 | const Home = () => { 7 | return ( 8 |
9 | 10 | 11 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default Home; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { BrowserRouter } from "react-router-dom"; 4 | import App from "./App"; 5 | import "./index.css"; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById("root")); 8 | root.render( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /src/hooks/useSmoothScroll.js: -------------------------------------------------------------------------------- 1 | import Lenis from "@studio-freight/lenis"; 2 | import { useEffect } from "react"; 3 | 4 | export const useSmoothScroll = () => { 5 | const lenis = new Lenis({ 6 | duration: 1.5, 7 | easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), 8 | direction: "vertical", 9 | gestureDirection: "vertical", 10 | smooth: true, 11 | }); 12 | 13 | // creating frame inside useEffect() 14 | useEffect(() => { 15 | function raf(time) { 16 | lenis.raf(time); 17 | requestAnimationFrame(raf); 18 | } 19 | 20 | requestAnimationFrame(raf); 21 | }, []); 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { useGsapFooterHeadline } from "../hooks/gsap"; 3 | 4 | const Footer = () => { 5 | // Creating reference 6 | const footerRef = useRef(null); 7 | const footerHeadlineRef = useRef(null); 8 | 9 | // Calling custom hook 10 | useGsapFooterHeadline(footerHeadlineRef, footerRef); 11 | 12 | return ( 13 |
14 |

Bonjour

15 |

16 | © {new Date().getFullYear()} Immemorial. Crafted by yours truly 17 |

18 |
19 | ); 20 | }; 21 | 22 | export default Footer; 23 | -------------------------------------------------------------------------------- /src/components/About.js: -------------------------------------------------------------------------------- 1 | const About = () => { 2 | return ( 3 |
4 |

About

5 |

6 | Explore the lost treasures and shining stars of the 1990s! Find your 7 | favorite cartoons, TV shows, music albums, & more with easy filtering 8 | functionality. With Immemorial, stay up-to-date with all your 90s 9 | favorites while turning back time. 10 |

11 |

12 | What's the only era that never seems to end? The 90s! Journey through 13 | appreciating items from 90s TV, music, and art. See if you remember old 14 | toys, cartoons, or prints of such. Indulge in some nostalgia before our 15 | world falls back into the dark ages. 16 |

17 |
18 | ); 19 | }; 20 | 21 | export default About; 22 | -------------------------------------------------------------------------------- /src/components/BlogItem.js: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { useGsapBlogPhotoReveal } from "../hooks/gsap"; 3 | 4 | const BlogItem = ({ image }) => { 5 | // Creating reference 6 | const blogItemRef = useRef(null); 7 | const blogTitleRef = useRef(null); 8 | const blogImgRef = useRef(null); 9 | 10 | // Calling custom hooks 11 | 12 | // useGsapBlogPhotoReveal(blogImgRef); 13 | 14 | return ( 15 |
16 |
17 | {image.title} 18 |
19 |
20 |

21 | {image.title} 22 |

23 | {/*

{image.description}

*/} 24 |
25 |
26 | ); 27 | }; 28 | 29 | export default BlogItem; 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project-immemorial", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@studio-freight/lenis": "^0.2.26", 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "gsap": "^3.11.3", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-router-dom": "^6.4.4", 14 | "react-scripts": "5.0.1", 15 | "web-vitals": "^2.1.4" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/GalleryItem.js: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { 3 | useGsapGalleryTitle, 4 | useGsapGalleryCategory, 5 | useGsapGalleryImage, 6 | } from "../hooks/gsap"; 7 | 8 | const GalleryItem = ({ image }) => { 9 | // Creating Reference 10 | const galleryTitleRef = useRef(null); 11 | const galleryCategoryRef = useRef(null); 12 | const galleryImageRef = useRef(null); 13 | 14 | // Calling custom hooks 15 | useGsapGalleryTitle(galleryTitleRef, galleryImageRef); 16 | useGsapGalleryCategory(galleryCategoryRef, galleryImageRef); 17 | useGsapGalleryImage(galleryImageRef); 18 | 19 | return ( 20 |
21 |

22 | {image.title} 23 |

24 |

25 | {image.category} 26 |

27 |
32 |
33 | ); 34 | }; 35 | 36 | export default GalleryItem; 37 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { Routes, Route } from "react-router-dom"; 2 | import About from "./components/About"; 3 | import Blog from "./components/Blog"; 4 | import Featured from "./components/Featured"; 5 | import Footer from "./components/Footer"; 6 | import Gallery from "./components/Gallery"; 7 | import Home from "./components/Home"; 8 | import Navbar from "./components/Navbar"; 9 | import NotFound from "./components/NotFound"; 10 | import { useSmoothScroll } from "./hooks/useSmoothScroll"; 11 | 12 | const App = () => { 13 | // Calling custom hook for smoothing scroll behaivior 14 | useSmoothScroll(); 15 | return ( 16 |
17 | 18 | 19 | } /> 20 | } /> 21 | } /> 22 | } /> 23 | } /> 24 | } /> 25 | 26 |
28 | ); 29 | }; 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useGsapDownStagger } from "../hooks/gsap"; 4 | 5 | const Navbar = () => { 6 | // Creating reference of every element 7 | const li1 = useRef(null); 8 | const li2 = useRef(null); 9 | const li3 = useRef(null); 10 | const logoRef = useRef(null); 11 | const blogRef = useRef(null); 12 | 13 | const liArr = [li1, li2, li3]; 14 | const favArr = [blogRef]; 15 | const logoArr = [logoRef]; 16 | 17 | // Calling custom hooks 18 | useGsapDownStagger(liArr, 1.5); 19 | useGsapDownStagger(logoArr, 2.2); 20 | useGsapDownStagger(favArr, 2.6); 21 | 22 | return ( 23 | 44 | ); 45 | }; 46 | 47 | export default Navbar; 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Immemorial - your guide to the golden era of the 90s! 2 | 3 | Immerse yourself in the nostalgia of the 90s with our web application, built using React, React Router, and GSAP animations. 4 | 5 | ## Description 6 | 7 | Our web application, called "Immemorial", is a platform designed for 90s kids to explore and reminisce about their favorite TV shows, music albums, and cartoons. With a user-friendly interface and easy-to-use filtering functionality, Immemorial makes it easy to discover your favorite 90s memories. The application is built using the following technologies: 8 | 9 | - React: a popular JavaScript library for building user interfaces. 10 | - React Router: a library that provides routing functionality to your React applications. 11 | - GSAP: an animation library that allows for the creation of smooth and engaging animations on the web. 12 | 13 | ## Installation 14 | 15 | To install and run this application on your local machine, follow these steps: 16 | 17 | 1. Clone this repository to your local machine. 18 | 2. In your terminal, navigate to the project directory and run `npm install` to install the necessary dependencies. 19 | 3. Once the installation is complete, run `npm start` to start the application. 20 | 4. Open your web browser and navigate to `http://localhost:3000` to view the application. 21 | 22 | ## Visit the live site 23 | 24 | [Immemorial](https://project-immemorial.netlify.app/) 25 | -------------------------------------------------------------------------------- /src/components/Gallery.js: -------------------------------------------------------------------------------- 1 | import GalleryItem from "./GalleryItem"; 2 | 3 | const images = [ 4 | { 5 | id: 1, 6 | src: "https://images.pexels.com/photos/4842487/pexels-photo-4842487.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 7 | title: "Arcade playtime for 90's kids", 8 | category: "Arcade Games", 9 | }, 10 | { 11 | id: 2, 12 | src: "https://images.pexels.com/photos/3356608/pexels-photo-3356608.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 13 | title: "No signal - no transmission", 14 | category: "TV", 15 | }, 16 | { 17 | id: 3, 18 | src: "https://images.pexels.com/photos/12668238/pexels-photo-12668238.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 19 | title: "Retro Closures", 20 | category: "Boombox", 21 | }, 22 | { 23 | id: 4, 24 | src: "https://images.pexels.com/photos/12204293/pexels-photo-12204293.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 25 | title: "Vinyl Loveless Happiness", 26 | category: "Vinyl Record", 27 | }, 28 | ]; 29 | 30 | const Gallery = () => { 31 | return ( 32 |
33 |

Gallery

34 |
35 | {images.map((image) => ( 36 | 37 | ))} 38 |
39 |
40 | ); 41 | }; 42 | 43 | export default Gallery; 44 | -------------------------------------------------------------------------------- /src/components/NotFound.js: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { useGsapNotFoundHeadline, useGsapNotFoundImg } from "../hooks/gsap"; 3 | 4 | const NotFound = ({ needFullHeight }) => { 5 | // Creating reference 6 | const leftHeadlineRef = useRef(null); 7 | const rightHeadlineRef = useRef(null); 8 | const leftImgRef = useRef(null); 9 | const rightImgRef = useRef(null); 10 | 11 | // Calling custom hooks 12 | useGsapNotFoundHeadline(leftHeadlineRef); 13 | useGsapNotFoundHeadline(rightHeadlineRef, "100vw"); 14 | useGsapNotFoundImg(rightImgRef); 15 | useGsapNotFoundImg(leftImgRef); 16 | 17 | return ( 18 |
19 |
20 | Sorry, we couldn't 21 |
22 | 23 |
24 | Woman Sitting on a Hood of an Old Volkswagen Golf Car 28 |
29 | 30 |
31 | Woman showing retro photo camera and holding blooming flower 35 |
36 | 37 |
38 | Find that page 39 |
40 |
41 | ); 42 | }; 43 | 44 | export default NotFound; 45 | -------------------------------------------------------------------------------- /src/components/Featured.js: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { 3 | useGsapFeatureLeftShutterUnveil, 4 | useGsapFeatureRightShutterUnveil, 5 | } from "../hooks/gsap"; 6 | 7 | const Featured = () => { 8 | // Creating Reference 9 | const featureRef = useRef(null); 10 | const featureLeftShutterRef = useRef(null); 11 | const featureRightShutterRef = useRef(null); 12 | 13 | // Calling custom hooks for animating feature images 14 | useGsapFeatureLeftShutterUnveil(featureLeftShutterRef, featureRef); 15 | useGsapFeatureRightShutterUnveil(featureRightShutterRef, featureRef); 16 | 17 | return ( 18 |
19 |

Featured

20 |
21 |
22 | 90'S TELEPHONE 23 | 90'S TELEPHONE 27 | 31 |
32 |
33 | 90'S CASSETTE PLAYER 34 | 90'S CASSETTE PLAYER 38 | 42 |
43 |
44 |
45 | ); 46 | }; 47 | 48 | export default Featured; 49 | -------------------------------------------------------------------------------- /src/components/Hero.js: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { 3 | useGsapShutterUnveil, 4 | useGsapPhotoDropping, 5 | useGsapPhotoLevitate, 6 | } from "../hooks/gsap"; 7 | 8 | const Hero = () => { 9 | // Creating Reference for hero texts 10 | const heroRef = useRef(null); 11 | const shutter1 = useRef(null); 12 | const shutter2 = useRef(null); 13 | 14 | // Creating Reference for hero images 15 | const photo1Ref = useRef(null); 16 | const photo2Ref = useRef(null); 17 | const photo3Ref = useRef(null); 18 | const photo4Ref = useRef(null); 19 | const photo5Ref = useRef(null); 20 | 21 | const photosArr = [photo1Ref, photo2Ref, photo3Ref, photo4Ref, photo5Ref]; 22 | 23 | // Calling custom hooks for animating hero texts 24 | useGsapShutterUnveil(shutter1, 0, heroRef); 25 | useGsapShutterUnveil(shutter2, 0.2, heroRef); 26 | 27 | // Calling custom hooks for animating hero images 28 | useGsapPhotoDropping(photosArr); 29 | useGsapPhotoLevitate(photosArr, heroRef); 30 | 31 | return ( 32 |
33 |

34 | Ethereal 35 |

36 |

37 | Canvas 38 |

39 | 40 |
41 |
49 |
57 |
65 |
73 |
81 |
82 |
83 | ); 84 | }; 85 | 86 | export default Hero; 87 | -------------------------------------------------------------------------------- /src/components/Blog.js: -------------------------------------------------------------------------------- 1 | import BlogItem from "./BlogItem"; 2 | 3 | const images = [ 4 | { 5 | id: 1, 6 | src: "https://images.pexels.com/photos/11691807/pexels-photo-11691807.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 7 | title: "Tom & Jerry", 8 | description: 9 | "Tom and Jerry was one show we never missed after coming from school. Despite the love-hate relationship between the cat and the mouse, this show taught us the importance of friendship.", 10 | }, 11 | { 12 | id: 2, 13 | src: "https://images.unsplash.com/photo-1607748851687-ba9a10438621?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=987&q=80", 14 | title: "Friends", 15 | description: 16 | "Friends is a Comedy TV show about 6 friends who go through just about every life experience imaginable together.I was really hooked on it. I remember it was on at 8:00 every Thursday and it was so important to be home on time for it.I also remember that my friends and I used to use catchphrases from the show. Like, “Could I BE any more ______?” or “How you doin’?” or “OH. MY. GOD!!!!”", 17 | }, 18 | { 19 | id: 3, 20 | src: "https://images.pexels.com/photos/4575406/pexels-photo-4575406.jpeg", 21 | title: "Virgin Cola", 22 | description: 23 | "Fair play to Richard Branson, he gets most things right. The madcap entrepreneur owns the world famous Virgin brand and runs successful airlines, train networks, music festivals and even now his own Formula One racing team. But Virgin Cola was a mistake.", 24 | }, 25 | { 26 | id: 4, 27 | src: "https://images.pexels.com/photos/9135083/pexels-photo-9135083.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 28 | title: "Yo-yos", 29 | description: 30 | "They say everything comes back around and in the late 90s the yoyo did return to the playgrounds of Britain, but unlike when my mum and dad’s generation were fascinated by them going up and down a piece of string, we took it to the next level.", 31 | }, 32 | { 33 | id: 5, 34 | src: "https://images.pexels.com/photos/12629104/pexels-photo-12629104.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 35 | title: "Spice Girls", 36 | description: 37 | "Simply the greatest thing to come out of the 90s, the Spice Girls dominated the entertainment industry during the second half of the decade and became the biggest thing on the planet overnight.", 38 | }, 39 | 40 | { 41 | id: 6, 42 | src: "https://images.pexels.com/photos/275033/pexels-photo-275033.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 43 | title: "Sega Mega Drive", 44 | description: 45 | "The Sega Mega Drive is to games consoles in the 90s as George Best was to football 30 year previously. Video gaming and football were obviously around before them but once they appeared on the scene the future would change forever.", 46 | }, 47 | 48 | { 49 | id: 7, 50 | src: "https://images.pexels.com/photos/11795116/pexels-photo-11795116.jpeg?auto=compress&cs=tinysrgb&w=600&lazy=load", 51 | title: "Swing", 52 | description: 53 | "Most of the girls are in love with swings irrespective of their age. Swinging in a swing makes them happy and gives them a peaceful feeling. Swinging just makes each of us calm and happy.", 54 | }, 55 | { 56 | id: 8, 57 | src: "https://images.pexels.com/photos/9067719/pexels-photo-9067719.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 58 | title: "Cricket", 59 | description: 60 | "The only childhood play, every bengali boy inherits as a child is Cricket.After Asr prayer, every boy would leave the house to play cricket. First they all divided into two teams, then tossed which team would bat first. Then they started playing.Sometimes there would be prizes for the winning team which added to the fun of the game.", 61 | }, 62 | { 63 | id: 9, 64 | src: "https://images.unsplash.com/photo-1600077063877-22118d6290eb?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80", 65 | title: "Football", 66 | description: 67 | "All the boys loved to play football during monsoons. They had more fun playing football in the rain. After playing football in the rain, they all bathed together in the pond and returned home.", 68 | }, 69 | { 70 | id: 10, 71 | src: "https://images.pexels.com/photos/10347115/pexels-photo-10347115.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 72 | title: "Kabaddi", 73 | description: 74 | "Kabaddi is a form of bengali wrestling.The sport not only rekindles childhood memories, but also it is easy to observe people narrate their kabaddi stories every time there is a discussion on Pro Kabaddi or World Kabaddi League. Be it of getting ones t-shirt torn or beating up the high school bully. There is always a story of a lesser popular David and Goliath in that.", 75 | }, 76 | 77 | { 78 | id: 11, 79 | src: "https://www.thesun.co.uk/wp-content/uploads/2022/09/LM_slapbracelet_offplat.jpg?strip=all&quality=100&w=1920&h=1080&crop=1", 80 | title: "Slap Bracelet", 81 | description: 82 | "The slap bracelet had two faces. It wasn't just a toy that could be extended and reformed, it was a serious fashion statement — perfect for kids on the cusp of childhood and adulthood, standing between the pre- and post-Internet eras. '90s kids liked their toys fashionable and their fashion playful, and slap bracelets gave them exactly that.", 83 | }, 84 | // { 85 | // id: 12, 86 | // src: "https://images.pexels.com/photos/3109671/pexels-photo-3109671.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", 87 | // title: "Load-shedding", 88 | // description: 89 | // "At one time in Bangladesh there were many problems of load shedding. Every evening the electricity went out. When the electricity went out, all the children would go out of the house. They used to talk together, have fun, play together. But now these things are not seen anymore.", 90 | // }, 91 | ]; 92 | 93 | const Blog = ({ needFullHeight }) => { 94 | return ( 95 |
98 |

Blog

99 | 100 |
101 | {images.map((image) => ( 102 | 103 | ))} 104 |
105 |
106 | ); 107 | }; 108 | 109 | export default Blog; 110 | -------------------------------------------------------------------------------- /src/hooks/gsap.js: -------------------------------------------------------------------------------- 1 | // Creating custom hook for animating hero texts 2 | import { useEffect } from "react"; 3 | import gsap, { Expo } from "gsap"; 4 | import { ScrollTrigger } from "gsap/ScrollTrigger"; 5 | 6 | gsap.registerPlugin(ScrollTrigger); // registering plugin 7 | 8 | export const useGsapShutterUnveil = (item, delay = 0, trig) => { 9 | useEffect(() => { 10 | const element = item.current; 11 | 12 | gsap.fromTo( 13 | element, 14 | { 15 | height: "100%", 16 | }, 17 | { 18 | height: "0", 19 | duration: 2, 20 | ease: Expo.easeInOut, 21 | delay: delay, 22 | scrollTrigger: { 23 | trigger: trig.current, 24 | toggleActions: "play reverse play reverse", 25 | }, 26 | } 27 | ); 28 | }, []); 29 | }; 30 | 31 | // Creating custom hook for animating navbar content 32 | export const useGsapDownStagger = (links, delay = 0) => { 33 | useEffect(() => { 34 | // creating element 35 | const el = links.map((link) => link.current); 36 | 37 | // creating twin 38 | gsap.fromTo( 39 | el, 40 | { 41 | y: "-100%", 42 | opacity: 0, 43 | }, 44 | { 45 | y: 0, 46 | opacity: 1, 47 | duration: 1.2, // restricted duration 48 | stagger: 0.1, // stagger delay:comes one after another 49 | ease: Expo.easeInOut, // timing function 50 | delay: delay, 51 | } 52 | ); 53 | }, []); 54 | }; 55 | 56 | // Creating custom hook for dropping hero images 57 | export const useGsapPhotoDropping = (photos) => { 58 | useEffect(() => { 59 | const el = photos.map((photo) => photo.current); 60 | 61 | gsap.fromTo( 62 | el, 63 | { 64 | y: "-100vh", 65 | scale: 0, // used in order to scale down photos 66 | }, 67 | { 68 | y: 0, 69 | scale: 1, // used in order to scale up photos 70 | duration: 2, 71 | stagger: 0.2, 72 | delay: 2.2, 73 | ease: Expo.easeInOut, 74 | } 75 | ); 76 | }, []); 77 | }; 78 | 79 | // Creating custom hook for levitating hero images(parallax effect) 80 | export const useGsapPhotoLevitate = (photos, trig) => { 81 | useEffect(() => { 82 | const el = photos.map((photo) => photo.current); 83 | 84 | gsap.fromTo( 85 | el, 86 | { 87 | y: 0, 88 | }, 89 | { 90 | y: "-30%", 91 | 92 | ease: Expo.easeInOut, 93 | scrollTrigger: { 94 | trigger: trig.current, 95 | scrub: 1, 96 | toggleActions: "play reverse play reverse", 97 | }, 98 | } 99 | ); 100 | }, []); 101 | }; 102 | 103 | // Creating custom hook for animating features left image 104 | 105 | export const useGsapFeatureLeftShutterUnveil = (item, trig) => { 106 | useEffect(() => { 107 | const el = item.current; 108 | 109 | gsap.fromTo( 110 | el, 111 | { 112 | height: "100%", 113 | }, 114 | { 115 | height: 0, 116 | duration: 1.2, 117 | ease: Expo.easeInOut, 118 | scrollTrigger: { 119 | trigger: trig.current, 120 | start: "top center", 121 | end: "bottom center", 122 | toggleActions: "play reverse play reverse", 123 | }, 124 | } 125 | ); 126 | }, []); 127 | }; 128 | 129 | // Creating custom hook for animating features right image 130 | export const useGsapFeatureRightShutterUnveil = (item, trig) => { 131 | useEffect(() => { 132 | const el = item.current; 133 | 134 | gsap.fromTo( 135 | el, 136 | { 137 | width: "100%", 138 | }, 139 | { 140 | width: 0, 141 | duration: 1.2, 142 | ease: Expo.easeInOut, 143 | scrollTrigger: { 144 | trigger: trig.current, 145 | start: "top center", 146 | end: "bottom center", 147 | toggleActions: "play reverse play reverse", 148 | }, 149 | } 150 | ); 151 | }, []); 152 | }; 153 | 154 | // Creating custom hook for animating gallery image 155 | export const useGsapGalleryImage = (image) => { 156 | useEffect(() => { 157 | const el = image.current; 158 | 159 | gsap.fromTo( 160 | el, 161 | { 162 | x: 0, 163 | width: 0, 164 | }, 165 | { 166 | x: "30%", 167 | width: "100%", 168 | duration: 1, 169 | ease: Expo.easeInOut, 170 | scrollTrigger: { 171 | trigger: el, 172 | start: "top center", 173 | end: "bottom top", 174 | toggleActions: "play reverse play reverse", 175 | }, 176 | } 177 | ); 178 | }, []); 179 | }; 180 | 181 | // Creating custom hook for animating gallery title 182 | export const useGsapGalleryTitle = (item, trig) => { 183 | useEffect(() => { 184 | const el = item.current; 185 | 186 | gsap.fromTo( 187 | el, 188 | { 189 | x: "30%", 190 | }, 191 | { 192 | x: 0, 193 | 194 | duration: 1, 195 | ease: Expo.easeInOut, 196 | scrollTrigger: { 197 | trigger: trig.current, 198 | start: "top center", 199 | end: "bottom top", 200 | toggleActions: "play reverse play reverse", 201 | }, 202 | } 203 | ); 204 | }, []); 205 | }; 206 | 207 | // Creating custom hook for animating gallery category 208 | export const useGsapGalleryCategory = (item, trig) => { 209 | useEffect(() => { 210 | const el = item.current; 211 | 212 | gsap.fromTo( 213 | el, 214 | { 215 | x: "-100vw", 216 | }, 217 | { 218 | x: 0, 219 | duration: 1, 220 | ease: Expo.easeInOut, 221 | scrollTrigger: { 222 | trigger: trig.current, 223 | start: "top center", 224 | end: "bottom top", 225 | toggleActions: "play reverse play reverse", 226 | }, 227 | } 228 | ); 229 | }, []); 230 | }; 231 | 232 | // Creating custom hook for animating footer headline 233 | export const useGsapFooterHeadline = (item, trig) => { 234 | useEffect(() => { 235 | const el = item.current; 236 | 237 | gsap.fromTo( 238 | el, 239 | { 240 | y: "-100%", 241 | }, 242 | { 243 | y: 0, 244 | duration: 1, 245 | ease: Expo.easeInOut, 246 | scrollTrigger: { 247 | trigger: trig.current, 248 | toggleActions: "play", 249 | }, 250 | } 251 | ); 252 | }, []); 253 | }; 254 | 255 | // Creating custom hook for animating the headline of not found page 256 | 257 | export const useGsapNotFoundHeadline = (item, vw = "-100vw") => { 258 | useEffect(() => { 259 | const el = item.current; 260 | 261 | gsap.fromTo( 262 | el, 263 | { 264 | x: vw, 265 | }, 266 | { 267 | x: 0, 268 | duration: 1.5, 269 | ease: Expo.easeInOut, 270 | } 271 | ); 272 | }, []); 273 | }; 274 | 275 | // Creating custom hook for animating the headline of not found page 276 | 277 | export const useGsapNotFoundImg = (img) => { 278 | useEffect(() => { 279 | const el = img.current; 280 | gsap.fromTo( 281 | el, 282 | { 283 | scale: 0, 284 | borderRadius: "50%", 285 | }, 286 | { 287 | scale: 1, 288 | borderRadius: 0, 289 | duration: 4, 290 | delay: 1, 291 | ease: "elastic", 292 | } 293 | ); 294 | }, []); 295 | }; 296 | 297 | // custom hook 298 | 299 | export const useGsapBlogPhotoReveal = (img) => { 300 | useEffect(() => { 301 | const el = img.current; 302 | 303 | gsap.fromTo( 304 | el, 305 | { 306 | width: 0, 307 | }, 308 | { 309 | width: "100%", 310 | duration: 2, 311 | ease: Expo.easeInOut, 312 | scrollTrigger: { 313 | trigger: img.current, 314 | toggleActions: "play reverse play reverse", 315 | }, 316 | } 317 | ); 318 | }, []); 319 | }; 320 | 321 | // export const useGsapBlogTitleReveal = (item, trig); 322 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* 1. Poppins */ 2 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap"); 3 | 4 | /* 2. Bai Jamjuree */ 5 | @import url("https://fonts.googleapis.com/css2?family=Bai+Jamjuree:wght@200;300;400;500;600;700&display=swap"); 6 | 7 | /* 3. Syncopate */ 8 | @import url("https://fonts.googleapis.com/css2?family=Syncopate:wght@400;700&display=swap"); 9 | 10 | /* 4. Bodoni Moda */ 11 | @import url("https://fonts.googleapis.com/css2?family=Bodoni+Moda:wght@400;500;600;700;800;900&display=swap"); 12 | 13 | /* **********CUSTOM VARIABLE DECLARATION********** */ 14 | :root { 15 | --color-primary: #d53f41; 16 | --color-dark: #626262; 17 | --color-darker: #464646; 18 | --color-light: #dbd8d6; 19 | --color-lighter: #f5f0ec; 20 | } 21 | 22 | /* **************UNIVERSAL SELECTION************** */ 23 | 24 | * { 25 | margin: 0; 26 | padding: 0; 27 | list-style: none; 28 | text-decoration: none; 29 | box-sizing: border-box; 30 | } 31 | 32 | /* ****************GENERAL STYLES**************** */ 33 | 34 | html, 35 | body { 36 | overflow-x: hidden; 37 | } 38 | 39 | html { 40 | font-size: 62.5%; 41 | } 42 | 43 | body { 44 | font-family: "Poppins", sans-serif; 45 | font-size: 2rem; 46 | font-weight: 400; 47 | line-height: 1.7; 48 | letter-spacing: 1px; 49 | background-color: var(--color-lighter); 50 | color: var(--color-darker); 51 | } 52 | 53 | .wrapper { 54 | margin: 0 5vw; 55 | } 56 | 57 | section { 58 | padding: 10vw 0; 59 | } 60 | 61 | .section-title { 62 | font-family: "Syncopate", sans-serif; 63 | font-size: 1.5rem; 64 | padding-bottom: 5vw; 65 | text-transform: lowercase; 66 | color: var(--color-primary); 67 | } 68 | 69 | /* HELPER CLASS */ 70 | 71 | .min-h-100vh { 72 | min-height: 100vh; 73 | } 74 | 75 | /* ******************NAVBAR STYLES****************** */ 76 | 77 | .navbar { 78 | display: flex; 79 | justify-content: space-between; 80 | padding: 2rem 0; 81 | align-items: flex-start; 82 | font-family: "Syncopate", sans-serif; 83 | font-size: 1.5rem; 84 | text-transform: lowercase; 85 | } 86 | 87 | .links, 88 | .blog-link { 89 | font-weight: 700; 90 | } 91 | 92 | .links a, 93 | .blog-link a { 94 | color: var(--color-dark); 95 | position: relative; 96 | } 97 | 98 | .links a::after, 99 | .blog-link a::after { 100 | content: ""; 101 | width: 0%; 102 | height: 0.2rem; 103 | position: absolute; 104 | top: 50%; 105 | left: 50%; 106 | transform: translate(-50%, -50%); 107 | background-color: var(--color-primary); 108 | transition: 0.5s; 109 | } 110 | 111 | .links a:hover::after, 112 | .blog-link a:hover::after { 113 | width: 120%; 114 | } 115 | 116 | .logo a { 117 | color: var(--color-darker); 118 | } 119 | 120 | /* ********************HERO STYLES******************** */ 121 | 122 | .hero { 123 | font-family: "Bai Jamjuree", sans-serif; 124 | text-transform: uppercase; 125 | text-align: center; 126 | font-size: 10vw; 127 | line-height: 1.2; 128 | color: var(--color-darker); 129 | display: flex; 130 | flex-direction: column; 131 | justify-content: center; 132 | min-height: 100vh; 133 | align-items: center; 134 | position: relative; 135 | } 136 | 137 | .ethereal, 138 | .canvas { 139 | position: relative; 140 | } 141 | 142 | .ethereal span, 143 | .canvas span { 144 | position: absolute; 145 | top: 0; 146 | left: 0; 147 | right: 0; 148 | bottom: 0; 149 | width: 100%; 150 | height: 100%; 151 | background-color: var(--color-lighter); 152 | } 153 | 154 | .photos { 155 | position: absolute; 156 | top: 0; 157 | left: 0; 158 | right: 0; 159 | bottom: 0; 160 | z-index: 1; 161 | display: grid; 162 | grid-template-columns: repeat(7, 1fr); 163 | grid-template-rows: repeat(5, 1fr); 164 | } 165 | 166 | .photo { 167 | width: 100%; 168 | height: 100%; 169 | background-position: center; 170 | background-repeat: no-repeat; 171 | background-size: cover; 172 | overflow: hidden; 173 | } 174 | 175 | .photo.one { 176 | grid-column: 1; 177 | grid-row: 2; 178 | } 179 | 180 | .photo.two { 181 | grid-column: 4; 182 | grid-row: 3; 183 | } 184 | 185 | .photo.three { 186 | grid-column: 2; 187 | grid-row: 5; 188 | } 189 | 190 | .photo.four { 191 | grid-column: 7; 192 | grid-row: 4; 193 | } 194 | 195 | .photo.five { 196 | grid-column: 5; 197 | grid-row: 1; 198 | } 199 | 200 | /* *****************Featured STYLES***************** */ 201 | 202 | .features { 203 | display: grid; 204 | grid-template-columns: 30% auto; 205 | align-items: center; 206 | gap: 10rem; 207 | } 208 | 209 | .feature-text { 210 | text-transform: uppercase; 211 | letter-spacing: 5px; 212 | font-weight: 500; 213 | } 214 | 215 | .features img { 216 | width: 100%; 217 | display: block; 218 | } 219 | 220 | .feature-left, 221 | .feature-right { 222 | display: grid; 223 | gap: 1rem; 224 | position: relative; 225 | } 226 | 227 | .feature-shutter-left, 228 | .feature-shutter-right { 229 | position: absolute; 230 | z-index: 1; 231 | height: 100%; 232 | width: 100%; 233 | top: 0; 234 | left: 0; 235 | right: 0; 236 | bottom: 0; 237 | background-color: var(--color-lighter); 238 | } 239 | 240 | /* ********************ABOUT STYLES******************** */ 241 | 242 | .about p { 243 | font-size: 3vw; 244 | line-height: 1.5; 245 | } 246 | 247 | .about p:last-child { 248 | margin-top: 3vw; 249 | } 250 | 251 | /* ********************GAllERY STYLES******************** */ 252 | 253 | .gallery .section-title { 254 | margin-left: 5vw; 255 | } 256 | 257 | .gallery-wrapper { 258 | display: grid; 259 | grid-template-columns: 1fr; 260 | justify-items: center; 261 | gap: 10vw; 262 | padding: 10vw; 263 | background-color: var(--color-primary); 264 | } 265 | 266 | .gallery-item { 267 | position: relative; 268 | width: 50%; 269 | } 270 | 271 | .gallery-item-title { 272 | position: absolute; 273 | top: 10%; 274 | left: -50%; 275 | font-family: "Bai Jamjuree", sans-serif; 276 | font-size: 8vw; 277 | line-height: 1.2; 278 | text-transform: uppercase; 279 | color: var(--color-lighter); 280 | z-index: 1; 281 | mix-blend-mode: color-dodge; 282 | } 283 | 284 | .gallery-item-category { 285 | position: absolute; 286 | left: 0; 287 | bottom: -5%; 288 | text-transform: uppercase; 289 | color: var(--color-lighter); 290 | letter-spacing: 10px; 291 | z-index: 1; 292 | } 293 | 294 | .gallery-item-img { 295 | background-position: center; 296 | background-repeat: no-repeat; 297 | background-size: cover; 298 | width: 100%; 299 | height: 100vh; 300 | } 301 | 302 | /* ********************FOOTER STYLES******************** */ 303 | 304 | .footer { 305 | text-align: center; 306 | } 307 | 308 | .footer h1 { 309 | font-family: "Bodoni Moda", serif; 310 | font-size: 10vw; 311 | text-transform: lowercase; 312 | color: var(--color-primary); 313 | z-index: -1; 314 | } 315 | 316 | /* *****************NOT FOUND STYLES***************** */ 317 | 318 | .not-found { 319 | display: grid; 320 | grid-template-columns: 1fr 1fr; 321 | align-content: flex-start; 322 | column-gap: 5vw; 323 | row-gap: 1vw; 324 | } 325 | 326 | .headline-1, 327 | .headline-2 { 328 | font-family: "Bai Jamjuree", sans-serif; 329 | font-size: 8vw; 330 | font-weight: 700; 331 | text-transform: capitalize; 332 | line-height: 1; 333 | } 334 | 335 | .img-1, 336 | .img-2 { 337 | width: 20vw; 338 | height: 20vw; 339 | overflow: hidden; 340 | display: flex; 341 | justify-content: center; 342 | align-items: center; 343 | z-index: 1; 344 | } 345 | 346 | .img-1 img, 347 | .img-2 img { 348 | display: block; 349 | width: 100%; 350 | } 351 | 352 | .img-2 { 353 | justify-self: self-end; 354 | } 355 | 356 | /* *********************BLOG STYLES********************* */ 357 | 358 | .blog-wrapper { 359 | display: grid; 360 | width: 95%; 361 | margin: 2rem auto; 362 | 363 | grid-template-areas: 364 | " a a d e" 365 | " b c d f" 366 | " g h h k" 367 | " g i j k "; 368 | 369 | gap: 2rem; 370 | } 371 | 372 | .blog-item:nth-child(1) { 373 | grid-area: b; 374 | } 375 | 376 | .blog-item:nth-child(2) { 377 | grid-area: k; 378 | } 379 | 380 | .blog-item:nth-child(3) { 381 | grid-area: i; 382 | } 383 | 384 | .blog-item:nth-child(4) { 385 | grid-area: e; 386 | } 387 | 388 | .blog-item:nth-child(5) { 389 | grid-area: a; 390 | } 391 | 392 | .blog-item:nth-child(12) { 393 | grid-area: j; 394 | } 395 | 396 | .blog-item:nth-child(7) { 397 | grid-area: d; 398 | } 399 | 400 | .blog-item:nth-child(8) { 401 | grid-area: g; 402 | } 403 | 404 | .blog-item:nth-child(9) { 405 | grid-area: c; 406 | } 407 | 408 | .blog-item:nth-child(10) { 409 | grid-area: h; 410 | } 411 | 412 | .blog-item:nth-child(6) { 413 | grid-area: f; 414 | } 415 | 416 | .blog-item { 417 | position: relative; 418 | overflow: hidden; 419 | } 420 | 421 | .blog-img { 422 | width: 100%; 423 | height: 100%; 424 | object-fit: cover; 425 | transition: 0.5s; 426 | } 427 | 428 | .blog-img img { 429 | display: block; 430 | width: 100%; 431 | height: 100%; 432 | transition: all 0.3s ease-in-out; 433 | } 434 | 435 | .blog-item:hover img { 436 | filter: saturate(0) brightness(0.5); 437 | } 438 | 439 | .blog-texts { 440 | position: absolute; 441 | width: 100%; 442 | right: 0; 443 | top: 0; 444 | left: 0; 445 | bottom: -100rem; 446 | background-image: linear-gradient(tobottom, transparent, black); 447 | padding: 2.5rem auto; 448 | transition: 0.5s; 449 | display: flex; 450 | flex-direction: column; 451 | align-items: center; 452 | justify-content: center; 453 | } 454 | 455 | .blog-item:hover .blog-texts { 456 | bottom: 0; 457 | } 458 | 459 | .blog-title { 460 | font-family: "Bodoni Moda", serif; 461 | font-size: 3vw; 462 | text-align: center; 463 | text-transform: capitalize; 464 | color: var(--color-light); 465 | letter-spacing: 0.5rem; 466 | margin-bottom: 15px; 467 | font-weight: 900; 468 | } 469 | --------------------------------------------------------------------------------