├── .dockerignore ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── Dockerfile ├── README.md ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prettier.config.js ├── public ├── favicon.ico ├── icons │ ├── aws.svg │ ├── css.svg │ ├── docker.svg │ ├── fram.svg │ ├── git.svg │ ├── github.svg │ ├── graphql.svg │ ├── html.svg │ ├── javascript.svg │ ├── mongodb.svg │ ├── mui.svg │ ├── nodejs.svg │ ├── postgresql.svg │ ├── postman.svg │ ├── prisma.svg │ ├── python.svg │ ├── react-router-color.svg │ ├── reactjs.svg │ ├── redux.svg │ ├── sass.svg │ ├── solidjs.svg │ ├── tailwindcss.svg │ ├── typescript.svg │ ├── vite.svg │ └── vscode.svg ├── images │ ├── heroProfile.png │ └── projects │ │ ├── 3DView.webp │ │ ├── covidTracker.webp │ │ ├── covidTrackerMap.webp │ │ ├── covidTrackerTable.webp │ │ ├── jsontreeDark.webp │ │ ├── jsontreeLight.webp │ │ ├── kanbanCardLight.webp │ │ ├── kanbanDark.webp │ │ ├── kanbanLight.webp │ │ ├── logos │ │ ├── covidtracker.ico │ │ ├── jsontree.ico │ │ ├── kanban.ico │ │ ├── manygames.ico │ │ └── stockpredictor.ico │ │ ├── manyGames2048.webp │ │ ├── manyGamesDark.webp │ │ ├── manyGamesLight.webp │ │ ├── manyGamesPuzzle.webp │ │ ├── manyGamesWordle.webp │ │ ├── portfolioDark.webp │ │ ├── portfolioLight.webp │ │ ├── stockPredictor.webp │ │ ├── stockPredictorCandleChart.webp │ │ ├── stockPredictorCompareChart.webp │ │ └── stockPredictorLineChart.webp └── static │ └── homepage.png ├── src ├── animation │ ├── animated-logo.tsx │ ├── fade-right.tsx │ └── fade-up.tsx ├── components │ ├── about-hero.tsx │ ├── contact-form │ │ ├── contact-button.tsx │ │ ├── contact-form-modal.tsx │ │ ├── contact-form.tsx │ │ ├── contact-mail-toast.tsx │ │ └── floating-mail-button.tsx │ ├── cursor-trail-canvas.tsx │ ├── duotone-image.tsx │ ├── experience │ │ ├── experience-showcase-list-item.tsx │ │ └── experience-showcase-list.tsx │ ├── icons.tsx │ ├── landing-hero.tsx │ ├── page-transition-animation.tsx │ ├── projects │ │ ├── project-card.tsx │ │ ├── project-showcase-list.tsx │ │ └── project-showcase.tsx │ ├── skills │ │ ├── skills-pill.tsx │ │ └── skills-showcase.tsx │ └── utility │ │ ├── corosel.tsx │ │ ├── custom-input.tsx │ │ ├── custom-textarea.tsx │ │ ├── custom-toast.tsx │ │ ├── menu-button.tsx │ │ ├── mobile-menu.tsx │ │ └── theme-switch.tsx ├── data │ ├── education.ts │ ├── experience.ts │ ├── navigationRoutes.ts │ ├── projects.ts │ ├── siteMetaData.mjs │ └── skills.ts ├── hooks │ ├── useAutoSizeTextarea.ts │ ├── useDebounceValue.ts │ └── useScreenBreakpoint.ts ├── layout │ ├── footer.tsx │ ├── main-layout.tsx │ └── navbar.tsx ├── pages │ ├── 404.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── about.tsx │ ├── api │ │ └── sendmail.ts │ ├── index.tsx │ └── projects.tsx ├── scripts │ └── generateSitemap.mjs ├── styles │ ├── globals.css │ └── theme-expamples.css └── utility │ ├── classNames.ts │ ├── cursor-trail.ts │ ├── rate-limiter.ts │ ├── sendMail.ts │ ├── types.ts │ └── verifyEmail.ts ├── tailwind.config.js └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | .vscode 4 | .husky -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Create your pass for nodemailer. Follow the steps from README.md 2 | 3 | NODEMAILER_USER=xxxxxxxxx@gmail.com 4 | NODEMAILER_PASS=zxxxxxxxxxxxxxxxxxx -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | .next -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "next/core-web-vitals", "prettier"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": ["error", { "endOfLine": "auto" }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.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* 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | #editor 38 | .vscode 39 | 40 | public/sitemap.xml 41 | public/robots.txt -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | yarn.lock 2 | package-lock.json 3 | node_modules 4 | .next 5 | .vscode 6 | public -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Install dependencies 2 | FROM node:20.10-bookworm-slim as Dependencies 3 | WORKDIR /app 4 | COPY package.json pnpm-lock.yaml ./ 5 | RUN npm install -g pnpm 6 | RUN pnpm install 7 | RUN pnpm install sharp 8 | 9 | # Building standalone app 10 | FROM node:20.10-bookworm-slim as Builder 11 | WORKDIR /app 12 | COPY --from=Dependencies /app/node_modules ./node_modules 13 | COPY . . 14 | ENV NEXT_TELEMETRY_DISABLED 1 15 | ENV BUILD_STANDALONE true 16 | RUN npm run build 17 | 18 | # Runner only copies required files to final image to keep the final image size minimum 19 | FROM node:20.10-bookworm-slim as Runner 20 | WORKDIR /app 21 | ENV NODE_ENV production 22 | COPY --from=Builder /app/public ./public 23 | COPY --from=Builder /app/.next/standalone ./ 24 | COPY --from=Builder /app/.next/static ./.next/static 25 | 26 | EXPOSE 3000 27 | ENV PORT 3000 28 | ENV HOSTNAME localhost 29 | 30 | CMD [ "node", "server.js" ] 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Portfolio 2 | 3 | This portfolio is crafted using [Next.js](https://nextjs.org/) 4 | 5 | ## 🖥️ Technologies Used 6 | 7 | - [Nextjs.js](https://nextjs.org/) : A React-based, open-source framework for building efficient and scalable web applications. 8 | - [Tailwind CSS](https://tailwindcss.com) : A utility-first CSS framework for rapid UI development. 9 | - [TypeScript](https://www.typescriptlang.org): A typed superset of JavaScript that provides enhanced tooling and developer productivity. 10 | - [Framer motion](https://www.framer.com/motion/): A React animation library that brings motion to your user interfaces. 11 | - [Nodemailer](https://nodemailer.com/): A Node.js library facilitating easy email integration with features like attachment handling, HTML content, and support for various email services. 12 | 13 | ## 🌐 Open Source 14 | 15 | Feel free to use it as a template for your own portfolio or any other projects. You are granted the freedom to modify, distribute, and use the code for any purpose, unleashing your creativity without any restrictions. 16 | 17 | If you have any improvements, ideas or find any bugs, don't hesitate to submit a pull request or open an issue. 18 | 19 | ## 🛑 Important Note 20 | 21 | 1. **Do not push your Nodemailer pass on Github** as it can give access to your google email. Use `.env` file 22 | 2. Use [Nextjs api routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) for nodemailer transporters and send mail function as they only run on server and cannot expose your google app variables to client 23 | 3. Update google site verification code with your own inside `/src/data/siteMetaData.mjs`. Can be created for free using your google email id at 24 | 25 | ## 🌟 Customizable theme 26 | 27 | There are some premade themes that I have made for this portfolio inside `theme-examples.css` file. Just copy paste the styles to `globals.css` after that you are good to go or Create your own theme by editing the css variables in `globals.css` 28 | 29 | ### Note 30 | 31 | 1. When creating custom theme the css variables only take hsl value seperated by space 32 | 2. Theme color for Animated Logo have to be hard coded. 33 | 34 | #### Eg. Some Premade themes 35 | 36 | ![Violet-theme](https://github.com/BUMBAIYA/amitchauhan-v2/assets/85615075/25db6c35-f9e2-4c19-9060-cac2f0b544de) 37 | ![rose-theme](https://github.com/BUMBAIYA/amitchauhan-v2/assets/85615075/f5dd1b90-3297-440d-a83c-d79c1cef7bd0) 38 | ![yellow-theme](https://github.com/BUMBAIYA/amitchauhan-v2/assets/85615075/e5576e96-0d9d-4f37-a7ad-e14ffa5b1d21) 39 | 40 | ## ✉️ Setup Nodemailer 41 | 42 | ### Create Nodemailer User and Password 43 | 44 | 1. Go to your Google Mail app or any other Google App. 45 | 2. Click on your `Profile` 46 | 3. Click on `Manage your Google Account` 47 | 4. Go to `Security` 48 | 5. Under `How you sign in Google` go to `2-Step Verification` 49 | 6. Under `2-Step Verification` go to `App passwords` 50 | 7. Create an app (e.g., portfolio-nodemailer), and copy the generated password securely. Use it as the value for the `NODEMAILER_PASS` variable and your email as the value for `NODEMAILER_USER` variable in `.env` file. [Note: Passwords are not visible once closed; if forgotten, delete the old app and create a new one.] 51 | 52 | ### ✨ Seo 53 | 54 | 1. The project automatically generates sitemap.xml and robots.txt files within the public folder by leveraging the project's file structure. This process is initiated through the scripts located at src/scripts/generateSitemap.mjs, executed either after the project is built or by running the command `pnpm sitemap`. 55 | 2. It's important to note that [dynamic routes](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes), identified by file or folder names in square brackets (e.g., [segmentName], [id], or [slug]), are excluded from the sitemap.xml. 56 | 3. Update google site verification code with your own inside `/src/data/siteMetaData.mjs`. Can be created for free using your google email id at 57 | 58 | ## 🛠️ Development setup 59 | 60 | ### Step 1 - Install dependencies 61 | 62 | ```bash 63 | pnpm install 64 | ``` 65 | 66 | ### Step 2 - Run the development server 67 | 68 | ```bash 69 | pnpm dev 70 | ``` 71 | 72 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the portfolio. 73 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withBundleAnalyzer = require("@next/bundle-analyzer")({ 2 | enabled: process.env.ANALYZE === "true", 3 | }); 4 | 5 | /** @type {import('next').NextConfig} */ 6 | const nextConfig = withBundleAnalyzer({ 7 | output: process.env.BUILD_STANDALONE === "true" ? "standalone" : undefined, 8 | reactStrictMode: true, 9 | pageExtensions: ["ts", "tsx", "js"], 10 | eslint: { 11 | dirs: ["src"], 12 | }, 13 | images: { 14 | domains: ["https://flagcdn.com"], 15 | }, 16 | webpack: (config) => { 17 | config.module.rules.push({ 18 | test: /\.svg$/i, 19 | use: ["@svgr/webpack"], 20 | }); 21 | config.resolve.fallback = { 22 | fs: false, 23 | net: false, 24 | dns: false, 25 | child_process: false, 26 | tls: false, 27 | }; 28 | 29 | return config; 30 | }, 31 | }); 32 | 33 | module.exports = nextConfig; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amitchauhan-v2", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "postbuild": "npm run sitemap", 9 | "start": "next start", 10 | "lint": "next lint", 11 | "sitemap": "cross-env NODE_OPTIONS='--experimental-json-modules' node ./src/scripts/generateSitemap.mjs", 12 | "analyze": "cross-env ANALYZE=true next build", 13 | "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx,mdx,css,json}\"", 14 | "format:fix": "prettier --write \"src/**/*.{js,jsx,ts,tsx,mdx,css,json}\"", 15 | "prepare": "husky install" 16 | }, 17 | "dependencies": { 18 | "@headlessui/react": "^1.7.17", 19 | "@tailwindcss/forms": "^0.5.7", 20 | "@vercel/analytics": "^1.3.1", 21 | "formik": "^2.4.5", 22 | "framer-motion": "^10.16.16", 23 | "lru-cache": "^10.1.0", 24 | "lucide-react": "^0.366.0", 25 | "nanoid": "^5.0.7", 26 | "next": "13.4.4", 27 | "next-seo": "^6.4.0", 28 | "next-themes": "^0.2.1", 29 | "nodemailer": "^6.9.7", 30 | "react": "18.2.0", 31 | "react-dom": "18.2.0", 32 | "react-icons": "^4.12.0", 33 | "sharp": "^0.32.6", 34 | "yup": "^1.3.3" 35 | }, 36 | "devDependencies": { 37 | "@next/bundle-analyzer": "^14.0.4", 38 | "@svgr/webpack": "^8.1.0", 39 | "@types/node": "^20.10.4", 40 | "@types/nodemailer": "^6.4.14", 41 | "@types/react": "^18.2.45", 42 | "@types/react-dom": "^18.2.18", 43 | "@types/uuid": "^9.0.7", 44 | "autoprefixer": "10.4.14", 45 | "cross-env": "^7.0.3", 46 | "eslint": "8.42.0", 47 | "eslint-config-next": "13.4.4", 48 | "eslint-config-prettier": "9.0.0", 49 | "eslint-plugin-prettier": "4.2.1", 50 | "globby": "^14.0.0", 51 | "husky": "^8.0.3", 52 | "lint-staged": "^13.3.0", 53 | "postcss": "8.4.24", 54 | "prettier": "^2.8.8", 55 | "prettier-plugin-tailwindcss": "^0.3.0", 56 | "tailwindcss": "3.3.0", 57 | "typescript": "5.1.3" 58 | }, 59 | "lint-staged": { 60 | "*.+(js|jsx|ts|tsx)": [ 61 | "eslint --fix" 62 | ], 63 | "*.+(js|jsx|ts|tsx|json|css|md|mdx)": [ 64 | "prettier --write" 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | semi: true, 4 | singleQuote: false, 5 | trailingComma: "all", 6 | printWidth: 80, 7 | tabWidth: 2, 8 | plugins: [require("prettier-plugin-tailwindcss")], 9 | }; 10 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/aws.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/css.svg: -------------------------------------------------------------------------------- 1 | 2 | file_type_css -------------------------------------------------------------------------------- /public/icons/docker.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/icons/fram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/git.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /public/icons/graphql.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/icons/html.svg: -------------------------------------------------------------------------------- 1 | 2 | file_type_html -------------------------------------------------------------------------------- /public/icons/javascript.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/icons/mongodb.svg: -------------------------------------------------------------------------------- 1 | 2 | file_type_mongo -------------------------------------------------------------------------------- /public/icons/mui.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/icons/nodejs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/icons/postgresql.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/icons/postman.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/prisma.svg: -------------------------------------------------------------------------------- 1 | 2 | file_type_light_prisma -------------------------------------------------------------------------------- /public/icons/python.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/icons/react-router-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/icons/reactjs.svg: -------------------------------------------------------------------------------- 1 | 2 | file_type_reactjs -------------------------------------------------------------------------------- /public/icons/redux.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/sass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/icons/solidjs.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/icons/tailwindcss.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/icons/typescript.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/icons/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/vscode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /public/images/heroProfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/heroProfile.png -------------------------------------------------------------------------------- /public/images/projects/3DView.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/3DView.webp -------------------------------------------------------------------------------- /public/images/projects/covidTracker.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/covidTracker.webp -------------------------------------------------------------------------------- /public/images/projects/covidTrackerMap.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/covidTrackerMap.webp -------------------------------------------------------------------------------- /public/images/projects/covidTrackerTable.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/covidTrackerTable.webp -------------------------------------------------------------------------------- /public/images/projects/jsontreeDark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/jsontreeDark.webp -------------------------------------------------------------------------------- /public/images/projects/jsontreeLight.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/jsontreeLight.webp -------------------------------------------------------------------------------- /public/images/projects/kanbanCardLight.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/kanbanCardLight.webp -------------------------------------------------------------------------------- /public/images/projects/kanbanDark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/kanbanDark.webp -------------------------------------------------------------------------------- /public/images/projects/kanbanLight.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/kanbanLight.webp -------------------------------------------------------------------------------- /public/images/projects/logos/covidtracker.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/logos/covidtracker.ico -------------------------------------------------------------------------------- /public/images/projects/logos/jsontree.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/logos/jsontree.ico -------------------------------------------------------------------------------- /public/images/projects/logos/kanban.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/logos/kanban.ico -------------------------------------------------------------------------------- /public/images/projects/logos/manygames.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/logos/manygames.ico -------------------------------------------------------------------------------- /public/images/projects/logos/stockpredictor.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/logos/stockpredictor.ico -------------------------------------------------------------------------------- /public/images/projects/manyGames2048.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/manyGames2048.webp -------------------------------------------------------------------------------- /public/images/projects/manyGamesDark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/manyGamesDark.webp -------------------------------------------------------------------------------- /public/images/projects/manyGamesLight.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/manyGamesLight.webp -------------------------------------------------------------------------------- /public/images/projects/manyGamesPuzzle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/manyGamesPuzzle.webp -------------------------------------------------------------------------------- /public/images/projects/manyGamesWordle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/manyGamesWordle.webp -------------------------------------------------------------------------------- /public/images/projects/portfolioDark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/portfolioDark.webp -------------------------------------------------------------------------------- /public/images/projects/portfolioLight.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/portfolioLight.webp -------------------------------------------------------------------------------- /public/images/projects/stockPredictor.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/stockPredictor.webp -------------------------------------------------------------------------------- /public/images/projects/stockPredictorCandleChart.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/stockPredictorCandleChart.webp -------------------------------------------------------------------------------- /public/images/projects/stockPredictorCompareChart.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/stockPredictorCompareChart.webp -------------------------------------------------------------------------------- /public/images/projects/stockPredictorLineChart.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/images/projects/stockPredictorLineChart.webp -------------------------------------------------------------------------------- /public/static/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BUMBAIYA/amitchauhan-v2/a1f34b5d6a118d373b353a664ab6c434aac396b4/public/static/homepage.png -------------------------------------------------------------------------------- /src/animation/animated-logo.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence, Variants, motion } from "framer-motion"; 2 | 3 | export default function AnimatedLogo() { 4 | const iconVariant: Variants = { 5 | hidden: { 6 | pathLength: 0, 7 | fill: "rgba(0, 0, 0, 0)", 8 | }, 9 | visible: { 10 | pathLength: 1, 11 | // Set fill as per your theme 12 | fill: "#1f8d93", 13 | }, 14 | }; 15 | 16 | return ( 17 | 18 | 23 | 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/animation/fade-right.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | import { motion } from "framer-motion"; 4 | 5 | export interface FadeRightProps { 6 | children: ReactNode; 7 | duration: number; 8 | delay?: number; 9 | className?: string; 10 | whileInView?: boolean; 11 | } 12 | 13 | export default function FadeRight({ 14 | children, 15 | duration, 16 | delay, 17 | className, 18 | whileInView = false, 19 | }: FadeRightProps) { 20 | const animation = { 21 | opacity: 1, 22 | x: 0, 23 | transition: { 24 | duration, 25 | ease: "easeInOut", 26 | delay, 27 | }, 28 | }; 29 | return ( 30 | 36 | {children} 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/animation/fade-up.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | import { motion } from "framer-motion"; 4 | 5 | export interface FadeUpProps { 6 | children: ReactNode; 7 | duration: number; 8 | delay?: number; 9 | whileInView?: boolean; 10 | } 11 | 12 | export default function FadeUp({ 13 | children, 14 | duration, 15 | delay, 16 | whileInView = false, 17 | }: FadeUpProps) { 18 | const animation = { 19 | opacity: 1, 20 | y: 0, 21 | transition: { 22 | duration, 23 | ease: "easeInOut", 24 | delay, 25 | }, 26 | }; 27 | return ( 28 | 33 | {children} 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/components/about-hero.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | 4 | import { AnimatePresence } from "framer-motion"; 5 | 6 | import FadeUp from "@/animation/fade-up"; 7 | import FadeRight from "@/animation/fade-right"; 8 | import heroProfileImg from "@/public/images/heroProfile.png"; 9 | import DuotoneImage from "./duotone-image"; 10 | 11 | export default function AboutHero() { 12 | return ( 13 |
14 |
15 | 16 | 17 | 27 | 28 | 29 |
30 |
31 | 32 | 33 |

34 | Hi, I'm Amit Chauhan 35 |

36 |
37 | 38 |

39 | I turn vision into reality with code. Whether I'm working on 40 | a website or any digital product, I bring my commitment to design 41 | excellence and user-centered thinking to every project I work on. 42 |

43 |
44 | 45 |

46 | Explore my latest{" "} 47 | 48 | projects 49 | {" "} 50 | showcasing my expertise in Reactjs, Nextjs, Javascript, Typescript 51 | and web development. 52 |

53 |
54 | 60 |
61 | Indian flag 68 |
69 | 70 | Mumbai, India 71 | 72 |
73 |
74 |
75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/components/contact-form/contact-button.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | 3 | import { MailIcon } from "lucide-react"; 4 | 5 | import FloatingMailButton, { 6 | floatingMailButtonoptions, 7 | } from "@/components/contact-form/floating-mail-button"; 8 | import ContactFormModal from "@/components/contact-form/contact-form-modal"; 9 | 10 | export default function ContactButton() { 11 | const refSendBtn = useRef(null); 12 | 13 | const [isBtnVisible, setIsBtnVisible] = useState(false); 14 | const [isOpenModal, setIsOpenModal] = useState(false); 15 | 16 | const observerCallback = (entries: IntersectionObserverEntry[]) => { 17 | const [entry] = entries; 18 | setIsBtnVisible(!entry.isIntersecting); 19 | }; 20 | 21 | useEffect(() => { 22 | const btn = refSendBtn.current; 23 | const observer = new IntersectionObserver( 24 | observerCallback, 25 | floatingMailButtonoptions, 26 | ); 27 | if (btn) observer.observe(btn); 28 | return () => { 29 | if (btn) observer.unobserve(btn); 30 | }; 31 | }, [refSendBtn]); 32 | 33 | return ( 34 | <> 35 | {isBtnVisible && !isOpenModal && ( 36 | 37 | )} 38 | 39 | 49 | 50 | 51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /src/components/contact-form/contact-form-modal.tsx: -------------------------------------------------------------------------------- 1 | import { Dispatch, Fragment, SetStateAction, useState } from "react"; 2 | 3 | import { Dialog, Transition } from "@headlessui/react"; 4 | import { MailIcon, XIcon } from "lucide-react"; 5 | 6 | import ContactForm, { 7 | type ContactFormValues, 8 | } from "@/components/contact-form/contact-form"; 9 | import ContactMailToast, { 10 | type MailSentToastState, 11 | } from "@/components/contact-form/contact-mail-toast"; 12 | 13 | export interface ContactFormModelProps { 14 | showModal: boolean; 15 | setShowModal: Dispatch>; 16 | } 17 | 18 | export default function ContactFormModal({ 19 | showModal, 20 | setShowModal, 21 | }: ContactFormModelProps) { 22 | const [isSendingMail, setIsSendingMail] = useState(false); 23 | const [toastState, setToastState] = useState({ 24 | type: null, 25 | value: false, 26 | message: "", 27 | }); 28 | 29 | const handleSubmit = async (values: ContactFormValues) => { 30 | setIsSendingMail(true); 31 | try { 32 | const response = await fetch("/api/sendmail", { 33 | method: "POST", 34 | headers: { "Content-Type": "application/json" }, 35 | body: JSON.stringify(values), 36 | }); 37 | if (response.ok) { 38 | setToastState({ 39 | type: "success", 40 | value: true, 41 | message: "Successfully sent email", 42 | }); 43 | setShowModal(false); 44 | } else { 45 | setToastState({ 46 | type: response.status === 429 ? "warning" : "failure", 47 | value: true, 48 | message: 49 | response.status === 429 50 | ? "Rate Limiter: Only 5 email per hour" 51 | : "Oop! Unable to send email", 52 | }); 53 | } 54 | } catch { 55 | setToastState({ 56 | type: "failure", 57 | value: true, 58 | message: "Oop! Unable to send email", 59 | }); 60 | } 61 | setIsSendingMail(false); 62 | }; 63 | return ( 64 | <> 65 | 66 | setShowModal(false)} 70 | > 71 | 80 |
81 | 82 |
83 | 92 | 93 |
94 | 95 | 96 | Send Message 97 | 98 | 104 |
105 | 109 |
110 |
111 |
112 |
113 |
114 | 115 | 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /src/components/contact-form/contact-form.tsx: -------------------------------------------------------------------------------- 1 | import { Field, Form, Formik } from "formik"; 2 | import * as Yup from "yup"; 3 | import { Loader2Icon } from "lucide-react"; 4 | 5 | import CustomInput from "@/components/utility/custom-input"; 6 | import CustomTextarea from "@/components/utility/custom-textarea"; 7 | import { type FormiKInputFieldProps } from "@/utility/types"; 8 | 9 | export const mailValidationSchema = Yup.object({ 10 | email: Yup.string().email("Invalid email").required("Email required"), 11 | name: Yup.string().required("Name required"), 12 | subject: Yup.string().required("Subject required"), 13 | message: Yup.string().required("Message required"), 14 | }); 15 | 16 | export type ContactFormValues = Yup.InferType; 17 | 18 | export type FormFields = { 19 | name: keyof ContactFormValues; 20 | label: string; 21 | type: "text"; 22 | fieldType: "text" | "textarea"; 23 | placeholder: string; 24 | }; 25 | 26 | const FormFieldsData: FormFields[] = [ 27 | { 28 | name: "email", 29 | label: "Email", 30 | type: "text", 31 | fieldType: "text", 32 | placeholder: "Email", 33 | }, 34 | { 35 | name: "name", 36 | label: "Name", 37 | type: "text", 38 | fieldType: "text", 39 | placeholder: "Name", 40 | }, 41 | { 42 | name: "subject", 43 | label: "Subject", 44 | type: "text", 45 | fieldType: "text", 46 | placeholder: "Subject", 47 | }, 48 | { 49 | name: "message", 50 | label: "Message", 51 | type: "text", 52 | fieldType: "textarea", 53 | placeholder: "Message", 54 | }, 55 | ]; 56 | 57 | const initialFormValues: ContactFormValues = { 58 | email: "", 59 | name: "", 60 | message: "", 61 | subject: "", 62 | }; 63 | 64 | export interface ContactFormProps { 65 | isSubmitting: boolean; 66 | // eslint-disable-next-line 67 | handleSubmit: (values: ContactFormValues) => Promise; 68 | } 69 | 70 | export default function ContactForm({ 71 | isSubmitting, 72 | handleSubmit, 73 | }: ContactFormProps) { 74 | return ( 75 | <> 76 | 82 |
83 | {FormFieldsData.map((form) => ( 84 |
85 |
86 | 92 |
93 |
94 | 95 | {({ field, meta }: FormiKInputFieldProps) => 96 | form.fieldType === "text" ? ( 97 | <> 98 | 105 | {Boolean(meta.touched && meta.error) && ( 106 | 107 | {meta.error} 108 | 109 | )} 110 | 111 | ) : ( 112 | <> 113 | 118 | {Boolean(meta.touched && meta.error) && ( 119 | 120 | {meta.error} 121 | 122 | )} 123 | 124 | ) 125 | } 126 | 127 |
128 |
129 | ))} 130 | 145 |
146 |
147 | 148 | ); 149 | } 150 | -------------------------------------------------------------------------------- /src/components/contact-form/contact-mail-toast.tsx: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction } from "react"; 2 | 3 | import { CircleCheckIcon, TriangleAlertIcon } from "lucide-react"; 4 | 5 | import CustomToast from "@/components/utility/custom-toast"; 6 | import { classNames } from "@/utility/classNames"; 7 | 8 | type ToastType = "success" | "failure" | "warning" | null; 9 | export type MailSentToastState = { 10 | type: ToastType; 11 | value: boolean; 12 | message: string; 13 | }; 14 | 15 | export interface MailSentToastProps { 16 | showToast: Dispatch>; 17 | toastState: MailSentToastState; 18 | } 19 | 20 | export default function ContactMailToast({ 21 | toastState, 22 | showToast, 23 | }: MailSentToastProps) { 24 | return toastState.value ? ( 25 | showToast((prev) => ({ ...prev, value: false }))} 29 | className={classNames( 30 | "fixed right-4 top-6 z-[9999] rounded-lg bg-accent px-4 py-2 font-semibold text-white shadow-xl", 31 | toastState.type === "success" 32 | ? "bg-teal-500" 33 | : toastState.type === "warning" 34 | ? "bg-yellow-500" 35 | : "bg-red-600", 36 | )} 37 | > 38 |
39 | {toastState.type === "success" ? ( 40 | 41 | ) : ( 42 | 43 | )} 44 | 45 | {toastState.message} 46 |
47 |
48 | ) : null; 49 | } 50 | -------------------------------------------------------------------------------- /src/components/contact-form/floating-mail-button.tsx: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction } from "react"; 2 | 3 | import { MailIcon } from "@/components/icons"; 4 | 5 | export interface FloatingMailButtonProps { 6 | openModal: Dispatch>; 7 | } 8 | 9 | export const floatingMailButtonoptions = { 10 | root: null, 11 | rootMargin: "100px", 12 | threshold: 0.1, 13 | }; 14 | 15 | export default function FloatingMailButton({ 16 | openModal, 17 | }: FloatingMailButtonProps) { 18 | return ( 19 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/components/cursor-trail-canvas.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, useEffect, useRef } from "react"; 2 | 3 | import { cursorTrail } from "@/utility/cursor-trail"; 4 | 5 | export interface CursorTrailCanvasProps { 6 | color?: string; 7 | className?: string; 8 | style?: CSSProperties; 9 | } 10 | 11 | export default function CursorTrailCanvas(props: CursorTrailCanvasProps) { 12 | const refCanvas = useRef(null); 13 | 14 | useEffect(() => { 15 | const { cleanUp, renderTrailCursor } = cursorTrail({ 16 | ref: refCanvas, 17 | color: props.color, 18 | }); 19 | renderTrailCursor(); 20 | 21 | return () => { 22 | cleanUp(); 23 | }; 24 | }, [props.color]); 25 | 26 | return ( 27 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/components/duotone-image.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import Image, { ImageProps } from "next/image"; 3 | import { StaticImageData } from "next/image"; 4 | 5 | interface DuotoneImageProps extends Omit { 6 | src: string | StaticImageData; 7 | lightColor?: string; 8 | darkColor?: string; 9 | contrastFactor?: number; // Added contrast control 10 | sharpnessFactor?: number; // Added sharpness control 11 | } 12 | 13 | const DuotoneImage: React.FC = ({ 14 | src, 15 | width, 16 | height, 17 | className = "", 18 | alt = "", 19 | lightColor = "#E0FFFF", 20 | darkColor = "#004D4D", 21 | contrastFactor = 1.2, // Default contrast enhancement 22 | sharpnessFactor = 0.5, // Default sharpness 23 | ...props 24 | }) => { 25 | const [processedImageUrl, setProcessedImageUrl] = useState(""); 26 | 27 | useEffect(() => { 28 | const canvas = document.createElement("canvas"); 29 | const ctx = canvas.getContext("2d"); 30 | const img = document.createElement("img"); 31 | 32 | img.crossOrigin = "anonymous"; 33 | img.onload = () => { 34 | canvas.width = img.width; 35 | canvas.height = img.height; 36 | 37 | if (ctx) { 38 | ctx.drawImage(img, 0, 0); 39 | 40 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 41 | const data = imageData.data; 42 | 43 | const light = hexToRgb(lightColor); 44 | const dark = hexToRgb(darkColor); 45 | 46 | if (light && dark) { 47 | // Process each pixel 48 | for (let y = 0; y < canvas.height; y++) { 49 | for (let x = 0; x < canvas.width; x++) { 50 | const i = (y * canvas.width + x) * 4; 51 | 52 | // Calculate brightness with increased contrast 53 | let brightness = 54 | (data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114) / 55 | 255; 56 | 57 | // Apply contrast enhancement 58 | brightness = (brightness - 0.5) * contrastFactor + 0.5; 59 | brightness = Math.max(0, Math.min(1, brightness)); 60 | 61 | // Apply sharpening by emphasizing differences from neighbors 62 | if ( 63 | x > 0 && 64 | x < canvas.width - 1 && 65 | y > 0 && 66 | y < canvas.height - 1 67 | ) { 68 | const center = brightness; 69 | const left = 70 | (data[i - 4] * 0.299 + 71 | data[i - 3] * 0.587 + 72 | data[i - 2] * 0.114) / 73 | 255; 74 | const right = 75 | (data[i + 4] * 0.299 + 76 | data[i + 3] * 0.587 + 77 | data[i + 2] * 0.114) / 78 | 255; 79 | const top = 80 | (data[i - canvas.width * 4] * 0.299 + 81 | data[i - canvas.width * 4 + 1] * 0.587 + 82 | data[i - canvas.width * 4 + 2] * 0.114) / 83 | 255; 84 | const bottom = 85 | (data[i + canvas.width * 4] * 0.299 + 86 | data[i + canvas.width * 4 + 1] * 0.587 + 87 | data[i + canvas.width * 4 + 2] * 0.114) / 88 | 255; 89 | 90 | const sharpenedBrightness = 91 | center + 92 | (center - (left + right + top + bottom) / 4) * 93 | sharpnessFactor; 94 | brightness = Math.max(0, Math.min(1, sharpenedBrightness)); 95 | } 96 | 97 | // Apply duotone colors with enhanced contrast 98 | data[i] = Math.round(lerp(dark.r, light.r, brightness)); 99 | data[i + 1] = Math.round(lerp(dark.g, light.g, brightness)); 100 | data[i + 2] = Math.round(lerp(dark.b, light.b, brightness)); 101 | } 102 | } 103 | } 104 | 105 | ctx.putImageData(imageData, 0, 0); 106 | setProcessedImageUrl(canvas.toDataURL("image/png")); 107 | } 108 | }; 109 | 110 | const imgSrc = typeof src === "string" ? src : src.src; 111 | img.src = imgSrc; 112 | 113 | return () => { 114 | if (processedImageUrl) { 115 | URL.revokeObjectURL(processedImageUrl); 116 | } 117 | }; 118 | }, [ 119 | src, 120 | lightColor, 121 | darkColor, 122 | contrastFactor, 123 | sharpnessFactor, 124 | processedImageUrl, 125 | ]); 126 | 127 | const hexToRgb = (hex: string) => { 128 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 129 | return result 130 | ? { 131 | r: parseInt(result[1], 16), 132 | g: parseInt(result[2], 16), 133 | b: parseInt(result[3], 16), 134 | } 135 | : null; 136 | }; 137 | 138 | const lerp = (start: number, end: number, amount: number): number => { 139 | return start * (1 - amount) + end * amount; 140 | }; 141 | 142 | if (!processedImageUrl) { 143 | return null; 144 | } 145 | 146 | return ( 147 | {alt} 156 | ); 157 | }; 158 | 159 | export default DuotoneImage; 160 | -------------------------------------------------------------------------------- /src/components/experience/experience-showcase-list-item.tsx: -------------------------------------------------------------------------------- 1 | import { RefObject, useRef } from "react"; 2 | import Link from "next/link"; 3 | 4 | import { motion, useScroll } from "framer-motion"; 5 | 6 | export interface ExperienceListIconProps { 7 | iconRef: RefObject; 8 | } 9 | 10 | function ShowCaseLiIcon(props: ExperienceListIconProps) { 11 | const { scrollYProgress } = useScroll({ 12 | target: props.iconRef, 13 | offset: ["center end", "center center"], 14 | layoutEffect: false, 15 | }); 16 | return ( 17 |
18 | 19 | 25 | 34 | 35 | 36 |
37 | ); 38 | } 39 | 40 | export interface ExperienceShowcaseListItemProps { 41 | title: string; 42 | organisation: { 43 | name: string; 44 | href: string; 45 | }; 46 | date: string; 47 | location: string; 48 | description: string; 49 | } 50 | 51 | export default function ExperienceShowcaseListItem( 52 | props: ExperienceShowcaseListItemProps, 53 | ) { 54 | const ref = useRef(null); 55 | return ( 56 |
  • 57 | 58 | 66 |

    67 | {props.title}{" "} 68 | 74 | @{props.organisation.name} 75 | 76 |

    77 | 78 | {props.date} | {props.location} 79 | 80 |

    81 | {props.description} 82 |

    83 |
    84 |
  • 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /src/components/experience/experience-showcase-list.tsx: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | 3 | import { motion, useScroll } from "framer-motion"; 4 | 5 | import ExperienceShowcaseListItem, { 6 | type ExperienceShowcaseListItemProps, 7 | } from "@/components/experience/experience-showcase-list-item"; 8 | 9 | export interface ExperienceShowcaseListProps { 10 | title: string; 11 | details: ExperienceShowcaseListItemProps[]; 12 | } 13 | 14 | export default function ExperienceShowcaseList( 15 | props: ExperienceShowcaseListProps, 16 | ) { 17 | const ref = useRef(null); 18 | const { scrollYProgress } = useScroll({ 19 | target: ref, 20 | offset: ["start end", "center start"], 21 | }); 22 | return ( 23 |
    24 |

    25 | {props.title} 26 |

    27 |
    28 | 32 |
      33 | {props.details.map((_details, index) => ( 34 | 35 | ))} 36 |
    37 |
    38 |
    39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/components/icons.tsx: -------------------------------------------------------------------------------- 1 | export type IconType = { 2 | className?: string; 3 | }; 4 | 5 | export const GithubIcon = (props: IconType) => ( 6 | 16 | 17 | 18 | ); 19 | 20 | export const LinkedinIcon = (props: IconType) => ( 21 | 31 | 32 | 33 | ); 34 | 35 | export const TwitterIcon = (props: IconType) => ( 36 | 46 | 47 | 48 | ); 49 | 50 | export const ArrowTopRight = (props: IconType) => ( 51 | 61 | 65 | 66 | ); 67 | 68 | export function NextJsIcon() { 69 | return ( 70 | 82 | 83 | 84 | 85 | 86 | ); 87 | } 88 | 89 | export function ReactRouterDomIcon({ className }: { className?: string }) { 90 | return ( 91 | 99 | 103 | 107 | 111 | 115 | 116 | ); 117 | } 118 | 119 | export function FramerMotionIcon({ className }: { className: string }) { 120 | return ( 121 | 129 | 133 | 134 | ); 135 | } 136 | 137 | export function VitePwaIcon({ className }: { className: string }) { 138 | return ( 139 | 147 | 151 | 155 | 159 | 160 | ); 161 | } 162 | 163 | export function MailIcon({ className }: { className?: string }) { 164 | return ( 165 | 182 | ); 183 | } 184 | 185 | export function CheckIcon({ className }: { className?: string }) { 186 | return ( 187 | 202 | ); 203 | } 204 | 205 | export function DangerIcon({ className }: { className?: string }) { 206 | return ( 207 | 222 | ); 223 | } 224 | -------------------------------------------------------------------------------- /src/components/landing-hero.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | 3 | import { AnimatePresence, motion } from "framer-motion"; 4 | 5 | import FadeUp from "@/animation/fade-up"; 6 | 7 | export default function LandingHero() { 8 | const [scrollY, setScrollY] = useState(0); 9 | const ref = useRef(null); 10 | 11 | let progress = 0; 12 | const { current: elContainer } = ref; 13 | 14 | if (elContainer) { 15 | progress = Math.min(1, scrollY / elContainer.clientHeight); 16 | } 17 | 18 | const handleScroll = () => { 19 | setScrollY(window.scrollY); 20 | }; 21 | 22 | useEffect(() => { 23 | document.addEventListener("scroll", handleScroll); 24 | 25 | return () => document.removeEventListener("scroll", handleScroll); 26 | }, []); 27 | 28 | return ( 29 | 37 |
    38 |
    39 | 40 | 41 |

    42 | Amit Chauhan 43 |

    44 | 45 | Software Developer 46 | 47 |
    48 | 49 |
    50 | I am a software developer specializing in building 51 | high-performance, user-focused web applications. Skilled in{" "} 52 | ReactJS,{" "} 53 | NextJS,{" "} 54 | SolidJS, and 55 | an expert in{" "} 56 | JavaScript,{" "} 57 | HTML and{" "} 58 | CSS 59 |
    60 |
    61 |
    62 |
    63 |
    64 |
    65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /src/components/page-transition-animation.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | 3 | export default function PageTransitionAnimation() { 4 | return ( 5 | <> 6 | 13 |
    14 | 21 | 22 | 23 |
    24 |
    25 | 31 |
    32 | 39 | 40 | 41 |
    42 |
    43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/components/projects/project-card.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | import { motion } from "framer-motion"; 4 | import { FiExternalLink } from "react-icons/fi"; 5 | 6 | import Corosel from "@/components/utility/corosel"; 7 | import { GithubIcon } from "@/components/icons"; 8 | 9 | export interface ProjectCardProps { 10 | name: string; 11 | favicon: string; 12 | imageUrl: string[]; 13 | description: string; 14 | sourceCodeHref: string; 15 | liveWebsiteHref?: string; 16 | } 17 | 18 | export default function ProjectCard(props: ProjectCardProps) { 19 | return ( 20 | 29 | 30 |
    31 |
    32 | 33 | logo 34 | 35 | {props.name} 36 |
    37 |
    38 |

    {props.description}

    39 |
    40 |
    41 | 46 | Source code 47 | 48 | {props.liveWebsiteHref && ( 49 | 54 | Live 55 | 56 | )} 57 |
    58 |
    59 |
    60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/components/projects/project-showcase-list.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | import { motion } from "framer-motion"; 4 | 5 | import { classNames } from "@/utility/classNames"; 6 | 7 | export type ProjectShowcaseListItem = { 8 | index: number; 9 | title: string; 10 | href: string; 11 | tags: string[]; 12 | image: { 13 | LIGHT: string; 14 | DARK?: string; 15 | }; 16 | }; 17 | 18 | export interface ProjectShowcaseListProps { 19 | data: ProjectShowcaseListItem; 20 | activeProject: number; 21 | toggleList: (index: number) => void; //eslint-disable-line no-unused-vars 22 | } 23 | 24 | export default function ProjectShowcaseList(props: ProjectShowcaseListProps) { 25 | return ( 26 | props.toggleList(props.data.index)} 29 | onFocus={() => props.toggleList(props.data.index)} 30 | > 31 | 39 | {props.data.index + 1}. 40 | 41 | 42 | {props.data.index + 1}. 43 | 44 |
    45 | 46 | 54 | {props.data.title} 55 | 56 | 57 | {props.data.title} 58 | 59 | 65 | 66 |

    67 | {props.data.tags.map((tag) => `#${tag} `)} 68 |

    69 |
    70 |
    71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/components/projects/project-showcase.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useState } from "react"; 2 | import Link from "next/link"; 3 | import Image from "next/image"; 4 | 5 | import { AnimatePresence, motion } from "framer-motion"; 6 | 7 | import { ArrowTopRight } from "@/components/icons"; 8 | import ProjectShowcaseList, { 9 | type ProjectShowcaseListItem, 10 | } from "@/components/projects/project-showcase-list"; 11 | 12 | const generateImageData = (proj: ProjectShowcaseListItem[]) => { 13 | return proj.map((p) => p.image); 14 | }; 15 | 16 | interface ProjectShowcaseProps { 17 | projects: ProjectShowcaseListItem[]; 18 | } 19 | 20 | export default function ProjectShowcase(props: ProjectShowcaseProps) { 21 | const [currentImage, setCurrentImage] = useState(0); 22 | 23 | const images = useMemo(() => { 24 | return generateImageData(props.projects); 25 | }, [props.projects]); 26 | 27 | const handleAnimate = (index: number) => { 28 | if (index === currentImage) return; 29 | setCurrentImage(index); 30 | }; 31 | 32 | return ( 33 |
    34 |
    35 |
    36 | 37 | 54 | {`project 62 | {images[currentImage].DARK !== undefined && ( 63 | {`project 71 | )} 72 | 73 | 74 |
    75 |

    76 | My projects 77 |

    78 |
    79 | {props.projects.map((proj, index) => ( 80 | 86 | ))} 87 |
    88 |
    89 | {props.projects.map((proj) => ( 90 | 95 |
    96 | 97 | {proj.index + 1}. 98 | 99 | 103 | {proj.title} 104 | 105 |
    106 |

    107 | {proj.tags.map((tag, index) => ( 108 | #{tag} 109 | ))} 110 |

    111 | 112 | ))} 113 |
    114 | 118 |
    119 | See more projects 120 | 121 |
    122 |
    123 | 124 |
    125 | 126 |
    127 |
    128 | ); 129 | } 130 | -------------------------------------------------------------------------------- /src/components/skills/skills-pill.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGProps } from "react"; 2 | 3 | export type SkillPillProps = { 4 | name: string; 5 | icon: FC>; 6 | }; 7 | 8 | export default function SkillPill(props: SkillPillProps) { 9 | const { name, icon: Icon } = props; 10 | return ( 11 |
    12 | 13 | {name} 14 |
    15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/skills/skills-showcase.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence } from "framer-motion"; 2 | 3 | import SkillPill, { 4 | type SkillPillProps, 5 | } from "@/components/skills/skills-pill"; 6 | import FadeRight from "@/animation/fade-right"; 7 | import { useScreenBreakpoint } from "@/hooks/useScreenBreakpoint"; 8 | import { useDebounceValue } from "@/hooks/useDebounceValue"; 9 | 10 | export interface SkillsShowcaseProps { 11 | skills: { 12 | sectionName: string; 13 | skills: SkillPillProps[]; 14 | }[]; 15 | } 16 | 17 | export default function SkillsShowcase({ skills }: SkillsShowcaseProps) { 18 | const isMobile = useScreenBreakpoint(640); 19 | const isMobileDebonced = useDebounceValue(isMobile, 600); 20 | return ( 21 |
    22 |
    23 |

    24 | Skills 25 |

    26 | {skills.map((section) => ( 27 | 28 |
    29 | 30 | {section.sectionName} 31 | 32 |
    33 | {section.skills.map((pill, index) => ( 34 | 41 | 42 | 43 | ))} 44 |
    45 |
    46 |
    47 | ))} 48 |
    49 |
    50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/components/utility/corosel.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | import { AnimatePresence, AnimationProps, motion, wrap } from "framer-motion"; 4 | import { BiSolidLeftArrow } from "react-icons/bi"; 5 | 6 | import { classNames } from "@/utility/classNames"; 7 | 8 | const variant: AnimationProps["variants"] = { 9 | enter: (direction: number) => { 10 | return { 11 | x: direction > 0 ? 100 : -100, 12 | opacity: 0, 13 | }; 14 | }, 15 | center: { 16 | zIndex: 1, 17 | x: 0, 18 | opacity: 1, 19 | }, 20 | exit: (direction: number) => { 21 | return { 22 | zIndex: 0, 23 | x: direction < 0 ? 100 : -100, 24 | opacity: 0, 25 | }; 26 | }, 27 | }; 28 | 29 | const swipeConfidenceThreshold = 10000; 30 | const swipePower = (offset: number, velocity: number) => { 31 | return Math.abs(offset) * velocity; 32 | }; 33 | 34 | export type CoroselProps = { 35 | aspectRatio: number; 36 | images: string[]; 37 | }; 38 | 39 | export default function Corosel({ aspectRatio = 1, images }: CoroselProps) { 40 | const [[page, direction], setPage] = useState([0, 0]); 41 | 42 | const imageIndex = wrap(0, images.length, page); 43 | 44 | const paginate = (newDirection: number) => { 45 | setPage([page + newDirection, newDirection]); 46 | }; 47 | 48 | return ( 49 |
    50 | 51 | { 70 | const swipe = swipePower(offset.x, velocity.x); 71 | if (swipe < -swipeConfidenceThreshold) { 72 | paginate(1); 73 | } else if (swipe > swipeConfidenceThreshold) { 74 | paginate(-1); 75 | } 76 | }} 77 | > 78 | 79 |
    80 | 86 | {images.map((_, index) => ( 87 | 96 | ))} 97 | 103 |
    104 |
    105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /src/components/utility/custom-input.tsx: -------------------------------------------------------------------------------- 1 | import { InputHTMLAttributes, forwardRef } from "react"; 2 | 3 | import { classNames } from "@/utility/classNames"; 4 | 5 | export interface InputProps extends InputHTMLAttributes {} 6 | 7 | const CustomInput = forwardRef( 8 | ({ className, ...props }, ref) => { 9 | return ( 10 | 18 | ); 19 | }, 20 | ); 21 | 22 | CustomInput.displayName = "CustomInput"; 23 | 24 | export default CustomInput; 25 | -------------------------------------------------------------------------------- /src/components/utility/custom-textarea.tsx: -------------------------------------------------------------------------------- 1 | import { InputHTMLAttributes, forwardRef } from "react"; 2 | 3 | import { classNames } from "@/utility/classNames"; 4 | 5 | export interface TextareaProps 6 | extends InputHTMLAttributes {} 7 | 8 | const CustomTextarea = forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |