├── .eslintrc.json
├── .vscode
└── settings.json
├── public
├── preview.png
├── icon-192x192.png
├── icon-256x256.png
├── icon-384x384.png
├── icon-512x512.png
├── sounds
│ ├── key1.wav
│ ├── key2.wav
│ ├── key3.wav
│ ├── key4.wav
│ ├── key5.wav
│ ├── key6.wav
│ ├── song.mp3
│ ├── glug-a.mp3
│ ├── charge-up.wav
│ ├── multi-pop.mp3
│ ├── pop-down.mp3
│ ├── enable-sound.mp3
│ ├── mouse-click.mp3
│ └── disable-sound.mp3
├── footer
│ ├── cyclist.gif
│ ├── background.png
│ └── volkswagen.gif
├── projects
│ ├── bot9.webp
│ ├── airbnb.webp
│ ├── buywow.webp
│ ├── dukaan.webp
│ ├── medium.webp
│ ├── round1.webp
│ ├── tesla.webp
│ ├── inshorts.webp
│ ├── nextboss.webp
│ ├── ready-ai.webp
│ ├── react-native-directory.webp
│ └── tech
│ │ ├── expo.svg
│ │ ├── chakra-ui.svg
│ │ ├── raycast.svg
│ │ ├── sanity.svg
│ │ ├── tailwindcss.svg
│ │ ├── javascript.svg
│ │ ├── nextjs.svg
│ │ ├── firebase.svg
│ │ ├── typescript.svg
│ │ ├── redux.svg
│ │ ├── mongodb.svg
│ │ ├── react-query.svg
│ │ ├── sass.svg
│ │ ├── alan.svg
│ │ ├── mapbox.svg
│ │ ├── styledcomponents.svg
│ │ ├── metamask.svg
│ │ └── react.svg
├── favicons
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ └── site.webmanifest
├── fonts
│ ├── Calibre
│ │ ├── Calibre-Black.woff2
│ │ ├── Calibre-Bold.woff2
│ │ ├── Calibre-Light.woff2
│ │ ├── Calibre-Thin.woff2
│ │ ├── Calibre-Medium.woff2
│ │ ├── Calibre-Regular.woff2
│ │ ├── Calibre-Semibold.woff2
│ │ ├── Calibre-BoldItalic.woff2
│ │ ├── Calibre-ThinItalic.woff2
│ │ ├── Calibre-BlackItalic.woff2
│ │ ├── Calibre-LightItalic.woff2
│ │ ├── Calibre-MediumItalic.woff2
│ │ ├── Calibre-RegularItalic.woff2
│ │ ├── Calibre-SemiboldItalic.woff2
│ │ └── _Calibre.scss
│ └── index.js
├── skills
│ ├── expo.svg
│ ├── html.svg
│ ├── webpack.svg
│ ├── figma.svg
│ ├── chakra-ui.svg
│ ├── css.svg
│ ├── sanity.svg
│ ├── cursor.svg
│ ├── tailwindcss.svg
│ ├── git.svg
│ ├── javascript.svg
│ ├── nextjs.svg
│ ├── firebase.svg
│ ├── sanity-io.svg
│ ├── vite.svg
│ ├── sass.svg
│ ├── antdesign.svg
│ ├── typescript.svg
│ ├── redux.svg
│ ├── mongodb.svg
│ ├── nodejs.svg
│ ├── react-query.svg
│ ├── mysql.svg
│ ├── styledcomponents.svg
│ ├── react.svg
│ └── tanstack.svg
├── manifest.json
├── logo.svg
├── left-pattern.svg
├── right-pattern.svg
└── footer-curve.svg
├── postcss.config.js
├── utils
├── cn.js
└── log.js
├── jsconfig.json
├── pages
├── api
│ └── hello.js
├── _document.js
├── _app.js
└── index.js
├── next.config.mjs
├── components
├── Icons
│ ├── index.js
│ ├── mail.js
│ ├── twitter.js
│ ├── icon.js
│ ├── linkedin.js
│ ├── external.js
│ ├── instagram.js
│ └── github.js
├── Profiles
│ ├── Profiles.module.scss
│ └── Profiles.js
├── Contact
│ ├── mailer.js
│ └── Contact.module.scss
├── Footer
│ ├── FooterBg
│ │ ├── FooterBg.js
│ │ └── FooterBg.module.scss
│ ├── Meteors
│ │ └── Meteors.js
│ └── Footer.js
├── Loader
│ ├── Loader.js
│ └── Loader.module.scss
├── ProgressIndicator
│ └── ProgressIndicator.js
├── Projects
│ ├── images.js
│ ├── ProjectTile
│ │ ├── ProjectTile.module.scss
│ │ └── ProjectTile.js
│ └── Projects.js
├── Header
│ ├── SoundBar
│ │ └── SoundBar.js
│ ├── Menu
│ │ └── Menu.js
│ └── Header.js
├── Hero
│ ├── Hero.module.scss
│ └── Hero.js
├── Work
│ ├── DotPattern
│ │ └── DotPattern.js
│ ├── Work.js
│ ├── Tabs
│ │ └── Tabs.js
│ └── StickyScroll
│ │ └── StickyScroll.js
├── Button
│ ├── Button.js
│ └── Button.module.scss
├── About
│ ├── About2.js
│ └── About1.js
├── Meta
│ └── Meta.js
├── Cursor
│ └── Cursor.js
├── Collaboration
│ └── Collaboration.js
└── Skills
│ └── Skills.js
├── .gitignore
├── LICENSE.md
├── package.json
├── tailwind.config.js
├── README.md
└── constants.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "svg.preview.background": "editor"
3 | }
4 |
--------------------------------------------------------------------------------
/public/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/preview.png
--------------------------------------------------------------------------------
/public/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/icon-192x192.png
--------------------------------------------------------------------------------
/public/icon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/icon-256x256.png
--------------------------------------------------------------------------------
/public/icon-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/icon-384x384.png
--------------------------------------------------------------------------------
/public/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/icon-512x512.png
--------------------------------------------------------------------------------
/public/sounds/key1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/key1.wav
--------------------------------------------------------------------------------
/public/sounds/key2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/key2.wav
--------------------------------------------------------------------------------
/public/sounds/key3.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/key3.wav
--------------------------------------------------------------------------------
/public/sounds/key4.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/key4.wav
--------------------------------------------------------------------------------
/public/sounds/key5.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/key5.wav
--------------------------------------------------------------------------------
/public/sounds/key6.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/key6.wav
--------------------------------------------------------------------------------
/public/sounds/song.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/song.mp3
--------------------------------------------------------------------------------
/public/footer/cyclist.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/footer/cyclist.gif
--------------------------------------------------------------------------------
/public/projects/bot9.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/bot9.webp
--------------------------------------------------------------------------------
/public/sounds/glug-a.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/glug-a.mp3
--------------------------------------------------------------------------------
/public/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/favicons/favicon.ico
--------------------------------------------------------------------------------
/public/footer/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/footer/background.png
--------------------------------------------------------------------------------
/public/footer/volkswagen.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/footer/volkswagen.gif
--------------------------------------------------------------------------------
/public/projects/airbnb.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/airbnb.webp
--------------------------------------------------------------------------------
/public/projects/buywow.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/buywow.webp
--------------------------------------------------------------------------------
/public/projects/dukaan.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/dukaan.webp
--------------------------------------------------------------------------------
/public/projects/medium.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/medium.webp
--------------------------------------------------------------------------------
/public/projects/round1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/round1.webp
--------------------------------------------------------------------------------
/public/projects/tesla.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/tesla.webp
--------------------------------------------------------------------------------
/public/sounds/charge-up.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/charge-up.wav
--------------------------------------------------------------------------------
/public/sounds/multi-pop.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/multi-pop.mp3
--------------------------------------------------------------------------------
/public/sounds/pop-down.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/pop-down.mp3
--------------------------------------------------------------------------------
/public/projects/inshorts.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/inshorts.webp
--------------------------------------------------------------------------------
/public/projects/nextboss.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/nextboss.webp
--------------------------------------------------------------------------------
/public/projects/ready-ai.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/ready-ai.webp
--------------------------------------------------------------------------------
/public/sounds/enable-sound.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/enable-sound.mp3
--------------------------------------------------------------------------------
/public/sounds/mouse-click.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/mouse-click.mp3
--------------------------------------------------------------------------------
/public/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/sounds/disable-sound.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/sounds/disable-sound.mp3
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/favicons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-Black.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-Bold.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-Light.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-Thin.woff2
--------------------------------------------------------------------------------
/public/favicons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/favicons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/favicons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-Medium.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-Regular.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-Semibold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-Semibold.woff2
--------------------------------------------------------------------------------
/public/projects/react-native-directory.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/projects/react-native-directory.webp
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-BoldItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-ThinItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-ThinItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-BlackItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-BlackItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-LightItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-MediumItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-RegularItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-RegularItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Calibre/Calibre-SemiboldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shubh73/devfolio/HEAD/public/fonts/Calibre/Calibre-SemiboldItalic.woff2
--------------------------------------------------------------------------------
/utils/cn.js:
--------------------------------------------------------------------------------
1 | import { clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export const cn = (...inputs) => twMerge(clsx(inputs));
5 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/components/*": ["components/*"]
6 | }
7 | },
8 | "exclude": ["node_modules"]
9 | }
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/skills/expo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/expo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/skills/html.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/skills/webpack.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/figma.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/chakra-ui.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/chakra-ui.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/components/Icons/mail.js:
--------------------------------------------------------------------------------
1 | const IconMail = () => (
2 |
14 |
15 |
16 |
17 | );
18 |
19 | export default IconMail;
20 |
--------------------------------------------------------------------------------
/public/projects/tech/raycast.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/Icons/twitter.js:
--------------------------------------------------------------------------------
1 | const IconTwitter = () => (
2 |
14 |
15 |
16 | );
17 |
18 | export default IconTwitter;
19 |
--------------------------------------------------------------------------------
/public/skills/css.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/linkedin.js:
--------------------------------------------------------------------------------
1 | const IconLinkedin = () => (
2 |
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export default IconLinkedin;
21 |
--------------------------------------------------------------------------------
/components/Icons/external.js:
--------------------------------------------------------------------------------
1 | const IconExternal = () => (
2 |
13 | External Link
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export default IconExternal;
21 |
--------------------------------------------------------------------------------
/components/Icons/instagram.js:
--------------------------------------------------------------------------------
1 | const IconInstagram = () => (
2 |
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export default IconInstagram;
21 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/skills/sanity.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/sanity.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/Icons/github.js:
--------------------------------------------------------------------------------
1 | const IconGithub = () => (
2 |
14 |
15 |
16 | );
17 |
18 | export default IconGithub;
19 |
--------------------------------------------------------------------------------
/public/skills/cursor.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/tailwindcss.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/tailwindcss.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/skills/git.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/images.js:
--------------------------------------------------------------------------------
1 | import nextboss from "../../public/projects/nextboss.webp";
2 | import readyAi from "../../public/projects/ready-ai.webp";
3 | import round1 from "../../public/projects/round1.webp";
4 | import reactNativeDirectory from "../../public/projects/react-native-directory.webp";
5 | import buywow from "../../public/projects/buywow.webp";
6 | import bot9 from "../../public/projects/bot9.webp";
7 | import dukaan from "../../public/projects/dukaan.webp";
8 | import airbnb from "../../public/projects/airbnb.webp";
9 | import medium from "../../public/projects/medium.webp";
10 | import inshorts from "../../public/projects/inshorts.webp";
11 | import tesla from "../../public/projects/tesla.webp";
12 |
13 | export const PROJECT_IMAGES = {
14 | nextboss,
15 | "ready-ai": readyAi,
16 | round1,
17 | "react-native-directory": reactNativeDirectory,
18 | buywow,
19 | bot9,
20 | dukaan,
21 | airbnb,
22 | medium,
23 | inshorts,
24 | tesla,
25 | };
26 |
--------------------------------------------------------------------------------
/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/Projects/ProjectTile/ProjectTile.module.scss:
--------------------------------------------------------------------------------
1 | .projectTile {
2 | height: 22rem;
3 | width: 38rem;
4 | transform-style: preserve-3d;
5 | transform: perspective(1000px);
6 |
7 | @media (min-width: 768px) {
8 | height: 26rem;
9 | }
10 |
11 | img {
12 | object-fit: cover;
13 | }
14 | }
15 |
16 | .projectImage {
17 | @apply absolute top-0 rounded-xl shadow-xl;
18 |
19 | width: 17rem !important;
20 | max-width: 17rem !important;
21 | transform: rotate(-22.5deg);
22 | height: unset !important;
23 | min-width: unset !important;
24 | max-height: unset !important;
25 | object-fit: contain !important;
26 | left: unset !important;
27 | right: 2rem !important;
28 | bottom: unset !important;
29 | clip-path: inset(0);
30 | z-index: 0;
31 |
32 | @media (max-width: 480px) {
33 | width: clamp(12rem, 40vw, 16rem) !important;
34 | right: clamp(-2rem, 2vw, 1.5rem) !important;
35 | }
36 | }
37 |
38 | .techIcons {
39 | transform: rotate(-22.5deg) translateZ(3rem);
40 | }
41 |
--------------------------------------------------------------------------------
/public/skills/javascript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/javascript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/skills/nextjs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/projects/tech/nextjs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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.33",
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 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
37 | }
38 |
--------------------------------------------------------------------------------
/public/skills/firebase.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/firebase.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/sanity-io.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/left-pattern.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/public/right-pattern.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/public/skills/sass.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/skills/antdesign.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | #### `bun install`
24 |
25 | #### `bun 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 |
--------------------------------------------------------------------------------
/public/skills/typescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/typescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/skills/redux.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/redux.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/skills/mongodb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/mongodb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/public/skills/nodejs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/skills/react-query.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/react-query.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/Work/Work.js:
--------------------------------------------------------------------------------
1 | import { useEffect, 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: "Grapevine",
15 | value: "grapevine",
16 | content: (
17 |
21 | ),
22 | },
23 | {
24 | title: "Dukaan",
25 | value: "dukaan",
26 | content: (
27 |
31 | ),
32 | },
33 | ],
34 | [isDesktop]
35 | );
36 |
37 | useEffect(() => {
38 | const ctx = gsap.context(() => {
39 | const tl = gsap
40 | .timeline({ defaults: { ease: "none" } })
41 | .from(
42 | sectionRef.current.querySelectorAll(".staggered-reveal"),
43 | { opacity: 0, duration: 0.5, stagger: 0.5 },
44 | "<"
45 | );
46 |
47 | ScrollTrigger.create({
48 | trigger: sectionRef.current.querySelector(".work-wrapper"),
49 | start: "100px bottom",
50 | end: "center center",
51 | scrub: 0,
52 | animation: tl,
53 | });
54 | });
55 |
56 | return () => ctx.revert();
57 | }, []);
58 |
59 | return (
60 |
91 | );
92 | };
93 |
94 | export default Work;
95 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/projects/tech/sass.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/footer-curve.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/skills/mysql.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/alan.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/mapbox.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | Mapbox_Logo_08
6 |
7 |
8 |
12 |
14 |
18 |
22 |
27 |
31 |
32 |
33 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/public/skills/styledcomponents.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/styledcomponents.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/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/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 | import { PROJECT_IMAGES } from "../images";
6 |
7 | const tiltOptions = {
8 | max: 5,
9 | speed: 400,
10 | glare: true,
11 | "max-glare": 0.2,
12 | gyroscope: false,
13 | };
14 |
15 | const ProjectTile = ({ project, classes, isDesktop }) => {
16 | const projectCard = useRef(null);
17 |
18 | const { name, imageKey, description, gradient, url, tech } = project;
19 |
20 | const image = PROJECT_IMAGES[imageKey];
21 |
22 | let additionalClasses = "";
23 | if (classes) {
24 | additionalClasses = classes;
25 | }
26 |
27 | useEffect(() => {
28 | VanillaTilt.init(projectCard.current, tiltOptions);
29 | }, [projectCard]);
30 |
31 | return (
32 |
43 |
50 |
56 |
63 | {!isDesktop && (
64 |
70 | )}
71 |
75 | {name}
76 |
77 |
82 |
83 | {tech.map((el, i) => (
84 |
92 | ))}
93 |
94 |
95 |
99 | {description}
100 |
101 |
102 |
103 | );
104 | };
105 |
106 | export default ProjectTile;
107 |
--------------------------------------------------------------------------------
/public/projects/tech/metamask.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 backgroundColors = ["#000000"];
7 |
8 | const linearGradients = [
9 | "linear-gradient(to bottom right, #ef008f, #6ec3f4)",
10 | "linear-gradient(to bottom right, #6ec3f4, #7038ff)",
11 | "linear-gradient(to bottom right, #7038ff, #c9c9c9)",
12 | ];
13 |
14 | const StickyScroll = ({ contentItems }) => {
15 | const [activeCard, setActiveCard] = useState(0);
16 | const containerRef = useRef(null);
17 |
18 | const { scrollYProgress } = useScroll({
19 | container: containerRef,
20 | offset: ["start start", "end start"],
21 | });
22 |
23 | const cardLength = contentItems.length;
24 |
25 | useMotionValueEvent(scrollYProgress, "change", (latest) => {
26 | const cardsBreakpoints = contentItems.map(
27 | (_, index) => index / cardLength - 0.1
28 | );
29 |
30 | const closestBreakpointIndex = cardsBreakpoints.reduce(
31 | (acc, breakpoint, index) => {
32 | const distance = Math.abs(latest - breakpoint);
33 | if (distance < Math.abs(latest - cardsBreakpoints[acc])) {
34 | return index;
35 | }
36 | return acc;
37 | },
38 | 0
39 | );
40 | setActiveCard(closestBreakpointIndex);
41 | });
42 |
43 | return (
44 |
45 |
55 |
56 |
57 |
58 |
59 |
67 |
68 |
69 | {contentItems.map((item, index) => (
70 |
71 |
80 | {item.title}
81 |
82 |
91 | {item.description}
92 |
93 |
94 | ))}
95 |
96 |
97 |
98 |
105 | {contentItems[activeCard].content ?? null}
106 |
107 |
108 |
109 | );
110 | };
111 |
112 | export default StickyScroll;
113 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/public/skills/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/projects/tech/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/public/skills/tanstack.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 Engineer",
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 Engineer",
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",
88 | ],
89 | librariesAndFrameworks: [
90 | "expo",
91 | "react",
92 | "redux",
93 | "nextjs",
94 | "tailwindcss",
95 | "styledcomponents",
96 | "antdesign",
97 | "chakra-ui",
98 | ],
99 | databases: ["mysql", "mongodb"],
100 | other: ["git", "cursor", "sanity"],
101 | };
102 |
103 | export const PROJECTS = [
104 | {
105 | name: "NextBoss",
106 | imageKey: "nextboss",
107 | description: "Get hired through DMs not resumes 🤝",
108 | gradient: ["#FF69B4", "#FFB6C1"], // hot pink to light pink (cherry blossom inspired)
109 | // url: "https://nextboss.in",
110 | tech: ["typescript", "react", "expo", "react-query"],
111 | },
112 | {
113 | name: "ReadyAI",
114 | imageKey: "ready-ai",
115 | description: "AI that gets you interview ready 🎯",
116 | gradient: ["#F4D03F", "#58D68D"], // warm gold to fresh green
117 | url: "https://play.google.com/store/apps/details?id=com.app.readyai",
118 | tech: ["typescript", "react"],
119 | },
120 | {
121 | name: "Grapevine Round1 AI",
122 | imageKey: "round1",
123 | description: "Ace your round one in 9 minutes 💼",
124 | gradient: ["#5D4037", "#8D6E63"], // dark brown to medium brown
125 | url: "https://play.google.com/store/apps/details?id=com.app.gvine",
126 | tech: ["typescript", "react", "react-query"],
127 | },
128 | {
129 | name: "React Native Directory",
130 | imageKey: "react-native-directory",
131 | description: "Search & filter React Native libraries via Raycast ⌨️",
132 | gradient: ["#000000", "#1A1A1A"], // pure black to dark gray
133 | url: "https://www.raycast.com/shubh_porwal/react-native-directory",
134 | tech: ["typescript", "react", "expo", "raycast"],
135 | },
136 | {
137 | name: "Buywow",
138 | imageKey: "buywow",
139 | description: "Official Wow Skin Science app 🌿",
140 | gradient: ["#FFD54F", "#FFB300"], // bright golden yellow to deep orange
141 | url: "https://play.google.com/store/apps/details?id=co.tapcart.app.id_99G6QNo3nu",
142 | tech: ["typescript", "react", "react-query"],
143 | },
144 | {
145 | name: "Bot9",
146 | imageKey: "bot9",
147 | description: "Automate support with AI 🤖",
148 | gradient: ["#3F51B5", "#7986CB"], // indigo to medium blue
149 | url: "https://bot9.ai/",
150 | tech: ["typescript", "react", "nextjs", "tailwindcss"],
151 | },
152 | {
153 | name: "Dukaan",
154 | imageKey: "dukaan",
155 | description: "Shopify for India 🛍️",
156 | gradient: ["#1976D2", "#1565C0"], // material blue to deep blue
157 | url: "https://play.google.com/store/apps/details?id=com.dukaan.app",
158 | tech: ["typescript", "react", "firebase", "tailwindcss", "react-query"],
159 | },
160 | {
161 | name: "Tesla",
162 | imageKey: "tesla",
163 | description: "Built with Expo 🏎️",
164 | gradient: ["#0F172A", "#1E293B"], // midnight blue to dark slate (electric/tech feel)
165 | url: "https://github.com/shubh73/tesla",
166 | tech: ["javascript", "expo"],
167 | },
168 | {
169 | name: "Airbnb",
170 | imageKey: "airbnb",
171 | description: "Built with NextJS + Tailwind CSS 🛏️",
172 | gradient: ["#1F2937", "#6B7280"], // dark gray to medium gray
173 | url: "https://shubh73-airbnb.vercel.app/",
174 | tech: ["javascript", "react", "nextjs", "mapbox", "tailwindcss"],
175 | },
176 | {
177 | name: "Medium",
178 | imageKey: "medium",
179 | description: "Built with NextJS + Tailwind CSS ✍🏻",
180 | gradient: ["#FF9800", "#F57C00"], // warm orange to deep orange
181 | url: "https://shubh73-medium.vercel.app/",
182 | tech: ["typescript", "react", "nextjs", "tailwindcss", "sanity"],
183 | },
184 | {
185 | name: "Inshorts",
186 | imageKey: "inshorts",
187 | description: "Voice-enabled news using Alan AI 🎙",
188 | gradient: ["#6366F1", "#4F46E5"], // indigo to deep indigo
189 | url: "https://shubh73-inshorts.netlify.app/",
190 | tech: ["javascript", "react", "chakra-ui", "alan"],
191 | },
192 | ];
193 |
194 | export const WORK_CONTENTS = {
195 | GRAPEVINE: [
196 | {
197 | title: "Grapevine",
198 | description:
199 | "Grapevine is your anonymous office chat, letting coworkers speak openly, share gossip and connect without filters. Building on that same belief, Round1 AI brings it to hiring using AI-driven interviews to replace guesswork with genuine and meaningful conversations.",
200 | content: (
201 |
202 | Connect anonymously. Share freely.
203 |
204 | ),
205 | },
206 | {
207 | title: "Pioneering",
208 | description:
209 | "Hiring was broken — manual, biased, and slow. We launched Round1 AI to fix that: authentic voice interviews, built-in bias checks and real-time insights. Today, it powers thousands of interviews each week, letting teams hire smarter, faster and fairer.",
210 | content: (
211 |
212 | Senior Frontend Engineer
213 |
214 | ),
215 | },
216 | {
217 | title: "Elevate",
218 | description:
219 | "The early web app struggled with slow performance and zero retention. I rebuilt it from the ground up with a sleek UI, faster load times and SEO-optimized. Then seeded a rich content layer to spark discovery. The payoff? 6x more impressions and 10x the organic installs all without ads, just momentum.",
220 | content: (
221 |
222 | Frontend Engineer
223 |
224 | ),
225 | },
226 | ],
227 | DUKAAN: [
228 | {
229 | title: "Dukaan",
230 | description:
231 | "Dukaan is a platform that enables businesses to launch their online stores at ease.",
232 | content: (
233 |
234 | Revolutionizing commerce, one click at a time
235 |
236 | ),
237 | },
238 | {
239 | title: "Transformation",
240 | description:
241 | "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.",
242 | content: (
243 |
244 | Senior Frontend Engineer
245 |
246 | ),
247 | },
248 | {
249 | title: "Evolution",
250 | description:
251 | "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.",
252 | content: (
253 |
254 | Frontend Engineer
255 |
256 | ),
257 | },
258 | {
259 | title: "Optimization",
260 | description:
261 | "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.",
262 | content: (
263 |
264 | Frontend Engineer Intern
265 |
266 | ),
267 | },
268 | ],
269 | // AVIATE: [
270 | // {
271 | // title: "Aviate",
272 | // description:
273 | // "Aviate is a preparation and mentorship platform for job-seekers that are seeking non-technical roles across top companies",
274 | // content: (
275 | //
276 | // Finding the right job isn't fate, it's navigation
277 | //
278 | // ),
279 | // },
280 | // {
281 | // title: "Innovation",
282 | // description:
283 | // "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.",
284 | // content: (
285 | //
286 | // Frontend Developer Intern
287 | //
288 | // ),
289 | // },
290 | // ],
291 | // SPACENOS: [
292 | // {
293 | // title: "Spacenos",
294 | // description:
295 | // "A dynamic startup dedicated to creating innovative products that make the world a better place.",
296 | // content: (
297 | //
298 | // We build apps that solve problems for the next billion people
299 | //
300 | // ),
301 | // },
302 | // {
303 | // title: "Trailblazing",
304 | // description:
305 | // "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.",
306 | // content: (
307 | //
308 | // Web Developer Intern
309 | //
310 | // ),
311 | // },
312 | // ],
313 | };
314 |
315 | export const GTAG = "G-5HCTL2TJ5W";
316 |
--------------------------------------------------------------------------------