├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── components ├── About │ ├── About1.js │ └── About2.js ├── Button │ ├── Button.js │ └── Button.module.scss ├── Collaboration │ └── Collaboration.js ├── Contact │ ├── Contact.js │ ├── Contact.module.scss │ └── mailer.js ├── Cursor │ └── Cursor.js ├── Footer │ ├── Footer.js │ ├── FooterBg │ │ ├── FooterBg.js │ │ └── FooterBg.module.scss │ └── Meteors │ │ └── Meteors.js ├── Header │ ├── Header.js │ ├── Menu │ │ └── Menu.js │ └── SoundBar │ │ └── SoundBar.js ├── Hero │ ├── Hero.js │ └── Hero.module.scss ├── Icons │ ├── external.js │ ├── github.js │ ├── icon.js │ ├── index.js │ ├── instagram.js │ ├── linkedin.js │ ├── mail.js │ └── twitter.js ├── Loader │ ├── Loader.js │ └── Loader.module.scss ├── Meta │ └── Meta.js ├── Profiles │ ├── Profiles.js │ └── Profiles.module.scss ├── ProgressIndicator │ └── ProgressIndicator.js ├── Projects │ ├── ProjectTile │ │ ├── ProjectTile.js │ │ └── ProjectTile.module.scss │ └── Projects.js ├── Skills │ └── Skills.js └── Work │ ├── DotPattern │ └── DotPattern.js │ ├── StickyScroll │ └── StickyScroll.js │ ├── Tabs │ └── Tabs.js │ └── Work.js ├── constants.js ├── jsconfig.json ├── next.config.mjs ├── package.json ├── pages ├── 404.js ├── _app.js ├── _document.js ├── api │ └── hello.js └── index.js ├── postcss.config.js ├── public ├── favicons │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ └── site.webmanifest ├── fonts │ ├── Calibre │ │ ├── Calibre-Black.woff2 │ │ ├── Calibre-BlackItalic.woff2 │ │ ├── Calibre-Bold.woff2 │ │ ├── Calibre-BoldItalic.woff2 │ │ ├── Calibre-Light.woff2 │ │ ├── Calibre-LightItalic.woff2 │ │ ├── Calibre-Medium.woff2 │ │ ├── Calibre-MediumItalic.woff2 │ │ ├── Calibre-Regular.woff2 │ │ ├── Calibre-RegularItalic.woff2 │ │ ├── Calibre-Semibold.woff2 │ │ ├── Calibre-SemiboldItalic.woff2 │ │ ├── Calibre-Thin.woff2 │ │ ├── Calibre-ThinItalic.woff2 │ │ └── _Calibre.scss │ └── index.js ├── footer-curve.svg ├── footer │ ├── background.png │ ├── cyclist.gif │ └── volkswagen.gif ├── icon-192x192.png ├── icon-256x256.png ├── icon-384x384.png ├── icon-512x512.png ├── left-pattern.svg ├── logo.svg ├── lottie │ └── lottie.json ├── manifest.json ├── preview.png ├── project-bg.svg ├── projects │ ├── airbnb.webp │ ├── blur │ │ ├── airbnb-blur.webp │ │ ├── inshorts-blur.webp │ │ ├── medium-blur.webp │ │ └── tesla-blur.webp │ ├── inshorts.webp │ ├── medium.webp │ ├── tech │ │ ├── alan.svg │ │ ├── chakra-ui.svg │ │ ├── firebase.svg │ │ ├── mapbox.svg │ │ ├── metamask.svg │ │ ├── mongodb.svg │ │ ├── nextjs.svg │ │ ├── react.svg │ │ ├── redux.svg │ │ ├── sanity.io.svg │ │ ├── sass.svg │ │ ├── styledcomponents.svg │ │ ├── tailwindcss.svg │ │ └── typescript.svg │ └── tesla.webp ├── right-pattern.svg ├── skills │ ├── antdesign.svg │ ├── chakra-ui.svg │ ├── css.svg │ ├── figma.svg │ ├── firebase.svg │ ├── git.svg │ ├── html.svg │ ├── javascript.svg │ ├── mongodb.svg │ ├── mysql.svg │ ├── nextjs.svg │ ├── nodejs.svg │ ├── react.svg │ ├── redux.svg │ ├── sanity-io.svg │ ├── sass.svg │ ├── styledcomponents.svg │ ├── tailwindcss.svg │ ├── tanstack-query.svg │ ├── typescript.svg │ ├── vite.svg │ └── webpack.svg └── sounds │ ├── charge-up.wav │ ├── disable-sound.mp3 │ ├── enable-sound.mp3 │ ├── glug-a.mp3 │ ├── key1.wav │ ├── key2.wav │ ├── key3.wav │ ├── key4.wav │ ├── key5.wav │ ├── key6.wav │ ├── mouse-click.mp3 │ ├── multi-pop.mp3 │ ├── pop-down.mp3 │ └── song.mp3 ├── styles └── globals.scss ├── tailwind.config.js ├── utils ├── cn.js └── log.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | /.env.local -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "svg.preview.background": "editor" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shubh Porwal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # devfolio 2 | 3 | 4 | devfolio 5 | 6 | 7 |

8 | Version 9 | 10 | License: MIT 11 | 12 | Star Badge 13 |

14 | 15 | 👨‍🎓 An eye-catching developer Portfolio, Built on NextJS, GSAP, Tailwind and React 16 | 17 | ### ✨ [Live Demo](http://www.shubhporwal.me/) 18 | 19 | ## Getting Started 20 | 21 | In the project directory, you can run: 22 | 23 | #### `yarn install` 24 | 25 | #### `yarn dev` 26 | 27 | Runs the app in the development mode.\ 28 | Open [`http://localhost:3000`](http://localhost:3000) to view it in the browser. 29 | 30 | ## Design 31 | 32 | You can always draw inspiration from the design, and no, you don't have to give me credits for that. 33 | 34 | ## Forking 35 | 36 | When people ask me whether they may use the code for their own website, I typically say yes as long as they provide proper attribution. 37 | 38 | Every time I learn that someone has duplicated my website without giving me credit, it saddens me. This version of my website took a significant amount of work to construct, and I'm pleased of it! All I ask is that you empathize with my situation and leave the footer unaltered. 39 | 40 | ## Star History 41 | 42 | [![Star History Chart](https://api.star-history.com/svg?repos=shubh73/devfolio&type=Date)](https://www.star-history.com/#shubh73/devfolio&Date) 43 | -------------------------------------------------------------------------------- /components/About/About1.js: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect, useRef } from "react"; 2 | import gsap from "gsap"; 3 | import { ScrollTrigger } from "gsap/dist/ScrollTrigger"; 4 | 5 | const About1 = ({ clientHeight }) => { 6 | const sectionRef = useRef(null); 7 | const quoteRef = useRef(null); 8 | 9 | useLayoutEffect(() => { 10 | const ctx = gsap.context(() => { 11 | const tl = gsap 12 | .timeline({ 13 | defaults: { ease: "none", duration: 0.1 }, 14 | }) 15 | .fromTo( 16 | quoteRef.current.querySelector(".about-1"), 17 | { opacity: 0.2 }, 18 | { opacity: 1 } 19 | ) 20 | .to(quoteRef.current.querySelector(".about-1"), { 21 | opacity: 0.2, 22 | delay: 0.5, 23 | }) 24 | .fromTo( 25 | quoteRef.current.querySelector(".about-2"), 26 | { opacity: 0.2 }, 27 | { opacity: 1 }, 28 | "<" 29 | ) 30 | .to(quoteRef.current.querySelector(".about-2"), { 31 | opacity: 0.2, 32 | delay: 1, 33 | }); 34 | 35 | ScrollTrigger.create({ 36 | trigger: sectionRef.current, 37 | start: "center 80%", 38 | end: "center top", 39 | scrub: 0, 40 | animation: tl, 41 | }); 42 | }); 43 | 44 | return () => ctx.revert(); 45 | }, []); 46 | 47 | return ( 48 |
49 |
650 ? "pt-28 pb-16" : "pt-80 pb-72" 52 | } section-container`} 53 | > 54 |

58 | 59 | I'm a passionate Engineer who's focused on building 60 | scalable and performant apps.{" "} 61 | 62 | 63 | I take responsibility to craft a good user experience using modern 64 | frontend architecture.{" "} 65 | 66 |

67 |
68 |
69 | ); 70 | }; 71 | 72 | export default About1; 73 | -------------------------------------------------------------------------------- /components/About/About2.js: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect, useRef } from "react"; 2 | import gsap from "gsap"; 3 | import { ScrollTrigger } from "gsap/dist/ScrollTrigger"; 4 | 5 | const About2 = ({ clientHeight }) => { 6 | const sectionRef = useRef(null); 7 | const quoteRef = useRef(null); 8 | 9 | useLayoutEffect(() => { 10 | const ctx = gsap.context(() => { 11 | const tl = gsap 12 | .timeline({ 13 | defaults: { ease: "none", duration: 0.1 }, 14 | }) 15 | .from(quoteRef.current, { opacity: 0, duration: 2 }) 16 | .to(quoteRef.current.querySelector(".about-3"), { 17 | backgroundPositionX: "100%", 18 | duration: 1, 19 | }); 20 | 21 | ScrollTrigger.create({ 22 | trigger: sectionRef.current, 23 | start: "center bottom", 24 | end: "center center", 25 | scrub: 0, 26 | animation: tl, 27 | }); 28 | }); 29 | 30 | return () => ctx.revert(); 31 | }, []); 32 | 33 | return ( 34 |
35 |
650 ? "py-80" : "py-72" 38 | } section-container`} 39 | > 40 |

44 | I have a{" "} 45 | 55 | strong 56 | {" "} 57 | obsession for attention to detail. 58 |

59 |
60 |
61 | ); 62 | }; 63 | 64 | export default About2; 65 | -------------------------------------------------------------------------------- /components/Button/Button.js: -------------------------------------------------------------------------------- 1 | import styles from "./Button.module.scss"; 2 | import PropTypes from "prop-types"; 3 | 4 | const Button = ({ href, onClick, children, classes, type, ...otherProps }) => { 5 | let additionalClasses = ""; 6 | if (classes) { 7 | additionalClasses = classes; 8 | } 9 | 10 | return ( 11 | 28 | {children} 29 | 30 | ); 31 | }; 32 | 33 | Button.propTypes = { 34 | href: PropTypes.string, 35 | onClick: PropTypes.func, 36 | children: PropTypes.string.isRequired, 37 | classes: PropTypes.string, 38 | type: PropTypes.string, 39 | }; 40 | 41 | export default Button; 42 | -------------------------------------------------------------------------------- /components/Button/Button.module.scss: -------------------------------------------------------------------------------- 1 | .primary__button { 2 | position: relative; 3 | display: inline-flex; 4 | align-items: center; 5 | text-decoration: none; 6 | font-family: theme("fontFamily.mono"); 7 | font-size: 0.8rem; 8 | font-weight: 800; 9 | 10 | padding: 0.65rem 1.75rem; 11 | background-clip: padding-box; 12 | border-radius: 0.4rem; 13 | color: #fff; 14 | background-size: 220%; 15 | outline: 2px solid theme("colors.indigo.dark"); 16 | background-image: linear-gradient( 17 | 120deg, 18 | transparent 0%, 19 | transparent 50%, 20 | theme("colors.purple") 50% 21 | ); 22 | transition: all 0.3s ease-in-out; 23 | 24 | &:hover, 25 | &:active { 26 | background-position: 100%; 27 | color: #fff; 28 | box-shadow: 0 0 1rem theme("colors.indigo.dark"); 29 | } 30 | } 31 | 32 | // .primary__button[disabled] { 33 | // pointer-events: none; 34 | // background-position: 120%; 35 | // box-shadow: 0 0 0rem theme("colors.indigo.dark"); 36 | 37 | // color: inherit; 38 | 39 | // background-image: linear-gradient( 40 | // 120deg, 41 | // transparent 33%, 42 | // theme("colors.purple") 33%, 43 | // theme("colors.purple")66%, 44 | // transparent 66% 45 | // ); 46 | 47 | // animation-name: load; 48 | // animation-duration: 1s; 49 | // animation-iteration-count: infinite; 50 | // } 51 | 52 | .secondary__button { 53 | position: relative; 54 | display: inline-flex; 55 | align-items: center; 56 | text-decoration: none; 57 | font-family: theme("fontFamily.mono"); 58 | font-size: 0.8rem; 59 | font-weight: 800; 60 | 61 | padding: 0.65rem 1.75rem; 62 | background-clip: padding-box; 63 | border-radius: 0.4rem; 64 | color: #fff; 65 | background-size: 220%; 66 | outline: 2px solid #fff; 67 | background-image: linear-gradient( 68 | 120deg, 69 | transparent 0%, 70 | transparent 50%, 71 | theme("colors.purple") 50% 72 | ); 73 | transition: all 0.3s ease-in-out; 74 | 75 | &:hover, 76 | &:active { 77 | background-position: 100%; 78 | color: #fff; 79 | box-shadow: 0 0 1rem theme("colors.gray.dark.5"); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /components/Collaboration/Collaboration.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import gsap from "gsap"; 3 | import { ScrollTrigger } from "gsap/dist/ScrollTrigger"; 4 | 5 | const Collaboration = ({ clientHeight }) => { 6 | const sectionRef = useRef(null); 7 | const quoteRef = useRef(null); 8 | 9 | useEffect(() => { 10 | const smallScreen = document.body.clientWidth < 767; 11 | 12 | const timeline = gsap.timeline({ 13 | defaults: { ease: "none" }, 14 | }); 15 | 16 | timeline 17 | .from(quoteRef.current, { opacity: 0, duration: 2 }) 18 | .to(quoteRef.current.querySelector(".text-strong"), { 19 | backgroundPositionX: "100%", 20 | duration: 1, 21 | }); 22 | 23 | const slidingTl = gsap.timeline({ defaults: { ease: "none" } }); 24 | 25 | slidingTl 26 | .to(sectionRef.current.querySelector(".ui-left"), { 27 | xPercent: smallScreen ? -500 : -150, 28 | }) 29 | .from( 30 | sectionRef.current.querySelector(".ui-right"), 31 | { xPercent: smallScreen ? -500 : -150 }, 32 | "<" 33 | ); 34 | 35 | ScrollTrigger.create({ 36 | trigger: sectionRef.current, 37 | start: "center bottom", 38 | end: "center center", 39 | scrub: 1, 40 | animation: timeline, 41 | }); 42 | 43 | ScrollTrigger.create({ 44 | trigger: sectionRef.current, 45 | start: "top bottom", 46 | end: "bottom top", 47 | scrub: 1, 48 | animation: slidingTl, 49 | }); 50 | 51 | return () => { 52 | timeline.kill(); 53 | slidingTl.kill(); 54 | }; 55 | }, [quoteRef, sectionRef]); 56 | 57 | return ( 58 |
59 |
650 ? "py-36" : "py-48" 62 | } section-container flex flex-col`} 63 | > 64 |

65 | {Array(5) 66 | .fill( 67 | " Software Engineering Problem Solving Software Architecture " 68 | ) 69 | .reduce((str, el) => str.concat(el), "")}{" "} 70 |

71 | 72 |

76 | Interested in{" "} 77 | 87 | Collaboration 88 | 89 | ? 90 |

91 | 92 |

93 | {Array(5) 94 | .fill( 95 | " Agile Development Frontend Development React Native Development " 96 | ) 97 | .reduce((str, el) => str.concat(el), "")}{" "} 98 |

99 |
100 |
101 | ); 102 | }; 103 | 104 | export default Collaboration; 105 | -------------------------------------------------------------------------------- /components/Contact/Contact.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef } from "react"; 2 | import Filter from "bad-words"; 3 | import toast, { Toaster } from "react-hot-toast"; 4 | import Fade from "react-reveal/Fade"; 5 | import gsap from "gsap"; 6 | import { ScrollTrigger } from "gsap/dist/ScrollTrigger"; 7 | import mail from "./mailer"; 8 | import styles from "./Contact.module.scss"; 9 | import { MENULINKS } from "../../constants"; 10 | 11 | const filter = new Filter(); 12 | filter.removeWords("hell", "god", "shit"); 13 | 14 | const toastOptions = { 15 | style: { 16 | borderRadius: "10px", 17 | background: "#333", 18 | color: "#fff", 19 | fontFamily: "sans-serif", 20 | }, 21 | }; 22 | 23 | const empty = () => 24 | toast.error("Please fill the required fields", { 25 | id: "error", 26 | }); 27 | 28 | const error = () => 29 | toast.error("Error sending your message", { 30 | id: "error", 31 | }); 32 | 33 | const success = () => 34 | toast.success("Message sent successfully", { 35 | id: "success", 36 | }); 37 | 38 | const Contact = () => { 39 | const initialState = { name: "", email: "", message: "" }; 40 | const [formData, setFormData] = useState(initialState); 41 | const [mailerResponse, setMailerResponse] = useState("not initiated"); 42 | const [isSending, setIsSending] = useState(false); 43 | const buttonElementRef = useRef(null); 44 | const sectionRef = useRef(null); 45 | 46 | const handleChange = ({ target }) => { 47 | const { id, value } = target; 48 | value.length === 0 ? setIsSending(false) : setIsSending(true); 49 | setFormData((prevVal) => { 50 | if ( 51 | value.trim() !== prevVal[id] && 52 | value.trim().length > prevVal[id].trim().length 53 | ) { 54 | return { ...prevVal, [id]: filter.clean(value.trim()) }; 55 | } else { 56 | return { ...prevVal, [id]: value }; 57 | } 58 | }); 59 | }; 60 | 61 | const emptyForm = () => { 62 | setFormData(initialState); 63 | }; 64 | 65 | const handleSubmit = (e) => { 66 | e.preventDefault(); 67 | 68 | const { name, email, message } = { 69 | name: formData.name, 70 | email: formData.email, 71 | message: formData.message, 72 | }; 73 | 74 | if (name === "" || email === "" || message === "") { 75 | empty(); 76 | return setMailerResponse("empty"); 77 | } 78 | 79 | setIsSending(true); 80 | mail({ name, email, message }) 81 | .then((res) => { 82 | if (res.status === 200) { 83 | setMailerResponse("success"); 84 | emptyForm(); 85 | } else { 86 | setMailerResponse("error"); 87 | } 88 | }) 89 | .catch((err) => { 90 | setMailerResponse("error"); 91 | console.error(err); 92 | }); 93 | }; 94 | 95 | useEffect(() => { 96 | setTimeout(() => { 97 | setMailerResponse("not initiated"); 98 | }, 10000); 99 | }, [mailerResponse]); 100 | 101 | useEffect(() => { 102 | buttonElementRef.current.addEventListener("click", (e) => { 103 | if (!buttonElementRef.current.classList.contains("active")) { 104 | buttonElementRef.current.classList.add("active"); 105 | 106 | gsap.to(buttonElementRef.current, { 107 | keyframes: [ 108 | { 109 | "--left-wing-first-x": 50, 110 | "--left-wing-first-y": 100, 111 | "--right-wing-second-x": 50, 112 | "--right-wing-second-y": 100, 113 | duration: 0.2, 114 | onComplete() { 115 | gsap.set(buttonElementRef.current, { 116 | "--left-wing-first-y": 0, 117 | "--left-wing-second-x": 40, 118 | "--left-wing-second-y": 100, 119 | "--left-wing-third-x": 0, 120 | "--left-wing-third-y": 100, 121 | "--left-body-third-x": 40, 122 | "--right-wing-first-x": 50, 123 | "--right-wing-first-y": 0, 124 | "--right-wing-second-x": 60, 125 | "--right-wing-second-y": 100, 126 | "--right-wing-third-x": 100, 127 | "--right-wing-third-y": 100, 128 | "--right-body-third-x": 60, 129 | }); 130 | }, 131 | }, 132 | { 133 | "--left-wing-third-x": 20, 134 | "--left-wing-third-y": 90, 135 | "--left-wing-second-y": 90, 136 | "--left-body-third-y": 90, 137 | "--right-wing-third-x": 80, 138 | "--right-wing-third-y": 90, 139 | "--right-body-third-y": 90, 140 | "--right-wing-second-y": 90, 141 | duration: 0.2, 142 | }, 143 | { 144 | "--rotate": 50, 145 | "--left-wing-third-y": 95, 146 | "--left-wing-third-x": 27, 147 | "--right-body-third-x": 45, 148 | "--right-wing-second-x": 45, 149 | "--right-wing-third-x": 60, 150 | "--right-wing-third-y": 83, 151 | duration: 0.25, 152 | }, 153 | { 154 | "--rotate": 60, 155 | "--plane-x": -8, 156 | "--plane-y": 40, 157 | duration: 0.2, 158 | }, 159 | { 160 | "--rotate": 40, 161 | "--plane-x": 45, 162 | "--plane-y": -300, 163 | "--plane-opacity": 0, 164 | duration: 0.375, 165 | onComplete() { 166 | setTimeout(() => { 167 | buttonElementRef.current.removeAttribute("style"); 168 | gsap.fromTo( 169 | buttonElementRef.current, 170 | { 171 | opacity: 0, 172 | y: -8, 173 | }, 174 | { 175 | opacity: 1, 176 | y: 0, 177 | clearProps: true, 178 | duration: 0.3, 179 | onComplete() { 180 | buttonElementRef.current.classList.remove("active"); 181 | }, 182 | } 183 | ); 184 | }, 1800); 185 | }, 186 | }, 187 | ], 188 | }); 189 | 190 | gsap.to(buttonElementRef.current, { 191 | keyframes: [ 192 | { 193 | "--text-opacity": 0, 194 | "--border-radius": 0, 195 | "--left-wing-background": "#9f55ff", 196 | "--right-wing-background": "#9f55ff", 197 | duration: 0.11, 198 | }, 199 | { 200 | "--left-wing-background": "#8b31ff", 201 | "--right-wing-background": "#8b31ff", 202 | duration: 0.14, 203 | }, 204 | { 205 | "--left-body-background": "#9f55ff", 206 | "--right-body-background": "#9f55ff", 207 | duration: 0.25, 208 | delay: 0.1, 209 | }, 210 | { 211 | "--trails-stroke": 171, 212 | duration: 0.22, 213 | delay: 0.22, 214 | }, 215 | { 216 | "--success-opacity": 1, 217 | "--success-x": 0, 218 | duration: 0.2, 219 | delay: 0.15, 220 | }, 221 | { 222 | "--success-stroke": 0, 223 | duration: 0.15, 224 | }, 225 | ], 226 | }); 227 | } 228 | }); 229 | }, [buttonElementRef]); 230 | 231 | useEffect(() => { 232 | const tl = gsap.timeline({ defaults: { ease: "none" } }); 233 | 234 | tl.from( 235 | sectionRef.current.querySelectorAll(".staggered-reveal"), 236 | { opacity: 0, duration: 0.5, stagger: 0.5 }, 237 | "<" 238 | ); 239 | 240 | ScrollTrigger.create({ 241 | trigger: sectionRef.current.querySelector(".contact-wrapper"), 242 | start: "100px bottom", 243 | end: "center center", 244 | scrub: 0, 245 | animation: tl, 246 | }); 247 | 248 | return () => tl.kill(); 249 | }, [sectionRef]); 250 | 251 | return ( 252 |
257 |
258 | 259 |
260 |
261 |
262 |
263 |

264 | CONTACT 265 |

266 |

267 | Contact 268 |

269 |
270 |

271 | Get In Touch.{" "} 272 |

273 |
274 | 275 |
276 | 277 |
278 | 286 | 292 |
293 | 294 |
295 | 303 | 309 |
310 | 311 |
312 |