├── src ├── app │ ├── favicon.ico │ ├── providers.tsx │ ├── opengraph-image.tsx │ ├── twitter-image.tsx │ ├── page.tsx │ ├── layout.tsx │ └── globals.css ├── components │ ├── sections │ │ ├── footer.tsx │ │ ├── education.tsx │ │ ├── experience.tsx │ │ ├── projects.tsx │ │ ├── skills.tsx │ │ ├── about-me.tsx │ │ ├── achievements.tsx │ │ ├── github.tsx │ │ ├── header.tsx │ │ ├── leetcode.tsx │ │ ├── blog.tsx │ │ ├── navbar.tsx │ │ └── contact.tsx │ ├── ui │ │ ├── FluidCursor.tsx │ │ ├── grid-pattern.tsx │ │ ├── theme-toggle.tsx │ │ ├── github-star-button.tsx │ │ └── scroll-to-top.tsx │ └── constants │ │ └── data.ts └── hooks │ └── use-FluidCursor.ts ├── public ├── profile-gif.gif └── profile-image.jpg ├── postcss.config.mjs ├── next-env.d.ts ├── .prettierrc ├── next.config.ts ├── .prettierignore ├── eslint.config.mjs ├── tsconfig.json ├── LICENSE ├── package.json ├── .vscode └── settings.json ├── .gitignore └── README.md /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shasbinas/my-portfolio-nextjs/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /public/profile-gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shasbinas/my-portfolio-nextjs/HEAD/public/profile-gif.gif -------------------------------------------------------------------------------- /public/profile-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shasbinas/my-portfolio-nextjs/HEAD/public/profile-image.jpg -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "useTabs": false, 8 | "arrowParens": "always", 9 | "endOfLine": "lf", 10 | "bracketSpacing": true, 11 | "jsxSingleQuote": false, 12 | "plugins": [] 13 | } 14 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | images: { 5 | remotePatterns: [ 6 | { 7 | protocol: "https", 8 | hostname: "skillicons.dev", 9 | }, 10 | ], 11 | dangerouslyAllowSVG: true, 12 | }, 13 | }; 14 | 15 | export default nextConfig; 16 | -------------------------------------------------------------------------------- /src/components/sections/footer.tsx: -------------------------------------------------------------------------------- 1 | import { ABOUT_ME } from "@/components/constants/data"; 2 | 3 | export default function Footer() { 4 | return ( 5 |
6 |
7 | © {new Date().getFullYear()} {ABOUT_ME.name} · crafted in Next.js 8 |
9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/app/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ThemeProvider } from "next-themes"; 4 | 5 | export function Providers({ children }: { children: React.ReactNode }) { 6 | return ( 7 | 13 | {children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/ui/FluidCursor.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useRef } from "react"; 3 | import useFluidCursor from "@/hooks/use-FluidCursor"; 4 | 5 | const FluidCursor = () => { 6 | const canvasRef = useRef(null); 7 | useFluidCursor(canvasRef); 8 | 9 | return ( 10 |
11 | 12 |
13 | ); 14 | }; 15 | 16 | export default FluidCursor; 17 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | .pnp 4 | .pnp.js 5 | 6 | # Testing 7 | coverage 8 | 9 | # Next.js 10 | .next 11 | out 12 | build 13 | dist 14 | 15 | # Production 16 | *.min.js 17 | *.min.css 18 | 19 | # Misc 20 | .DS_Store 21 | *.pem 22 | 23 | # Debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Local env files 29 | .env*.local 30 | .env 31 | 32 | # Vercel 33 | .vercel 34 | 35 | # TypeScript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | # Package managers 40 | package-lock.json 41 | yarn.lock 42 | pnpm-lock.yaml 43 | 44 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | { 15 | rules: { 16 | // Disable ESLint formatting rules that conflict with Prettier 17 | "prettier/prettier": "off", 18 | }, 19 | }, 20 | ]; 21 | 22 | export default eslintConfig; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /src/components/ui/grid-pattern.tsx: -------------------------------------------------------------------------------- 1 | export default function GridPattern() { 2 | return ( 3 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/ui/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTheme } from "next-themes"; 4 | import { useEffect, useState } from "react"; 5 | import { FiSun, FiMoon } from "react-icons/fi"; 6 | 7 | export default function ThemeToggle() { 8 | const { theme, setTheme } = useTheme(); 9 | const [mounted, setMounted] = useState(false); 10 | 11 | useEffect(() => { 12 | setMounted(true); 13 | }, []); 14 | 15 | if (!mounted) { 16 | return ( 17 |
18 |
{/* Matches icon size */} 19 |
20 | ); 21 | } 22 | 23 | return ( 24 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Shasbin AS 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 | -------------------------------------------------------------------------------- /src/app/opengraph-image.tsx: -------------------------------------------------------------------------------- 1 | import { ImageResponse } from "next/og"; 2 | import { ABOUT_ME, SOCIAL_LINKS } from "../components/constants/data"; 3 | 4 | export const runtime = "edge"; 5 | export const alt = `${ABOUT_ME.name} - Portfolio Website`; 6 | export const size = { 7 | width: 1200, 8 | height: 630, 9 | }; 10 | export const contentType = "image/png"; 11 | 12 | export default async function Image() { 13 | return new ImageResponse( 14 | ( 15 |
26 |

27 | {ABOUT_ME.name} 28 |

29 |

30 | {ABOUT_ME.title} 31 |

32 |

33 | {SOCIAL_LINKS.github.replace("https://github.com/", "github.com/")} 34 |

35 |
36 | ), 37 | { 38 | width: 1200, 39 | height: 630, 40 | } 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/app/twitter-image.tsx: -------------------------------------------------------------------------------- 1 | import { ImageResponse } from "next/og"; 2 | import { ABOUT_ME, SOCIAL_LINKS } from "../components/constants/data"; 3 | 4 | export const runtime = "edge"; 5 | export const alt = `${ABOUT_ME.name} - Portfolio Website`; 6 | export const size = { 7 | width: 1200, 8 | height: 630, 9 | }; 10 | export const contentType = "image/png"; 11 | 12 | export default async function TwitterImage() { 13 | return new ImageResponse( 14 | ( 15 |
26 |

27 | {ABOUT_ME.name} 28 |

29 |

30 | {ABOUT_ME.title} 31 |

32 |

33 | {SOCIAL_LINKS.github.replace("https://github.com/", "github.com/")} 34 |

35 |
36 | ), 37 | { 38 | width: 1200, 39 | height: 630, 40 | } 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-portfolio-nextjs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/shasbinas/my-portfolio-nextjs.git" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "bugs": { 14 | "url": "https://github.com/shasbinas/my-portfolio-nextjs/issues" 15 | }, 16 | "homepage": "https://github.com/shasbinas/my-portfolio-nextjs#readme", 17 | "scripts": { 18 | "dev": "next dev --turbopack", 19 | "build": "next build", 20 | "start": "next start", 21 | "lint": "next lint", 22 | "format": "prettier --write .", 23 | "format:check": "prettier --check ." 24 | }, 25 | "dependencies": { 26 | "@vercel/analytics": "^1.5.0", 27 | "framer-motion": "^12.23.25", 28 | "lucide-react": "^0.536.0", 29 | "next": "^15.5.6", 30 | "next-themes": "^0.4.6", 31 | "react": "19.1.0", 32 | "react-dom": "19.1.0", 33 | "react-github-calendar": "^4.5.9", 34 | "react-icons": "^5.5.0" 35 | }, 36 | "devDependencies": { 37 | "@eslint/eslintrc": "^3", 38 | "@tailwindcss/postcss": "^4", 39 | "@types/node": "^20", 40 | "@types/react": "^19", 41 | "@types/react-dom": "^19", 42 | "eslint": "^9", 43 | "eslint-config-next": "15.4.5", 44 | "eslint-config-prettier": "^10.1.8", 45 | "prettier": "^3.6.2", 46 | "tailwindcss": "^4", 47 | "typescript": "^5" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/sections/education.tsx: -------------------------------------------------------------------------------- 1 | import { EDUCATION } from "@/components/constants/data"; 2 | import Link from "next/link"; 3 | import { MdOutlineArrowOutward } from "react-icons/md"; 4 | 5 | export default function Education() { 6 | return ( 7 |
8 |
9 |

education.

10 |

11 | Continuous learning through formal programs and self-directed 12 | research. 13 |

14 |
15 | 16 |
17 | {EDUCATION.map((edu, index) => ( 18 |
19 |
20 |

{edu.degree}

21 | 22 | {edu.period} 23 | 24 |
25 | 31 | {edu.institution} 32 | 33 | 34 |
35 | ))} 36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/components/ui/github-star-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Link from "next/link"; 3 | import { SiGithub } from "react-icons/si"; 4 | 5 | export default function GithubStarButton() { 6 | return ( 7 |
8 | 15 | 20 | 21 | {/* Tooltip */} 22 | 23 | Give it a ⭐ on GitHub! 24 | {/* Tooltip arrow pointing left */} 25 | 26 | 27 | 28 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#ab307e", 4 | "activityBar.background": "#ab307e", 5 | "activityBar.foreground": "#e7e7e7", 6 | "activityBar.inactiveForeground": "#e7e7e799", 7 | "activityBarBadge.background": "#25320e", 8 | "activityBarBadge.foreground": "#e7e7e7", 9 | "commandCenter.border": "#e7e7e799", 10 | "sash.hoverBorder": "#ab307e", 11 | "statusBar.background": "#832561", 12 | "statusBar.foreground": "#e7e7e7", 13 | "statusBarItem.hoverBackground": "#ab307e", 14 | "statusBarItem.remoteBackground": "#832561", 15 | "statusBarItem.remoteForeground": "#e7e7e7", 16 | "titleBar.activeBackground": "#832561", 17 | "titleBar.activeForeground": "#e7e7e7", 18 | "titleBar.inactiveBackground": "#83256199", 19 | "titleBar.inactiveForeground": "#e7e7e799" 20 | }, 21 | "peacock.color": "#832561", 22 | "editor.defaultFormatter": "esbenp.prettier-vscode", 23 | "editor.formatOnSave": true, 24 | "editor.codeActionsOnSave": { 25 | "source.fixAll.eslint": "explicit" 26 | }, 27 | "[javascript]": { 28 | "editor.defaultFormatter": "esbenp.prettier-vscode" 29 | }, 30 | "[javascriptreact]": { 31 | "editor.defaultFormatter": "esbenp.prettier-vscode" 32 | }, 33 | "[typescript]": { 34 | "editor.defaultFormatter": "esbenp.prettier-vscode" 35 | }, 36 | "[typescriptreact]": { 37 | "editor.defaultFormatter": "esbenp.prettier-vscode" 38 | }, 39 | "[json]": { 40 | "editor.defaultFormatter": "esbenp.prettier-vscode" 41 | }, 42 | "[jsonc]": { 43 | "editor.defaultFormatter": "esbenp.prettier-vscode" 44 | }, 45 | "[css]": { 46 | "editor.defaultFormatter": "esbenp.prettier-vscode" 47 | }, 48 | "[scss]": { 49 | "editor.defaultFormatter": "esbenp.prettier-vscode" 50 | }, 51 | "[html]": { 52 | "editor.defaultFormatter": "esbenp.prettier-vscode" 53 | }, 54 | "[markdown]": { 55 | "editor.defaultFormatter": "esbenp.prettier-vscode" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import GridPattern from "@/components/ui/grid-pattern"; 2 | import ScrollToTop from "@/components/ui/scroll-to-top"; 3 | import GithubStarButton from "@/components/ui/github-star-button"; 4 | import Navbar from "@/components/sections/navbar"; 5 | import Header from "@/components/sections/header"; 6 | import AboutMe from "@/components/sections/about-me"; 7 | import Skills from "@/components/sections/skills"; 8 | import Projects from "@/components/sections/projects"; 9 | import Experience from "@/components/sections/experience"; 10 | import Education from "@/components/sections/education"; 11 | import Achievements from "@/components/sections/achievements"; 12 | import Github from "@/components/sections/github"; 13 | import LeetCode from "@/components/sections/leetcode"; 14 | // import Blog from "@/components/sections/blog"; 15 | import Contact from "@/components/sections/contact"; 16 | import Footer from "@/components/sections/footer"; 17 | 18 | export default function Home() { 19 | return ( 20 |
21 | 22 |
23 | 24 |
25 | 26 | 27 | 28 | 29 | 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 | -------------------------------------------------------------------------------- /src/components/sections/experience.tsx: -------------------------------------------------------------------------------- 1 | import { EXPERIENCE } from "@/components/constants/data"; 2 | import Link from "next/link"; 3 | import { MdOutlineArrowOutward } from "react-icons/md"; 4 | 5 | export default function Experience() { 6 | return ( 7 |
8 |
9 |

work experience.

10 |

11 | Collaborating with teams to ship resilient platforms, streamline 12 | developer workflows, and ship products with a focus on security, 13 | scale, and craft. 14 |

15 |
16 | 17 |
18 | {EXPERIENCE.map((exp, index) => ( 19 |
20 |
21 |
22 |

23 | {exp.period} 24 |

25 |

{exp.role}

26 | 32 | {exp.company} 33 | 34 | 35 |
36 | 37 | 38 | {exp.location} 39 | 40 |
41 | 42 |

43 | {exp.description} 44 |

45 | 46 |
47 | {exp.skills.map((skill) => ( 48 | 49 | {skill} 50 | 51 | ))} 52 |
53 |
54 | ))} 55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /src/components/sections/projects.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { PROJECTS } from "@/components/constants/data"; 3 | 4 | export default function Projects() { 5 | return ( 6 |
7 |
8 |

featured builds.

9 |

10 | Shipping polished experiences from rapid prototypes to 11 | production-ready systems. Each build blends performance, resilience, 12 | and a touch of futurism. 13 |

14 |
15 | 16 |
17 | {PROJECTS.map((project) => ( 18 |
19 |
20 |
21 |

22 | {project.name} 23 |

24 |

25 | featured case study 26 |

27 |
28 |
29 | {project.link && ( 30 | 37 | live 38 | 39 | )} 40 | 47 | code 48 | 49 |
50 |
51 | 52 |

53 | {project.description} 54 |

55 | 56 |
57 | {project.tech.map((tech) => ( 58 | 59 | {tech} 60 | 61 | ))} 62 |
63 |
64 | ))} 65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/components/ui/scroll-to-top.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState, useEffect } from "react"; 3 | import { FaArrowUp } from "react-icons/fa"; 4 | 5 | export default function ScrollToTop() { 6 | const [isVisible, setIsVisible] = useState(false); 7 | const [scrollProgress, setScrollProgress] = useState(0); 8 | 9 | useEffect(() => { 10 | const handleScroll = () => { 11 | const scrollTop = window.pageYOffset; 12 | const docHeight = 13 | document.documentElement.scrollHeight - window.innerHeight; 14 | const scrollPercent = (scrollTop / docHeight) * 100; 15 | 16 | setIsVisible(scrollTop > 100); 17 | setScrollProgress(scrollPercent); 18 | }; 19 | 20 | window.addEventListener("scroll", handleScroll); 21 | return () => window.removeEventListener("scroll", handleScroll); 22 | }, []); 23 | 24 | const scrollToTop = () => { 25 | window.scrollTo({ top: 0, behavior: "smooth" }); 26 | }; 27 | 28 | return ( 29 |
36 | 77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional stylelint cache 57 | .stylelintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variable files 69 | .env 70 | .env.* 71 | !.env.example 72 | 73 | # parcel-bundler cache (https://parceljs.org/) 74 | .cache 75 | .parcel-cache 76 | 77 | # Next.js build output 78 | .next 79 | out 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and not Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # vuepress v2.x temp and cache directory 95 | .temp 96 | .cache 97 | 98 | # Sveltekit cache directory 99 | .svelte-kit/ 100 | 101 | # vitepress build output 102 | **/.vitepress/dist 103 | 104 | # vitepress cache directory 105 | **/.vitepress/cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # Firebase cache directory 120 | .firebase/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v3 129 | .pnp.* 130 | .yarn/* 131 | !.yarn/patches 132 | !.yarn/plugins 133 | !.yarn/releases 134 | !.yarn/sdks 135 | !.yarn/versions 136 | 137 | # Vite logs files 138 | vite.config.js.timestamp-* 139 | vite.config.ts.timestamp-* 140 | -------------------------------------------------------------------------------- /src/components/sections/skills.tsx: -------------------------------------------------------------------------------- 1 | import { SKILLS } from "@/components/constants/data"; 2 | import Image from "next/image"; 3 | 4 | const SKILL_ICONS: Record = { 5 | HTML: "html", 6 | CSS: "css", 7 | JavaScript: "js", 8 | TypeScript: "ts", 9 | React: "react", 10 | "Next.js": "nextjs", 11 | "Vue.js": "vue", 12 | Angular: "angular", 13 | Redux: "redux", 14 | TailwindCSS: "tailwind", 15 | Bootstrap: "bootstrap", 16 | Sass: "sass", 17 | Less: "less", 18 | jQuery: "jquery", 19 | "Node.js": "nodejs", 20 | "Express.js": "express", 21 | NestJS: "nestjs", 22 | Django: "django", 23 | Flask: "flask", 24 | "Spring Boot": "spring", 25 | "Ruby on Rails": "rails", 26 | Laravel: "laravel", 27 | "ASP.NET": "dotnet", 28 | FastAPI: "fastapi", 29 | MongoDB: "mongodb", 30 | MySQL: "mysql", 31 | PostgreSQL: "postgresql", 32 | SQLite: "sqlite", 33 | Redis: "redis", 34 | Firebase: "firebase", 35 | Supabase: "supabase", 36 | C: "c", 37 | "C++": "cpp", 38 | "C#": "cs", 39 | Java: "java", 40 | Python: "py", 41 | Go: "go", 42 | Rust: "rust", 43 | Ruby: "ruby", 44 | PHP: "php", 45 | Kotlin: "kotlin", 46 | Swift: "swift", 47 | Dart: "dart", 48 | Scala: "scala", 49 | AWS: "aws", 50 | Azure: "azure", 51 | GCP: "gcp", 52 | Docker: "docker", 53 | Kubernetes: "kubernetes", 54 | Vercel: "vercel", 55 | Netlify: "netlify", 56 | Heroku: "heroku", 57 | "GitHub Actions": "githubactions", 58 | Jenkins: "jenkins", 59 | "VS Code": "vscode", 60 | Git: "git", 61 | GitHub: "github", 62 | GitLab: "gitlab", 63 | Bitbucket: "bitbucket", 64 | Postman: "postman", 65 | Figma: "figma", 66 | Vite: "vite", 67 | Webpack: "webpack", 68 | Babel: "babel", 69 | Jest: "jest", 70 | Cypress: "cypress", 71 | Linux: "linux", 72 | Windows: "windows", 73 | MacOS: "apple", 74 | }; 75 | 76 | const SkillBadge = ({ skill }: { skill: string }) => ( 77 | 78 | {SKILL_ICONS[skill] && ( 79 | {`${skill} 86 | )} 87 | {skill} 88 | 89 | ); 90 | 91 | const Skills = () => { 92 | return ( 93 |
94 |

technical skills.

95 |
96 | {Object.entries(SKILLS).map(([key, skills]) => ( 97 |
102 |

103 | {"< " + key + " />"} 104 |

105 |
106 | {skills.map((skill) => ( 107 | 108 | ))} 109 |
110 |
111 | ))} 112 |
113 |
114 | ); 115 | }; 116 | 117 | export default Skills; 118 | -------------------------------------------------------------------------------- /src/components/sections/about-me.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | import { ABOUT_ME, SOCIAL_LINKS } from "@/components/constants/data"; 5 | import { Mail, FileText } from "lucide-react"; 6 | import { SiLeetcode } from "react-icons/si"; 7 | import { FaGithub, FaLinkedin } from "react-icons/fa"; 8 | 9 | // ============================================= 10 | // SOCIAL BUTTONS DATA 11 | // ============================================= 12 | const SOCIAL_BUTTONS = [ 13 | { 14 | href: SOCIAL_LINKS.github, 15 | label: "GitHub", 16 | icon: , 17 | }, 18 | { 19 | href: SOCIAL_LINKS.linkedin, 20 | label: "LinkedIn", 21 | icon: , 22 | }, 23 | { 24 | href: SOCIAL_LINKS.leetcode, 25 | label: "LeetCode", 26 | icon: , 27 | }, 28 | { 29 | href: SOCIAL_LINKS.resume, 30 | label: "Resume", 31 | icon: ( 32 | <> 33 | Resume 34 | 35 | ), 36 | className: "flex items-center gap-2 font-medium", 37 | }, 38 | { 39 | href: SOCIAL_LINKS.email, 40 | label: "Email", 41 | icon: ( 42 | <> 43 | Email 44 | 45 | ), 46 | className: "flex items-center gap-2 font-medium", 47 | }, 48 | ]; 49 | 50 | const handleSpecialNavigation = ( 51 | event: React.MouseEvent, 52 | href: string 53 | ) => { 54 | if (!href) return; 55 | const trimmed = href.trim(); 56 | if ( 57 | typeof window !== "undefined" && 58 | (trimmed.startsWith("mailto:") || trimmed.startsWith("tel:")) 59 | ) { 60 | event.preventDefault(); 61 | window.location.href = trimmed; 62 | } 63 | }; 64 | 65 | // ============================================= 66 | // MAIN COMPONENT 67 | // ============================================= 68 | export default function AboutMe() { 69 | return ( 70 |
71 |
72 |
73 | {ABOUT_ME.description.map((para, i) => ( 74 |

{para}

75 | ))} 76 |
77 | 78 |
79 | {SOCIAL_BUTTONS.filter( 80 | (btn) => btn.href && btn.href.trim() !== "" 81 | ).map((btn) => { 82 | const href = btn.href.trim(); 83 | const isExternal = href.startsWith("http"); 84 | 85 | const isSpecial = 86 | href.startsWith("mailto:") || href.startsWith("tel:"); 87 | 88 | if (isExternal) { 89 | return ( 90 | 99 | {btn.icon} 100 | 101 | ); 102 | } 103 | 104 | return ( 105 | 109 | isSpecial && handleSpecialNavigation(event, href) 110 | } 111 | className={`inline-flex items-center gap-2 rounded-full border px-4 py-2 text-sm font-medium text-foreground/85 tracking-wide transition-all hover-lift ${btn.className || ""}`} 112 | style={{ borderColor: "hsl(var(--border) / 0.6)" }} 113 | aria-label={btn.label} 114 | > 115 | {btn.icon} 116 | 117 | ); 118 | })} 119 |
120 |
121 |
122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /src/components/sections/achievements.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | const PLACEHOLDER_BADGES = [ 4 | // { 5 | // title: "GitHub Foundations", 6 | // platform: "GitHub", 7 | // description: "GitHub Foundations certification", 8 | // imagePath: "https://images.credly.com/images/024d0122-724d-4c5a-bd83-cfe3c4b7a073/image.png" 9 | // }, 10 | { 11 | title: "50 Days Badge", 12 | platform: "LeetCode", 13 | description: "Solved problems for 50 consecutive days", 14 | imagePath: "https://assets.leetcode.com/static_assets/others/2550.gif", 15 | }, 16 | { 17 | title: "100 Days Badge", 18 | platform: "LeetCode", 19 | description: "Solved problems for 100 consecutive days", 20 | imagePath: 21 | "https://assets.leetcode.com/static_assets/marketing/2024-100.gif", 22 | }, 23 | { 24 | title: "Pull Shark", 25 | platform: "GitHub", 26 | description: "Opened pull requests that have been merged", 27 | imagePath: 28 | "https://github.githubassets.com/images/modules/profile/achievements/pull-shark-default.png", 29 | }, 30 | { 31 | title: "Quickdraw", 32 | platform: "GitHub", 33 | description: "Closed an issue or pull request within 5 minutes", 34 | imagePath: 35 | "https://github.githubassets.com/images/modules/profile/achievements/quickdraw-default.png", 36 | }, 37 | { 38 | title: "YOLO", 39 | platform: "GitHub", 40 | description: "Merged a pull request without code review", 41 | imagePath: 42 | "https://github.githubassets.com/images/modules/profile/achievements/yolo-default.png", 43 | }, 44 | { 45 | title: "Starstruck", 46 | platform: "GitHub", 47 | description: "Created a repository that has 16+ stars", 48 | imagePath: 49 | "https://github.githubassets.com/images/modules/profile/achievements/starstruck-default.png", 50 | }, 51 | { 52 | title: "Pair Extraordinaire", 53 | platform: "GitHub", 54 | description: "Co-authored commits on merged pull requests", 55 | imagePath: 56 | "https://github.githubassets.com/images/modules/profile/achievements/pair-extraordinaire-default.png", 57 | }, 58 | ]; 59 | 60 | const BadgeCard = ({ 61 | title, 62 | platform, 63 | description, 64 | imagePath, 65 | }: (typeof PLACEHOLDER_BADGES)[number]) => ( 66 |
67 |
68 | {imagePath ? ( 69 | // eslint-disable-next-line @next/next/no-img-element 70 | {title} 75 | ) : ( 76 | 77 | Badge Image 78 | 79 | )} 80 |
81 |
82 |

{title}

83 |

84 | {platform} 85 |

86 |

{description}

87 |
88 |
89 | ); 90 | 91 | export default function Achievements() { 92 | return ( 93 |
94 |
95 |

achievements.

96 |

97 | Showcase GitHub trophies, LeetCode badges, hackathon recognitions, or 98 | certifications. Drop your media into any card to keep this reel up to 99 | date. 100 |

101 |
102 | 103 |
104 | {PLACEHOLDER_BADGES.map((badge) => ( 105 | 106 | ))} 107 |
108 |
109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Outfit } from "next/font/google"; 3 | import "./globals.css"; 4 | import { Providers } from "./providers"; 5 | import { Analytics } from "@vercel/analytics/next"; 6 | import FluidCursor from "@/components/ui/FluidCursor"; 7 | 8 | const outfit = Outfit({ 9 | variable: "--font-outfit", 10 | subsets: ["latin"], 11 | display: "swap", 12 | preload: true, 13 | }); 14 | 15 | export const metadata: Metadata = { 16 | title: "Shasbin AS - Portfolio Website", 17 | description: 18 | "Your portfolio showcasing your development projects and skills. Update this description with your own details.", 19 | keywords: [ 20 | "Shasbin AS ", 21 | "Full Stack Developer", 22 | "React", 23 | "Next.js", 24 | "TypeScript", 25 | "Web Development", 26 | "Portfolio", 27 | "Frontend Developer", 28 | "Backend Developer", 29 | "JavaScript", 30 | "Node.js", 31 | "Your City", 32 | "Your Country", 33 | ], 34 | authors: [{ name: "Shasbin AS " }], 35 | creator: "Shasbin AS ", 36 | publisher: "Shasbin AS ", 37 | metadataBase: new URL("https://your-portfolio-url.com"), 38 | alternates: { 39 | canonical: "/", 40 | }, 41 | openGraph: { 42 | type: "website", 43 | locale: "en_US", 44 | url: "https://your-portfolio-url.com", 45 | title: "Shasbin AS - Portfolio Website for a Full Stack Developer", 46 | description: 47 | "Personal portfolio showcasing skills, projects, experience, and more. Built with modern web technologies.", 48 | siteName: "Shasbin AS Portfolio", 49 | }, 50 | twitter: { 51 | card: "summary_large_image", 52 | title: "Shasbin AS - Portfolio Website for a Full Stack Developer", 53 | description: 54 | "Personal portfolio showcasing skills, projects, experience, and more. Built with modern web technologies.", 55 | creator: "@your_twitter_handle", 56 | }, 57 | robots: { 58 | index: true, 59 | follow: true, 60 | googleBot: { 61 | index: true, 62 | follow: true, 63 | "max-video-preview": -1, 64 | "max-image-preview": "large", 65 | "max-snippet": -1, 66 | }, 67 | }, 68 | verification: { 69 | google: "", 70 | }, 71 | }; 72 | 73 | export default function RootLayout({ 74 | children, 75 | }: Readonly<{ 76 | children: React.ReactNode; 77 | }>) { 78 | return ( 79 | 84 | 85 | 86 | 87 | 92 | 93 | 94 |