feat: portfolio done + minimal working blog
Signed-off-by: Ameya Shenoy <shenoy.ameya@gmail.com>
This commit is contained in:
parent
360fb2d3d6
commit
e74f75ae4b
9 changed files with 962 additions and 64 deletions
575
frontend/package-lock.json
generated
575
frontend/package-lock.json
generated
|
|
@ -13,13 +13,17 @@
|
|||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dompurify": "^3.2.6",
|
||||
"gsap": "^3.13.0",
|
||||
"lucide-react": "^0.511.0",
|
||||
"marked": "^15.0.12",
|
||||
"marked-shiki": "^1.2.0",
|
||||
"matter-js": "^0.20.0",
|
||||
"next": "15.3.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"shiki": "^3.7.0",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tw-to-css": "^0.0.12"
|
||||
},
|
||||
|
|
@ -771,6 +775,17 @@
|
|||
"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": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
|
|
@ -1314,6 +1329,73 @@
|
|||
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
|
||||
"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": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
|
|
@ -1605,6 +1687,24 @@
|
|||
"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": {
|
||||
"version": "20.17.57",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz",
|
||||
|
|
@ -1635,6 +1735,25 @@
|
|||
"@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": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
|
|
@ -1775,6 +1894,36 @@
|
|||
],
|
||||
"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": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
|
|
@ -1891,6 +2040,16 @@
|
|||
"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": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
|
|
@ -1933,6 +2092,15 @@
|
|||
"devOptional": true,
|
||||
"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": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
|
|
@ -1949,6 +2117,19 @@
|
|||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
|
||||
"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": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
|
|
@ -1961,6 +2142,15 @@
|
|||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||
"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": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
|
|
@ -2156,6 +2346,52 @@
|
|||
"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": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
|
|
@ -2557,12 +2793,55 @@
|
|||
"@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": {
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/matter-js/-/matter-js-0.20.0.tgz",
|
||||
"integrity": "sha512-iC9fYR7zVT3HppNnsFsp9XOoQdQN2tUyfaKg4CHLH8bN+j6GT4Gw7IH2rP0tflAebrHFw730RR3DkVSZRX8hwA==",
|
||||
"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": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
|
|
@ -2572,6 +2851,95 @@
|
|||
"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": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
|
|
@ -2786,6 +3154,23 @@
|
|||
"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": {
|
||||
"version": "1.0.1",
|
||||
"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==",
|
||||
"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": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
|
@ -3116,6 +3511,30 @@
|
|||
"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": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
|
|
@ -3254,6 +3673,22 @@
|
|||
"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": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
|
|
@ -3285,6 +3720,16 @@
|
|||
"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": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
|
|
@ -3352,6 +3797,20 @@
|
|||
"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": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
|
|
@ -3524,6 +3983,16 @@
|
|||
"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": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
|
|
@ -3703,6 +4172,74 @@
|
|||
"dev": true,
|
||||
"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": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
|
||||
|
|
@ -3752,6 +4289,34 @@
|
|||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"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": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
@ -3879,6 +4444,16 @@
|
|||
"engines": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,17 @@
|
|||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dompurify": "^3.2.6",
|
||||
"gsap": "^3.13.0",
|
||||
"lucide-react": "^0.511.0",
|
||||
"marked": "^15.0.12",
|
||||
"marked-shiki": "^1.2.0",
|
||||
"matter-js": "^0.20.0",
|
||||
"next": "15.3.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"shiki": "^3.7.0",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tw-to-css": "^0.0.12"
|
||||
},
|
||||
|
|
|
|||
24
frontend/src/app/blog/docker_primer/page.tsx
Normal file
24
frontend/src/app/blog/docker_primer/page.tsx
Normal 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>;
|
||||
}
|
||||
}
|
||||
|
|
@ -138,6 +138,111 @@
|
|||
color: white;
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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 { Icon } from "@iconify/react";
|
||||
import Link from "next/link";
|
||||
|
|
@ -23,13 +23,6 @@ export default async function Portfolio() {
|
|||
TypeScript: "vscode-icons:file-type-typescript-official",
|
||||
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: {
|
||||
LangChain: "simple-icons:langchain",
|
||||
Livekit: null,
|
||||
|
|
@ -47,8 +40,25 @@ export default async function Portfolio() {
|
|||
qdrant: "logos:qdrant-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 = {
|
||||
"Web Frameworks": {
|
||||
FastAPI: "devicon:fastapi",
|
||||
Django: "material-icon-theme:django",
|
||||
"Next.js": "devicon:nextjs",
|
||||
Flask: "simple-icons:flask",
|
||||
ReactJS: "material-icon-theme:react",
|
||||
},
|
||||
|
||||
DevOps: {
|
||||
Kubernetes: "material-icon-theme:kubernetes",
|
||||
Ansible: "devicon:ansible",
|
||||
|
|
@ -70,15 +80,6 @@ export default async function Portfolio() {
|
|||
Kafka: "devicon:apachekafka",
|
||||
// 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: {
|
||||
"Robot Operating System ROS": "devicon:ros",
|
||||
},
|
||||
|
|
@ -144,61 +145,85 @@ export default async function Portfolio() {
|
|||
];
|
||||
|
||||
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="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>
|
||||
<Link href="https://resume.codingcoffee.me">
|
||||
<div className="flex border-gray-200 border-2 p-2 ">
|
||||
Download
|
||||
<FileUser />
|
||||
<Link
|
||||
href="https://resume.codingcoffee.me"
|
||||
>
|
||||
<div className="flex border-gray-200 border-2 p-2 pointer-cursor underline">
|
||||
Resume
|
||||
<Download />
|
||||
</div>
|
||||
</Link>
|
||||
</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 className="text-5xl md:text-6xl">Hi, I'm</div>
|
||||
<div className="pb-10">
|
||||
<div className="text-5xl md:text-6xl pb-5">Hi, I'm</div>
|
||||
<div className="text-5xl md:text-6xl text-red-500">
|
||||
Ameya Shenoy
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-5 text-gray-500 dark:text-gray-100 text-lg">
|
||||
<div className="flex items-center pt-5">
|
||||
<div className="text-gray-500 dark:text-gray-100 text-lg pb-10">
|
||||
<div className="flex items-center pb-2">
|
||||
<MapPin className="stroke-1 text-red-500" />
|
||||
<span className="pl-2">Based in India</span>
|
||||
</div>
|
||||
<div className="flex items-center pt-5">
|
||||
<div className="flex items-center pb-2">
|
||||
<Code className="stroke-1 text-red-500" />
|
||||
<span className="pl-2">
|
||||
Principal Engineer / Engineering Lead
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center pt-5">
|
||||
<div className="flex items-center pb-2">
|
||||
<Briefcase className="stroke-1 text-red-500" />
|
||||
<span className="pl-2">8+ years experience</span>
|
||||
</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
|
||||
system architecture, leading high performing teams, and delivering
|
||||
ai driven solutions while also maintaining the role of an individual
|
||||
contributor.
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:w-[40vw] w-[95vw] md:min-w-3xl md:p-5">
|
||||
<div className="text-3xl">Key Skills</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-5">
|
||||
<div className="md:w-[40vw] w-[95vw] md:min-w-3xl">
|
||||
<div className="text-3xl pb-10">Work Experience</div>
|
||||
{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>
|
||||
{Object.entries(skillsLeft).map(([topic, skillsMap]) => (
|
||||
<div key={topic}>
|
||||
<div className="text-xl pt-5 pb-3">{topic}</div>
|
||||
<div className="flex items-center flex-wrap gap-1">
|
||||
<div className="text-xl pb-5">{topic}</div>
|
||||
<div className="flex items-center flex-wrap pb-5 gap-2">
|
||||
{Object.entries(skillsMap).map(([language, icon]) => (
|
||||
<span
|
||||
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}
|
||||
</span>
|
||||
|
|
@ -210,12 +235,12 @@ export default async function Portfolio() {
|
|||
<div>
|
||||
{Object.entries(skillsRight).map(([topic, skillsMap]) => (
|
||||
<div key={topic}>
|
||||
<div className="text-xl pt-5 pb-3">{topic}</div>
|
||||
<div className="flex items-center flex-wrap gap-1">
|
||||
<div className="text-xl pb-5">{topic}</div>
|
||||
<div className="flex items-center flex-wrap pb-5 gap-2">
|
||||
{Object.entries(skillsMap).map(([language, icon]) => (
|
||||
<span
|
||||
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}
|
||||
</span>
|
||||
|
|
@ -226,28 +251,6 @@ export default async function Portfolio() {
|
|||
</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>
|
||||
</main>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import gsap from "gsap";
|
|||
|
||||
const GSAPCursor: FC = () => {
|
||||
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
|
||||
if (typeof window !== "undefined" && "ontouchstart" in window) return null;
|
||||
|
|
@ -28,7 +29,20 @@ const GSAPCursor: FC = () => {
|
|||
|
||||
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 () => {
|
||||
links.forEach(link => {
|
||||
link.removeEventListener('mouseover', handleMouseOver);
|
||||
link.removeEventListener('mouseout', handleMouseOut);
|
||||
});
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
};
|
||||
}, []);
|
||||
|
|
@ -45,7 +59,7 @@ const GSAPCursor: FC = () => {
|
|||
<div>
|
||||
<div
|
||||
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={{
|
||||
transform: "translate(-50%, -50%)",
|
||||
}}
|
||||
|
|
|
|||
95
frontend/src/components/MarkdownRenderer.tsx
Normal file
95
frontend/src/components/MarkdownRenderer.tsx
Normal 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;
|
||||
78
frontend/src/hooks/useMarkdownRenderer.ts
Normal file
78
frontend/src/hooks/useMarkdownRenderer.ts
Normal 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;
|
||||
}
|
||||
Loading…
Reference in a new issue