feat: portfolio done + minimal working blog

Signed-off-by: Ameya Shenoy <shenoy.ameya@gmail.com>
This commit is contained in:
Ameya Shenoy 2025-06-23 09:22:26 +05:30
parent 360fb2d3d6
commit e74f75ae4b
9 changed files with 962 additions and 64 deletions

View file

@ -13,13 +13,17 @@
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dompurify": "^3.2.6",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"lucide-react": "^0.511.0", "lucide-react": "^0.511.0",
"marked": "^15.0.12",
"marked-shiki": "^1.2.0",
"matter-js": "^0.20.0", "matter-js": "^0.20.0",
"next": "15.3.3", "next": "15.3.3",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"shiki": "^3.7.0",
"tailwind-merge": "^3.3.0", "tailwind-merge": "^3.3.0",
"tw-to-css": "^0.0.12" "tw-to-css": "^0.0.12"
}, },
@ -771,6 +775,17 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@opentelemetry/api": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"optional": true,
"peer": true,
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@pkgjs/parseargs": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -1314,6 +1329,73 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@shikijs/core": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.7.0.tgz",
"integrity": "sha512-yilc0S9HvTPyahHpcum8eonYrQtmGTU0lbtwxhA6jHv4Bm1cAdlPFRCJX4AHebkCm75aKTjjRAW+DezqD1b/cg==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.7.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4",
"hast-util-to-html": "^9.0.5"
}
},
"node_modules/@shikijs/engine-javascript": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.7.0.tgz",
"integrity": "sha512-0t17s03Cbv+ZcUvv+y33GtX75WBLQELgNdVghnsdhTgU3hVcWcMsoP6Lb0nDTl95ZJfbP1mVMO0p3byVh3uuzA==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.7.0",
"@shikijs/vscode-textmate": "^10.0.2",
"oniguruma-to-es": "^4.3.3"
}
},
"node_modules/@shikijs/engine-oniguruma": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.7.0.tgz",
"integrity": "sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.7.0",
"@shikijs/vscode-textmate": "^10.0.2"
}
},
"node_modules/@shikijs/langs": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.7.0.tgz",
"integrity": "sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.7.0"
}
},
"node_modules/@shikijs/themes": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.7.0.tgz",
"integrity": "sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.7.0"
}
},
"node_modules/@shikijs/types": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.7.0.tgz",
"integrity": "sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
}
},
"node_modules/@shikijs/vscode-textmate": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
"license": "MIT"
},
"node_modules/@swc/counter": { "node_modules/@swc/counter": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@ -1605,6 +1687,24 @@
"tailwindcss": "4.1.8" "tailwindcss": "4.1.8"
} }
}, },
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"license": "MIT",
"dependencies": {
"@types/unist": "*"
}
},
"node_modules/@types/mdast": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
"license": "MIT",
"dependencies": {
"@types/unist": "*"
}
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.17.57", "version": "20.17.57",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz",
@ -1635,6 +1735,25 @@
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
"license": "MIT"
},
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"license": "ISC"
},
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
@ -1775,6 +1894,36 @@
], ],
"license": "CC-BY-4.0" "license": "CC-BY-4.0"
}, },
"node_modules/ccount": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/character-entities-html4": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
"integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/character-entities-legacy": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
"integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@ -1891,6 +2040,16 @@
"simple-swizzle": "^0.2.2" "simple-swizzle": "^0.2.2"
} }
}, },
"node_modules/comma-separated-tokens": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
"integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/commander": { "node_modules/commander": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@ -1933,6 +2092,15 @@
"devOptional": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
@ -1949,6 +2117,19 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/devlop": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/didyoumean": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@ -1961,6 +2142,15 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/dompurify": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/eastasianwidth": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -2156,6 +2346,52 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/unist": "^3.0.0",
"ccount": "^2.0.0",
"comma-separated-tokens": "^2.0.0",
"hast-util-whitespace": "^3.0.0",
"html-void-elements": "^3.0.0",
"mdast-util-to-hast": "^13.0.0",
"property-information": "^7.0.0",
"space-separated-tokens": "^2.0.0",
"stringify-entities": "^4.0.0",
"zwitch": "^2.0.4"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-whitespace": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
"integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/html-void-elements": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-arrayish": { "node_modules/is-arrayish": {
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
@ -2557,12 +2793,55 @@
"@jridgewell/sourcemap-codec": "^1.5.0" "@jridgewell/sourcemap-codec": "^1.5.0"
} }
}, },
"node_modules/marked": {
"version": "15.0.12",
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/marked-shiki": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/marked-shiki/-/marked-shiki-1.2.0.tgz",
"integrity": "sha512-N924hp8veE6Mc91g5/kCNVoTU7TkeJfB2G2XEWb+k1fVA0Bck2T0rVt93d39BlOYH6ohP4Q9BFlPk+UkblhXbg==",
"license": "MIT",
"peerDependencies": {
"marked": ">=7.0.0",
"shiki": ">=1.0.0"
}
},
"node_modules/matter-js": { "node_modules/matter-js": {
"version": "0.20.0", "version": "0.20.0",
"resolved": "https://registry.npmjs.org/matter-js/-/matter-js-0.20.0.tgz", "resolved": "https://registry.npmjs.org/matter-js/-/matter-js-0.20.0.tgz",
"integrity": "sha512-iC9fYR7zVT3HppNnsFsp9XOoQdQN2tUyfaKg4CHLH8bN+j6GT4Gw7IH2rP0tflAebrHFw730RR3DkVSZRX8hwA==", "integrity": "sha512-iC9fYR7zVT3HppNnsFsp9XOoQdQN2tUyfaKg4CHLH8bN+j6GT4Gw7IH2rP0tflAebrHFw730RR3DkVSZRX8hwA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/mdast-util-to-hast": {
"version": "13.2.0",
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
"integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/mdast": "^4.0.0",
"@ungap/structured-clone": "^1.0.0",
"devlop": "^1.0.0",
"micromark-util-sanitize-uri": "^2.0.0",
"trim-lines": "^3.0.0",
"unist-util-position": "^5.0.0",
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -2572,6 +2851,95 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/micromark-util-character": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
"integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
"funding": [
{
"type": "GitHub Sponsors",
"url": "https://github.com/sponsors/unifiedjs"
},
{
"type": "OpenCollective",
"url": "https://opencollective.com/unified"
}
],
"license": "MIT",
"dependencies": {
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
}
},
"node_modules/micromark-util-encode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
"integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
"funding": [
{
"type": "GitHub Sponsors",
"url": "https://github.com/sponsors/unifiedjs"
},
{
"type": "OpenCollective",
"url": "https://opencollective.com/unified"
}
],
"license": "MIT"
},
"node_modules/micromark-util-sanitize-uri": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
"integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
"funding": [
{
"type": "GitHub Sponsors",
"url": "https://github.com/sponsors/unifiedjs"
},
{
"type": "OpenCollective",
"url": "https://opencollective.com/unified"
}
],
"license": "MIT",
"dependencies": {
"micromark-util-character": "^2.0.0",
"micromark-util-encode": "^2.0.0",
"micromark-util-symbol": "^2.0.0"
}
},
"node_modules/micromark-util-symbol": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
"integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
"funding": [
{
"type": "GitHub Sponsors",
"url": "https://github.com/sponsors/unifiedjs"
},
{
"type": "OpenCollective",
"url": "https://opencollective.com/unified"
}
],
"license": "MIT"
},
"node_modules/micromark-util-types": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
"integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
"funding": [
{
"type": "GitHub Sponsors",
"url": "https://github.com/sponsors/unifiedjs"
},
{
"type": "OpenCollective",
"url": "https://opencollective.com/unified"
}
],
"license": "MIT"
},
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@ -2786,6 +3154,23 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/oniguruma-parser": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz",
"integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==",
"license": "MIT"
},
"node_modules/oniguruma-to-es": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz",
"integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==",
"license": "MIT",
"dependencies": {
"oniguruma-parser": "^0.12.1",
"regex": "^6.0.1",
"regex-recursion": "^6.0.2"
}
},
"node_modules/package-json-from-dist": { "node_modules/package-json-from-dist": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@ -2981,6 +3366,16 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/property-information": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -3116,6 +3511,30 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz",
"integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==",
"license": "MIT",
"dependencies": {
"regex-utilities": "^2.3.0"
}
},
"node_modules/regex-recursion": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz",
"integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
"license": "MIT",
"dependencies": {
"regex-utilities": "^2.3.0"
}
},
"node_modules/regex-utilities": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
"integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
"license": "MIT"
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.10", "version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@ -3254,6 +3673,22 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/shiki": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-3.7.0.tgz",
"integrity": "sha512-ZcI4UT9n6N2pDuM2n3Jbk0sR4Swzq43nLPgS/4h0E3B/NrFn2HKElrDtceSf8Zx/OWYOo7G1SAtBLypCp+YXqg==",
"license": "MIT",
"dependencies": {
"@shikijs/core": "3.7.0",
"@shikijs/engine-javascript": "3.7.0",
"@shikijs/engine-oniguruma": "3.7.0",
"@shikijs/langs": "3.7.0",
"@shikijs/themes": "3.7.0",
"@shikijs/types": "3.7.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
}
},
"node_modules/signal-exit": { "node_modules/signal-exit": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@ -3285,6 +3720,16 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/space-separated-tokens": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/streamsearch": { "node_modules/streamsearch": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@ -3352,6 +3797,20 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/stringify-entities": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
"integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
"license": "MIT",
"dependencies": {
"character-entities-html4": "^2.0.0",
"character-entities-legacy": "^3.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/strip-ansi": { "node_modules/strip-ansi": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
@ -3524,6 +3983,16 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/trim-lines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
"integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/ts-interface-checker": { "node_modules/ts-interface-checker": {
"version": "0.1.13", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
@ -3703,6 +4172,74 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unist-util-is": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
"integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-position": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
"integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-stringify-position": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
"integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-visit": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
"integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-is": "^6.0.0",
"unist-util-visit-parents": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-visit-parents": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
"integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-is": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/use-callback-ref": { "node_modules/use-callback-ref": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
@ -3752,6 +4289,34 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/vfile": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
"integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0",
"vfile-message": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/vfile-message": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
"integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-stringify-position": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -3879,6 +4444,16 @@
"engines": { "engines": {
"node": ">= 14.6" "node": ">= 14.6"
} }
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
"integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
} }
} }
} }

View file

@ -14,13 +14,17 @@
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dompurify": "^3.2.6",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"lucide-react": "^0.511.0", "lucide-react": "^0.511.0",
"marked": "^15.0.12",
"marked-shiki": "^1.2.0",
"matter-js": "^0.20.0", "matter-js": "^0.20.0",
"next": "15.3.3", "next": "15.3.3",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"shiki": "^3.7.0",
"tailwind-merge": "^3.3.0", "tailwind-merge": "^3.3.0",
"tw-to-css": "^0.0.12" "tw-to-css": "^0.0.12"
}, },

View file

@ -0,0 +1,24 @@
import MarkdownRenderer from "@/components/MarkdownRenderer";
import { promises as fs } from "fs";
export default async function BlogPage() {
try {
// Read markdown file from project root
const filePath = "src/app/blog/docker_primer/content.md";
const markdownContent = await fs.readFile(filePath, "utf8");
return (
<main className="flex flex-1 flex-col justify-end items-center font-[family-name:var(--font-inter-sans)] pt-10 md:pt-20 pb-25 md:pb-0">
<div className="md:w-[786px] w-[95%]">
<div className="p-5 pt-10 rounded-lg markdown">
<MarkdownRenderer markdown={markdownContent} />
</div>
</div>
</main>
);
} catch (error) {
console.error("Error loading markdown:", error);
return <div>Failed to load content</div>;
}
}

View file

@ -138,6 +138,111 @@
color: white; color: white;
pointer-events: none; pointer-events: none;
} }
pre.shiki {
position: relative;
z-index: 1;
padding: 0.5rem 0;
background: transparent;
border-radius: 0.5rem;
margin: 20px 0px;
}
pre.shiki code {
display: block;
padding: 2rem 1rem 0.5rem 1rem;
transition: color 0.5s;
overflow-x: auto; /* Enables horizontal scrolling */
white-space: pre; /* Prevents line wrapping */
max-width: 100%; /* Ensures it doesn't overflow its container */
}
pre.shiki .copy-code-btn {
position: absolute;
right: 8px;
top: 8px;
z-index: 10;
background: #333;
color: #fff;
border: none;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
}
pre.shiki .filetype-code-btn {
position: absolute;
left: 8px;
top: 8px;
z-index: 10;
background: rgba(255, 255, 255, 0.15);
color: #fff;
border-radius: 4px;
padding: 2px 8px;
font-size: 12px;
font-family: monospace;
text-transform: uppercase;
pointer-events: none;
user-select: none;
}
.markdown hr {
height: 4px;
background: var(--sidebar-accent);
border: none;
}
.markdown p {
margin: 1em 0;
}
.markdown ul,
.markdown ol {
margin: 1em 0 1em 2em;
padding: 0;
list-style-position: outside;
}
.markdown ul {
list-style-type: disc;
}
.markdown ol {
list-style-type: decimal;
}
.markdown li {
margin: 0.3em 0;
line-height: 1.6;
}
.markdown blockquote {
border-left: 4px solid #ccc;
margin: 1em 0;
padding: 0.5em 1em;
background: var(--sidebar-accent);
}
.markdown table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
.markdown th,
.markdown td {
border: 1px solid #ddd;
padding: 0.5em 1em;
text-align: left;
}
.markdown th {
background: var(--sidebar-accent);
font-weight: bold;
}
.markdown a {
text-decoration: underline;
}
} }
@media (min-width: 768px) { @media (min-width: 768px) {

View file

@ -1,4 +1,4 @@
import { MapPin, Code, Briefcase, FileUser } from "lucide-react"; import { MapPin, Code, Briefcase, Download } from "lucide-react";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import Link from "next/link"; import Link from "next/link";
@ -23,13 +23,6 @@ export default async function Portfolio() {
TypeScript: "vscode-icons:file-type-typescript-official", TypeScript: "vscode-icons:file-type-typescript-official",
Golang: "material-icon-theme:go", Golang: "material-icon-theme:go",
}, },
"Web Frameworks": {
FastAPI: "devicon:fastapi",
Django: "material-icon-theme:django",
Flask: "simple-icons:flask",
ReactJS: "material-icon-theme:react",
"Next.js": "devicon:nextjs",
},
AI: { AI: {
LangChain: "simple-icons:langchain", LangChain: "simple-icons:langchain",
Livekit: null, Livekit: null,
@ -47,8 +40,25 @@ export default async function Portfolio() {
qdrant: "logos:qdrant-icon", qdrant: "logos:qdrant-icon",
MySQL: "logos:mysql-icon", MySQL: "logos:mysql-icon",
}, },
"Cloud Providers": {
AWS: "logos:aws",
"Microsoft Azure": "material-icon-theme:azure",
"Google Cloud Platform GCP": "material-icon-theme:gcp",
DigitalOcean: "devicon:digitalocean",
// Scaleway: "simple-icons:scaleway",
// Hetzner: "simple-icons:hetzner",
Cloudflare: "devicon:cloudflare",
},
}; };
const skillsRight = { const skillsRight = {
"Web Frameworks": {
FastAPI: "devicon:fastapi",
Django: "material-icon-theme:django",
"Next.js": "devicon:nextjs",
Flask: "simple-icons:flask",
ReactJS: "material-icon-theme:react",
},
DevOps: { DevOps: {
Kubernetes: "material-icon-theme:kubernetes", Kubernetes: "material-icon-theme:kubernetes",
Ansible: "devicon:ansible", Ansible: "devicon:ansible",
@ -70,15 +80,6 @@ export default async function Portfolio() {
Kafka: "devicon:apachekafka", Kafka: "devicon:apachekafka",
// Selenium: "devicon:selenium", // Selenium: "devicon:selenium",
}, },
"Cloud Providers": {
AWS: "logos:aws",
"Microsoft Azure": "material-icon-theme:azure",
"Google Cloud Platform GCP": "material-icon-theme:gcp",
DigitalOcean: "devicon:digitalocean",
// Scaleway: "simple-icons:scaleway",
// Hetzner: "simple-icons:hetzner",
Cloudflare: "devicon:cloudflare",
},
Robotics: { Robotics: {
"Robot Operating System ROS": "devicon:ros", "Robot Operating System ROS": "devicon:ros",
}, },
@ -144,61 +145,85 @@ export default async function Portfolio() {
]; ];
return ( return (
<main className="flex items-top justify-center md:pt-20 md:pb-0 pb-20 font-[family-name:var(--font-inter-sans)]"> <main className="flex items-top justify-center font-[family-name:var(--font-inter-sans)] pt-10 md:pt-20 pb-25 md:pb-0">
<div className=""> <div className="">
<div className="p-5 uppercase tracking-[0.2em] text-gray-500 dark:text-gray-100 flex items-center justify-between"> <div className="uppercase tracking-[0.2em] text-gray-500 dark:text-gray-100 flex items-center justify-between pb-20">
<div>About Me</div> <div>About Me</div>
<Link href="https://resume.codingcoffee.me"> <Link
<div className="flex border-gray-200 border-2 p-2 "> href="https://resume.codingcoffee.me"
Download >
<FileUser /> <div className="flex border-gray-200 border-2 p-2 pointer-cursor underline">
Resume
<Download />
</div> </div>
</Link> </Link>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 md:w-[40vw] w-[95vw] md:min-w-3xl md:gap-5 md:p-5"> <div className="grid grid-cols-1 md:grid-cols-2 md:w-[40vw] w-[95vw] md:min-w-3xl">
<div> <div>
<div> <div className="pb-10">
<div className="text-5xl md:text-6xl">Hi, I'm</div> <div className="text-5xl md:text-6xl pb-5">Hi, I'm</div>
<div className="text-5xl md:text-6xl text-red-500"> <div className="text-5xl md:text-6xl text-red-500">
Ameya Shenoy Ameya Shenoy
</div> </div>
</div> </div>
<div className="pb-5 text-gray-500 dark:text-gray-100 text-lg"> <div className="text-gray-500 dark:text-gray-100 text-lg pb-10">
<div className="flex items-center pt-5"> <div className="flex items-center pb-2">
<MapPin className="stroke-1 text-red-500" /> <MapPin className="stroke-1 text-red-500" />
<span className="pl-2">Based in India</span> <span className="pl-2">Based in India</span>
</div> </div>
<div className="flex items-center pt-5"> <div className="flex items-center pb-2">
<Code className="stroke-1 text-red-500" /> <Code className="stroke-1 text-red-500" />
<span className="pl-2"> <span className="pl-2">
Principal Engineer / Engineering Lead Principal Engineer / Engineering Lead
</span> </span>
</div> </div>
<div className="flex items-center pt-5"> <div className="flex items-center pb-2">
<Briefcase className="stroke-1 text-red-500" /> <Briefcase className="stroke-1 text-red-500" />
<span className="pl-2">8+ years experience</span> <span className="pl-2">8+ years experience</span>
</div> </div>
</div> </div>
</div> </div>
<div className="md:text-2xl/10 text-xl/8"> <div className="md:text-2xl/10 text-xl/8 pb-20">
Engineering lead with 8+ years of expertise in designing scalable Engineering lead with 8+ years of expertise in designing scalable
system architecture, leading high performing teams, and delivering system architecture, leading high performing teams, and delivering
ai driven solutions while also maintaining the role of an individual ai driven solutions while also maintaining the role of an individual
contributor. contributor.
</div> </div>
</div> </div>
<div className="md:w-[40vw] w-[95vw] md:min-w-3xl md:p-5"> <div className="md:w-[40vw] w-[95vw] md:min-w-3xl">
<div className="text-3xl">Key Skills</div> <div className="text-3xl pb-10">Work Experience</div>
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-5"> {workExperience.map((job, index) => (
<div key={index} className="">
<div className="flex items-center justify-between pb-5">
<Link
href={job.link}
target="_blank"
rel="noopener noreferrer"
className="text-xl underline"
>
{job.company}
</Link>
<p className="text-xl">{job.tenure}</p>
</div>
<p className="text-2xl text-red-500 pb-5">{job.title}</p>
<p className="text-gray-500 dark:text-gray-100 text-justify text-xl/8 pb-8">
{job.summary}
</p>
</div>
))}
</div>
<div className="md:w-[40vw] w-[95vw] md:min-w-3xl">
<div className="text-3xl pb-10 gap-10">Key Skills</div>
<div className="grid grid-cols-1 md:grid-cols-2">
<div> <div>
{Object.entries(skillsLeft).map(([topic, skillsMap]) => ( {Object.entries(skillsLeft).map(([topic, skillsMap]) => (
<div key={topic}> <div key={topic}>
<div className="text-xl pt-5 pb-3">{topic}</div> <div className="text-xl pb-5">{topic}</div>
<div className="flex items-center flex-wrap gap-1"> <div className="flex items-center flex-wrap pb-5 gap-2">
{Object.entries(skillsMap).map(([language, icon]) => ( {Object.entries(skillsMap).map(([language, icon]) => (
<span <span
key={language} key={language}
className="flex items-center gap-2 bg-red-50 dark:bg-red-900 px-3 py-1 rounded-full text-gray-700 dark:text-white" className="flex items-center bg-red-50 dark:bg-red-900 px-3 py-1 rounded-full text-gray-700 dark:text-white gap-2"
> >
<Icon icon={icon} className="inline-block" /> {language} <Icon icon={icon} className="inline-block" /> {language}
</span> </span>
@ -210,12 +235,12 @@ export default async function Portfolio() {
<div> <div>
{Object.entries(skillsRight).map(([topic, skillsMap]) => ( {Object.entries(skillsRight).map(([topic, skillsMap]) => (
<div key={topic}> <div key={topic}>
<div className="text-xl pt-5 pb-3">{topic}</div> <div className="text-xl pb-5">{topic}</div>
<div className="flex items-center flex-wrap gap-1"> <div className="flex items-center flex-wrap pb-5 gap-2">
{Object.entries(skillsMap).map(([language, icon]) => ( {Object.entries(skillsMap).map(([language, icon]) => (
<span <span
key={language} key={language}
className="flex items-center gap-2 bg-red-50 dark:bg-red-900 px-3 py-1 rounded-full text-gray-700 dark:text-white" className="flex items-center bg-red-50 dark:bg-red-900 px-3 py-1 rounded-full text-gray-700 dark:text-white gap-2"
> >
<Icon icon={icon} className="inline-block" /> {language} <Icon icon={icon} className="inline-block" /> {language}
</span> </span>
@ -226,28 +251,6 @@ export default async function Portfolio() {
</div> </div>
</div> </div>
</div> </div>
<div className="md:w-[40vw] w-[95vw] md:min-w-3xl md:gap-5 md:p-5">
<div className="text-3xl">Work Experience</div>
{workExperience.map((job, index) => (
<div key={index} className="p-3">
<div className="flex items-center justify-between">
<Link
href={job.link}
target="_blank"
rel="noopener noreferrer"
className="text-xl hover:underline"
>
{job.company}
</Link>
<p className="text-xl">{job.tenure}</p>
</div>
<p className="text-2xl pt-2 text-red-500">{job.title}</p>
<p className="pt-2 text-gray-500 dark:text-gray-100 text-justify">
{job.summary}
</p>
</div>
))}
</div>
</div> </div>
</main> </main>
); );

View file

@ -5,7 +5,8 @@ import gsap from "gsap";
const GSAPCursor: FC = () => { const GSAPCursor: FC = () => {
const cursorRef = useRef<HTMLDivElement>(null); const cursorRef = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState({ x: 0, y: 0 }); const [position, setPosition] = useState({ x: 0, y: 0 });
const [isHoveringLink, setIsHoveringLink] = useState(false);
// For touch devices, return null // For touch devices, return null
if (typeof window !== "undefined" && "ontouchstart" in window) return null; if (typeof window !== "undefined" && "ontouchstart" in window) return null;
@ -28,7 +29,20 @@ const GSAPCursor: FC = () => {
window.addEventListener("mousemove", onMouseMove); window.addEventListener("mousemove", onMouseMove);
const handleMouseOver = () => setIsHoveringLink(true);
const handleMouseOut = () => setIsHoveringLink(false);
const links = document.querySelectorAll('a');
links.forEach(link => {
link.addEventListener('mouseover', handleMouseOver);
link.addEventListener('mouseout', handleMouseOut);
});
return () => { return () => {
links.forEach(link => {
link.removeEventListener('mouseover', handleMouseOver);
link.removeEventListener('mouseout', handleMouseOut);
});
window.removeEventListener("mousemove", onMouseMove); window.removeEventListener("mousemove", onMouseMove);
}; };
}, []); }, []);
@ -45,7 +59,7 @@ const GSAPCursor: FC = () => {
<div> <div>
<div <div
ref={cursorRef} ref={cursorRef}
className="fixed w-8 h-8 rounded-full border-2 border-dotted border-red-500 bg-transparent pointer-events-none z-60" className={`fixed w-8 h-8 ${isHoveringLink ? 'rounded-none' : 'rounded-full'} border-2 border-dotted border-red-500 bg-transparent pointer-events-none z-60`}
style={{ style={{
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
}} }}

View file

@ -0,0 +1,95 @@
// components/MarkdownRenderer.tsx
"use client";
import React, { useEffect, useRef } from "react";
import { useTheme } from "next-themes";
import { useMarkdownRenderer } from "@/hooks/useMarkdownRenderer";
interface MarkdownRendererProps {
markdown: string;
}
const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ markdown }) => {
const { theme } = useTheme();
const html = useMarkdownRenderer(markdown, theme);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const linkTags = containerRef.current.querySelectorAll("a");
linkTags.forEach((block) => {
block.setAttribute("target", "_blank");
block.setAttribute("rel", "noopener noreferrer");
});
// Find all code blocks (Shiki wraps them in <pre class="shiki">)
const codeBlocks = containerRef.current.querySelectorAll("pre.shiki");
(codeBlocks as NodeListOf<HTMLPreElement>).forEach((block) => {
// Skip if already has a copy button
if (block.querySelector(".copy-code-btn")) return;
const button = document.createElement("button");
button.className = "copy-code-btn";
button.textContent = "Copy";
button.style.position = "absolute";
button.style.right = "8px";
button.style.top = "8px";
button.style.zIndex = "10";
button.style.background = "#333";
button.style.color = "#fff";
button.style.border = "none";
button.style.borderRadius = "4px";
button.style.padding = "4px 8px";
button.style.cursor = "pointer";
button.style.fontSize = "12px";
const codeElement = block.querySelector("code");
let language = codeElement?.getAttribute("data-language") || "text";
const fileType = document.createElement("div");
fileType.className = "filetype-code-btn";
fileType.textContent = language;
fileType.style.position = "absolute";
fileType.style.left = "8px";
fileType.style.top = "8px";
fileType.style.zIndex = "10";
fileType.style.background = "#333";
fileType.style.color = "#fff";
fileType.style.border = "none";
fileType.style.borderRadius = "4px";
fileType.style.padding = "4px 8px";
fileType.style.fontSize = "12px";
button.addEventListener("click", () => {
const code = block.querySelector("code")?.textContent || "";
navigator.clipboard.writeText(code).then(() => {
button.textContent = "Copied!";
setTimeout(() => {
button.textContent = "Copy";
}, 2000);
});
});
// Make the block relative so the absolute button works
block.style.position = "relative";
block.appendChild(button);
block.appendChild(fileType);
});
}, [html]);
return (
<>
<div
ref={containerRef}
className="markdown"
dangerouslySetInnerHTML={{ __html: html }}
/>
</>
);
};
export default MarkdownRenderer;

View file

@ -0,0 +1,78 @@
"use client";
import { useEffect, useState } from "react";
import { Marked } from "marked";
import markedShiki from "marked-shiki";
import { createHighlighter, bundledLanguages } from "shiki";
import DOMPurify from "dompurify";
const darkTheme = "monokai";
const lightTheme = "rose-pine-dawn";
// You may want to parameterize theme/langs for your use case
const highlighterPromise = createHighlighter({
// TODO: can be used to import highlighting for all languages, but it slows things down considerably
langs: [
"ts",
"tsx",
"md",
"python",
"js",
"jsx",
"bash",
"json",
"rust",
"go",
"cpp",
"sql",
"ruby",
"nix",
"html",
"css",
"markdown",
"docker",
"tf",
],
themes: [lightTheme, darkTheme],
});
export function useMarkdownRenderer(
markdown: string,
theme: string | undefined,
): string {
const [html, setHtml] = useState<string>("");
useEffect(() => {
let isMounted = true;
async function renderMarkdown() {
const highlighter = await highlighterPromise;
const marked = new Marked().use(
markedShiki({
highlight(code, lang, props) {
return highlighter.codeToHtml(code, {
lang,
theme: theme === "dark" ? darkTheme : lightTheme,
meta: { __raw: props.join(" ") },
transformers: [
{
code(node) {
node.properties["data-language"] = lang;
},
},
],
});
},
}),
);
const rawHtml = await marked.parse(markdown);
const cleanHtml = DOMPurify.sanitize(rawHtml);
if (isMounted) setHtml(cleanHtml);
}
renderMarkdown();
return () => {
isMounted = false;
};
}, [markdown, theme]);
return html;
}