├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
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 | [](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 |
400 | );
401 | };
402 |
403 | export default Contact;
404 |
--------------------------------------------------------------------------------
/components/Contact/Contact.module.scss:
--------------------------------------------------------------------------------
1 | .button {
2 | --primary: #000000;
3 | --primary-dark: #9f55ff;
4 | --shadow: #{rgba(#9f55ff, 0.3)};
5 | --text: #fff;
6 | --text-opacity: 1;
7 | --success: #eeecff;
8 | --success-x: -12;
9 | --success-stroke: 14;
10 | --success-opacity: 0;
11 | --border-radius: 7;
12 | --overflow: hidden;
13 | --x: 0;
14 | --y: 0;
15 | --rotate: 0;
16 | --plane-x: 0;
17 | --plane-y: 0;
18 | --plane-opacity: 1;
19 | --trails: #{rgba(#fff, 0.15)};
20 | --trails-stroke: 57;
21 | --left-wing-background: var(--primary);
22 | --left-wing-first-x: 0;
23 | --left-wing-first-y: 0;
24 | --left-wing-second-x: 50;
25 | --left-wing-second-y: 0;
26 | --left-wing-third-x: 0;
27 | --left-wing-third-y: 100;
28 | --left-body-background: var(--primary);
29 | --left-body-first-x: 51;
30 | --left-body-first-y: 0;
31 | --left-body-second-x: 51;
32 | --left-body-second-y: 100;
33 | --left-body-third-x: 0;
34 | --left-body-third-y: 100;
35 | --right-wing-background: var(--primary);
36 | --right-wing-first-x: 49;
37 | --right-wing-first-y: 0;
38 | --right-wing-second-x: 100;
39 | --right-wing-second-y: 0;
40 | --right-wing-third-x: 100;
41 | --right-wing-third-y: 100;
42 | --right-body-background: var(--primary);
43 | --right-body-first-x: 49;
44 | --right-body-first-y: 0;
45 | --right-body-second-x: 49;
46 | --right-body-second-y: 100;
47 | --right-body-third-x: 100;
48 | --right-body-third-y: 100;
49 | display: inline-flex; //block
50 | cursor: none;
51 | position: relative;
52 | padding: 0.65rem 1.75rem;
53 | min-width: 100px;
54 | text-align: center;
55 | margin: 0;
56 | line-height: 24px;
57 | font-family: theme("fontFamily.mono");
58 | font-weight: 600;
59 | font-size: 1rem;
60 | outline: none;
61 | background-clip: padding-box;
62 | border-radius: 0.4rem;
63 | background-size: 220%;
64 | outline: 2px solid theme("colors.indigo.dark");
65 | color: var(--text);
66 | transition: all 0.3s ease-in-out;
67 | -webkit-appearance: none;
68 | -webkit-tap-highlight-color: transparent;
69 |
70 | &:hover,
71 | &:active {
72 | background-position: 98%;
73 | color: #fff;
74 | box-shadow: 0 0 1rem theme("colors.indigo.dark");
75 | }
76 |
77 | .plane,
78 | .trails {
79 | pointer-events: none;
80 | position: absolute;
81 | }
82 | .plane {
83 | left: 0;
84 | top: 0;
85 | right: 0;
86 | bottom: 0;
87 | filter: drop-shadow(0 3px 6px var(--shadow));
88 | transform: translate(calc(var(--x) * 1px), calc(var(--y) * 1px))
89 | rotate(calc(var(--rotate) * 1deg)) translateZ(0);
90 | .left,
91 | .right {
92 | position: absolute;
93 | left: 0;
94 | top: 0;
95 | right: 0;
96 | bottom: 0;
97 | opacity: var(--plane-opacity);
98 | transform: translate(
99 | calc(var(--plane-x) * 1px),
100 | calc(var(--plane-y) * 1px)
101 | )
102 | translateZ(0);
103 | &:before,
104 | &:after {
105 | content: "";
106 | position: absolute;
107 | left: 0;
108 | top: 0;
109 | right: 0;
110 | bottom: 0;
111 | border-radius: calc(var(--border-radius) * 1px);
112 | transform: translate(var(--part-x, 0.4%), var(--part-y, 0))
113 | translateZ(0);
114 | z-index: var(--z-index, 2);
115 | background: var(--background, var(--left-wing-background));
116 | clip-path: polygon(
117 | calc(var(--first-x, var(--left-wing-first-x)) * 1%)
118 | calc(var(--first-y, var(--left-wing-first-y)) * 1%),
119 | calc(var(--second-x, var(--left-wing-second-x)) * 1%)
120 | calc(var(--second-y, var(--left-wing-second-y)) * 1%),
121 | calc(var(--third-x, var(--left-wing-third-x)) * 1%)
122 | calc(var(--third-y, var(--left-wing-third-y)) * 1%)
123 | );
124 | }
125 | }
126 | .left:after {
127 | --part-x: -1%;
128 | --z-index: 1;
129 | --background: var(--left-body-background);
130 | --first-x: var(--left-body-first-x);
131 | --first-y: var(--left-body-first-y);
132 | --second-x: var(--left-body-second-x);
133 | --second-y: var(--left-body-second-y);
134 | --third-x: var(--left-body-third-x);
135 | --third-y: var(--left-body-third-y);
136 | }
137 | .right:before {
138 | --part-x: -1%;
139 | --z-index: 2;
140 | --background: var(--right-wing-background);
141 | --first-x: var(--right-wing-first-x);
142 | --first-y: var(--right-wing-first-y);
143 | --second-x: var(--right-wing-second-x);
144 | --second-y: var(--right-wing-second-y);
145 | --third-x: var(--right-wing-third-x);
146 | --third-y: var(--right-wing-third-y);
147 | }
148 | .right:after {
149 | --part-x: 0;
150 | --z-index: 1;
151 | --background: var(--right-body-background);
152 | --first-x: var(--right-body-first-x);
153 | --first-y: var(--right-body-first-y);
154 | --second-x: var(--right-body-second-x);
155 | --second-y: var(--right-body-second-y);
156 | --third-x: var(--right-body-third-x);
157 | --third-y: var(--right-body-third-y);
158 | }
159 | }
160 | .trails {
161 | display: block;
162 | width: 33px;
163 | height: 64px;
164 | top: -4px;
165 | left: 16px;
166 | fill: none;
167 | stroke: var(--trails);
168 | stroke-linecap: round;
169 | stroke-width: 2;
170 | stroke-dasharray: 57px;
171 | stroke-dashoffset: calc(var(--trails-stroke) * 1px);
172 | transform: rotate(68deg) translateZ(0);
173 | }
174 |
175 | span {
176 | display: block;
177 | position: relative;
178 | z-index: 4;
179 | opacity: var(--text-opacity);
180 | &.success {
181 | z-index: 0;
182 | position: absolute;
183 | left: 0;
184 | right: 0;
185 | top: 9px;
186 | transform: translateX(calc(var(--success-x) * 1px)) translateZ(0);
187 | opacity: var(--success-opacity);
188 | color: var(--success);
189 | svg {
190 | display: inline-block;
191 | vertical-align: top;
192 | width: 16px;
193 | height: 16px;
194 | margin: 4px 8px 0 0;
195 | fill: none;
196 | stroke-width: 2;
197 | stroke-linecap: round;
198 | stroke-linejoin: round;
199 | stroke-dasharray: 14px;
200 | stroke: var(--success);
201 | stroke-dashoffset: calc(var(--success-stroke) * 1px);
202 | }
203 | }
204 | }
205 | }
--------------------------------------------------------------------------------
/components/Contact/mailer.js:
--------------------------------------------------------------------------------
1 | import emailjs from "@emailjs/browser";
2 |
3 | const mail = ({ name, email, message }) =>
4 | emailjs.send(
5 | process.env.NEXT_PUBLIC_SERVICE_ID,
6 | process.env.NEXT_PUBLIC_TEMPLATE_ID,
7 | { name, email, message },
8 | {
9 | publicKey: process.env.NEXT_PUBLIC_USER_ID,
10 | limitRate: {
11 | throttle: 10000, // 10s
12 | },
13 | }
14 | );
15 |
16 | export default mail;
17 |
--------------------------------------------------------------------------------
/components/Cursor/Cursor.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import gsap from "gsap";
3 |
4 | const Cursor = ({ isDesktop }) => {
5 | const cursor = useRef(null);
6 | const follower = useRef(null);
7 |
8 | useEffect(() => {
9 | if (isDesktop && document.body.clientWidth > 767) {
10 | follower.current.classList.remove("hidden");
11 | cursor.current.classList.remove("hidden");
12 |
13 | const moveCircle = (e) => {
14 | gsap.to(cursor.current, {
15 | x: e.clientX,
16 | y: e.clientY,
17 | duration: 0.1,
18 | ease: "none",
19 | });
20 | gsap.to(follower.current, {
21 | x: e.clientX,
22 | y: e.clientY,
23 | duration: 0.3,
24 | ease: "none",
25 | });
26 | };
27 |
28 | const hover = () => {
29 | gsap.to(cursor.current, {
30 | scale: 0.5,
31 | duration: 0.3,
32 | });
33 | gsap.to(follower.current, {
34 | scale: 3,
35 | duration: 0.3,
36 | });
37 | };
38 |
39 | const unHover = () => {
40 | gsap.to(cursor.current, {
41 | scale: 1,
42 | duration: 0.3,
43 | });
44 | gsap.to(follower.current, {
45 | scale: 1,
46 | duration: 0.3,
47 | });
48 | };
49 |
50 | document.addEventListener("mousemove", moveCircle);
51 |
52 | document.querySelectorAll(".link").forEach((el) => {
53 | el.addEventListener("mouseenter", hover);
54 | el.addEventListener("mouseleave", unHover);
55 | });
56 |
57 | return () => {
58 | document.removeEventListener("mousemove", moveCircle);
59 |
60 | document.querySelectorAll(".link").forEach((el) => {
61 | el.removeEventListener("mouseenter", hover);
62 | el.removeEventListener("mouseleave", unHover);
63 | });
64 | };
65 | }
66 | }, [cursor, follower, isDesktop]);
67 |
68 | return (
69 | <>
70 |
74 |
78 | >
79 | );
80 | };
81 |
82 | export default Cursor;
83 |
--------------------------------------------------------------------------------
/components/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | import { useState } from "react";
3 | import { Fade } from "react-reveal";
4 | import { Howl } from "howler";
5 | import Button from "../Button/Button";
6 | import FooterBg from "./FooterBg/FooterBg";
7 | import Profiles from "../Profiles/Profiles";
8 | import { theme } from "tailwind.config";
9 | import { MENULINKS } from "../../constants";
10 |
11 | const Footer = () => {
12 | const [playbackRate, setPlaybackRate] = useState(0.75);
13 |
14 | const heartClickSound = new Howl({
15 | src: ["/sounds/glug-a.mp3"],
16 | rate: playbackRate,
17 | volume: 0.5,
18 | });
19 |
20 | const handleClick = () => {
21 | setPlaybackRate((rate) => rate + 0.1);
22 | heartClickSound.play();
23 | };
24 |
25 | return (
26 |
32 |
33 |
34 |
35 |
36 |
37 | Feel free to connect on social media.
38 |
39 |
42 |
43 |
48 | Let's Talk
49 |
50 |
51 |
52 | Developed with{" "}
53 |
54 | ❤️
55 | {" "}
56 | by Shubh Porwal
57 |
58 |
59 |
60 |
61 |
68 |
69 | );
70 | };
71 |
72 | export default Footer;
73 |
--------------------------------------------------------------------------------
/components/Footer/FooterBg/FooterBg.js:
--------------------------------------------------------------------------------
1 | import Meteors from "../Meteors/Meteors";
2 | import styles from "./FooterBg.module.scss";
3 |
4 | const FooterBg = () => {
5 | return (
6 |
13 | );
14 | };
15 |
16 | export default FooterBg;
17 |
--------------------------------------------------------------------------------
/components/Footer/FooterBg/FooterBg.module.scss:
--------------------------------------------------------------------------------
1 | .top {
2 | padding: 120px 0px 270px;
3 | position: relative;
4 | overflow-x: hidden;
5 |
6 | .background {
7 | position: absolute;
8 | bottom: 0;
9 | background: url("/footer/background.png") no-repeat scroll center 0;
10 | width: 100%;
11 | height: 266px;
12 | }
13 |
14 | .background__one {
15 | background: url("/footer/volkswagen.gif") no-repeat center center;
16 | width: 330px;
17 | height: 105px;
18 | background-size: 100%;
19 | position: absolute;
20 | bottom: 0;
21 | left: 30%;
22 | -webkit-animation: first 22s linear infinite;
23 | animation: first 22s linear infinite;
24 | }
25 |
26 | .background__two {
27 | background: url("/footer/cyclist.gif") no-repeat center center;
28 | width: 88px;
29 | height: 100px;
30 | background-size: 100%;
31 | bottom: 0;
32 | left: 38%;
33 | position: absolute;
34 | -webkit-animation: first 30s linear infinite;
35 | animation: first 30s linear infinite;
36 | }
37 | }
38 |
39 | @keyframes first {
40 | 0% {
41 | left: -25%;
42 | }
43 | 100% {
44 | left: 100%;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/components/Footer/Meteors/Meteors.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { cn } from "utils/cn";
3 |
4 | const Meteors = ({ number = 10 }) => {
5 | const [meteorStyles, setMeteorStyles] = useState([]);
6 |
7 | useEffect(() => {
8 | const styles = [...new Array(number)].map(() => ({
9 | top: -5,
10 | left: Math.floor(Math.random() * window.innerWidth) + "px",
11 | animationDelay: Math.random() * 1 + 0.2 + "s",
12 | animationDuration: Math.floor(Math.random() * 9 + 2) + "s",
13 | }));
14 |
15 | setMeteorStyles(styles);
16 | }, [number]);
17 |
18 | return (
19 | <>
20 | {[...meteorStyles].map((style, idx) => (
21 | // Meteor Head
22 |
29 | {/* Meteor Tail */}
30 |
31 |
32 | ))}
33 | >
34 | );
35 | };
36 |
37 | export default Meteors;
38 |
--------------------------------------------------------------------------------
/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef } from "react";
2 | import Image from "next/image";
3 | import { Howl } from "howler";
4 | import SoundBar from "./SoundBar/SoundBar";
5 |
6 | const multiPop = new Howl({
7 | src: ["/sounds/multi-pop.mp3"],
8 | });
9 |
10 | const Header = ({ children }) => {
11 | const inputRef = useRef(null);
12 |
13 | const handleClick = useCallback((e) => {
14 | if (e.target.checked) multiPop.play();
15 | }, []);
16 |
17 | const handleKeyDown = useCallback((e) => {
18 | if (e.key === "Escape" && inputRef.current?.checked) {
19 | inputRef.current.checked = false;
20 | }
21 | }, []);
22 |
23 | useEffect(() => {
24 | document.addEventListener("keydown", handleKeyDown);
25 |
26 | return () => {
27 | document.removeEventListener("keydown", handleKeyDown);
28 | };
29 | }, [handleKeyDown]);
30 |
31 | return (
32 |
33 |
34 |
35 |
41 |
42 |
43 |
44 |
52 |
55 | {children}
56 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default Header;
63 |
--------------------------------------------------------------------------------
/components/Header/Menu/Menu.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { MENULINKS } from "../../../constants";
3 |
4 | const Menu = () => {
5 | useEffect(() => {
6 | const anchorNodes = document.querySelectorAll('a[href^="#"]');
7 |
8 | anchorNodes.forEach((el) => {
9 | el.addEventListener("click", () => {
10 | const checkbox = document.querySelector(".checkbox-toggle");
11 | checkbox.checked = false;
12 | });
13 | });
14 | }, []);
15 |
16 | return (
17 |
35 | );
36 | };
37 |
38 | export default Menu;
39 |
--------------------------------------------------------------------------------
/components/Header/SoundBar/SoundBar.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from "react";
2 | import audio from "../../../public/sounds/song.mp3";
3 |
4 | const SoundBar = () => {
5 | const soundBarEl = useRef(null);
6 | const [isPlaying, setIsPlaying] = useState(false);
7 |
8 | const togglePlayPause = () => {
9 | setIsPlaying(!isPlaying);
10 | if (!isPlaying) soundBarEl.current.play();
11 | else soundBarEl.current.pause();
12 | };
13 |
14 | useEffect(() => {
15 | document.querySelector(".soundBars").onclick = function () {
16 | this.classList.toggle("play");
17 | };
18 | }, []);
19 |
20 | return (
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default SoundBar;
35 |
--------------------------------------------------------------------------------
/components/Hero/Hero.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef, useLayoutEffect } from "react";
2 | import Typed from "typed.js";
3 | import gsap from "gsap";
4 | import Button from "../Button/Button";
5 | import Profiles from "../Profiles/Profiles";
6 | import styles from "./Hero.module.scss";
7 | import { MENULINKS, TYPED_STRINGS } from "../../constants";
8 |
9 | const options = {
10 | strings: TYPED_STRINGS,
11 | typeSpeed: 50,
12 | startDelay: 1500,
13 | backSpeed: 50,
14 | backDelay: 8000,
15 | loop: true,
16 | };
17 |
18 | const Hero = () => {
19 | const [lottie, setLottie] = useState(null);
20 |
21 | const sectionRef = useRef(null);
22 | const typedElementRef = useRef(null);
23 | const lottieRef = useRef(null);
24 |
25 | useLayoutEffect(() => {
26 | const ctx = gsap.context(() => {
27 | gsap
28 | .timeline({ defaults: { ease: "none" } })
29 | .to(sectionRef.current, { opacity: 1, duration: 2 })
30 | .from(
31 | sectionRef.current.querySelectorAll(".staggered-reveal"),
32 | { opacity: 0, duration: 0.5, stagger: 0.5 },
33 | "<"
34 | );
35 | });
36 |
37 | return () => ctx.revert();
38 | }, []);
39 |
40 | useEffect(() => {
41 | const typed = new Typed(typedElementRef.current, options);
42 |
43 | return () => typed.destroy();
44 | }, [typedElementRef]);
45 |
46 | useEffect(() => {
47 | import("lottie-web").then((Lottie) => setLottie(Lottie.default));
48 | }, []);
49 |
50 | useEffect(() => {
51 | if (lottie && lottieRef.current) {
52 | const animation = lottie.loadAnimation({
53 | container: lottieRef.current,
54 | renderer: "svg",
55 | loop: true,
56 | autoplay: true,
57 | animationData: require("../../public/lottie/lottie.json"),
58 | });
59 |
60 | return () => animation.destroy();
61 | }
62 | }, [lottie]);
63 |
64 | return (
65 |
110 | );
111 | };
112 |
113 | export default Hero;
114 |
--------------------------------------------------------------------------------
/components/Hero/Hero.module.scss:
--------------------------------------------------------------------------------
1 | .intro {
2 | animation: fadeAppear 1s 0.2s;
3 | }
4 |
5 | .heroName{
6 | line-height: 1.5em;
7 | animation: fadeAppear 1.3s 0.3s;
8 |
9 | .emphasize {
10 | &::after {
11 | content: '';
12 | position: absolute;
13 | bottom: 0.7rem;
14 | left: 0;
15 | width: 100%;
16 | height: 0.35rem;
17 | border-radius: 1rem;
18 | background-image: linear-gradient(to right, theme("colors.indigo.light"), theme("colors.indigo.dark"));
19 | box-shadow: 0 0 1rem theme("colors.indigo.dark");
20 | animation: growHorizontal 1.3s cubic-bezier(0.9, 0, 0.5, 0.9) 0.8s;
21 | }
22 | }
23 | }
24 |
25 |
26 | @keyframes fadeAppear {
27 | 0% {
28 | opacity: 0;
29 | }
30 |
31 | 50% {
32 | opacity: 0;
33 | }
34 |
35 | 100% {
36 | opacity: 1;
37 | }
38 | }
39 |
40 | @keyframes growHorizontal {
41 | from {
42 | width: 0;
43 | }
44 | to {
45 | width: 98%;
46 | }
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/components/Icons/external.js:
--------------------------------------------------------------------------------
1 | const IconExternal = () => (
2 |
13 | External Link
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export default IconExternal;
21 |
--------------------------------------------------------------------------------
/components/Icons/github.js:
--------------------------------------------------------------------------------
1 | const IconGithub = () => (
2 |
14 |
15 |
16 | );
17 |
18 | export default IconGithub;
19 |
--------------------------------------------------------------------------------
/components/Icons/icon.js:
--------------------------------------------------------------------------------
1 | import {
2 | IconMail,
3 | IconLinkedin,
4 | IconInstagram,
5 | IconTwitter,
6 | IconGithub,
7 | IconExternal,
8 | } from "@/components/Icons";
9 |
10 | const Icon = ({ name }) => {
11 | switch (name) {
12 | case "mail":
13 | return ;
14 | case "github":
15 | return ;
16 | case "linkedin":
17 | return ;
18 | case "instagram":
19 | return ;
20 | case "twitter":
21 | return ;
22 | default:
23 | return ;
24 | }
25 | };
26 |
27 | export default Icon;
28 |
--------------------------------------------------------------------------------
/components/Icons/index.js:
--------------------------------------------------------------------------------
1 | export { default as IconMail} from "./mail";
2 | export { default as IconGithub} from "./github";
3 | export { default as IconLinkedin} from "./linkedin";
4 | export { default as IconInstagram} from "./instagram";
5 | export { default as IconTwitter} from "./twitter";
6 | export { default as IconExternal} from "./external";
7 | export { default as Icon } from './icon';
8 |
--------------------------------------------------------------------------------
/components/Icons/instagram.js:
--------------------------------------------------------------------------------
1 | const IconInstagram = () => (
2 |
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export default IconInstagram;
21 |
--------------------------------------------------------------------------------
/components/Icons/linkedin.js:
--------------------------------------------------------------------------------
1 | const IconLinkedin = () => (
2 |
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export default IconLinkedin;
21 |
--------------------------------------------------------------------------------
/components/Icons/mail.js:
--------------------------------------------------------------------------------
1 | const IconMail = () => (
2 |
14 |
15 |
16 |
17 | );
18 |
19 | export default IconMail;
20 |
--------------------------------------------------------------------------------
/components/Icons/twitter.js:
--------------------------------------------------------------------------------
1 | const IconTwitter = () => (
2 |
14 |
15 |
16 | );
17 |
18 | export default IconTwitter;
19 |
--------------------------------------------------------------------------------
/components/Loader/Loader.js:
--------------------------------------------------------------------------------
1 | import styles from "./Loader.module.scss";
2 |
3 | const Loader = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default Loader;
32 |
--------------------------------------------------------------------------------
/components/Loader/Loader.module.scss:
--------------------------------------------------------------------------------
1 | .screen {
2 | background: black;
3 | height: 100%;
4 | width: 100%;
5 | position: fixed;
6 | z-index: 9999;
7 | }
8 |
9 | .loader {
10 | position: absolute;
11 | top: 50%;
12 | margin-left: -60px;
13 | left: 50%;
14 | animation: speeder 0.4s linear infinite;
15 | z-index: 9999;
16 |
17 | > span {
18 | height: 5px;
19 | width: 35px;
20 | background: linear-gradient(to right, #7000ff, #8b31ff);
21 | position: absolute;
22 | top: -19px;
23 | left: 60px;
24 | border-radius: 2px 10px 1px 0;
25 | }
26 | }
27 |
28 | .loader > span > span:nth-child(1),
29 | .loader > span > span:nth-child(2),
30 | .loader > span > span:nth-child(3),
31 | .loader > span > span:nth-child(4) {
32 | width: 30px;
33 | height: 1px;
34 | background: linear-gradient(to right, #7000ff, #8b31ff);
35 | position: absolute;
36 | animation: fazer1 0.2s linear infinite;
37 | }
38 |
39 | .loader > span > span:nth-child(2) {
40 | top: 3px;
41 | animation: fazer2 0.4s linear infinite;
42 | }
43 |
44 | .loader > span > span:nth-child(3) {
45 | top: 1px;
46 | animation: fazer3 0.4s linear infinite;
47 | animation-delay: -1s;
48 | }
49 |
50 | .loader > span > span:nth-child(4) {
51 | top: 4px;
52 | animation: fazer4 1s linear infinite;
53 | animation-delay: -1s;
54 | }
55 |
56 | .base {
57 | span {
58 | position: absolute;
59 | width: 0;
60 | height: 0;
61 | border-top: 6px solid transparent;
62 | border-right: 100px solid #8b31ff;
63 | border-bottom: 6px solid transparent;
64 |
65 | &:before {
66 | content: "";
67 | height: 22px;
68 | width: 22px;
69 | border-radius: 50%;
70 | background: #8b31ff;
71 | position: absolute;
72 | right: -110px;
73 | top: -16px;
74 | }
75 |
76 | &:after {
77 | content: "";
78 | position: absolute;
79 | width: 0;
80 | height: 0;
81 | border-top: 0 solid transparent;
82 | border-right: 55px solid #8b31ff;
83 | border-bottom: 16px solid transparent;
84 | top: -16px;
85 | right: -98px;
86 | }
87 | }
88 | }
89 |
90 | .face {
91 | position: absolute;
92 | height: 12px;
93 | width: 20px;
94 | background: linear-gradient(to right, #7000ff, #8b31ff);
95 | border-radius: 20px 20px 0 0;
96 | transform: rotate(-40deg);
97 | right: -125px;
98 | top: -15px;
99 |
100 | &:after {
101 | content: "";
102 | height: 12px;
103 | width: 12px;
104 | background: linear-gradient(to right, #7000ff, #8b31ff);
105 | right: 3px;
106 | top: 7px;
107 | position: absolute;
108 | transform: rotate(40deg);
109 | transform-origin: 50% 50%;
110 | border-radius: 0 0 0 2px;
111 | }
112 | }
113 |
114 | .progress {
115 | width: 9rem;
116 | margin: 0 auto;
117 | border-radius: 0.5rem;
118 | height: 0.3rem;
119 | position: absolute;
120 | top: 63%;
121 | left: 50%;
122 | margin-left: -4rem;
123 | background-color: #1e1b20;
124 | overflow: hidden;
125 | animation-name: fadeAppear;
126 | animation-duration: 1s;
127 |
128 | &:after {
129 | content: "";
130 | position: absolute;
131 | top: 0;
132 | display: block;
133 | border-radius: 0.5rem;
134 | height: 0.3rem;
135 | width: 100%;
136 | left: -100%;
137 | background-image: linear-gradient(to right, #8b31ff, #7000ff);
138 | animation-name: load;
139 | animation-duration: 1.7s; //1.7s
140 | animation-delay: 1s;
141 | animation-iteration-count: infinite;
142 | }
143 | }
144 |
145 | @keyframes fadeAppear {
146 | 0% {
147 | opacity: 0;
148 | }
149 |
150 | 50% {
151 | opacity: 0;
152 | }
153 |
154 | 100% {
155 | opacity: 1;
156 | }
157 | }
158 |
159 | @keyframes load {
160 | 0% {
161 | left: -100%;
162 | }
163 |
164 | 100% {
165 | left: 100%;
166 | }
167 | }
168 |
169 | @keyframes fazer1 {
170 | 0% {
171 | left: 0;
172 | }
173 | 100% {
174 | left: -80px;
175 | opacity: 0;
176 | }
177 | }
178 |
179 | @keyframes fazer2 {
180 | 0% {
181 | left: 0;
182 | }
183 | 100% {
184 | left: -100px;
185 | opacity: 0;
186 | }
187 | }
188 |
189 | @keyframes fazer3 {
190 | 0% {
191 | left: 0;
192 | }
193 | 100% {
194 | left: -50px;
195 | opacity: 0;
196 | }
197 | }
198 |
199 | @keyframes fazer4 {
200 | 0% {
201 | left: 0;
202 | }
203 | 100% {
204 | left: -150px;
205 | opacity: 0;
206 | }
207 | }
208 |
209 | @keyframes speeder {
210 | 0% {
211 | transform: translate(2px, 1px) rotate(0deg);
212 | }
213 | 10% {
214 | transform: translate(-1px, -3px) rotate(-1deg);
215 | }
216 | 20% {
217 | transform: translate(-2px, 0px) rotate(1deg);
218 | }
219 | 30% {
220 | transform: translate(1px, 2px) rotate(0deg);
221 | }
222 | 40% {
223 | transform: translate(1px, -1px) rotate(1deg);
224 | }
225 | 50% {
226 | transform: translate(-1px, 3px) rotate(-1deg);
227 | }
228 | 60% {
229 | transform: translate(-1px, 1px) rotate(0deg);
230 | }
231 | 70% {
232 | transform: translate(3px, 1px) rotate(-1deg);
233 | }
234 | 80% {
235 | transform: translate(-2px, -1px) rotate(1deg);
236 | }
237 | 90% {
238 | transform: translate(2px, 1px) rotate(0deg);
239 | }
240 | 100% {
241 | transform: translate(1px, -2px) rotate(-1deg);
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/components/Meta/Meta.js:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import { METADATA } from "../../constants";
3 |
4 | const Meta = () => (
5 |
6 | {METADATA.title}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {/* Open Graph / Facebook */}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {/* Twitter */}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
45 |
51 |
52 |
53 |
54 | );
55 |
56 | export default Meta;
57 |
--------------------------------------------------------------------------------
/components/Profiles/Profiles.js:
--------------------------------------------------------------------------------
1 | import { SOCIAL_LINKS } from "../../constants";
2 | import { Icon } from "@/components/Icons";
3 | import styles from "./Profiles.module.scss";
4 |
5 | const Profiles = () => {
6 | return (
7 |
8 | {SOCIAL_LINKS &&
9 | SOCIAL_LINKS.map(({ name, url }) => (
10 |
18 |
19 |
20 | ))}
21 |
22 | );
23 | };
24 |
25 | export default Profiles;
26 |
--------------------------------------------------------------------------------
/components/Profiles/Profiles.module.scss:
--------------------------------------------------------------------------------
1 | .profile {
2 | display: inline-flex;
3 | padding: 1.7rem 0 1rem 0;
4 | gap: 1.5rem;
5 |
6 | & > a {
7 | &:hover,
8 | &:focus {
9 | filter: drop-shadow(0 0 1rem theme("colors.indigo.light"));
10 | filter: brightness(1.1);
11 | svg {
12 | stroke: theme("colors.indigo.light");
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/components/ProgressIndicator/ProgressIndicator.js:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect } from "react";
2 |
3 | const ProgressIndicator = () => {
4 | const progressRef = useRef(null);
5 |
6 | useEffect(() => {
7 | window.addEventListener("scroll", () => {
8 | const totalScroll =
9 | document.body.scrollTop || document.documentElement.scrollTop;
10 | const windowHeight =
11 | document.documentElement.scrollHeight -
12 | document.documentElement.clientHeight;
13 | const scrolled = totalScroll / windowHeight;
14 | progressRef.current
15 | ? (progressRef.current.style.transform = `scaleX(${scrolled})`)
16 | : "";
17 | });
18 | }, [progressRef]);
19 |
20 | return (
21 |
24 | );
25 | };
26 |
27 | export default ProgressIndicator;
28 |
--------------------------------------------------------------------------------
/components/Projects/ProjectTile/ProjectTile.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import Image from "next/image";
3 | import VanillaTilt from "vanilla-tilt";
4 | import styles from "./ProjectTile.module.scss";
5 |
6 | const ProjectTile = ({ project, classes, isDesktop }) => {
7 | const { name, image, blurImage, description, gradient, url, tech } = project;
8 | const projectCard = useRef(null);
9 | let additionalClasses = "";
10 | if (classes) {
11 | additionalClasses = classes;
12 | }
13 |
14 | const options = {
15 | max: 10,
16 | speed: 400,
17 | glare: true,
18 | "max-glare": 0.2,
19 | gyroscope: false,
20 | };
21 |
22 | useEffect(() => {
23 | VanillaTilt.init(projectCard.current, options);
24 | }, [projectCard]);
25 |
26 | return (
27 |
39 |
45 |
50 |
58 |
64 |
70 |
74 | {name}
75 |
76 |
81 |
82 | {project.tech.map((el, i) => (
83 |
91 | ))}
92 |
93 |
94 |
98 | {description}
99 |
100 |
101 |
102 | );
103 | };
104 |
105 | export default ProjectTile;
106 |
--------------------------------------------------------------------------------
/components/Projects/ProjectTile/ProjectTile.module.scss:
--------------------------------------------------------------------------------
1 | .projectTile {
2 | transform-style: preserve-3d;
3 | transform: perspective(1000px);
4 |
5 | img {
6 | object-fit: cover;
7 | }
8 | }
9 |
10 | .projectImage {
11 | @apply absolute top-0 rounded-xl shadow-xl;
12 |
13 | width: 17rem !important;
14 | transform: rotate(-22.5deg);
15 | height: unset !important;
16 | min-width: unset !important;
17 | max-height: unset !important;
18 | object-fit: contain !important;
19 | left: unset !important;
20 | right: 2rem !important;
21 | bottom: unset !important;
22 | }
23 |
24 | .techIcons {
25 | transform: rotate(-22.5deg) translateZ(2rem);
26 | will-change: transform;
27 | }
--------------------------------------------------------------------------------
/components/Projects/Projects.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import { MENULINKS, PROJECTS } from "../../constants";
3 | import gsap from "gsap";
4 | import { ScrollTrigger } from "gsap/dist/ScrollTrigger";
5 | import ProjectTile from "./ProjectTile/ProjectTile";
6 |
7 | const Projects = ({ isDesktop, clientHeight }) => {
8 | const sectionRef = useRef(null);
9 | const sectionTitleRef = useRef(null);
10 |
11 | useEffect(() => {
12 | let projectsScrollTrigger;
13 | let projectsTimeline;
14 |
15 | if (isDesktop) {
16 | [projectsTimeline, projectsScrollTrigger] = getProjectsSt();
17 | } else {
18 | const projectWrapper =
19 | sectionRef.current.querySelector(".project-wrapper");
20 | projectWrapper.style.width = "calc(100vw - 1rem)";
21 | projectWrapper.style.overflowX = "scroll";
22 | }
23 |
24 | const [revealTimeline, revealScrollTrigger] = getRevealSt();
25 |
26 | return () => {
27 | projectsScrollTrigger && projectsScrollTrigger.kill();
28 | projectsTimeline && projectsTimeline.kill();
29 | revealScrollTrigger && revealScrollTrigger.kill();
30 | revealTimeline && revealTimeline.progress(1);
31 | };
32 | }, [sectionRef, sectionTitleRef, isDesktop]);
33 |
34 | const getRevealSt = () => {
35 | const revealTl = gsap.timeline({ defaults: { ease: "none" } });
36 |
37 | revealTl.from(
38 | sectionRef.current.querySelectorAll(".staggered-reveal"),
39 | { opacity: 0, duration: 0.5, stagger: 0.5 },
40 | "<"
41 | );
42 |
43 | const scrollTrigger = ScrollTrigger.create({
44 | trigger: sectionRef.current,
45 | start: "top bottom",
46 | end: "bottom bottom",
47 | scrub: 0,
48 | animation: revealTl,
49 | });
50 |
51 | return [revealTl, scrollTrigger];
52 | };
53 |
54 | const getProjectsSt = () => {
55 | const timeline = gsap.timeline({ defaults: { ease: "none" } });
56 | const sidePadding =
57 | document.body.clientWidth -
58 | sectionRef.current.querySelector(".inner-container").clientWidth;
59 | const elementWidth =
60 | sidePadding +
61 | sectionRef.current.querySelector(".project-wrapper").clientWidth;
62 | sectionRef.current.style.width = `${elementWidth}px`;
63 | const width = window.innerWidth - elementWidth;
64 | const duration = `${(elementWidth / window.innerHeight) * 100}%`;
65 | timeline
66 | .to(sectionRef.current, { x: width })
67 | .to(sectionTitleRef.current, { x: -width }, "<");
68 |
69 | const scrollTrigger = ScrollTrigger.create({
70 | trigger: sectionRef.current,
71 | start: "top top",
72 | end: duration,
73 | scrub: 0,
74 | pin: true,
75 | animation: timeline,
76 | pinSpacing: "margin",
77 | });
78 |
79 | return [timeline, scrollTrigger];
80 | };
81 |
82 | return (
83 |
123 | );
124 | };
125 |
126 | export default Projects;
127 |
--------------------------------------------------------------------------------
/components/Skills/Skills.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 | import { useLayoutEffect, useRef } from "react";
3 | import Image from "next/image";
4 | import gsap from "gsap";
5 | import { ScrollTrigger } from "gsap/dist/ScrollTrigger";
6 | import { MENULINKS, SKILLS } from "../../constants";
7 |
8 | const Skills = () => {
9 | const sectionRef = useRef(null);
10 |
11 | useLayoutEffect(() => {
12 | const ctx = gsap.context(() => {
13 | const tl = gsap
14 | .timeline({ defaults: { ease: "none" } })
15 | .from(
16 | sectionRef.current.querySelectorAll(".staggered-reveal"),
17 | { opacity: 0, duration: 0.5, stagger: 0.5 },
18 | "<"
19 | );
20 |
21 | ScrollTrigger.create({
22 | trigger: sectionRef.current.querySelector(".skills-wrapper"),
23 | start: "100px bottom",
24 | end: "center center",
25 | scrub: 0,
26 | animation: tl,
27 | });
28 | });
29 |
30 | return () => ctx.revert();
31 | }, []);
32 |
33 | return (
34 |
130 | );
131 | };
132 |
133 | export default Skills;
134 |
--------------------------------------------------------------------------------
/components/Work/DotPattern/DotPattern.js:
--------------------------------------------------------------------------------
1 | import { useId } from "react";
2 | import { cn } from "utils/cn";
3 |
4 | const DotPattern = ({
5 | width = 16,
6 | height = 16,
7 | x = 0,
8 | y = 0,
9 | cx = 1,
10 | cy = 1,
11 | cr = 1,
12 | className,
13 | ...props
14 | }) => {
15 | const id = useId();
16 |
17 | return (
18 |
26 |
27 |
36 |
37 |
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default DotPattern;
45 |
--------------------------------------------------------------------------------
/components/Work/StickyScroll/StickyScroll.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import { useMotionValueEvent, useScroll, motion } from "framer-motion";
3 | import DotPattern from "../DotPattern/DotPattern";
4 | import { cn } from "utils/cn";
5 |
6 | const StickyScroll = ({ contentItems }) => {
7 | const [activeCard, setActiveCard] = useState(0);
8 | const containerRef = useRef(null);
9 |
10 | const { scrollYProgress } = useScroll({
11 | container: containerRef,
12 | offset: ["start start", "end start"],
13 | });
14 |
15 | const cardLength = contentItems.length;
16 |
17 | useMotionValueEvent(scrollYProgress, "change", (latest) => {
18 | const cardsBreakpoints = contentItems.map(
19 | (_, index) => index / cardLength - 0.1
20 | );
21 |
22 | const closestBreakpointIndex = cardsBreakpoints.reduce(
23 | (acc, breakpoint, index) => {
24 | const distance = Math.abs(latest - breakpoint);
25 | if (distance < Math.abs(latest - cardsBreakpoints[acc])) {
26 | return index;
27 | }
28 | return acc;
29 | },
30 | 0
31 | );
32 | setActiveCard(closestBreakpointIndex);
33 | });
34 |
35 | const backgroundColors = ["#000000"];
36 | const linearGradients = [
37 | "linear-gradient(to bottom right, #ef008f, #6ec3f4)",
38 | "linear-gradient(to bottom right, #6ec3f4, #7038ff)",
39 | "linear-gradient(to bottom right, #7038ff, #c9c9c9)",
40 | ];
41 |
42 | return (
43 |
44 |
54 |
55 |
56 |
57 |
58 |
66 |
67 |
68 | {contentItems.map((item, index) => (
69 |
70 |
79 | {item.title}
80 |
81 |
90 | {item.description}
91 |
92 |
93 | ))}
94 |
95 |
96 |
97 |
104 | {contentItems[activeCard].content ?? null}
105 |
106 |
107 |
108 | );
109 | };
110 |
111 | export default StickyScroll;
112 |
--------------------------------------------------------------------------------
/components/Work/Tabs/Tabs.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { motion } from "framer-motion";
3 | import { cn } from "utils/cn";
4 |
5 | const Tab = ({ index, tab, activeTab, handleOnClick, setIsHovering }) => {
6 | return (
7 | handleOnClick(index)}
9 | onMouseEnter={() => setIsHovering(true)}
10 | onMouseLeave={() => setIsHovering(false)}
11 | className="relative px-4 py-1 rounded-full cursor-none"
12 | style={{
13 | transformStyle: "preserve-3d",
14 | }}
15 | >
16 | {activeTab.value === tab.value && (
17 |
22 | )}
23 |
24 |
30 | {tab.title}
31 |
32 |
33 | );
34 | };
35 |
36 | const TabsContent = ({ tabs, isHovering }) => {
37 | return (
38 |
39 | {tabs.map((tab, index) => {
40 | return (
41 |
55 | {tab.content}
56 |
57 | );
58 | })}
59 |
60 | );
61 | };
62 |
63 | const mouseClickSound = new Howl({
64 | src: ["/sounds/mouse-click.mp3"],
65 | });
66 |
67 | const Tabs = ({ tabItems }) => {
68 | const [isHovering, setIsHovering] = useState(false);
69 | const [tabs, setTabs] = useState(tabItems);
70 | const [activeTab, setActiveTab] = useState(tabItems[0]);
71 |
72 | const handleOnClick = (index) => {
73 | const updatedTabs = [...tabItems];
74 | const selectedTab = updatedTabs.splice(index, 1);
75 | updatedTabs.unshift(selectedTab[0]);
76 | setTabs(updatedTabs);
77 | setActiveTab(updatedTabs[0]);
78 | mouseClickSound.play();
79 | };
80 |
81 | return (
82 |
83 |
84 | {tabItems.map((tab, index) => (
85 |
93 | ))}
94 |
95 |
101 |
102 | );
103 | };
104 |
105 | export default Tabs;
106 |
--------------------------------------------------------------------------------
/components/Work/Work.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
2 | import gsap from "gsap";
3 | import { ScrollTrigger } from "gsap/dist/ScrollTrigger";
4 | import Tabs from "./Tabs/Tabs";
5 | import StickyScroll from "./StickyScroll/StickyScroll";
6 | import { MENULINKS, WORK_CONTENTS } from "../../constants";
7 |
8 | const Work = ({ isDesktop }) => {
9 | const sectionRef = useRef(null);
10 |
11 | const tabItems = useMemo(
12 | () => [
13 | {
14 | title: "Dukaan",
15 | value: "dukaan",
16 | content: (
17 |
21 | ),
22 | },
23 | {
24 | title: "Aviate",
25 | value: "Aviate",
26 | content: (
27 |
31 | ),
32 | },
33 | {
34 | title: "Spacenos",
35 | value: "spacenos",
36 | content: (
37 |
41 | ),
42 | },
43 | ],
44 | [isDesktop]
45 | );
46 |
47 | useEffect(() => {
48 | const ctx = gsap.context(() => {
49 | const tl = gsap
50 | .timeline({ defaults: { ease: "none" } })
51 | .from(
52 | sectionRef.current.querySelectorAll(".staggered-reveal"),
53 | { opacity: 0, duration: 0.5, stagger: 0.5 },
54 | "<"
55 | );
56 |
57 | ScrollTrigger.create({
58 | trigger: sectionRef.current.querySelector(".work-wrapper"),
59 | start: "100px bottom",
60 | end: "center center",
61 | scrub: 0,
62 | animation: tl,
63 | });
64 | });
65 |
66 | return () => ctx.revert();
67 | }, []);
68 |
69 | return (
70 |
100 | );
101 | };
102 |
103 | export default Work;
104 |
--------------------------------------------------------------------------------
/constants.js:
--------------------------------------------------------------------------------
1 | export const METADATA = {
2 | author: "Shubh Porwal",
3 | title: "Portfolio | Shubh Porwal",
4 | description:
5 | "Shubh Porwal is a passionate Frontend Engineer, dedicated to crafting aesthetic and modern apps that captivate and engage users.",
6 | siteUrl: "https://www.shubhporwal.me/",
7 | twitterHandle: "@shubhporwal24",
8 | keywords: [
9 | "Shubh Porwal",
10 | "Frontend Engineer",
11 | "React Native Developer",
12 | "Software Engineer",
13 | "Portfolio",
14 | "Devfolio",
15 | "Folio",
16 | ].join(", "),
17 | image:
18 | "https://res.cloudinary.com/dywdhyojt/image/upload/v1721378510/social-preview.png",
19 | language: "English",
20 | themeColor: "#000000",
21 | };
22 |
23 | export const MENULINKS = [
24 | {
25 | name: "Home",
26 | ref: "home",
27 | },
28 | {
29 | name: "Skills",
30 | ref: "skills",
31 | },
32 | {
33 | name: "Projects",
34 | ref: "projects",
35 | },
36 | {
37 | name: "Work",
38 | ref: "work",
39 | },
40 | {
41 | name: "Contact",
42 | ref: "contact",
43 | },
44 | ];
45 |
46 | export const TYPED_STRINGS = [
47 | "A pragmatic Frontend Developer",
48 | "I build things for the web",
49 | "I create aesthetic and modern apps",
50 | ];
51 |
52 | export const SOCIAL_LINKS = [
53 | {
54 | name: "mail",
55 | url: "mailto: shubhporwal73@gmail.com",
56 | },
57 | {
58 | name: "linkedin",
59 | url: "https://www.linkedin.com/in/shubhporwal/",
60 | },
61 | {
62 | name: "github",
63 | url: "https://github.com/shubh73",
64 | },
65 | {
66 | name: "instagram",
67 | url: "https://www.instagram.com/shubhii73/",
68 | },
69 | {
70 | name: "twitter",
71 | url: "https://x.com/shubhporwal24",
72 | },
73 | ];
74 |
75 | export const SKILLS = {
76 | languagesAndTools: [
77 | "html",
78 | "css",
79 | "javascript",
80 | "typescript",
81 | "sass",
82 | "nodejs",
83 | "webpack",
84 | "vite",
85 | "firebase",
86 | "figma",
87 | "tanstack-query",
88 | ],
89 | librariesAndFrameworks: [
90 | "react",
91 | "redux",
92 | "nextjs",
93 | "tailwindcss",
94 | "styledcomponents",
95 | "antdesign",
96 | "chakra-ui",
97 | ],
98 | databases: ["mysql", "mongodb"],
99 | other: ["git", "sanity-io"],
100 | };
101 |
102 | export const PROJECTS = [
103 | {
104 | name: "Airbnb",
105 | image: "/projects/airbnb.webp",
106 | blurImage: "/projects/blur/airbnb-blur.webp",
107 | description: "Airbnb UI clone using NextJS + Tailwind CSS 🛏️",
108 | gradient: ["#F14658", "#DC2537"],
109 | url: "https://shubh73-airbnb.vercel.app/",
110 | tech: ["react", "nextjs", "tailwindcss", "mapbox"],
111 | },
112 | {
113 | name: "Medium",
114 | image: "/projects/medium.webp",
115 | blurImage: "/projects/blur/medium-blur.webp",
116 | description: "Medium UI clone using NextJS + Tailwind CSS ✍🏻",
117 | gradient: ["#FFA62E", "#EA4D2C"],
118 | url: "https://shubh73-medium.vercel.app/",
119 | tech: ["typescript", "react", "nextjs", "tailwindcss", "sanity.io"],
120 | },
121 | {
122 | name: "Inshorts",
123 | image: "/projects/inshorts.webp",
124 | blurImage: "/projects/blur/airbnb-blur.webp",
125 | description:
126 | "Conversational Voice Controlled React News Application using Alan AI 🎙",
127 | gradient: ["#000066", "#6699FF"],
128 | url: "https://shubh73-inshorts.netlify.app/",
129 | tech: ["react", "chakra-ui", "alan"],
130 | },
131 | {
132 | name: "Tesla",
133 | image: "/projects/tesla.webp",
134 | blurImage: "/projects/blur/tesla-blur.webp",
135 | description: "A Tesla React Native App 🏎️",
136 | gradient: ["#142D46", "#2E4964"],
137 | url: "https://github.com/shubh73/tesla",
138 | tech: ["react"],
139 | },
140 | ];
141 |
142 | // export const WORK = [
143 | // {
144 | // id: 1,
145 | // company: "Dukaan",
146 | // title: "Frontend Developer",
147 | // location: "Bangalore, Karnataka",
148 | // range: "December - Current",
149 | // responsibilities: [
150 | // "Led creation of a captivating cross-platform e-commerce solution.",
151 | // "Enhanced UX with gamification and personalized push notifications ensuring an ever-improving shopping journey.",
152 | // "The app boasts a DAU base of 32k and an extensive MAU count of 180k.",
153 | // ],
154 | // url: "https://mydukaan.io/",
155 | // video: "/work/dukaan.mp4",
156 | // },
157 | // {
158 | // id: 2,
159 | // company: "Aviate",
160 | // title: "Frontend Developer Intern",
161 | // location: "Goa",
162 | // range: "May - October 2022",
163 | // responsibilities: [
164 | // "Built their flagship product Q-Rate, a voice-based applicant screening platform.",
165 | // "Developed pixel-perfect responsive web applications achieving daily traffic of 1000-2000 users.",
166 | // "Successfully rolled out an error-logging and bug reporting system that cut user-reported bugs by 30%.",
167 | // ],
168 | // url: "https://www.aviate.jobs/",
169 | // video: "/work/aviate.mp4",
170 | // },
171 | // {
172 | // id: 3,
173 | // company: "Spacenos",
174 | // title: "Web Developer Intern",
175 | // location: "Bangalore, Karnataka",
176 | // range: "September - December 2021",
177 | // responsibilities: [
178 | // "Led the Full Stack revamp on the Admin Portal.",
179 | // "Developed app integration with REST APIs, Google Maps, User Auth, Stripe and other libraries.",
180 | // "Implemented CRUD features for all the services and providers.",
181 | // ],
182 | // url: "https://spacenos.com/",
183 | // video: "/work/spacenos.mp4",
184 | // },
185 | // ];
186 |
187 | export const WORK_CONTENTS = {
188 | DUKAAN: [
189 | {
190 | title: "Dukaan",
191 | description:
192 | "Dukaan is a platform that enables businesses to launch their online stores at ease.",
193 | content: (
194 |
195 | Revolutionizing commerce, one click at a time
196 |
197 | ),
198 | },
199 | {
200 | title: "Transformation",
201 | description:
202 | "Since 2023, the Dukaan Seller Dashboard struggled with technical issues and a broken user experience due to accumulated technical debt. Leading a team of two junior developers, we migrated the dashboard from CSR to SSR, redesigned the UI, and overhauled the codebase in the process. This resolved the technical debt and vastly improved the user experience, making it Dukaan's largest and most impactful migration.",
203 | content: (
204 |
205 | Senior Frontend Engineer
206 |
207 | ),
208 | },
209 | {
210 | title: "Evolution",
211 | description:
212 | "Recognizing the need for improved performance and user engagement, I spearheaded the migration of the Dukaan App from native to React-Native for iOS and Android platforms. This strategic move led to a X% enhancement in app performance and a Y% boost in user engagement, representing a significant milestone in the app's evolution.",
213 | content: (
214 |
215 | Frontend Engineer
216 |
217 | ),
218 | },
219 | {
220 | title: "Optimization",
221 | description:
222 | "Leveraging user feedback and analytics, I improved the seller web dashboard design, reducing bounce rates. Simultaneously, I overhauled the build process, slashing bundle size and boosting overall performance.",
223 | content: (
224 |
225 | Frontend Engineer Intern
226 |
227 | ),
228 | },
229 | ],
230 | AVIATE: [
231 | {
232 | title: "Aviate",
233 | description:
234 | "Aviate is a preparation and mentorship platform for job-seekers that are seeking non-technical roles across top companies",
235 | content: (
236 |
237 | Finding the right job isn't fate, it's navigation
238 |
239 | ),
240 | },
241 | {
242 | title: "Innovation",
243 | description:
244 | "I spearheaded the development of Q-Rate, their flagship product, a voice-based applicant screening platform. Moreover, I took initiatives to enhance user engagement and drive substantial increases in daily traffic. Additionally, I implemented an error-logging and bug reporting system, significantly diminishing user-reported bugs.",
245 | content: (
246 |
247 | Frontend Developer Intern
248 |
249 | ),
250 | },
251 | ],
252 | SPACENOS: [
253 | {
254 | title: "Spacenos",
255 | description:
256 | "A dynamic startup dedicated to creating innovative products that make the world a better place.",
257 | content: (
258 |
259 | We build apps that solve problems for the next billion people
260 |
261 | ),
262 | },
263 | {
264 | title: "Trailblazing",
265 | description:
266 | "I led the comprehensive overhaul of the Admin Portal, implementing CRUD features for all services and providers. Additionally, I architected a feature enabling precise customer location tracking and delivering insightful usage statistics. Through optimized and compressed file serving, I catalyzed a remarkable Yx increase in page speed, resulting in a X% boost in customer retention.",
267 | content: (
268 |
269 | Web Developer Intern
270 |
271 | ),
272 | },
273 | ],
274 | };
275 |
276 | export const GTAG = "G-5HCTL2TJ5W";
277 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/components/*": ["components/*"]
6 | }
7 | },
8 | "exclude": ["node_modules"]
9 | }
10 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | const nextConfig = {
2 | webpack: (config) => {
3 | config.module.rules.push({
4 | test: /\.(mp3|wav)$/i,
5 | use: {
6 | loader: "url-loader",
7 | },
8 | });
9 |
10 | return config;
11 | },
12 | };
13 |
14 | export default nextConfig;
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "next dev",
5 | "build": "next build",
6 | "start": "next start",
7 | "lint": "next lint"
8 | },
9 | "dependencies": {
10 | "@emailjs/browser": "^4.3.3",
11 | "@next/third-parties": "^14.2.13",
12 | "bad-words": "^3.0.4",
13 | "clsx": "^2.1.1",
14 | "framer-motion": "^11.1.9",
15 | "gsap": "^3.12.5",
16 | "howler": "^2.2.4",
17 | "lottie-web": "^5.12.2",
18 | "next": "^14.2.13",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "react-hot-toast": "^2.4.1",
22 | "react-reveal": "^1.2.2",
23 | "sass": "^1.77.1",
24 | "tailwind-merge": "^2.3.0",
25 | "typed.js": "^2.1.0",
26 | "url-loader": "^4.1.1",
27 | "vanilla-tilt": "^1.8.1"
28 | },
29 | "devDependencies": {
30 | "autoprefixer": "^10.4.15",
31 | "eslint": "8.1.0",
32 | "eslint-config-next": "^14.2.13",
33 | "postcss": "^8.4.28",
34 | "tailwindcss": "^3.3.3"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import { calibre, jetbrains_mono } from "public/fonts";
2 | import { GoogleAnalytics } from "@next/third-parties/google";
3 | import Meta from "@/components/Meta/Meta";
4 | import "../styles/globals.scss";
5 | import { GTAG } from "constants";
6 |
7 | const App = ({ Component, pageProps }) => {
8 | return (
9 | <>
10 |
11 |
14 |
15 |
16 |
17 | >
18 | );
19 | };
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from "next/document";
2 |
3 | const Document = () => {
4 | return (
5 |
6 |
8 |
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default Document;
16 |
--------------------------------------------------------------------------------
/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function helloAPI(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import gsap from "gsap";
3 | import ScrollTrigger from "gsap/dist/ScrollTrigger";
4 | import Loader from "@/components/Loader/Loader";
5 | import Header from "@/components/Header/Header";
6 | import Menu from "@/components/Header/Menu/Menu";
7 | import ProgressIndicator from "@/components/ProgressIndicator/ProgressIndicator";
8 | import Cursor from "@/components/Cursor/Cursor";
9 | import Hero from "@/components/Hero/Hero";
10 | import About1 from "@/components/About/About1";
11 | import Skills from "@/components/Skills/Skills";
12 | import About2 from "@/components/About/About2";
13 | import Projects from "@/components/Projects/Projects";
14 | import Work from "@/components/Work/Work";
15 | import Collaboration from "@/components/Collaboration/Collaboration";
16 | import Contact from "@/components/Contact/Contact";
17 | import Footer from "@/components/Footer/Footer";
18 | import { displayFancyLogs } from "utils/log";
19 |
20 | gsap.registerPlugin(ScrollTrigger);
21 | gsap.config({ nullTargetWarn: false });
22 |
23 | export default function Home() {
24 | const [isLoading, setIsLoading] = useState(true);
25 | const [isDesktop, setIsDesktop] = useState(true);
26 | const [clientHeight, setClientHeight] = useState(0);
27 | const [clientWidth, setClientWidth] = useState(0);
28 |
29 | useEffect(() => {
30 | setTimeout(() => {
31 | setIsLoading(false);
32 | }, 2600);
33 |
34 | displayFancyLogs();
35 | }, []);
36 |
37 | useEffect(() => {
38 | const { innerWidth, innerHeight, orientation, history } = window;
39 |
40 | const result =
41 | typeof orientation === "undefined" &&
42 | navigator.userAgent.indexOf("IEMobile") === -1;
43 | history.scrollRestoration = "manual";
44 |
45 | setIsDesktop(result);
46 | setClientHeight(innerHeight);
47 | setClientWidth(innerWidth);
48 | }, [isDesktop]);
49 |
50 | return (
51 | <>
52 | {isLoading ? (
53 |
54 | ) : (
55 | <>
56 |
59 |
60 |
61 |
62 |
66 | DEV
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | >
80 | )}
81 | >
82 | );
83 | }
84 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/favicons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/favicons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/favicons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/favicons/favicon.ico
--------------------------------------------------------------------------------
/public/favicons/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/favicons/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/favicons/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-Black.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-BlackItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-BlackItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-Bold.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-BoldItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-Light.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-LightItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-Medium.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-MediumItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-Regular.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-RegularItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-RegularItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Semibold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-Semibold.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-SemiboldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-SemiboldItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-Thin.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-ThinItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/fonts/Calibre/Calibre-ThinItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/_Calibre.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Calibre';
3 | src: local('Calibre Light'), local('Calibre-Light'),
4 | url('Calibre-Light.woff2') format('woff2');
5 | font-weight: 300;
6 | font-style: normal;
7 | font-display: swap;
8 | }
9 |
10 | @font-face {
11 | font-family: 'Calibre';
12 | src: local('Calibre Thin'), local('Calibre-Thin'),
13 | url('Calibre-Thin.woff2') format('woff2');
14 | font-weight: 100;
15 | font-style: normal;
16 | font-display: swap;
17 | }
18 |
19 | @font-face {
20 | font-family: 'Calibre';
21 | src: local('Calibre Light Italic'), local('Calibre-LightItalic'),
22 | url('Calibre-LightItalic.woff2') format('woff2');
23 | font-weight: 300;
24 | font-style: italic;
25 | font-display: swap;
26 | }
27 |
28 | @font-face {
29 | font-family: 'Calibre';
30 | src: local('Calibre Medium Italic'), local('Calibre-MediumItalic'),
31 | url('Calibre-MediumItalic.woff2') format('woff2');
32 | font-weight: 500;
33 | font-style: italic;
34 | font-display: swap;
35 | }
36 |
37 | @font-face {
38 | font-family: 'Calibre';
39 | src: local('Calibre Black Italic'), local('Calibre-BlackItalic'),
40 | url('Calibre-BlackItalic.woff2') format('woff2');
41 | font-weight: 900;
42 | font-style: italic;
43 | font-display: swap;
44 | }
45 |
46 | @font-face {
47 | font-family: 'Calibre';
48 | src: local('Calibre Semibold'), local('Calibre-Semibold'),
49 | url('Calibre-Semibold.woff2') format('woff2');
50 | font-weight: 600;
51 | font-style: normal;
52 | font-display: swap;
53 | }
54 |
55 | @font-face {
56 | font-family: 'Calibre';
57 | src: local('Calibre Black'), local('Calibre-Black'),
58 | url('Calibre-Black.woff2') format('woff2');
59 | font-weight: 900;
60 | font-style: normal;
61 | font-display: swap;
62 | }
63 |
64 | @font-face {
65 | font-family: 'Calibre';
66 | src: local('Calibre Medium'), local('Calibre-Medium'),
67 | url('Calibre-Medium.woff2') format('woff2');
68 | font-weight: 500;
69 | font-style: normal;
70 | font-display: swap;
71 | }
72 |
73 | @font-face {
74 | font-family: 'Calibre';
75 | src: local('Calibre Regular Italic'), local('Calibre-RegularItalic'),
76 | url('Calibre-RegularItalic.woff2') format('woff2');
77 | font-weight: normal;
78 | font-style: italic;
79 | font-display: swap;
80 | }
81 |
82 | @font-face {
83 | font-family: 'Calibre';
84 | src: local('Calibre Bold Italic'), local('Calibre-BoldItalic'),
85 | url('Calibre-BoldItalic.woff2') format('woff2');
86 | font-weight: bold;
87 | font-style: italic;
88 | font-display: swap;
89 | }
90 |
91 | @font-face {
92 | font-family: 'Calibre';
93 | src: local('Calibre Bold'), local('Calibre-Bold'),
94 | url('Calibre-Bold.woff2') format('woff2');
95 | font-weight: bold;
96 | font-style: normal;
97 | font-display: swap;
98 | }
99 |
100 | @font-face {
101 | font-family: 'Calibre';
102 | src: local('Calibre'), local('Calibre-Regular'),
103 | url('Calibre-Regular.woff2') format('woff2');
104 | font-weight: normal;
105 | font-style: normal;
106 | font-display: swap;
107 | }
108 |
109 | @font-face {
110 | font-family: 'Calibre';
111 | src: local('Calibre Semibold Italic'), local('Calibre-SemiboldItalic'),
112 | url('Calibre-SemiboldItalic.woff2') format('woff2');
113 | font-weight: 600;
114 | font-style: italic;
115 | font-display: swap;
116 | }
117 |
118 | @font-face {
119 | font-family: 'Calibre';
120 | src: local('Calibre Thin Italic'), local('Calibre-ThinItalic'),
121 | url('Calibre-ThinItalic.woff2') format('woff2');
122 | font-weight: 100;
123 | font-style: italic;
124 | font-display: swap;
125 | }
126 |
127 |
--------------------------------------------------------------------------------
/public/fonts/index.js:
--------------------------------------------------------------------------------
1 | import localFont from "next/font/local";
2 | import { JetBrains_Mono } from "next/font/google";
3 |
4 | export const calibre = localFont({
5 | src: [
6 | {
7 | path: "./Calibre/Calibre-Thin.woff2",
8 | weight: "100",
9 | style: "normal",
10 | },
11 | {
12 | path: "./Calibre/Calibre-ThinItalic.woff2",
13 | weight: "100",
14 | style: "italic",
15 | },
16 | {
17 | path: "./Calibre/Calibre-Light.woff2",
18 | weight: "300",
19 | style: "normal",
20 | },
21 | {
22 | path: "./Calibre/Calibre-LightItalic.woff2",
23 | weight: "300",
24 | style: "italic",
25 | },
26 | {
27 | path: "./Calibre/Calibre-Regular.woff2",
28 | weight: "400",
29 | style: "normal",
30 | },
31 | {
32 | path: "./Calibre/Calibre-RegularItalic.woff2",
33 | weight: "400",
34 | style: "italic",
35 | },
36 | {
37 | path: "./Calibre/Calibre-Medium.woff2",
38 | weight: "500",
39 | style: "normal",
40 | },
41 | {
42 | path: "./Calibre/Calibre-MediumItalic.woff2",
43 | weight: "500",
44 | style: "italic",
45 | },
46 | {
47 | path: "./Calibre/Calibre-Semibold.woff2",
48 | weight: "600",
49 | style: "normal",
50 | },
51 | {
52 | path: "./Calibre/Calibre-SemiboldItalic.woff2",
53 | weight: "600",
54 | style: "italic",
55 | },
56 | {
57 | path: "./Calibre/Calibre-Bold.woff2",
58 | weight: "700",
59 | style: "normal",
60 | },
61 | {
62 | path: "./Calibre/Calibre-BoldItalic.woff2",
63 | weight: "700",
64 | style: "italic",
65 | },
66 | {
67 | path: "./Calibre/Calibre-Black.woff2",
68 | weight: "900",
69 | style: "normal",
70 | },
71 | {
72 | path: "./Calibre/Calibre-BlackItalic.woff2",
73 | weight: "900",
74 | style: "italic",
75 | },
76 | ],
77 | display: "swap",
78 | variable: "--font-calibre",
79 | });
80 |
81 | export const jetbrains_mono = JetBrains_Mono({
82 | subsets: ["latin"],
83 | variable: "--font-jetbrains-mono",
84 | });
85 |
--------------------------------------------------------------------------------
/public/footer-curve.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/footer/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/footer/background.png
--------------------------------------------------------------------------------
/public/footer/cyclist.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/footer/cyclist.gif
--------------------------------------------------------------------------------
/public/footer/volkswagen.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/footer/volkswagen.gif
--------------------------------------------------------------------------------
/public/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/icon-192x192.png
--------------------------------------------------------------------------------
/public/icon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/icon-256x256.png
--------------------------------------------------------------------------------
/public/icon-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/icon-384x384.png
--------------------------------------------------------------------------------
/public/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/icon-512x512.png
--------------------------------------------------------------------------------
/public/left-pattern.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Portfolio | Shubh Porwal",
3 | "short_name": "Shubh Porwal",
4 | "description": "Shubh Porwal's Portfolio Website.",
5 | "icons": [
6 | {
7 | "src": "/icon-192x192.png",
8 | "sizes": "192x192",
9 | "type": "image/png",
10 | "purpose": "any maskable"
11 | },
12 | {
13 | "src": "/icon-256x256.png",
14 | "sizes": "256x256",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "/icon-384x384.png",
19 | "sizes": "384x384",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "/icon-512x512.png",
24 | "sizes": "512x512",
25 | "type": "image/png"
26 | }
27 | ],
28 | "theme_color": "#7000FF",
29 | "background_color": "#000000",
30 | "start_url": "/",
31 | "display": "standalone",
32 | "orientation": "portrait"
33 | }
34 |
--------------------------------------------------------------------------------
/public/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/preview.png
--------------------------------------------------------------------------------
/public/projects/airbnb.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/projects/airbnb.webp
--------------------------------------------------------------------------------
/public/projects/blur/airbnb-blur.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/projects/blur/airbnb-blur.webp
--------------------------------------------------------------------------------
/public/projects/blur/inshorts-blur.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/projects/blur/inshorts-blur.webp
--------------------------------------------------------------------------------
/public/projects/blur/medium-blur.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/projects/blur/medium-blur.webp
--------------------------------------------------------------------------------
/public/projects/blur/tesla-blur.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/projects/blur/tesla-blur.webp
--------------------------------------------------------------------------------
/public/projects/inshorts.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/projects/inshorts.webp
--------------------------------------------------------------------------------
/public/projects/medium.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/projects/medium.webp
--------------------------------------------------------------------------------
/public/projects/tech/alan.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/chakra-ui.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/firebase.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/mapbox.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/metamask.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/mongodb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/nextjs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/redux.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/sanity.io.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/sass.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/styledcomponents.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/tailwindcss.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/typescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tesla.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/projects/tesla.webp
--------------------------------------------------------------------------------
/public/right-pattern.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/public/skills/antdesign.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/chakra-ui.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/css.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/figma.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/firebase.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/git.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/html.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/javascript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/mongodb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/mysql.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/nextjs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/nodejs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/redux.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/sanity-io.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/sass.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/styledcomponents.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/tailwindcss.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/tanstack-query.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | emblem-light
5 | Created with Sketch.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/public/skills/typescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/vite.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/public/skills/webpack.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/sounds/charge-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/charge-up.wav
--------------------------------------------------------------------------------
/public/sounds/disable-sound.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/disable-sound.mp3
--------------------------------------------------------------------------------
/public/sounds/enable-sound.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/enable-sound.mp3
--------------------------------------------------------------------------------
/public/sounds/glug-a.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/glug-a.mp3
--------------------------------------------------------------------------------
/public/sounds/key1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/key1.wav
--------------------------------------------------------------------------------
/public/sounds/key2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/key2.wav
--------------------------------------------------------------------------------
/public/sounds/key3.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/key3.wav
--------------------------------------------------------------------------------
/public/sounds/key4.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/key4.wav
--------------------------------------------------------------------------------
/public/sounds/key5.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/key5.wav
--------------------------------------------------------------------------------
/public/sounds/key6.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/key6.wav
--------------------------------------------------------------------------------
/public/sounds/mouse-click.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/mouse-click.mp3
--------------------------------------------------------------------------------
/public/sounds/multi-pop.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/multi-pop.mp3
--------------------------------------------------------------------------------
/public/sounds/pop-down.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/pop-down.mp3
--------------------------------------------------------------------------------
/public/sounds/song.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/0a8248f384bbb8c13f1ba68e6023597cf85f623b/public/sounds/song.mp3
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const defaultTheme = require("tailwindcss/defaultTheme");
2 |
3 | module.exports = {
4 | content: ["./pages/**/*.{js, jsx}", "./components/**/*.{js, jsx}"],
5 | theme: {
6 | screens: {
7 | xs: "475px",
8 | ...defaultTheme.screens,
9 | },
10 | colors: {
11 | transparent: "transparent",
12 | current: "currentColor",
13 | white: "#ffffff",
14 | black: "#000000",
15 | purple: "#8b31ff",
16 | red: "#cf0000",
17 | green: "#00ac56",
18 | indigo: {
19 | light: "#9f55ff",
20 | dark: "#7000ff",
21 | },
22 | gray: {
23 | light: {
24 | 1: "#f0f0f0",
25 | 2: "#dbdbdb",
26 | 3: "#aaaaaa",
27 | 4: "#8a8a8a",
28 | },
29 | dark: {
30 | 1: "#323133",
31 | 2: "#242225",
32 | 3: "#1e1b20",
33 | 4: "#1a171e",
34 | 5: "#120e16",
35 | },
36 | },
37 | },
38 | fontFamily: {
39 | sans: ["var(--font-calibre)"],
40 | mono: ["var(--font-jetbrains-mono)"],
41 | },
42 | extend: {
43 | animation: {
44 | meteor: "meteor 5s linear infinite",
45 | },
46 | keyframes: {
47 | meteor: {
48 | "0%": {
49 | transform: "rotate(215deg) translateX(0)",
50 | opacity: 1,
51 | },
52 | "70%": {
53 | opacity: 1,
54 | },
55 | "100%": {
56 | transform: "rotate(215deg) translateX(-500px)",
57 | opacity: 0,
58 | },
59 | },
60 | },
61 | },
62 | },
63 | plugins: [],
64 | };
65 |
--------------------------------------------------------------------------------
/utils/cn.js:
--------------------------------------------------------------------------------
1 | import { clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export const cn = (...inputs) => twMerge(clsx(inputs));
5 |
--------------------------------------------------------------------------------
/utils/log.js:
--------------------------------------------------------------------------------
1 | export const displayFancyLogs = () => {
2 | console.log(
3 | "%c ____ _ _ _ ____ _\n / ___|| |__ _ _| |__ | |__ | _ \\ ___ _ ____ ____ _| |\n \\___ \\| '_ \\| | | | '_ \\| '_ \\ | |_) / _ \\| '__\\ \\ /\\ / / _` | |\n ___) | | | | |_| | |_) | | | | | __/ (_) | | \\ V V / (_| | |\n |____/|_| |_|\\__,_|_.__/|_| |_| |_| \\___/|_| \\_/\\_/ \\__,_|_|\n",
4 | "color: #6b17e8;"
5 | );
6 |
7 | console.log(
8 | "%c Hope you like what you see :)",
9 | "color: #6b17e8; padding: 6px;"
10 | );
11 |
12 | // Easter egg hint
13 | console.log(
14 | "%c 💡 Psst! There's a secret hiding in plain sight. Follow your heart, it might lead to something... interesting.",
15 | "color: #6b17e8; font-style: italic; padding: 6px;"
16 | );
17 | };
18 |
--------------------------------------------------------------------------------