feat: 初始化项目并添加多个功能组件

style: 调整UI组件样式和布局

docs: 更新README和添加文档内容

chore: 添加依赖项和配置文件

fix: 修复一些小问题和优化代码

perf: 优化性能相关代码

refactor: 重构部分组件结构

test: 添加测试相关文件

build: 更新构建配置

ci: 添加CI配置文件
This commit is contained in:
2026-01-21 16:16:01 +08:00
commit 426e9e0210
89 changed files with 13447 additions and 0 deletions

229
src/components/icons.tsx Normal file
View File

@@ -0,0 +1,229 @@
import { GlobeIcon, MailIcon } from "lucide-react";
export type IconProps = React.HTMLAttributes<SVGElement>;
export const Icons = {
globe: (props: IconProps) => <GlobeIcon {...props} />,
email: (props: IconProps) => <MailIcon {...props} />,
linkedin: (props: IconProps) => (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
<title>LinkedIn</title>
<path
fill="currentColor"
d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"
/>
</svg>
),
x: (props: IconProps) => (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
<title>X</title>
<path
fill="currentColor"
d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"
/>
</svg>
),
youtube: (props: IconProps) => (
<svg
width="32px"
height="32px"
viewBox="0 0 32 32"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<title>youtube</title>
<path d="M29.41,9.26a3.5,3.5,0,0,0-2.47-2.47C24.76,6.2,16,6.2,16,6.2s-8.76,0-10.94.59A3.5,3.5,0,0,0,2.59,9.26,36.13,36.13,0,0,0,2,16a36.13,36.13,0,0,0,.59,6.74,3.5,3.5,0,0,0,2.47,2.47C7.24,25.8,16,25.8,16,25.8s8.76,0,10.94-.59a3.5,3.5,0,0,0,2.47-2.47A36.13,36.13,0,0,0,30,16,36.13,36.13,0,0,0,29.41,9.26ZM13.2,20.2V11.8L20.47,16Z" />
</svg>
),
nextjs: (props: IconProps) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
className="size-8"
fill="currentColor"
{...props}
>
<title>Next.js</title>
<path d="M11.5725 0c-.1763 0-.3098.0013-.3584.0067-.0516.0053-.2159.021-.3636.0328-3.4088.3073-6.6017 2.1463-8.624 4.9728C1.1004 6.584.3802 8.3666.1082 10.255c-.0962.659-.108.8537-.108 1.7474s.012 1.0884.108 1.7476c.652 4.506 3.8591 8.2919 8.2087 9.6945.7789.2511 1.6.4223 2.5337.5255.3636.04 1.9354.04 2.299 0 1.6117-.1783 2.9772-.577 4.3237-1.2643.2065-.1056.2464-.1337.2183-.1573-.0188-.0139-.8987-1.1938-1.9543-2.62l-1.919-2.592-2.4047-3.5583c-1.3231-1.9564-2.4117-3.556-2.4211-3.556-.0094-.0026-.0187 1.5787-.0235 3.509-.0067 3.3802-.0093 3.5162-.0516 3.596-.061.115-.108.1618-.2064.2134-.075.0374-.1408.0445-.495.0445h-.406l-.1078-.068a.4383.4383 0 01-.1572-.1712l-.0493-.1056.0053-4.703.0067-4.7054.0726-.0915c.0376-.0493.1174-.1125.1736-.143.0962-.047.1338-.0517.5396-.0517.4787 0 .5584.0187.6827.1547.0353.0377 1.3373 1.9987 2.895 4.3608a10760.433 10760.433 0 004.7344 7.1706l1.9002 2.8782.096-.0633c.8518-.5536 1.7525-1.3418 2.4657-2.1627 1.5179-1.7429 2.4963-3.868 2.8247-6.134.0961-.6591.1078-.854.1078-1.7475 0-.8937-.012-1.0884-.1078-1.7476-.6522-4.506-3.8592-8.2919-8.2087-9.6945-.7672-.2487-1.5836-.42-2.4985-.5232-.169-.0176-1.0835-.0366-1.6123-.037zm4.0685 7.217c.3473 0 .4082.0053.4857.047.1127.0562.204.1642.237.2767.0186.061.0234 1.3653.0186 4.3044l-.0067 4.2175-.7436-1.14-.7461-1.14v-3.066c0-1.982.0093-3.0963.0234-3.1502.0375-.1313.1196-.2346.2323-.2955.0961-.0494.1313-.054.4997-.054z" />
</svg>
),
framermotion: (props: IconProps) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
className="size-8"
fill="none"
stroke="currentColor"
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<title>Framer Motion</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 12l-8 -8v16l16 -16v16l-4 -4" />
<path d="M20 12l-8 8l-4 -4" />
</svg>
),
tailwindcss: (props: IconProps) => (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
className="size-8"
fill="currentColor"
{...props}
>
<title>Tailwind CSS</title>
<path d="m12.001 4.8c-3.2 0-5.2 1.6-6 4.8 1.2-1.6 2.6-2.2 4.2-1.8.913.228 1.565.89 2.288 1.624 1.177 1.194 2.538 2.576 5.512 2.576 3.2 0 5.2-1.6 6-4.8-1.2 1.6-2.6 2.2-4.2 1.8-.913-.228-1.565-.89-2.288-1.624-1.176-1.194-2.537-2.576-5.512-2.576zm-6 7.2c-3.2 0-5.2 1.6-6 4.8 1.2-1.6 2.6-2.2 4.2-1.8.913.228 1.565.89 2.288 1.624 1.177 1.194 2.538 2.576 5.512 2.576 3.2 0 5.2-1.6 6-4.8-1.2 1.6-2.6 2.2-4.2 1.8-.913-.228-1.565-.89-2.288-1.624-1.176-1.194-2.537-2.576-5.512-2.576z" />
</svg>
),
typescript: (props: IconProps) => (
<svg
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
className="size-8"
fill="currentColor"
{...props}
>
<path d="m0 16v16h32v-32h-32zm25.786-1.276c.813.203 1.432.568 2.005 1.156.292.312.729.885.766 1.026.01.042-1.38.974-2.224 1.495-.031.021-.156-.109-.292-.313-.411-.599-.844-.859-1.505-.906-.969-.063-1.594.443-1.589 1.292-.005.208.042.417.135.599.214.443.615.708 1.854 1.245 2.292.984 3.271 1.635 3.88 2.557.682 1.031.833 2.677.375 3.906-.51 1.328-1.771 2.234-3.542 2.531-.547.099-1.849.083-2.438-.026-1.286-.229-2.505-.865-3.255-1.698-.297-.323-.87-1.172-.833-1.229.016-.021.146-.104.292-.188s.682-.396 1.188-.688l.922-.536.193.286c.271.411.859.974 1.214 1.161 1.021.542 2.422.464 3.115-.156.281-.234.438-.594.417-.958 0-.37-.047-.536-.24-.813-.25-.354-.755-.656-2.198-1.281-1.651-.714-2.365-1.151-3.01-1.854-.406-.464-.708-1.01-.88-1.599-.12-.453-.151-1.589-.057-2.042.339-1.599 1.547-2.708 3.281-3.036.563-.109 1.875-.068 2.427.068zm-7.51 1.339.01 1.307h-4.167v11.839h-2.948v-11.839h-4.161v-1.281c0-.714.016-1.307.036-1.323.016-.021 2.547-.031 5.62-.026l5.594.016z" />
</svg>
),
react: (props: IconProps) => (
<svg
role="img"
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
className="size-8"
fill="currentColor"
{...props}
>
<title>React</title>
<path d="m16 13.146c-1.573 0-2.854 1.281-2.854 2.854s1.281 2.854 2.854 2.854 2.854-1.281 2.854-2.854-1.281-2.854-2.854-2.854zm-7.99 8.526-.63-.156c-4.688-1.188-7.38-3.198-7.38-5.521s2.693-4.333 7.38-5.521l.63-.156.177.625c.474 1.635 1.083 3.229 1.818 4.771l.135.281-.135.286c-.734 1.536-1.344 3.13-1.818 4.771zm-.921-9.74c-3.563 1-5.75 2.536-5.75 4.063s2.188 3.057 5.75 4.063c.438-1.391.964-2.745 1.578-4.063-.615-1.318-1.141-2.672-1.578-4.063zm16.901 9.74-.177-.625c-.474-1.635-1.083-3.229-1.818-4.766l-.135-.286.135-.286c.734-1.536 1.344-3.13 1.818-4.771l.177-.62.63.156c4.688 1.188 7.38 3.198 7.38 5.521s-2.693 4.333-7.38 5.521zm-.657-5.677c.641 1.385 1.172 2.745 1.578 4.063 3.568-1.005 5.75-2.536 5.75-4.063s-2.188-3.057-5.75-4.063c-.438 1.385-.964 2.745-1.578 4.063zm-16.255-4.068-.177-.625c-1.318-4.646-.917-7.979 1.099-9.141 1.979-1.141 5.151.208 8.479 3.625l.453.464-.453.464c-1.182 1.229-2.26 2.552-3.229 3.958l-.182.255-.313.026c-1.703.135-3.391.406-5.047.813zm2.531-8.838c-.359 0-.677.073-.943.229-1.323.766-1.557 3.422-.646 7.005 1.422-.318 2.859-.542 4.313-.672.833-1.188 1.75-2.323 2.734-3.391-2.078-2.026-4.047-3.172-5.458-3.172zm12.787 27.145c-.005 0-.005 0 0 0-1.901 0-4.344-1.427-6.875-4.031l-.453-.464.453-.464c1.182-1.229 2.26-2.552 3.229-3.958l.177-.255.313-.031c1.703-.13 3.391-.401 5.052-.813l.63-.156.177.625c1.318 4.646.917 7.974-1.099 9.135-.49.281-1.042.422-1.604.411zm-5.464-4.505c2.078 2.026 4.047 3.172 5.458 3.172h.005c.354 0 .672-.078.938-.229 1.323-.766 1.563-3.422.646-7.005-1.422.318-2.865.542-4.313.667-.833 1.193-1.75 2.323-2.734 3.396zm7.99-13.802-.63-.161c-1.661-.406-3.349-.677-5.052-.813l-.313-.026-.177-.255c-.969-1.406-2.047-2.729-3.229-3.958l-.453-.464.453-.464c3.328-3.417 6.5-4.766 8.479-3.625 2.016 1.161 2.417 4.495 1.099 9.141zm-5.255-2.276c1.521.141 2.969.365 4.313.672.917-3.583.677-6.24-.646-7.005-1.318-.76-3.797.406-6.401 2.943.984 1.073 1.896 2.203 2.734 3.391zm-10.058 20.583c-.563.01-1.12-.13-1.609-.411-2.016-1.161-2.417-4.49-1.099-9.135l.177-.625.63.156c1.542.391 3.24.661 5.047.813l.313.031.177.255c.969 1.406 2.047 2.729 3.229 3.958l.453.464-.453.464c-2.526 2.604-4.969 4.031-6.865 4.031zm-1.588-8.567c-.917 3.583-.677 6.24.646 7.005 1.318.75 3.792-.406 6.401-2.943-.984-1.073-1.901-2.203-2.734-3.396-1.453-.125-2.891-.349-4.313-.667zm7.979.838c-1.099 0-2.224-.047-3.354-.141l-.313-.026-.182-.26c-.635-.917-1.24-1.859-1.797-2.828-.563-.969-1.078-1.958-1.557-2.969l-.135-.286.135-.286c.479-1.01.995-2 1.557-2.969.552-.953 1.156-1.906 1.797-2.828l.182-.26.313-.026c2.234-.188 4.479-.188 6.708 0l.313.026.182.26c1.276 1.833 2.401 3.776 3.354 5.797l.135.286-.135.286c-.953 2.021-2.073 3.964-3.354 5.797l-.182.26-.313.026c-1.125.094-2.255.141-3.354.141zm-2.927-1.448c1.969.151 3.885.151 5.859 0 1.099-1.609 2.078-3.302 2.927-5.063-.844-1.76-1.823-3.453-2.932-5.063-1.948-.151-3.906-.151-5.854 0-1.109 1.609-2.089 3.302-2.932 5.063.849 1.76 1.828 3.453 2.932 5.063z" />
</svg>
),
github: (props: IconProps) => (
<svg
viewBox="0 0 438.549 438.549"
xmlns="http://www.w3.org/2000/svg"
role="img"
{...props}
>
<title>GitHub</title>
<path
fill="currentColor"
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
></path>
</svg>
),
notion: (props: IconProps) => (
<svg
width="100"
height="100"
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z"
fill="#fff"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.723 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z"
fill="#000"
/>
</svg>
),
openai: (props: IconProps) => (
<svg role="img" viewBox="0 0 24 24" {...props}>
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
</svg>
),
googleDrive: (props: IconProps) => (
<svg viewBox="0 0 87.3 78" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z"
fill="#0066da"
/>
<path
d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z"
fill="#00ac47"
/>
<path
d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z"
fill="#ea4335"
/>
<path
d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z"
fill="#00832d"
/>
<path
d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z"
fill="#2684fc"
/>
<path
d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z"
fill="#ffba00"
/>
</svg>
),
whatsapp: (props: IconProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 175.216 175.552"
{...props}
>
<defs>
<linearGradient
id="b"
x1="85.915"
x2="86.535"
y1="32.567"
y2="137.092"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#57d163" />
<stop offset="1" stopColor="#23b33a" />
</linearGradient>
<filter
id="a"
width="1.115"
height="1.114"
x="-.057"
y="-.057"
colorInterpolationFilters="sRGB"
>
<feGaussianBlur stdDeviation="3.531" />
</filter>
</defs>
<path
fill="#b3b3b3"
d="m54.532 138.45 2.235 1.324c9.387 5.571 20.15 8.518 31.126 8.523h.023c33.707 0 61.139-27.426 61.153-61.135.006-16.335-6.349-31.696-17.895-43.251A60.75 60.75 0 0 0 87.94 25.983c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.312-6.179 22.558zm-40.811 23.544L24.16 123.88c-6.438-11.154-9.825-23.808-9.821-36.772.017-40.556 33.021-73.55 73.578-73.55 19.681.01 38.154 7.669 52.047 21.572s21.537 32.383 21.53 52.037c-.018 40.553-33.027 73.553-73.578 73.553h-.032c-12.313-.005-24.412-3.094-35.159-8.954zm0 0"
filter="url(#a)"
/>
<path
fill="#fff"
d="m12.966 161.238 10.439-38.114a73.42 73.42 0 0 1-9.821-36.772c.017-40.556 33.021-73.55 73.578-73.55 19.681.01 38.154 7.669 52.047 21.572s21.537 32.383 21.53 52.037c-.018 40.553-33.027 73.553-73.578 73.553h-.032c-12.313-.005-24.412-3.094-35.159-8.954z"
/>
<path
fill="url(#linearGradient1780)"
d="M87.184 25.227c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.312-6.179 22.559 23.146-6.069 2.235 1.324c9.387 5.571 20.15 8.518 31.126 8.524h.023c33.707 0 61.14-27.426 61.153-61.135a60.75 60.75 0 0 0-17.895-43.251 60.75 60.75 0 0 0-43.235-17.929z"
/>
<path
fill="url(#b)"
d="M87.184 25.227c-33.733 0-61.166 27.423-61.178 61.13a60.98 60.98 0 0 0 9.349 32.535l1.455 2.313-6.179 22.558 23.146-6.069 2.235 1.324c9.387 5.571 20.15 8.517 31.126 8.523h.023c33.707 0 61.14-27.426 61.153-61.135a60.75 60.75 0 0 0-17.895-43.251 60.75 60.75 0 0 0-43.235-17.928z"
/>
<path
fill="#fff"
fillRule="evenodd"
d="M68.772 55.603c-1.378-3.061-2.828-3.123-4.137-3.176l-3.524-.043c-1.226 0-3.218.46-4.902 2.3s-6.435 6.287-6.435 15.332 6.588 17.785 7.506 19.013 12.718 20.381 31.405 27.75c15.529 6.124 18.689 4.906 22.061 4.6s10.877-4.447 12.408-8.74 1.532-7.971 1.073-8.74-1.685-1.226-3.525-2.146-10.877-5.367-12.562-5.981-2.91-.919-4.137.921-4.746 5.979-5.819 7.206-2.144 1.381-3.984.462-7.76-2.861-14.784-9.124c-5.465-4.873-9.154-10.891-10.228-12.73s-.114-2.835.808-3.751c.825-.824 1.838-2.147 2.759-3.22s1.224-1.84 1.836-3.065.307-2.301-.153-3.22-4.032-10.011-5.666-13.647"
/>
</svg>
),
};

View File

@@ -0,0 +1,86 @@
"use client";
import { cn } from "@/lib/utils";
import { motion, Variants } from "motion/react";
import { useMemo } from "react";
interface BlurFadeTextProps {
text: string;
className?: string;
variant?: {
hidden: { y: number };
visible: { y: number };
};
duration?: number;
characterDelay?: number;
delay?: number;
yOffset?: number;
animateByCharacter?: boolean;
}
const BlurFadeText = ({
text,
className,
variant,
duration = 0.4,
characterDelay = 0.03,
delay = 0,
yOffset = 8,
animateByCharacter = false,
}: BlurFadeTextProps) => {
const defaultVariants: Variants = {
hidden: { y: -yOffset, opacity: 0, filter: "blur(8px)" },
visible: { y: 0, opacity: 1, filter: "blur(0px)" },
};
const combinedVariants = variant || defaultVariants;
const characters = useMemo(() => Array.from(text), [text]);
if (animateByCharacter) {
return (
<div className="flex">
{characters.map((char, i) => {
const charVariants: Variants = {
hidden: { y: -yOffset, opacity: 0, filter: "blur(8px)" },
visible: { y: 0, opacity: 1, filter: "blur(0px)" },
};
return (
<motion.span
key={i}
initial="hidden"
animate="visible"
variants={charVariants}
transition={{
duration,
delay: delay + i * characterDelay,
ease: "easeOut",
}}
className={cn("inline-block", className)}
style={{ width: char.trim() === "" ? "0.2em" : "auto" }}
>
{char}
</motion.span>
);
})}
</div>
);
}
return (
<div className="flex">
<motion.span
initial="hidden"
animate="visible"
variants={combinedVariants}
transition={{
duration,
delay,
ease: "easeOut",
}}
className={cn("inline-block", className)}
>
{text}
</motion.span>
</div>
);
};
export default BlurFadeText;

View File

@@ -0,0 +1,63 @@
"use client";
import { AnimatePresence, motion, useInView, Variants } from "motion/react";
import { useRef } from "react";
interface BlurFadeProps {
children: React.ReactNode;
className?: string;
variant?: {
hidden: { y: number };
visible: { y: number };
};
duration?: number;
delay?: number;
yOffset?: number;
inView?: boolean;
inViewMargin?: string;
blur?: string;
}
const BlurFade = ({
children,
className,
variant,
duration = 0.4,
delay = 0,
yOffset = 6,
inView = false,
inViewMargin = "-50px",
blur = "6px",
}: BlurFadeProps) => {
const ref = useRef(null);
const inViewResult = useInView(ref, {
once: true,
...(inViewMargin ? { margin: inViewMargin as any } : {})
});
const isInView = !inView || inViewResult;
const defaultVariants: Variants = {
hidden: { y: -yOffset, opacity: 0, filter: `blur(${blur})` },
visible: { y: 0, opacity: 1, filter: `blur(0px)` },
};
const combinedVariants = variant || defaultVariants;
return (
<AnimatePresence>
<motion.div
ref={ref}
initial="hidden"
animate={isInView ? "visible" : "hidden"}
exit="hidden"
variants={combinedVariants}
transition={{
delay: 0.04 + delay,
duration,
ease: "easeOut",
}}
className={className}
>
{children}
</motion.div>
</AnimatePresence>
);
};
export default BlurFade;

View File

@@ -0,0 +1,91 @@
"use client";
import { cn } from "@/lib/utils";
import { motion, type MotionValue, useMotionValue, useSpring, useTransform } from "motion/react";
import { createContext, useContext, useRef, type ReactNode } from "react";
interface DockProps {
className?: string;
children: ReactNode;
magnification?: number;
distance?: number;
}
interface DockIconProps {
className?: string;
children?: ReactNode;
}
const DEFAULT_MAGNIFICATION = 60;
const DEFAULT_DISTANCE = 100;
const BASE_SIZE = 40;
const BASE_ICON_SIZE = 20;
const ICON_SIZE_RATIO = 0.5;
const SPRING = { mass: 0.1, stiffness: 150, damping: 12 };
interface DockContextValue {
mouseX: MotionValue<number>;
magnification: number;
distance: number;
}
const DockContext = createContext<DockContextValue | null>(null);
const Dock = ({ className, children, magnification = DEFAULT_MAGNIFICATION, distance = DEFAULT_DISTANCE }: DockProps) => {
const mouseX = useMotionValue(Infinity);
return (
<DockContext.Provider value={{ mouseX, magnification, distance }}>
<motion.div
onMouseMove={(e) => mouseX.set(e.pageX)}
onMouseLeave={() => mouseX.set(Infinity)}
className={cn("mx-auto w-max h-full flex items-end justify-center overflow-visible rounded-full border", className)}
>
{children}
</motion.div>
</DockContext.Provider>
);
};
const DockIcon = ({ className, children }: DockIconProps) => {
const ref = useRef<HTMLDivElement>(null);
const context = useContext(DockContext);
if (!context) {
throw new Error("DockIcon must be used within a Dock component");
}
const { mouseX, magnification, distance } = context;
const distanceCalc = useTransform(mouseX, (val: number) => {
const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 };
return val - bounds.x - bounds.width / 2;
});
const containerSize = useSpring(
useTransform(distanceCalc, [-distance, 0, distance], [BASE_SIZE, magnification, BASE_SIZE]),
SPRING
);
const iconSize = useSpring(
useTransform(distanceCalc, [-distance, 0, distance], [BASE_ICON_SIZE, magnification * ICON_SIZE_RATIO, BASE_ICON_SIZE]),
SPRING
);
return (
<motion.div
ref={ref}
style={{ width: containerSize, height: containerSize }}
className={cn("relative flex aspect-square items-center justify-center rounded-full shrink-0", className)}
>
<motion.div
style={{ width: iconSize, height: iconSize }}
className="flex items-center justify-center"
>
{children}
</motion.div>
</motion.div>
);
};
export { Dock, DockIcon };
export type { DockProps, DockIconProps };

View File

@@ -0,0 +1,241 @@
"use client"
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { cn } from "@/lib/utils"
interface FlickeringGridProps extends React.HTMLAttributes<HTMLDivElement> {
squareSize?: number
gridGap?: number
flickerChance?: number
color?: string
width?: number
height?: number
className?: string
maxOpacity?: number
}
export const FlickeringGrid: React.FC<FlickeringGridProps> = ({
squareSize = 4,
gridGap = 6,
flickerChance = 0.3,
color,
width,
height,
className,
maxOpacity = 0.3,
...props
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const [isInView, setIsInView] = useState(false)
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 })
const [resolvedColor, setResolvedColor] = useState<string>("rgb(0, 0, 0)")
const resolveColor = useCallback((colorValue: string | undefined): string => {
if (typeof window === "undefined") {
return "rgb(0, 0, 0)"
}
const colorToResolve = colorValue || "var(--foreground)"
if (colorToResolve.startsWith("var(")) {
const tempEl = document.createElement("div")
tempEl.style.color = colorToResolve
tempEl.style.position = "absolute"
tempEl.style.visibility = "hidden"
document.body.appendChild(tempEl)
const computedColor = window.getComputedStyle(tempEl).color
document.body.removeChild(tempEl)
return computedColor || "rgb(0, 0, 0)"
}
return colorToResolve
}, [])
useEffect(() => {
const updateColor = () => {
const resolved = resolveColor(color)
setResolvedColor(resolved)
}
updateColor()
const observer = new MutationObserver(() => {
updateColor()
})
if (typeof window !== "undefined") {
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
})
}
return () => {
observer.disconnect()
}
}, [color, resolveColor])
const memoizedColor = useMemo(() => {
const toRGBA = (colorValue: string) => {
if (typeof window === "undefined") {
return `rgba(0, 0, 0,`
}
const canvas = document.createElement("canvas")
canvas.width = canvas.height = 1
const ctx = canvas.getContext("2d")
if (!ctx) return "rgba(255, 0, 0,"
ctx.fillStyle = colorValue
ctx.fillRect(0, 0, 1, 1)
const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data)
return `rgba(${r}, ${g}, ${b},`
}
return toRGBA(resolvedColor)
}, [resolvedColor])
const setupCanvas = useCallback(
(canvas: HTMLCanvasElement, width: number, height: number) => {
const dpr = window.devicePixelRatio || 1
canvas.width = width * dpr
canvas.height = height * dpr
canvas.style.width = `${width}px`
canvas.style.height = `${height}px`
const cols = Math.floor(width / (squareSize + gridGap))
const rows = Math.floor(height / (squareSize + gridGap))
const squares = new Float32Array(cols * rows)
for (let i = 0; i < squares.length; i++) {
squares[i] = Math.random() * maxOpacity
}
return { cols, rows, squares, dpr }
},
[squareSize, gridGap, maxOpacity]
)
const updateSquares = useCallback(
(squares: Float32Array, deltaTime: number) => {
for (let i = 0; i < squares.length; i++) {
if (Math.random() < flickerChance * deltaTime) {
squares[i] = Math.random() * maxOpacity
}
}
},
[flickerChance, maxOpacity]
)
const drawGrid = useCallback(
(
ctx: CanvasRenderingContext2D,
width: number,
height: number,
cols: number,
rows: number,
squares: Float32Array,
dpr: number
) => {
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = "transparent"
ctx.fillRect(0, 0, width, height)
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
const opacity = squares[i * rows + j]
ctx.fillStyle = `${memoizedColor}${opacity})`
ctx.fillRect(
i * (squareSize + gridGap) * dpr,
j * (squareSize + gridGap) * dpr,
squareSize * dpr,
squareSize * dpr
)
}
}
},
[memoizedColor, squareSize, gridGap]
)
useEffect(() => {
const canvas = canvasRef.current
const container = containerRef.current
if (!canvas || !container) return
const ctx = canvas.getContext("2d")
if (!ctx) return
let animationFrameId: number
let gridParams: ReturnType<typeof setupCanvas>
const updateCanvasSize = () => {
const newWidth = width || container.clientWidth
const newHeight = height || container.clientHeight
setCanvasSize({ width: newWidth, height: newHeight })
gridParams = setupCanvas(canvas, newWidth, newHeight)
}
updateCanvasSize()
let lastTime = 0
const animate = (time: number) => {
if (!isInView) return
const deltaTime = (time - lastTime) / 1000
lastTime = time
updateSquares(gridParams.squares, deltaTime)
drawGrid(
ctx,
canvas.width,
canvas.height,
gridParams.cols,
gridParams.rows,
gridParams.squares,
gridParams.dpr
)
animationFrameId = requestAnimationFrame(animate)
}
const resizeObserver = new ResizeObserver(() => {
updateCanvasSize()
})
resizeObserver.observe(container)
const intersectionObserver = new IntersectionObserver(
([entry]) => {
setIsInView(entry.isIntersecting)
},
{ threshold: 0 }
)
intersectionObserver.observe(canvas)
if (isInView) {
animationFrameId = requestAnimationFrame(animate)
}
return () => {
cancelAnimationFrame(animationFrameId)
resizeObserver.disconnect()
intersectionObserver.disconnect()
}
}, [setupCanvas, updateSquares, drawGrid, width, height, isInView])
return (
<div
ref={containerRef}
className={cn(`h-full w-full ${className}`)}
{...props}
>
<canvas
ref={canvasRef}
className="pointer-events-none"
style={{
width: canvasSize.width,
height: canvasSize.height,
}}
/>
</div>
)
}

View File

@@ -0,0 +1,110 @@
"use client";
import { useState, useRef, useEffect, type ComponentProps } from "react";
import { Copy, Check } from "lucide-react";
import { Button } from "../ui/button";
import { codeToHtml } from "shiki/bundle/web";
import { cn } from "@/lib/utils";
type CodeBlockProps = ComponentProps<"pre">;
function extractLanguage(className?: string): string {
if (!className) return "plaintext";
const match = className.match(/language-([a-z0-9-]+)/i);
return match ? match[1] : "plaintext";
}
export function CodeBlock({ children, ...props }: CodeBlockProps) {
const [copied, setCopied] = useState(false);
const [{ html, className, title }, setRenderState] = useState<{
html: string;
className: string;
title: string | null;
}>({ html: "", className: "", title: null });
const preRef = useRef<HTMLPreElement>(null);
useEffect(() => {
const pre = preRef.current;
const codeEl = pre?.querySelector("code");
if (!pre || !codeEl) return;
const codeText = codeEl.textContent || "";
const lang = extractLanguage(codeEl.className);
const nextTitle = codeEl.getAttribute("data-title");
const nextClassName = codeEl.className || "";
void codeToHtml(codeText, {
lang: lang as any,
themes: {
light: "github-light",
dark: "github-dark",
},
defaultColor: false,
})
.then((html) => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
setRenderState({
html: doc.querySelector("code")?.innerHTML ?? "",
className: nextClassName,
title: nextTitle,
});
})
.catch((error) => {
console.error("Failed to highlight code:", error);
setRenderState({ html: "", className: nextClassName, title: nextTitle });
});
}, [children]);
const handleCopy = async () => {
const code = preRef.current?.textContent || "";
try {
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (error) {
console.error("Failed to copy code:", error);
}
};
return (
<div className="group relative rounded-xl overflow-hidden border border-border">
<pre
ref={preRef}
{...props}
className={cn("p-0! m-0! overflow-x-auto", props.className)}
>
{title && (
<div className="p-3 text-xs font-medium border-b border-border rounded-t-xl bg-muted/50 text-foreground">
{title}
</div>
)}
<Button
onClick={handleCopy}
variant="outline"
size="icon"
className={cn("absolute size-8 text-primary cursor-pointer right-3 opacity-100 lg:opacity-0 lg:group-hover:opacity-100 transition-opacity rounded-md border border-border shadow-none", title ? "top-13" : "top-3", props.className)}
aria-label="Copy code"
>
{copied ? <Check className="size-4" /> : <Copy className="size-4" />}
</Button>
{html && (
<div className="p-3">
<code
className={`shiki ${className}`}
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>
)}
{!html && (
<div className="p-4">
{children}
</div>
)}
</pre >
</div >
);
}

View File

@@ -0,0 +1,34 @@
/* eslint-disable @next/next/no-img-element */
interface MediaContainerProps {
src: string;
alt?: string;
type?: "image" | "video";
className?: string;
}
export function MediaContainer({
src,
alt = "",
type = "image",
className = "",
}: MediaContainerProps) {
return (
<div className={`ring-4 ring-muted w-full h-[300px] rounded-lg overflow-hidden flex items-center justify-center ${className}`}>
{type === "image" ? (
<img
src={src}
alt={alt}
className="w-full h-full object-cover object-center max-w-full max-h-full"
/>
) : (
<video
src={src}
className="w-full h-full object-cover object-center max-w-full max-h-full"
controls
/>
)}
</div>
);
}

View File

@@ -0,0 +1,23 @@
"use client";
import { Button } from "@/components/ui/button";
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
import { useTheme } from "next-themes";
import { cn } from "@/lib/utils";
export function ModeToggle({ className }: { className?: string }) {
const { theme, setTheme } = useTheme();
return (
<Button
type="button"
variant="link"
size="icon"
className={cn(className)}
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
>
<SunIcon className="h-full w-full" />
<MoonIcon className="hidden h-full w-full" />
</Button>
);
}

97
src/components/navbar.tsx Normal file
View File

@@ -0,0 +1,97 @@
import { Dock, DockIcon } from "@/components/magicui/dock";
import { ModeToggle } from "@/components/mode-toggle";
import { Separator } from "@/components/ui/separator";
import {
Tooltip,
TooltipArrow,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { DATA } from "@/data/resume";
export default function Navbar() {
return (
<div className="pointer-events-none fixed inset-x-0 bottom-4 z-30">
<Dock className="z-50 pointer-events-auto relative h-14 p-2 w-fit mx-auto flex gap-2 border bg-card/90 backdrop-blur-3xl shadow-[0_0_10px_3px] shadow-primary/5">
{DATA.navbar.map((item) => {
const isExternal = item.href.startsWith("http");
return (
<Tooltip key={item.href}>
<TooltipTrigger asChild>
<a
href={item.href}
target={isExternal ? "_blank" : undefined}
rel={isExternal ? "noopener noreferrer" : undefined}
>
<DockIcon className="rounded-3xl cursor-pointer size-full bg-background p-0 text-muted-foreground hover:text-foreground hover:bg-muted backdrop-blur-3xl border border-border transition-colors">
<item.icon className="size-full rounded-sm overflow-hidden object-contain" />
</DockIcon>
</a>
</TooltipTrigger>
<TooltipContent
side="top"
sideOffset={8}
className="rounded-xl bg-primary text-primary-foreground px-4 py-2 text-sm shadow-[0_10px_40px_-10px_rgba(0,0,0,0.3)] dark:shadow-[0_10px_40px_-10px_rgba(0,0,0,0.5)]"
>
<p>{item.label}</p>
<TooltipArrow className="fill-primary" />
</TooltipContent>
</Tooltip>
);
})}
<Separator
orientation="vertical"
className="h-2/3 m-auto w-px bg-border"
/>
{Object.entries(DATA.contact.social)
.filter(([_, social]) => social.navbar)
.map(([name, social], index) => {
const isExternal = social.url.startsWith("http");
const IconComponent = social.icon;
return (
<Tooltip key={`social-${name}-${index}`}>
<TooltipTrigger asChild>
<a
href={social.url}
target={isExternal ? "_blank" : undefined}
rel={isExternal ? "noopener noreferrer" : undefined}
>
<DockIcon className="rounded-3xl cursor-pointer size-full bg-background p-0 text-muted-foreground hover:text-foreground hover:bg-muted backdrop-blur-3xl border border-border transition-colors">
<IconComponent className="size-full rounded-sm overflow-hidden object-contain" />
</DockIcon>
</a>
</TooltipTrigger>
<TooltipContent
side="top"
sideOffset={8}
className="rounded-xl bg-primary text-primary-foreground px-4 py-2 text-sm shadow-[0_10px_40px_-10px_rgba(0,0,0,0.3)] dark:shadow-[0_10px_40px_-10px_rgba(0,0,0,0.5)]"
>
<p>{name}</p>
<TooltipArrow className="fill-primary" />
</TooltipContent>
</Tooltip>
);
})}
<Separator
orientation="vertical"
className="h-2/3 m-auto w-px bg-border"
/>
<Tooltip>
<TooltipTrigger asChild>
<DockIcon className="rounded-3xl cursor-pointer size-full bg-background p-0 text-muted-foreground hover:text-foreground hover:bg-muted backdrop-blur-3xl border border-border transition-colors">
<ModeToggle className="size-full cursor-pointer" />
</DockIcon>
</TooltipTrigger>
<TooltipContent
side="top"
sideOffset={8}
className="rounded-xl bg-primary text-primary-foreground px-4 py-2 text-sm shadow-[0_10px_40px_-10px_rgba(0,0,0,0.3)] dark:shadow-[0_10px_40px_-10px_rgba(0,0,0,0.5)]"
>
<p>Theme</p>
<TooltipArrow className="fill-primary" />
</TooltipContent>
</Tooltip>
</Dock>
</div>
);
}

View File

@@ -0,0 +1,143 @@
/* eslint-disable @next/next/no-img-element */
"use client";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import { ArrowUpRight } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import Markdown from "react-markdown";
function ProjectImage({ src, alt }: { src: string; alt: string }) {
const [imageError, setImageError] = useState(false);
if (!src || imageError) {
return <div className="w-full h-48 bg-muted" />;
}
return (
<img
src={src}
alt={alt}
className="w-full h-48 object-cover"
onError={() => setImageError(true)}
/>
);
}
interface Props {
title: string;
href?: string;
description: string;
dates: string;
tags: readonly string[];
link?: string;
image?: string;
video?: string;
links?: readonly {
icon: React.ReactNode;
type: string;
href: string;
}[];
className?: string;
}
export function ProjectCard({
title,
href,
description,
dates,
tags,
link,
image,
video,
links,
className,
}: Props) {
return (
<div
className={cn(
"flex flex-col h-full border border-border rounded-xl overflow-hidden hover:ring-2 cursor-pointer hover:ring-muted transition-all duration-200",
className
)}
>
<div className="relative shrink-0">
<Link
href={href || "#"}
target="_blank"
rel="noopener noreferrer"
className="block"
>
{video ? (
<video
src={video}
autoPlay
loop
muted
playsInline
className="w-full h-48 object-cover"
/>
) : image ? (
<ProjectImage src={image} alt={title} />
) : (
<div className="w-full h-48 bg-muted" />
)}
</Link>
{links && links.length > 0 && (
<div className="absolute top-2 right-2 flex flex-wrap gap-2">
{links.map((link, idx) => (
<Link
href={link.href}
key={idx}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
<Badge
className="flex items-center gap-1.5 text-xs bg-black text-white hover:bg-black/90"
variant="default"
>
{link.icon}
{link.type}
</Badge>
</Link>
))}
</div>
)}
</div>
<div className="p-6 flex flex-col gap-3 flex-1">
<div className="flex items-start justify-between gap-2">
<div className="flex flex-col gap-1">
<h3 className="font-semibold">{title}</h3>
<time className="text-xs text-muted-foreground">{dates}</time>
</div>
<Link
href={href || "#"}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-sm"
aria-label={`Open ${title}`}
>
<ArrowUpRight className="h-4 w-4" aria-hidden />
</Link>
</div>
<div className="text-xs flex-1 prose max-w-full text-pretty font-sans leading-relaxed text-muted-foreground dark:prose-invert">
<Markdown>{description}</Markdown>
</div>
{tags && tags.length > 0 && (
<div className="flex flex-wrap gap-1 mt-auto">
{tags.map((tag) => (
<Badge
key={tag}
className="text-[11px] font-medium border border-border h-6 w-fit px-2"
variant="outline"
>
{tag}
</Badge>
))}
</div>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,43 @@
import Link from "next/link";
import { FlickeringGrid } from "@/components/magicui/flickering-grid";
import { DATA } from "@/data/resume";
export default function ContactSection() {
return (
<div className="border rounded-xl p-10 relative">
<div className="absolute -top-4 border bg-primary z-10 rounded-xl px-4 py-1 left-1/2 -translate-x-1/2">
<span className="text-background text-sm font-medium">Contact</span>
</div>
<div className="absolute inset-0 top-0 left-0 right-0 h-1/2 rounded-xl overflow-hidden">
<FlickeringGrid
className="h-full w-full"
squareSize={2}
gridGap={2}
style={{
maskImage: "linear-gradient(to bottom, black, transparent)",
WebkitMaskImage: "linear-gradient(to bottom, black, transparent)",
}}
/>
</div>
<div className="relative flex flex-col items-center gap-4 text-center">
<h2 className="text-3xl font-bold tracking-tighter sm:text-5xl">
Get in Touch
</h2>
<p className="mx-auto max-w-lg text-muted-foreground text-balance">
Want to chat? Just shoot me a dm{" "}
<Link
href={DATA.contact.social.X.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline underline-offset-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-sm"
>
with a direct question on twitter
</Link>{" "}
and I&apos;ll respond whenever I can. I will ignore all
soliciting.
</p>
</div>
</div>
);
}

View File

@@ -0,0 +1,81 @@
/* eslint-disable @next/next/no-img-element */
import { Badge } from "@/components/ui/badge";
import Link from "next/link";
import { DATA } from "@/data/resume";
import { Timeline, TimelineItem, TimelineConnectItem } from "@/components/timeline";
export default function HackathonsSection() {
return (
<section id="hackathons" className="overflow-hidden">
<div className="flex min-h-0 flex-col gap-y-8 w-full">
<div className="flex flex-col gap-y-4 items-center justify-center">
<div className="flex items-center w-full">
<div className="flex-1 h-px bg-linear-to-r from-transparent from-5% via-border via-95% to-transparent" />
<div className="border bg-primary z-10 rounded-xl px-4 py-1">
<span className="text-background text-sm font-medium">Hackathons</span>
</div>
<div className="flex-1 h-px bg-linear-to-l from-transparent from-5% via-border via-95% to-transparent" />
</div>
<div className="flex flex-col gap-y-3 items-center justify-center">
<h2 className="text-3xl font-bold tracking-tighter sm:text-4xl">I like building things</h2>
<p className="text-muted-foreground md:text-lg/relaxed lg:text-base/relaxed xl:text-lg/relaxed text-balance text-center">
During my time in university, I attended {DATA.hackathons.length}+
hackathons. People from around the country would come together and
build incredible things in 2-3 days. It was eye-opening to see the endless possibilities brought to life by a group of motivated and passionate individuals.
</p>
</div>
</div>
<Timeline>
{DATA.hackathons.map((hackathon) => (
<TimelineItem key={hackathon.title + hackathon.dates} className="w-full flex items-start justify-between gap-10">
<TimelineConnectItem className="flex items-start justify-center">
{hackathon.image ? (
<img
src={hackathon.image}
alt={hackathon.title}
className="size-10 bg-card z-10 shrink-0 overflow-hidden p-1 border rounded-full shadow ring-2 ring-border object-contain flex-none"
/>
) : (
<div className="size-10 bg-card z-10 shrink-0 overflow-hidden p-1 border rounded-full shadow ring-2 ring-border flex-none" />
)}
</TimelineConnectItem>
<div className="flex flex-1 flex-col justify-start gap-2 min-w-0">
{hackathon.dates && (
<time className="text-xs text-muted-foreground">{hackathon.dates}</time>
)}
{hackathon.title && (
<h3 className="font-semibold leading-none">{hackathon.title}</h3>
)}
{hackathon.location && (
<p className="text-sm text-muted-foreground">{hackathon.location}</p>
)}
{hackathon.description && (
<p className="text-sm text-muted-foreground leading-relaxed wrap-break-word">
{hackathon.description}
</p>
)}
{hackathon.links && hackathon.links.length > 0 && (
<div className="mt-1 flex flex-row flex-wrap items-start gap-2">
{hackathon.links.map((link, idx) => (
<Link
href={link.href}
key={idx}
target="_blank"
rel="noopener noreferrer"
>
<Badge className="flex items-center gap-1.5 text-xs bg-primary text-primary-foreground">
{link.icon}
{link.title}
</Badge>
</Link>
))}
</div>
)}
</div>
</TimelineItem>
))}
</Timeline>
</div>
</section>
);
}

View File

@@ -0,0 +1,59 @@
import BlurFade from "@/components/magicui/blur-fade";
import { ProjectCard } from "@/components/project-card";
import { DATA } from "@/data/resume";
const BLUR_FADE_DELAY = 0.04;
export default function ProjectsSection() {
return (
<section id="projects">
<div className="flex min-h-0 flex-col gap-y-8">
<div className="flex flex-col gap-y-4 items-center justify-center">
<div className="flex items-center w-full">
<div
className="flex-1 h-px bg-linear-to-r from-transparent from-5% via-border via-95% to-transparent"
/>
<div className="border bg-primary z-10 rounded-xl px-4 py-1">
<span className="text-background text-sm font-medium">My Projects</span>
</div>
<div
className="flex-1 h-px bg-linear-to-l from-transparent from-5% via-border via-95% to-transparent"
/>
</div>
<div className="flex flex-col gap-y-3 items-center justify-center">
<h2 className="text-3xl font-bold tracking-tighter sm:text-4xl">Check out my latest work</h2>
<p className="text-muted-foreground md:text-lg/relaxed lg:text-base/relaxed xl:text-lg/relaxed text-balance text-center">
I&apos;ve worked on a variety of projects, from simple
websites to complex web applications. Here are a few of my
favorites.
</p>
</div>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 max-w-[800px] mx-auto auto-rows-fr">
{DATA.projects.map((project, id) => (
<BlurFade
key={project.title}
delay={BLUR_FADE_DELAY * 12 + id * 0.05}
className="h-full"
>
<ProjectCard
href={project.href}
key={project.title}
title={project.title}
description={project.description}
dates={project.dates}
tags={project.technologies}
image={project.image}
video={project.video}
links={project.links}
/>
</BlurFade>
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,87 @@
/* eslint-disable @next/next/no-img-element */
"use client";
import { useState } from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { DATA } from "@/data/resume";
import { ChevronDown, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
function LogoImage({ src, alt }: { src: string; alt: string }) {
const [imageError, setImageError] = useState(false);
if (!src || imageError) {
return (
<div className="size-8 md:size-10 p-1 border rounded-full shadow ring-2 ring-border bg-muted flex-none" />
);
}
return (
<img
src={src}
alt={alt}
className="size-8 md:size-10 p-1 border rounded-full shadow ring-2 ring-border overflow-hidden object-contain flex-none"
onError={() => setImageError(true)}
/>
);
}
export default function WorkSection() {
return (
<Accordion type="single" collapsible className="w-full grid gap-6">
{DATA.work.map((work) => (
<AccordionItem
key={work.company}
value={work.company}
className="w-full border-b-0 grid gap-2"
>
<AccordionTrigger className="hover:no-underline p-0 cursor-pointer transition-colors rounded-none group [&>svg]:hidden">
<div className="flex items-center gap-x-3 justify-between w-full text-left">
<div className="flex items-center gap-x-3 flex-1 min-w-0">
<LogoImage src={work.logoUrl} alt={work.company} />
<div className="flex-1 min-w-0 gap-0.5 flex flex-col">
<div className="font-semibold leading-none flex items-center gap-2">
{work.company}
<span className="relative inline-flex items-center w-3.5 h-3.5">
<ChevronRight
className={cn(
"absolute h-3.5 w-3.5 shrink-0 text-muted-foreground stroke-2 transition-all duration-300 ease-out",
"translate-x-0 opacity-0",
"group-hover:translate-x-1 group-hover:opacity-100",
"group-data-[state=open]:opacity-0 group-data-[state=open]:translate-x-0"
)}
/>
<ChevronDown
className={cn(
"absolute h-3.5 w-3.5 shrink-0 text-muted-foreground stroke-2 transition-all duration-200",
"opacity-0 rotate-0",
"group-data-[state=open]:opacity-100 group-data-[state=open]:rotate-180"
)}
/>
</span>
</div>
<div className="font-sans text-sm text-muted-foreground">
{work.title}
</div>
</div>
</div>
<div className="flex items-center gap-1 text-xs tabular-nums text-muted-foreground text-right flex-none">
<span>
{work.start} - {work.end ?? "Present"}
</span>
</div>
</div>
</AccordionTrigger>
<AccordionContent className="p-0 ml-13 text-xs sm:text-sm text-muted-foreground">
{work.description}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
);
}

View File

@@ -0,0 +1,8 @@
"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import type { ThemeProviderProps } from "next-themes";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

View File

@@ -0,0 +1,84 @@
import { ReactNode } from "react";
import { cn } from "@/lib/utils";
type Orientation = "vertical" | "horizontal";
export interface TimelineItemProps {
children: ReactNode;
className?: string;
}
export interface TimelineProps {
children: ReactNode;
className?: string;
orientation?: Orientation;
}
export interface TimelineConnectItemProps {
children: ReactNode;
className?: string;
}
export function TimelineConnectItem({
children,
className,
}: TimelineConnectItemProps) {
return (
<div
className={cn(
"relative flex shrink-0 justify-center items-center self-stretch",
className
)}
>
<div
data-timeline-line
className={cn(
"absolute bg-border",
"group-data-[orientation=vertical]:left-1/2 group-data-[orientation=vertical]:-translate-x-1/2",
"group-data-[orientation=vertical]:top-0 group-data-[orientation=vertical]:h-[calc(50%+var(--timeline-gap)+50%)]",
"group-data-[orientation=vertical]:w-px",
"group-data-[orientation=horizontal]:top-1/2 group-data-[orientation=horizontal]:-translate-y-1/2",
"group-data-[orientation=horizontal]:left-1/2 group-data-[orientation=horizontal]:w-[calc(50%+var(--timeline-gap)+50%)]",
"group-data-[orientation=horizontal]:h-px"
)}
/>
<div className="relative z-20 shrink-0">{children}</div>
</div>
);
}
export function TimelineItem({ children, className }: TimelineItemProps) {
return (
<div className={cn("relative", className)}>
{children}
</div>
);
}
export function Timeline({
children,
className,
orientation = "vertical",
}: TimelineProps) {
return (
<div
data-orientation={orientation}
className={cn(
"group relative [--timeline-gap:2rem]",
orientation === "vertical" && "flex flex-col gap-4 p-4 w-full",
orientation === "horizontal" && "flex flex-row gap-4 p-4 h-full",
className
)}
>
<div
className={cn(
"relative [&>*:last-child_[data-timeline-line]]:hidden",
orientation === "vertical" && "space-y-8 w-full",
orientation === "horizontal" && "flex flex-row gap-8 h-full"
)}
>
{children}
</div>
</div>
);
}

View File

@@ -0,0 +1,57 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]_svg]:rotate-180",
className
)}
{...props}
>
{children}
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@@ -0,0 +1,50 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }

View File

@@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View File

@@ -0,0 +1,57 @@
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9 rounded-full",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";
export { Button, buttonVariants };

View File

@@ -0,0 +1,86 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-lg bg-card text-card-foreground", className)}
{...props}
/>
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col", className)} {...props} />
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
));
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"text-pretty font-sans text-sm text-muted-foreground",
className
)}
{...props}
/>
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center pt-2", className)}
{...props}
/>
));
CardFooter.displayName = "CardFooter";
export {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
};

View File

@@ -0,0 +1,31 @@
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }

View File

@@ -0,0 +1,28 @@
import type { SVGProps } from "react";
const Csharp = (props: SVGProps<SVGSVGElement>) => (
<svg
{...props}
preserveAspectRatio="xMidYMid"
viewBox="0 -1.43 255.58 290.11"
>
<path
fill="#a179dc"
d="M255.57 84.45c0-4.83-1.04-9.1-3.13-12.76a24.4 24.4 0 0 0-9.24-9C209.17 43.05 175.1 23.5 141.1 3.86c-9.17-5.3-18.06-5.1-27.16.27-13.54 7.98-81.35 46.83-101.55 58.53C4.06 67.5.02 74.87 0 84.44v118.37c0 4.72 1 8.9 2.99 12.51 2.05 3.72 5.17 6.82 9.38 9.26 20.21 11.7 88.02 50.55 101.56 58.53 9.11 5.38 18 5.57 27.17.27 34.02-19.64 68.08-39.2 102.1-58.81a24.33 24.33 0 0 0 9.4-9.25c1.99-3.61 2.98-7.8 2.98-12.52l-.01-118.35"
/>
<path
fill="#280068"
d="M128.18 143.24 2.98 215.33c2.06 3.7 5.18 6.8 9.4 9.25 20.2 11.7 88.01 50.55 101.55 58.53 9.11 5.38 18 5.57 27.17.27 34.02-19.64 68.08-39.2 102.1-58.81a24.33 24.33 0 0 0 9.4-9.25z"
/>
<path
fill="#390091"
d="M255.57 84.45c0-4.83-1.04-9.1-3.13-12.76l-124.26 71.55 124.41 72.07c2-3.6 2.99-7.79 3-12.51 0 0 0-78.9-.02-118.35"
/>
<g fill="#fff">
<path d="M201.9 116.3v13.47h13.47v-13.48h6.73v13.48h13.48v6.73H222.1v13.48h13.48v6.74H222.1v13.47h-6.73V156.7h-13.48v13.48h-6.73V156.7h-13.48v-6.73h13.47V136.5h-13.47v-6.74h13.47v-13.48zm13.47 20.2h-13.48v13.48h13.48z" />
<path d="M128.46 48.63a94.96 94.96 0 0 1 82.26 47.45l-.16-.27-41.35 23.8A47.28 47.28 0 0 0 129 96.33h-.54a47.3 47.3 0 0 0-47.3 47.3 47.08 47.08 0 0 0 6.23 23.47 47.28 47.28 0 0 0 82.29-.27l-.2.35 41.29 23.91a94.97 94.97 0 0 1-81.25 47.54h-1.06a94.96 94.96 0 0 1-95-95 95 95 0 0 1 95-95z" />
</g>
</svg>
);
export { Csharp };

View File

@@ -0,0 +1,9 @@
import type { SVGProps } from "react";
const Docker = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 24 24" fill="#008fe2">
<path d="M13.98 11.08h2.12a.19.19 0 0 0 .19-.19V9.01a.19.19 0 0 0-.19-.19h-2.12a.18.18 0 0 0-.18.18v1.9c0 .1.08.18.18.18m-2.95-5.43h2.12a.19.19 0 0 0 .18-.19V3.57a.19.19 0 0 0-.18-.18h-2.12a.18.18 0 0 0-.19.18v1.9c0 .1.09.18.19.18m0 2.71h2.12a.19.19 0 0 0 .18-.18V6.29a.19.19 0 0 0-.18-.18h-2.12a.18.18 0 0 0-.19.18v1.89c0 .1.09.18.19.18m-2.93 0h2.12a.19.19 0 0 0 .18-.18V6.29a.18.18 0 0 0-.18-.18H8.1a.18.18 0 0 0-.18.18v1.89c0 .1.08.18.18.18m-2.96 0h2.11a.19.19 0 0 0 .19-.18V6.29a.18.18 0 0 0-.19-.18H5.14a.19.19 0 0 0-.19.18v1.89c0 .1.08.18.19.18m5.89 2.72h2.12a.19.19 0 0 0 .18-.19V9.01a.19.19 0 0 0-.18-.19h-2.12a.18.18 0 0 0-.19.18v1.9c0 .1.09.18.19.18m-2.93 0h2.12a.18.18 0 0 0 .18-.19V9.01a.18.18 0 0 0-.18-.19H8.1a.18.18 0 0 0-.18.18v1.9c0 .1.08.18.18.18m-2.96 0h2.11a.18.18 0 0 0 .19-.19V9.01a.18.18 0 0 0-.18-.19H5.14a.19.19 0 0 0-.19.19v1.88c0 .1.08.19.19.19m-2.92 0h2.12a.18.18 0 0 0 .18-.19V9.01a.18.18 0 0 0-.18-.19H2.22a.18.18 0 0 0-.19.18v1.9c0 .1.08.18.19.18m21.54-1.19c-.06-.05-.67-.51-1.95-.51-.34 0-.68.03-1.01.09a3.77 3.77 0 0 0-1.72-2.57l-.34-.2-.23.33a4.6 4.6 0 0 0-.6 1.43c-.24.97-.1 1.88.4 2.66a4.7 4.7 0 0 1-1.75.42H.76a.75.75 0 0 0-.76.75 11.38 11.38 0 0 0 .7 4.06 6.03 6.03 0 0 0 2.4 3.12c1.18.73 3.1 1.14 5.28 1.14.98 0 1.96-.08 2.93-.26a12.25 12.25 0 0 0 3.82-1.4 10.5 10.5 0 0 0 2.61-2.13c1.25-1.42 2-3 2.55-4.4h.23c1.37 0 2.21-.55 2.68-1 .3-.3.55-.66.7-1.06l.1-.28Z" />
</svg>
);
export { Docker };

View File

@@ -0,0 +1,20 @@
import type { SVGProps } from "react";
const Golang = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 207 78">
<g fill="currentColor" fillRule="evenodd">
<path d="m16.2 24.1c-.4 0-.5-.2-.3-.5l2.1-2.7c.2-.3.7-.5 1.1-.5h35.7c.4 0 .5.3.3.6l-1.7 2.6c-.2.3-.7.6-1 .6z" />
<path d="m1.1 33.3c-.4 0-.5-.2-.3-.5l2.1-2.7c.2-.3.7-.5 1.1-.5h45.6c.4 0 .6.3.5.6l-.8 2.4c-.1.4-.5.6-.9.6z" />
<path d="m25.3 42.5c-.4 0-.5-.3-.3-.6l1.4-2.5c.2-.3.6-.6 1-.6h20c.4 0 .6.3.6.7l-.2 2.4c0 .4-.4.7-.7.7z" />
<g transform="translate(55)">
<path d="m74.1 22.3c-6.3 1.6-10.6 2.8-16.8 4.4-1.5.4-1.6.5-2.9-1-1.5-1.7-2.6-2.8-4.7-3.8-6.3-3.1-12.4-2.2-18.1 1.5-6.8 4.4-10.3 10.9-10.2 19 .1 8 5.6 14.6 13.5 15.7 6.8.9 12.5-1.5 17-6.6.9-1.1 1.7-2.3 2.7-3.7-3.6 0-8.1 0-19.3 0-2.1 0-2.6-1.3-1.9-3 1.3-3.1 3.7-8.3 5.1-10.9.3-.6 1-1.6 2.5-1.6h36.4c-.2 2.7-.2 5.4-.6 8.1-1.1 7.2-3.8 13.8-8.2 19.6-7.2 9.5-16.6 15.4-28.5 17-9.8 1.3-18.9-.6-26.9-6.6-7.4-5.6-11.6-13-12.7-22.2-1.3-10.9 1.9-20.7 8.5-29.3 7.1-9.3 16.5-15.2 28-17.3 9.4-1.7 18.4-.6 26.5 4.9 5.3 3.5 9.1 8.3 11.6 14.1.6.9.2 1.4-1 1.7z" />
<path
d="m107.2 77.6c-9.1-.2-17.4-2.8-24.4-8.8-5.9-5.1-9.6-11.6-10.8-19.3-1.8-11.3 1.3-21.3 8.1-30.2 7.3-9.6 16.1-14.6 28-16.7 10.2-1.8 19.8-.8 28.5 5.1 7.9 5.4 12.8 12.7 14.1 22.3 1.7 13.5-2.2 24.5-11.5 33.9-6.6 6.7-14.7 10.9-24 12.8-2.7.5-5.4.6-8 .9zm23.8-40.4c-.1-1.3-.1-2.3-.3-3.3-1.8-9.9-10.9-15.5-20.4-13.3-9.3 2.1-15.3 8-17.5 17.4-1.8 7.8 2 15.7 9.2 18.9 5.5 2.4 11 2.1 16.3-.6 7.9-4.1 12.2-10.5 12.7-19.1z"
fillRule="nonzero"
/>
</g>
</g>
</svg>
);
export { Golang };

View File

@@ -0,0 +1,20 @@
import type { SVGProps } from "react";
const GolangDark = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 207 78">
<g fill="#fff" fillRule="evenodd">
<path d="m16.2 24.1c-.4 0-.5-.2-.3-.5l2.1-2.7c.2-.3.7-.5 1.1-.5h35.7c.4 0 .5.3.3.6l-1.7 2.6c-.2.3-.7.6-1 .6z" />
<path d="m1.1 33.3c-.4 0-.5-.2-.3-.5l2.1-2.7c.2-.3.7-.5 1.1-.5h45.6c.4 0 .6.3.5.6l-.8 2.4c-.1.4-.5.6-.9.6z" />
<path d="m25.3 42.5c-.4 0-.5-.3-.3-.6l1.4-2.5c.2-.3.6-.6 1-.6h20c.4 0 .6.3.6.7l-.2 2.4c0 .4-.4.7-.7.7z" />
<g transform="translate(55)">
<path d="m74.1 22.3c-6.3 1.6-10.6 2.8-16.8 4.4-1.5.4-1.6.5-2.9-1-1.5-1.7-2.6-2.8-4.7-3.8-6.3-3.1-12.4-2.2-18.1 1.5-6.8 4.4-10.3 10.9-10.2 19 .1 8 5.6 14.6 13.5 15.7 6.8.9 12.5-1.5 17-6.6.9-1.1 1.7-2.3 2.7-3.7-3.6 0-8.1 0-19.3 0-2.1 0-2.6-1.3-1.9-3 1.3-3.1 3.7-8.3 5.1-10.9.3-.6 1-1.6 2.5-1.6h36.4c-.2 2.7-.2 5.4-.6 8.1-1.1 7.2-3.8 13.8-8.2 19.6-7.2 9.5-16.6 15.4-28.5 17-9.8 1.3-18.9-.6-26.9-6.6-7.4-5.6-11.6-13-12.7-22.2-1.3-10.9 1.9-20.7 8.5-29.3 7.1-9.3 16.5-15.2 28-17.3 9.4-1.7 18.4-.6 26.5 4.9 5.3 3.5 9.1 8.3 11.6 14.1.6.9.2 1.4-1 1.7z" />
<path
d="m107.2 77.6c-9.1-.2-17.4-2.8-24.4-8.8-5.9-5.1-9.6-11.6-10.8-19.3-1.8-11.3 1.3-21.3 8.1-30.2 7.3-9.6 16.1-14.6 28-16.7 10.2-1.8 19.8-.8 28.5 5.1 7.9 5.4 12.8 12.7 14.1 22.3 1.7 13.5-2.2 24.5-11.5 33.9-6.6 6.7-14.7 10.9-24 12.8-2.7.5-5.4.6-8 .9zm23.8-40.4c-.1-1.3-.1-2.3-.3-3.3-1.8-9.9-10.9-15.5-20.4-13.3-9.3 2.1-15.3 8-17.5 17.4-1.8 7.8 2 15.7 9.2 18.9 5.5 2.4 11 2.1 16.3-.6 7.9-4.1 12.2-10.5 12.7-19.1z"
fillRule="nonzero"
/>
</g>
</g>
</svg>
);
export { GolangDark };

View File

@@ -0,0 +1,28 @@
import type { SVGProps } from "react";
const Java = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} preserveAspectRatio="xMidYMid" viewBox="0 0 256 346">
<path
d="M83 267s-14 8 9 11c27 3 41 2 71-3 0 0 8 5 19 9-67 29-153-2-99-17M74 230s-15 11 8 13c29 3 52 3 92-4 0 0 6 5 15 8-82 24-173 2-115-17"
fill="#5382A1"
/>
<path
d="M144 166c17 19-4 36-4 36s42-22 22-49c-18-26-32-38 44-82 0 0-119 29-62 95"
fill="#E76F00"
/>
<path
d="M233 295s10 8-10 15c-39 12-163 15-197 0-12-5 11-13 18-14l12-2c-14-9-89 19-38 28 138 22 251-10 215-27M89 190s-63 15-22 21c17 2 51 2 83-1 26-2 52-7 52-7l-16 9c-64 16-187 8-151-9 30-14 54-13 54-13M202 253c64-33 34-66 13-61l-7 2s2-3 6-5c41-14 73 43-14 66l2-2"
fill="#5382A1"
/>
<path
d="M162 0s36 36-34 91c-56 45-12 70 0 99-32-30-56-56-40-80 23-35 89-53 74-110"
fill="#E76F00"
/>
<path
d="M95 345c62 4 158-3 160-32 0 0-4 11-51 20-53 10-119 9-158 2 0 0 8 7 49 10"
fill="#5382A1"
/>
</svg>
);
export { Java };

View File

@@ -0,0 +1,58 @@
import type { SVGProps } from "react";
const Kubernetes = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 722.8 702">
<path
style={{
fill: "#326ce5",
fillOpacity: "1",
stroke: "#fff",
strokeWidth: "0",
strokeMiterlimit: "4",
strokeOpacity: "1",
strokeDasharray: "none",
}}
d="M365 185a47 46 0 0 0-18 4L103 306a47 46 0 0 0-25 32L18 600a47 46 0 0 0 6 35 47 46 0 0 0 3 4l169 210a47 46 0 0 0 36 18h271a47 46 0 0 0 37-18l169-210a47 46 0 0 0 9-39l-60-263a47 46 0 0 0-26-31L388 189a47 46 0 0 0-23-4z"
transform="translate(-6 -175)"
/>
<path
d="M368 274c-8 0-15 7-15 16v4l2 14 2 27c-1 3-3 6-5 7v7a190 190 0 0 0-122 58l-6-3c-2 0-5 1-8-1l-20-18-10-10-3-3c-3-2-7-4-10-4-5 0-9 2-12 5-5 7-3 16 4 22l3 3 12 7 22 15c2 2 3 7 3 8l5 5c-26 37-37 84-30 131l-6 2c-2 2-4 6-7 7l-26 4-14 1-4 1c-9 2-14 10-13 18 2 8 11 12 19 11h1v-1h4l13-5c9-4 17-7 25-8l9 3 6-1c15 45 45 82 84 105l-2 7c1 2 2 5 1 8l-13 24-8 11-2 4c-4 8-1 18 6 21 8 4 17 0 20-8l2-4 5-13c3-10 6-20 11-27l6-3 3-6a189 189 0 0 0 135 1l4 5c2 1 5 2 7 5l10 24 4 14 2 4c4 8 13 11 20 8 8-4 10-13 7-21l-2-4-8-12c-6-8-10-15-13-23-1-4 0-6 1-8l-2-6c40-24 70-62 84-106l6 1c2-2 4-4 8-3 8 1 16 4 26 7l13 5 4 1c9 2 17-3 19-10 2-8-4-16-12-18l-5-1-14-1c-10-1-18-2-26-5-3-1-5-5-6-6l-6-2a189 189 0 0 0-31-131l6-5c0-3 0-5 2-8 6-6 13-10 22-16l12-7 4-2c7-6 8-16 3-22s-15-7-22-1l-3 2-10 11c-7 7-13 13-19 17-3 2-7 2-9 1l-6 4c-31-33-75-54-121-58v-7c-2-2-5-3-5-7-1-8 0-16 1-27l2-14v-4c0-9-6-16-14-16zm-19 113-4 77a13 13 0 0 1-21 10l-63-44a151 151 0 0 1 88-43zm37 0c33 5 64 20 88 43l-63 44a13 13 0 0 1-21-10zm-148 71 58 52a13 13 0 0 1-5 22l-74 22c-4-35 4-68 21-96zm259 0a153 153 0 0 1 22 95l-74-21a13 13 0 0 1-5-22h-1l58-52zm-141 56h23l15 18-5 23-21 10-21-10-6-23zm75 62h3l77 13c-12 32-33 59-61 77l-30-72a13 13 0 0 1 11-18zm-128 1a13 13 0 0 1 12 18l-29 71c-27-18-49-44-61-77l76-12h2zm64 31c2-1 4 0 6 1 3 1 5 3 6 5l38 68a154 154 0 0 1-98-1l37-67c3-4 7-6 11-6z"
style={{
fontSize: "medium",
fontStyle: "normal",
fontVariant: "normal",
fontWeight: "400",
fontStretch: "normal",
textIndent: "0",
textAlign: "start",
textDecoration: "none",
lineHeight: "normal",
letterSpacing: "normal",
wordSpacing: "normal",
textTransform: "none",
direction: "ltr",
blockProgression: "tb",
writingMode: "lr-tb" as any,
textAnchor: "start",
baselineShift: "baseline",
color: "#000",
fill: "#fff",
fillOpacity: "1",
stroke: "#fff",
strokeWidth: ".25",
strokeMiterlimit: "4",
strokeOpacity: "1",
strokeDasharray: "none",
marker: "none",
visibility: "visible",
display: "inline",
overflow: "visible",
fontFamily: "Sans",
InkscapeFontSpecification: "Sans",
} as React.CSSProperties}
transform="translate(-6 -175)"
/>
</svg>
);
export { Kubernetes };

View File

@@ -0,0 +1,57 @@
import type { SVGProps } from "react";
const NextjsIconDark = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 180 180">
<mask
height="180"
id=":r8:mask0_408_134"
maskUnits="userSpaceOnUse"
width="180"
x="0"
y="0"
style={{ maskType: "alpha" }}
>
<circle cx="90" cy="90" fill="black" r="90" />
</mask>
<g mask="url(#:r8:mask0_408_134)">
<circle cx="90" cy="90" data-circle="true" fill="black" r="90" />
<path
d="M149.508 157.52L69.142 54H54V125.97H66.1136V69.3836L139.999 164.845C143.333 162.614 146.509 160.165 149.508 157.52Z"
fill="url(#:r8:paint0_linear_408_134)"
/>
<rect
fill="url(#:r8:paint1_linear_408_134)"
height="72"
width="12"
x="115"
y="54"
/>
</g>
<defs>
<linearGradient
gradientUnits="userSpaceOnUse"
id=":r8:paint0_linear_408_134"
x1="109"
x2="144.5"
y1="116.5"
y2="160.5"
>
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id=":r8:paint1_linear_408_134"
x1="121"
x2="120.799"
y1="54"
y2="106.875"
>
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0" />
</linearGradient>
</defs>
</svg>
);
export { NextjsIconDark };

View File

@@ -0,0 +1,42 @@
import type { SVGProps } from "react";
const NextjsLogoDark = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 394 80" fill="none">
<path
d="M261.919 0.0330722H330.547V12.7H303.323V79.339H289.71V12.7H261.919V0.0330722Z"
fill="white"
/>
<path
d="M149.052 0.0330722V12.7H94.0421V33.0772H138.281V45.7441H94.0421V66.6721H149.052V79.339H80.43V12.7H80.4243V0.0330722H149.052Z"
fill="white"
/>
<path
d="M183.32 0.0661486H165.506L229.312 79.3721H247.178L215.271 39.7464L247.127 0.126654L229.312 0.154184L206.352 28.6697L183.32 0.0661486Z"
fill="white"
/>
<path
d="M201.6 56.7148L192.679 45.6229L165.455 79.4326H183.32L201.6 56.7148Z"
fill="white"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M80.907 79.339L17.0151 0H0V79.3059H13.6121V16.9516L63.8067 79.339H80.907Z"
fill="white"
/>
<path
d="M333.607 78.8546C332.61 78.8546 331.762 78.5093 331.052 77.8186C330.342 77.1279 329.991 76.2917 330 75.3011C329.991 74.3377 330.342 73.5106 331.052 72.8199C331.762 72.1292 332.61 71.7838 333.607 71.7838C334.566 71.7838 335.405 72.1292 336.115 72.8199C336.835 73.5106 337.194 74.3377 337.204 75.3011C337.194 75.9554 337.028 76.5552 336.696 77.0914C336.355 77.6368 335.922 78.064 335.377 78.373C334.842 78.6911 334.252 78.8546 333.607 78.8546Z"
fill="white"
/>
<path
d="M356.84 45.4453H362.872V68.6846C362.863 70.8204 362.401 72.6472 361.498 74.1832C360.585 75.7191 359.321 76.8914 357.698 77.7185C356.084 78.5364 354.193 78.9546 352.044 78.9546C350.079 78.9546 348.318 78.6001 346.75 77.9094C345.182 77.2187 343.937 76.1826 343.024 74.8193C342.101 73.456 341.649 71.7565 341.649 69.7207H347.691C347.7 70.6114 347.903 71.3838 348.29 72.0291C348.677 72.6744 349.212 73.1651 349.895 73.5105C350.586 73.8559 351.38 74.0286 352.274 74.0286C353.243 74.0286 354.073 73.8286 354.746 73.4196C355.419 73.0197 355.936 72.4199 356.296 71.6201C356.646 70.8295 356.831 69.8479 356.84 68.6846V45.4453Z"
fill="white"
/>
<path
d="M387.691 54.5338C387.544 53.1251 386.898 52.0254 385.773 51.2438C384.638 50.4531 383.172 50.0623 381.373 50.0623C380.11 50.0623 379.022 50.2532 378.118 50.6258C377.214 51.0075 376.513 51.5164 376.033 52.1617C375.554 52.807 375.314 53.5432 375.295 54.3703C375.295 55.061 375.461 55.6608 375.784 56.1607C376.107 56.6696 376.54 57.0968 377.103 57.4422C377.656 57.7966 378.274 58.0874 378.948 58.3237C379.63 58.56 380.313 58.76 380.995 58.9236L384.14 59.6961C385.404 59.9869 386.631 60.3778 387.802 60.8776C388.973 61.3684 390.034 61.9955 390.965 62.7498C391.897 63.5042 392.635 64.413 393.179 65.4764C393.723 66.5397 394 67.7848 394 69.2208C394 71.1566 393.502 72.8562 392.496 74.3285C391.491 75.7917 390.043 76.9369 388.143 77.764C386.252 78.582 383.965 79 381.272 79C378.671 79 376.402 78.6002 374.493 77.8004C372.575 77.0097 371.08 75.8463 370.001 74.3194C368.922 72.7926 368.341 70.9294 368.258 68.7391H374.235C374.318 69.8842 374.687 70.8386 375.314 71.6111C375.95 72.3745 376.78 72.938 377.795 73.3197C378.819 73.6923 379.962 73.8832 381.226 73.8832C382.545 73.8832 383.707 73.6832 384.712 73.2924C385.708 72.9016 386.492 72.3564 387.055 71.6475C387.627 70.9476 387.913 70.1206 387.922 69.1754C387.913 68.312 387.654 67.5939 387.156 67.0304C386.649 66.467 385.948 65.9944 385.053 65.6127C384.15 65.231 383.098 64.8856 381.899 64.5857L378.081 63.6223C375.323 62.9225 373.137 61.8592 371.541 60.4323C369.937 59.0054 369.143 57.115 369.143 54.7429C369.143 52.798 369.678 51.0894 370.758 49.6261C371.827 48.1629 373.294 47.0268 375.148 46.2179C377.011 45.4 379.114 45 381.456 45C383.836 45 385.92 45.4 387.719 46.2179C389.517 47.0268 390.929 48.1538 391.952 49.5897C392.976 51.0257 393.511 52.6707 393.539 54.5338H387.691Z"
fill="white"
/>
</svg>
);
export { NextjsLogoDark };

View File

@@ -0,0 +1,42 @@
import type { SVGProps } from "react";
const NextjsLogoLight = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 394 80" fill="none">
<path
d="M261.919 0.0330722H330.547V12.7H303.323V79.339H289.71V12.7H261.919V0.0330722Z"
fill="black"
/>
<path
d="M149.052 0.0330722V12.7H94.0421V33.0772H138.281V45.7441H94.0421V66.6721H149.052V79.339H80.43V12.7H80.4243V0.0330722H149.052Z"
fill="black"
/>
<path
d="M183.32 0.0661486H165.506L229.312 79.3721H247.178L215.271 39.7464L247.127 0.126654L229.312 0.154184L206.352 28.6697L183.32 0.0661486Z"
fill="black"
/>
<path
d="M201.6 56.7148L192.679 45.6229L165.455 79.4326H183.32L201.6 56.7148Z"
fill="black"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M80.907 79.339L17.0151 0H0V79.3059H13.6121V16.9516L63.8067 79.339H80.907Z"
fill="black"
/>
<path
d="M333.607 78.8546C332.61 78.8546 331.762 78.5093 331.052 77.8186C330.342 77.1279 329.991 76.2917 330 75.3011C329.991 74.3377 330.342 73.5106 331.052 72.8199C331.762 72.1292 332.61 71.7838 333.607 71.7838C334.566 71.7838 335.405 72.1292 336.115 72.8199C336.835 73.5106 337.194 74.3377 337.204 75.3011C337.194 75.9554 337.028 76.5552 336.696 77.0914C336.355 77.6368 335.922 78.064 335.377 78.373C334.842 78.6911 334.252 78.8546 333.607 78.8546Z"
fill="black"
/>
<path
d="M356.84 45.4453H362.872V68.6846C362.863 70.8204 362.401 72.6472 361.498 74.1832C360.585 75.7191 359.321 76.8914 357.698 77.7185C356.084 78.5364 354.193 78.9546 352.044 78.9546C350.079 78.9546 348.318 78.6001 346.75 77.9094C345.182 77.2187 343.937 76.1826 343.024 74.8193C342.101 73.456 341.649 71.7565 341.649 69.7207H347.691C347.7 70.6114 347.903 71.3838 348.29 72.0291C348.677 72.6744 349.212 73.1651 349.895 73.5105C350.586 73.8559 351.38 74.0286 352.274 74.0286C353.243 74.0286 354.073 73.8286 354.746 73.4196C355.419 73.0197 355.936 72.4199 356.296 71.6201C356.646 70.8295 356.831 69.8479 356.84 68.6846V45.4453Z"
fill="black"
/>
<path
d="M387.691 54.5338C387.544 53.1251 386.898 52.0254 385.773 51.2438C384.638 50.4531 383.172 50.0623 381.373 50.0623C380.11 50.0623 379.022 50.2532 378.118 50.6258C377.214 51.0075 376.513 51.5164 376.033 52.1617C375.554 52.807 375.314 53.5432 375.295 54.3703C375.295 55.061 375.461 55.6608 375.784 56.1607C376.107 56.6696 376.54 57.0968 377.103 57.4422C377.656 57.7966 378.274 58.0874 378.948 58.3237C379.63 58.56 380.313 58.76 380.995 58.9236L384.14 59.6961C385.404 59.9869 386.631 60.3778 387.802 60.8776C388.973 61.3684 390.034 61.9955 390.965 62.7498C391.897 63.5042 392.635 64.413 393.179 65.4764C393.723 66.5397 394 67.7848 394 69.2208C394 71.1566 393.502 72.8562 392.496 74.3285C391.491 75.7917 390.043 76.9369 388.143 77.764C386.252 78.582 383.965 79 381.272 79C378.671 79 376.402 78.6002 374.493 77.8004C372.575 77.0097 371.08 75.8463 370.001 74.3194C368.922 72.7926 368.341 70.9294 368.258 68.7391H374.235C374.318 69.8842 374.687 70.8386 375.314 71.6111C375.95 72.3745 376.78 72.938 377.795 73.3197C378.819 73.6923 379.962 73.8832 381.226 73.8832C382.545 73.8832 383.707 73.6832 384.712 73.2924C385.708 72.9016 386.492 72.3564 387.055 71.6475C387.627 70.9476 387.913 70.1206 387.922 69.1754C387.913 68.312 387.654 67.5939 387.156 67.0304C386.649 66.467 385.948 65.9944 385.053 65.6127C384.15 65.231 383.098 64.8856 381.899 64.5857L378.081 63.6223C375.323 62.9225 373.137 61.8592 371.541 60.4323C369.937 59.0054 369.143 57.115 369.143 54.7429C369.143 52.798 369.678 51.0894 370.758 49.6261C371.827 48.1629 373.294 47.0268 375.148 46.2179C377.011 45.4 379.114 45 381.456 45C383.836 45 385.92 45.4 387.719 46.2179C389.517 47.0268 390.929 48.1538 391.952 49.5897C392.976 51.0257 393.511 52.6707 393.539 54.5338H387.691Z"
fill="black"
/>
</svg>
);
export { NextjsLogoLight };

View File

@@ -0,0 +1,79 @@
import type { SVGProps } from "react";
const Nodejs = (props: SVGProps<SVGSVGElement>) => (
<svg
{...props}
viewBox="0 0 256 292"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<defs>
<linearGradient
id="a"
x1="68.188%"
x2="27.823%"
y1="17.487%"
y2="89.755%"
>
<stop offset="0%" stopColor="#41873F" />
<stop offset="32.88%" stopColor="#418B3D" />
<stop offset="63.52%" stopColor="#419637" />
<stop offset="93.19%" stopColor="#3FA92D" />
<stop offset="100%" stopColor="#3FAE2A" />
</linearGradient>
<linearGradient
id="c"
x1="43.277%"
x2="159.245%"
y1="55.169%"
y2="-18.306%"
>
<stop offset="13.76%" stopColor="#41873F" />
<stop offset="40.32%" stopColor="#54A044" />
<stop offset="71.36%" stopColor="#66B848" />
<stop offset="90.81%" stopColor="#6CC04A" />
</linearGradient>
<linearGradient
id="f"
x1="-4.389%"
x2="101.499%"
y1="49.997%"
y2="49.997%"
>
<stop offset="9.192%" stopColor="#6CC04A" />
<stop offset="28.64%" stopColor="#66B848" />
<stop offset="59.68%" stopColor="#54A044" />
<stop offset="86.24%" stopColor="#41873F" />
</linearGradient>
<path
id="b"
d="M134.923 1.832c-4.344-2.443-9.502-2.443-13.846 0L6.787 67.801C2.443 70.244 0 74.859 0 79.745v132.208c0 4.887 2.715 9.502 6.787 11.945l114.29 65.968c4.344 2.444 9.502 2.444 13.846 0l114.29-65.968c4.344-2.443 6.787-7.058 6.787-11.945V79.745c0-4.886-2.715-9.501-6.787-11.944L134.923 1.832Z"
/>
<path
id="e"
d="M134.923 1.832c-4.344-2.443-9.502-2.443-13.846 0L6.787 67.801C2.443 70.244 0 74.859 0 79.745v132.208c0 4.887 2.715 9.502 6.787 11.945l114.29 65.968c4.344 2.444 9.502 2.444 13.846 0l114.29-65.968c4.344-2.443 6.787-7.058 6.787-11.945V79.745c0-4.886-2.715-9.501-6.787-11.944L134.923 1.832Z"
/>
</defs>
<path
fill="url(#a)"
d="M134.923 1.832c-4.344-2.443-9.502-2.443-13.846 0L6.787 67.801C2.443 70.244 0 74.859 0 79.745v132.208c0 4.887 2.715 9.502 6.787 11.945l114.29 65.968c4.344 2.444 9.502 2.444 13.846 0l114.29-65.968c4.344-2.443 6.787-7.058 6.787-11.945V79.745c0-4.886-2.715-9.501-6.787-11.944L134.923 1.832Z"
/>
<mask id="d" fill="#fff">
<use xlinkHref="#b" />
</mask>
<path
fill="url(#c)"
d="M249.485 67.8 134.65 1.833c-1.086-.542-2.443-1.085-3.529-1.357L2.443 220.912c1.086 1.357 2.444 2.443 3.8 3.258l114.834 65.968c3.258 1.9 7.059 2.443 10.588 1.357L252.47 70.515c-.815-1.086-1.9-1.9-2.986-2.714Z"
mask="url(#d)"
/>
<mask id="g" fill="#fff">
<use xlinkHref="#e" />
</mask>
<path
fill="url(#f)"
d="M249.756 223.898c3.258-1.9 5.701-5.158 6.787-8.687L130.579.204c-3.258-.543-6.787-.272-9.773 1.628L6.786 67.53l122.979 224.238c1.628-.272 3.529-.815 5.158-1.63l114.833-66.239Z"
mask="url(#g)"
/>
</svg>
);
export { Nodejs };

View File

@@ -0,0 +1,62 @@
import type { SVGProps } from "react";
const Postgresql = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} xmlSpace="preserve" viewBox="0 0 432.071 445.383">
<g
style={{
fillRule: "nonzero",
clipRule: "nonzero",
fill: "none",
stroke: "#fff",
strokeWidth: "12.4651",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeMiterlimit: "4",
}}
>
<path
d="M323.205 324.227c2.833-23.601 1.984-27.062 19.563-23.239l4.463.392c13.517.615 31.199-2.174 41.587-7 22.362-10.376 35.622-27.7 13.572-23.148-50.297 10.376-53.755-6.655-53.755-6.655 53.111-78.803 75.313-178.836 56.149-203.322-52.27-66.789-142.748-35.206-144.262-34.386l-.482.089c-9.938-2.062-21.06-3.294-33.554-3.496-22.761-.374-40.032 5.967-53.133 15.904 0 0-161.408-66.498-153.899 83.628 1.597 31.936 45.777 241.655 98.47 178.31 19.259-23.163 37.871-42.748 37.871-42.748 9.242 6.14 20.307 9.272 31.912 8.147l.897-.765c-.281 2.876-.157 5.689.359 9.019-13.572 15.167-9.584 17.83-36.723 23.416-27.457 5.659-11.326 15.734-.797 18.367 12.768 3.193 42.305 7.716 62.268-20.224l-.795 3.188c5.325 4.26 4.965 30.619 5.72 49.452.756 18.834 2.017 36.409 5.856 46.771 3.839 10.36 8.369 37.05 44.036 29.406 29.809-6.388 52.6-15.582 54.677-101.107"
style={{
fill: "#000",
stroke: "#000",
strokeWidth: "37.3953",
strokeLinecap: "butt",
strokeLinejoin: "miter",
}}
/>
<path
d="M402.395 271.23c-50.302 10.376-53.76-6.655-53.76-6.655 53.111-78.808 75.313-178.843 56.153-203.326-52.27-66.785-142.752-35.2-144.262-34.38l-.486.087c-9.938-2.063-21.06-3.292-33.56-3.496-22.761-.373-40.026 5.967-53.127 15.902 0 0-161.411-66.495-153.904 83.63 1.597 31.938 45.776 241.657 98.471 178.312 19.26-23.163 37.869-42.748 37.869-42.748 9.243 6.14 20.308 9.272 31.908 8.147l.901-.765c-.28 2.876-.152 5.689.361 9.019-13.575 15.167-9.586 17.83-36.723 23.416-27.459 5.659-11.328 15.734-.796 18.367 12.768 3.193 42.307 7.716 62.266-20.224l-.796 3.188c5.319 4.26 9.054 27.711 8.428 48.969-.626 21.259-1.044 35.854 3.147 47.254 4.191 11.4 8.368 37.05 44.042 29.406 29.809-6.388 45.256-22.942 47.405-50.555 1.525-19.631 4.976-16.729 5.194-34.28l2.768-8.309c3.192-26.611.507-35.196 18.872-31.203l4.463.392c13.517.615 31.208-2.174 41.591-7 22.358-10.376 35.618-27.7 13.573-23.148z"
style={{ fill: "#336791", stroke: "none" }}
stroke="none"
/>
<path d="M215.866 286.484c-1.385 49.516.348 99.377 5.193 111.495 4.848 12.118 15.223 35.688 50.9 28.045 29.806-6.39 40.651-18.756 45.357-46.051 3.466-20.082 10.148-75.854 11.005-87.281M173.104 38.256S11.583-27.76 19.092 122.365c1.597 31.938 45.779 241.664 98.473 178.316 19.256-23.166 36.671-41.335 36.671-41.335M260.349 26.207c-5.591 1.753 89.848-34.889 144.087 34.417 19.159 24.484-3.043 124.519-56.153 203.329" />
<path
d="M348.282 263.953s3.461 17.036 53.764 6.653c22.04-4.552 8.776 12.774-13.577 23.155-18.345 8.514-59.474 10.696-60.146-1.069-1.729-30.355 21.647-21.133 19.96-28.739-1.525-6.85-11.979-13.573-18.894-30.338-6.037-14.633-82.796-126.849 21.287-110.183 3.813-.789-27.146-99.002-124.553-100.599-97.385-1.597-94.19 119.762-94.19 119.762"
style={{ strokeLinejoin: "bevel" }}
/>
<path d="M188.604 274.334c-13.577 15.166-9.584 17.829-36.723 23.417-27.459 5.66-11.326 15.733-.797 18.365 12.768 3.195 42.307 7.718 62.266-20.229 6.078-8.509-.036-22.086-8.385-25.547-4.034-1.671-9.428-3.765-16.361 3.994z" />
<path d="M187.715 274.069c-1.368-8.917 2.93-19.528 7.536-31.942 6.922-18.626 22.893-37.255 10.117-96.339-9.523-44.029-73.396-9.163-73.436-3.193-.039 5.968 2.889 30.26-1.067 58.548-5.162 36.913 23.488 68.132 56.479 64.938" />
<path
d="M172.517 141.7c-.288 2.039 3.733 7.48 8.976 8.207 5.234.73 9.714-3.522 9.998-5.559.284-2.039-3.732-4.285-8.977-5.015-5.237-.731-9.719.333-9.996 2.367z"
style={{
fill: "#fff",
strokeWidth: "4.155",
strokeLinecap: "butt",
strokeLinejoin: "miter",
}}
/>
<path
d="M331.941 137.543c.284 2.039-3.732 7.48-8.976 8.207-5.238.73-9.718-3.522-10.005-5.559-.277-2.039 3.74-4.285 8.979-5.015 5.239-.73 9.718.333 10.002 2.368z"
style={{
fill: "#fff",
strokeWidth: "2.0775",
strokeLinecap: "butt",
strokeLinejoin: "miter",
}}
/>
<path d="M350.676 123.432c.863 15.994-3.445 26.888-3.988 43.914-.804 24.748 11.799 53.074-7.191 81.435" />
</g>
</svg>
);
export { Postgresql };

View File

@@ -0,0 +1,77 @@
import type { SVGProps } from "react";
const PostgresqlWordmarkDark = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 1416.8 445.5">
<path
stroke="#000"
strokeWidth="37.395"
d="M323.175 324.211c2.833-23.601 1.984-27.062 19.563-23.239l4.463.392c13.517.615 31.199-2.174 41.587-7 22.362-10.376 35.622-27.7 13.572-23.148-50.297 10.376-53.755-6.655-53.755-6.655 53.111-78.803 75.313-178.836 56.149-203.322-52.27-66.789-142.748-35.206-144.262-34.386l-.482.089c-9.938-2.062-21.06-3.294-33.554-3.496-22.761-.374-40.032 5.967-53.133 15.904 0 0-161.408-66.498-153.899 83.628 1.597 31.936 45.777 241.655 98.47 178.31 19.259-23.163 37.871-42.748 37.871-42.748 9.242 6.14 20.307 9.272 31.912 8.147l.897-.765c-.281 2.876-.157 5.689.359 9.019-13.572 15.167-9.584 17.83-36.723 23.416-27.457 5.659-11.326 15.734-.797 18.367 12.768 3.193 42.305 7.716 62.268-20.224l-.795 3.188c5.325 4.26 4.965 30.619 5.72 49.452.756 18.834 2.017 36.409 5.856 46.771 3.839 10.36 8.369 37.05 44.036 29.406 29.809-6.388 52.6-15.582 54.677-101.107"
/>
<path
fill="#336791"
d="M402.365 271.214c-50.302 10.376-53.76-6.655-53.76-6.655 53.111-78.808 75.313-178.843 56.153-203.326-52.27-66.785-142.752-35.2-144.262-34.38l-.486.087c-9.938-2.063-21.06-3.292-33.56-3.496-22.761-.373-40.026 5.967-53.127 15.902 0 0-161.411-66.495-153.904 83.63 1.597 31.938 45.776 241.657 98.471 178.312 19.26-23.163 37.869-42.748 37.869-42.748 9.243 6.14 20.308 9.272 31.908 8.147l.901-.765c-.28 2.876-.152 5.689.361 9.019-13.575 15.167-9.586 17.83-36.723 23.416-27.459 5.659-11.328 15.734-.796 18.367 12.768 3.193 42.307 7.716 62.266-20.224l-.796 3.188c5.319 4.26 9.054 27.711 8.428 48.969s-1.044 35.854 3.147 47.254 8.368 37.05 44.042 29.406c29.809-6.388 45.256-22.942 47.405-50.555 1.525-19.631 4.976-16.729 5.194-34.28l2.768-8.309c3.192-26.611.507-35.196 18.872-31.203l4.463.392c13.517.615 31.208-2.174 41.591-7 22.358-10.376 35.618-27.7 13.573-23.148z"
/>
<path
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="12.465"
d="M215.836 286.468c-1.385 49.516.348 99.377 5.193 111.495 4.848 12.118 15.223 35.688 50.9 28.045 29.806-6.39 40.651-18.756 45.357-46.051 3.466-20.082 10.148-75.854 11.005-87.281M173.074 38.24S11.553-27.776 19.062 122.349c1.597 31.938 45.779 241.664 98.473 178.316 19.256-23.166 36.671-41.335 36.671-41.335M260.319 26.191c-5.591 1.753 89.848-34.889 144.087 34.417 19.159 24.484-3.043 124.519-56.153 203.329"
/>
<path
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="bevel"
strokeWidth="12.465"
d="M348.252 263.937s3.461 17.036 53.764 6.653c22.04-4.552 8.776 12.774-13.577 23.155-18.345 8.514-59.474 10.696-60.146-1.069-1.729-30.355 21.647-21.133 19.96-28.739-1.525-6.85-11.979-13.573-18.894-30.338-6.037-14.633-82.796-126.849 21.287-110.183 3.813-.789-27.146-99.002-124.553-100.599-97.385-1.597-94.19 119.762-94.19 119.762"
/>
<path
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="12.465"
d="M188.574 274.318c-13.577 15.166-9.584 17.829-36.723 23.417-27.459 5.66-11.326 15.733-.797 18.365 12.768 3.195 42.307 7.718 62.266-20.229 6.078-8.509-.036-22.086-8.385-25.547-4.034-1.671-9.428-3.765-16.361 3.994"
/>
<path
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="12.465"
d="M187.685 274.053c-1.368-8.917 2.93-19.528 7.536-31.942 6.922-18.626 22.893-37.255 10.117-96.339-9.523-44.029-73.396-9.163-73.436-3.193-.039 5.968 2.889 30.26-1.067 58.548-5.162 36.913 23.488 68.132 56.479 64.938"
/>
<path
fill="#fff"
stroke="#fff"
strokeWidth="4.155"
d="M172.487 141.684c-.288 2.039 3.733 7.48 8.976 8.207 5.234.73 9.714-3.522 9.998-5.559.284-2.039-3.732-4.285-8.977-5.015-5.237-.731-9.719.333-9.996 2.367z"
/>
<path
fill="#fff"
stroke="#fff"
strokeWidth="2.078"
d="M331.911 137.527c.284 2.039-3.732 7.48-8.976 8.207-5.238.73-9.718-3.522-10.005-5.559-.277-2.039 3.74-4.285 8.979-5.015s9.718.333 10.002 2.368z"
/>
<path
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="12.465"
d="M350.646 123.416c.863 15.994-3.445 26.888-3.988 43.914-.804 24.748 11.799 53.074-7.191 81.435"
/>
<path
fill="#fff"
d="M503.964 133.681c8.155-1.357 18.819-2.523 32.409-2.523 16.677 0 28.907 3.882 36.669 10.867 7.178 6.206 11.447 15.714 11.447 27.362 0 11.836-3.499 21.148-10.097 27.939-8.925 9.506-23.475 14.357-39.968 14.357-5.043 0-9.701-.193-13.574-1.163v52.392h-16.886zm16.886 63.056c3.686.969 8.345 1.356 13.97 1.356 20.365 0 32.79-9.894 32.79-27.937 0-17.267-12.224-25.611-30.85-25.611-7.372 0-13 .583-15.91 1.359zm165.884 18.432c0 34.729-24.065 49.869-46.764 49.869-25.417 0-45.013-18.626-45.013-48.319 0-31.43 20.566-49.862 46.566-49.862 26.975-.007 45.211 19.596 45.211 48.312m-74.501.97c0 20.565 11.835 36.086 28.521 36.086 16.296 0 28.513-15.327 28.513-36.476 0-15.909-7.948-36.086-28.133-36.086-20.186 0-28.901 18.626-28.901 36.476m93.703 29.297c5.045 3.296 13.97 6.789 22.505 6.789 12.417 0 18.239-6.209 18.239-13.968 0-8.151-4.845-12.613-17.462-17.269-16.88-6.015-24.835-15.327-24.835-26.581 0-15.133 12.224-27.55 32.402-27.55 9.506 0 17.857 2.716 23.096 5.822l-4.269 12.417c-3.686-2.329-10.478-5.433-19.209-5.433-10.088 0-15.714 5.82-15.714 12.805 0 7.761 5.633 11.254 17.857 15.909 16.296 6.209 24.641 14.357 24.641 28.327 0 16.491-12.807 28.131-35.119 28.131-10.281 0-19.789-2.52-26.387-6.402zm99.732-103.421v26.971h24.438v12.997h-24.438v50.639c0 11.641 3.292 18.239 12.804 18.239 4.462 0 7.761-.583 9.897-1.166l.774 12.807c-3.29 1.357-8.536 2.326-15.124 2.326-7.955 0-14.366-2.522-18.439-7.178-4.843-5.043-6.598-13.387-6.598-24.445v-51.222h-14.551v-12.997h14.551v-22.508zm123.395 26.978c-.396 6.789-.776 14.356-.776 25.804v54.518c0 21.536-4.269 34.729-13.387 42.884-9.118 8.538-22.312 11.254-34.149 11.254-11.251 0-23.668-2.716-31.243-7.761l4.269-12.998c6.208 3.88 15.91 7.372 27.55 7.372 17.46 0 30.267-9.118 30.267-32.789v-10.478h-.389c-5.237 8.732-15.327 15.717-29.878 15.717-23.281 0-39.967-19.79-39.967-45.797 0-31.817 20.759-49.862 42.294-49.862 16.306 0 25.231 8.538 29.306 16.299h.387l.777-14.163zm-17.655 37.055c0-2.909-.194-5.432-.97-7.759-3.106-9.894-11.448-18.045-23.865-18.045-16.306 0-27.937 13.776-27.937 35.505 0 18.433 9.311 33.76 27.744 33.76 10.477 0 19.982-6.596 23.668-17.461.97-2.911 1.367-6.208 1.367-9.12v-16.88zm44.033-7.759c0-11.06-.193-20.565-.776-29.296h14.947l.576 18.429h.776c4.267-12.61 14.55-20.565 25.998-20.565 1.94 0 3.288.196 4.838.583v16.103c-1.73-.387-3.488-.583-5.808-.583-12.03 0-20.575 9.121-22.895 21.925-.387 2.329-.776 5.045-.776 7.955v50.056h-16.88zm71.019 20.759c.39 23.089 15.13 32.596 32.2 32.596 12.23 0 19.59-2.135 26-4.852l2.91 12.224c-6.02 2.716-16.3 5.819-31.24 5.819-28.91 0-46.18-19.012-46.18-47.339s16.69-50.646 44.05-50.646c30.65 0 38.8 26.968 38.8 44.237 0 3.492-.39 6.208-.58 7.954h-65.96zm50.06-12.223c.18-10.865-4.47-27.744-23.67-27.744-17.27 0-24.84 15.909-26.2 27.744z"
/>
<path
fill="#336791"
d="M1112.4 242.33c7.57 4.656 18.63 8.538 30.27 8.538 17.27 0 27.35-9.118 27.35-22.312 0-12.224-6.98-19.209-24.64-26-21.34-7.566-34.53-18.624-34.53-37.056 0-20.372 16.88-35.505 42.29-35.505 13.39 0 23.08 3.103 28.91 6.402l-4.65 13.776c-4.28-2.329-13-6.208-24.84-6.208-17.85 0-24.64 10.671-24.64 19.595 0 12.222 7.95 18.237 26 25.222 22.12 8.544 33.37 19.208 33.37 38.415 0 20.178-14.95 37.638-45.79 37.638-12.61 0-26.38-3.686-33.38-8.341zm205.86 39.774c-17.68-4.656-34.93-9.894-50.09-15.133-2.69-.97-5.39-1.94-7.94-1.94-31.04-1.165-57.62-24.058-57.62-66.161 0-41.908 25.61-68.875 60.94-68.875 35.5 0 58.57 27.55 58.57 66.159 0 33.565-15.5 55.101-37.23 62.669v.776c12.99 3.297 27.17 6.402 38.21 8.342zm-13.98-85.174c0-26.191-13.57-53.168-41.32-53.168-28.52 0-42.5 26.387-42.3 54.721-.2 27.744 15.14 52.772 41.52 52.772 26.98 0 42.1-24.445 42.1-54.325m39.77-64.802h16.87v116.604h55.89v14.164h-72.76z"
/>
</svg>
);
export { PostgresqlWordmarkDark };

View File

@@ -0,0 +1,74 @@
import type { SVGProps } from "react";
const PostgresqlWordmarkLight = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 1416.8 445.5">
<path
stroke="#000"
strokeWidth="37.395"
d="M323.175 324.211c2.833-23.601 1.984-27.062 19.563-23.239l4.463.392c13.517.615 31.199-2.174 41.587-7 22.362-10.376 35.622-27.7 13.572-23.148-50.297 10.376-53.755-6.655-53.755-6.655 53.111-78.803 75.313-178.836 56.149-203.322-52.27-66.789-142.748-35.206-144.262-34.386l-.482.089c-9.938-2.062-21.06-3.294-33.554-3.496-22.761-.374-40.032 5.967-53.133 15.904 0 0-161.408-66.498-153.899 83.628 1.597 31.936 45.777 241.655 98.47 178.31 19.259-23.163 37.871-42.748 37.871-42.748 9.242 6.14 20.307 9.272 31.912 8.147l.897-.765c-.281 2.876-.157 5.689.359 9.019-13.572 15.167-9.584 17.83-36.723 23.416-27.457 5.659-11.326 15.734-.797 18.367 12.768 3.193 42.305 7.716 62.268-20.224l-.795 3.188c5.325 4.26 4.965 30.619 5.72 49.452.756 18.834 2.017 36.409 5.856 46.771 3.839 10.36 8.369 37.05 44.036 29.406 29.809-6.388 52.6-15.582 54.677-101.107"
/>
<path
fill="#336791"
d="M402.365 271.214c-50.302 10.376-53.76-6.655-53.76-6.655 53.111-78.808 75.313-178.843 56.153-203.326-52.27-66.785-142.752-35.2-144.262-34.38l-.486.087c-9.938-2.063-21.06-3.292-33.56-3.496-22.761-.373-40.026 5.967-53.127 15.902 0 0-161.411-66.495-153.904 83.63 1.597 31.938 45.776 241.657 98.471 178.312 19.26-23.163 37.869-42.748 37.869-42.748 9.243 6.14 20.308 9.272 31.908 8.147l.901-.765c-.28 2.876-.152 5.689.361 9.019-13.575 15.167-9.586 17.83-36.723 23.416-27.459 5.659-11.328 15.734-.796 18.367 12.768 3.193 42.307 7.716 62.266-20.224l-.796 3.188c5.319 4.26 9.054 27.711 8.428 48.969s-1.044 35.854 3.147 47.254 8.368 37.05 44.042 29.406c29.809-6.388 45.256-22.942 47.405-50.555 1.525-19.631 4.976-16.729 5.194-34.28l2.768-8.309c3.192-26.611.507-35.196 18.872-31.203l4.463.392c13.517.615 31.208-2.174 41.591-7 22.358-10.376 35.618-27.7 13.573-23.148z"
/>
<path
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="12.465"
d="M215.836 286.468c-1.385 49.516.348 99.377 5.193 111.495 4.848 12.118 15.223 35.688 50.9 28.045 29.806-6.39 40.651-18.756 45.357-46.051 3.466-20.082 10.148-75.854 11.005-87.281M173.074 38.24S11.553-27.776 19.062 122.349c1.597 31.938 45.779 241.664 98.473 178.316 19.256-23.166 36.671-41.335 36.671-41.335M260.319 26.191c-5.591 1.753 89.848-34.889 144.087 34.417 19.159 24.484-3.043 124.519-56.153 203.329"
/>
<path
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="bevel"
strokeWidth="12.465"
d="M348.252 263.937s3.461 17.036 53.764 6.653c22.04-4.552 8.776 12.774-13.577 23.155-18.345 8.514-59.474 10.696-60.146-1.069-1.729-30.355 21.647-21.133 19.96-28.739-1.525-6.85-11.979-13.573-18.894-30.338-6.037-14.633-82.796-126.849 21.287-110.183 3.813-.789-27.146-99.002-124.553-100.599-97.385-1.597-94.19 119.762-94.19 119.762"
/>
<path
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="12.465"
d="M188.574 274.318c-13.577 15.166-9.584 17.829-36.723 23.417-27.459 5.66-11.326 15.733-.797 18.365 12.768 3.195 42.307 7.718 62.266-20.229 6.078-8.509-.036-22.086-8.385-25.547-4.034-1.671-9.428-3.765-16.361 3.994"
/>
<path
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="12.465"
d="M187.685 274.053c-1.368-8.917 2.93-19.528 7.536-31.942 6.922-18.626 22.893-37.255 10.117-96.339-9.523-44.029-73.396-9.163-73.436-3.193-.039 5.968 2.889 30.26-1.067 58.548-5.162 36.913 23.488 68.132 56.479 64.938"
/>
<path
fill="#fff"
stroke="#fff"
strokeWidth="4.155"
d="M172.487 141.684c-.288 2.039 3.733 7.48 8.976 8.207 5.234.73 9.714-3.522 9.998-5.559.284-2.039-3.732-4.285-8.977-5.015-5.237-.731-9.719.333-9.996 2.367z"
/>
<path
fill="#fff"
stroke="#fff"
strokeWidth="2.078"
d="M331.911 137.527c.284 2.039-3.732 7.48-8.976 8.207-5.238.73-9.718-3.522-10.005-5.559-.277-2.039 3.74-4.285 8.979-5.015s9.718.333 10.002 2.368z"
/>
<path
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="12.465"
d="M350.646 123.416c.863 15.994-3.445 26.888-3.988 43.914-.804 24.748 11.799 53.074-7.191 81.435"
/>
<path d="M503.964 133.681c8.155-1.357 18.819-2.523 32.409-2.523 16.677 0 28.907 3.882 36.669 10.867 7.178 6.206 11.447 15.714 11.447 27.362 0 11.836-3.499 21.148-10.097 27.939-8.925 9.506-23.475 14.357-39.968 14.357-5.043 0-9.701-.193-13.574-1.163v52.392h-16.886zm16.886 63.056c3.686.969 8.345 1.356 13.97 1.356 20.365 0 32.79-9.894 32.79-27.937 0-17.267-12.224-25.611-30.85-25.611-7.372 0-13 .583-15.91 1.359zm165.884 18.432c0 34.729-24.065 49.869-46.764 49.869-25.417 0-45.013-18.626-45.013-48.319 0-31.43 20.566-49.862 46.566-49.862 26.975-.007 45.211 19.596 45.211 48.312m-74.501.97c0 20.565 11.835 36.086 28.521 36.086 16.296 0 28.513-15.327 28.513-36.476 0-15.909-7.948-36.086-28.133-36.086-20.186 0-28.901 18.626-28.901 36.476m93.703 29.297c5.045 3.296 13.97 6.789 22.505 6.789 12.417 0 18.239-6.209 18.239-13.968 0-8.151-4.845-12.613-17.462-17.269-16.88-6.015-24.835-15.327-24.835-26.581 0-15.133 12.224-27.55 32.402-27.55 9.506 0 17.857 2.716 23.096 5.822l-4.269 12.417c-3.686-2.329-10.478-5.433-19.209-5.433-10.088 0-15.714 5.82-15.714 12.805 0 7.761 5.633 11.254 17.857 15.909 16.296 6.209 24.641 14.357 24.641 28.327 0 16.491-12.807 28.131-35.119 28.131-10.281 0-19.789-2.52-26.387-6.402zm99.732-103.421v26.971h24.438v12.997h-24.438v50.639c0 11.641 3.292 18.239 12.804 18.239 4.462 0 7.761-.583 9.897-1.166l.774 12.807c-3.29 1.357-8.536 2.326-15.124 2.326-7.955 0-14.366-2.522-18.439-7.178-4.843-5.043-6.598-13.387-6.598-24.445v-51.222h-14.551v-12.997h14.551v-22.508zm123.395 26.978c-.396 6.789-.776 14.356-.776 25.804v54.518c0 21.536-4.269 34.729-13.387 42.884-9.118 8.538-22.312 11.254-34.149 11.254-11.251 0-23.668-2.716-31.243-7.761l4.269-12.998c6.208 3.88 15.91 7.372 27.55 7.372 17.46 0 30.267-9.118 30.267-32.789v-10.478h-.389c-5.237 8.732-15.327 15.717-29.878 15.717-23.281 0-39.967-19.79-39.967-45.797 0-31.817 20.759-49.862 42.294-49.862 16.306 0 25.231 8.538 29.306 16.299h.387l.777-14.163zm-17.655 37.055c0-2.909-.194-5.432-.97-7.759-3.106-9.894-11.448-18.045-23.865-18.045-16.306 0-27.937 13.776-27.937 35.505 0 18.433 9.311 33.76 27.744 33.76 10.477 0 19.982-6.596 23.668-17.461.97-2.911 1.367-6.208 1.367-9.12v-16.88zm44.033-7.759c0-11.06-.193-20.565-.776-29.296h14.947l.576 18.429h.776c4.267-12.61 14.55-20.565 25.998-20.565 1.94 0 3.288.196 4.838.583v16.103c-1.73-.387-3.488-.583-5.808-.583-12.03 0-20.575 9.121-22.895 21.925-.387 2.329-.776 5.045-.776 7.955v50.056h-16.88zm71.019 20.759c.39 23.089 15.13 32.596 32.2 32.596 12.23 0 19.59-2.135 26-4.852l2.91 12.224c-6.02 2.716-16.3 5.819-31.24 5.819-28.91 0-46.18-19.012-46.18-47.339s16.69-50.646 44.05-50.646c30.65 0 38.8 26.968 38.8 44.237 0 3.492-.39 6.208-.58 7.954h-65.96zm50.06-12.223c.18-10.865-4.47-27.744-23.67-27.744-17.27 0-24.84 15.909-26.2 27.744z" />
<path
fill="#336791"
d="M1112.4 242.33c7.57 4.656 18.63 8.538 30.27 8.538 17.27 0 27.35-9.118 27.35-22.312 0-12.224-6.98-19.209-24.64-26-21.34-7.566-34.53-18.624-34.53-37.056 0-20.372 16.88-35.505 42.29-35.505 13.39 0 23.08 3.103 28.91 6.402l-4.65 13.776c-4.28-2.329-13-6.208-24.84-6.208-17.85 0-24.64 10.671-24.64 19.595 0 12.222 7.95 18.237 26 25.222 22.12 8.544 33.37 19.208 33.37 38.415 0 20.178-14.95 37.638-45.79 37.638-12.61 0-26.38-3.686-33.38-8.341zm205.86 39.774c-17.68-4.656-34.93-9.894-50.09-15.133-2.69-.97-5.39-1.94-7.94-1.94-31.04-1.165-57.62-24.058-57.62-66.161 0-41.908 25.61-68.875 60.94-68.875 35.5 0 58.57 27.55 58.57 66.159 0 33.565-15.5 55.101-37.23 62.669v.776c12.99 3.297 27.17 6.402 38.21 8.342zm-13.98-85.174c0-26.191-13.57-53.168-41.32-53.168-28.52 0-42.5 26.387-42.3 54.721-.2 27.744 15.14 52.772 41.52 52.772 26.98 0 42.1-24.445 42.1-54.325m39.77-64.802h16.87v116.604h55.89v14.164h-72.76z"
/>
</svg>
);
export { PostgresqlWordmarkLight };

View File

@@ -0,0 +1,40 @@
import type { SVGProps } from "react";
const Python = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} fill="none" viewBox="16 16 32 32">
<path
fill="url(#a)"
d="M31.885 16c-8.124 0-7.617 3.523-7.617 3.523l.01 3.65h7.752v1.095H21.197S16 23.678 16 31.876c0 8.196 4.537 7.906 4.537 7.906h2.708v-3.804s-.146-4.537 4.465-4.537h7.688s4.32.07 4.32-4.175v-7.019S40.374 16 31.885 16zm-4.275 2.454a1.394 1.394 0 1 1 0 2.79 1.393 1.393 0 0 1-1.395-1.395c0-.771.624-1.395 1.395-1.395z"
/>
<path
fill="url(#b)"
d="M32.115 47.833c8.124 0 7.617-3.523 7.617-3.523l-.01-3.65H31.97v-1.095h10.832S48 40.155 48 31.958c0-8.197-4.537-7.906-4.537-7.906h-2.708v3.803s.146 4.537-4.465 4.537h-7.688s-4.32-.07-4.32 4.175v7.019s-.656 4.247 7.833 4.247zm4.275-2.454a1.393 1.393 0 0 1-1.395-1.395 1.394 1.394 0 1 1 1.395 1.395z"
/>
<defs>
<linearGradient
id="a"
x1="19.075"
x2="34.898"
y1="18.782"
y2="34.658"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#387EB8" />
<stop offset="1" stopColor="#366994" />
</linearGradient>
<linearGradient
id="b"
x1="28.809"
x2="45.803"
y1="28.882"
y2="45.163"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FFE052" />
<stop offset="1" stopColor="#FFC331" />
</linearGradient>
</defs>
</svg>
);
export { Python };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
import type { SVGProps } from "react";
const Typescript = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} viewBox="0 0 256 256" preserveAspectRatio="xMidYMid">
<path
d="M20 0h216c11.046 0 20 8.954 20 20v216c0 11.046-8.954 20-20 20H20c-11.046 0-20-8.954-20-20V20C0 8.954 8.954 0 20 0Z"
fill="#3178C6"
/>
<path
d="M150.518 200.475v27.62c4.492 2.302 9.805 4.028 15.938 5.179 6.133 1.151 12.597 1.726 19.393 1.726 6.622 0 12.914-.633 18.874-1.899 5.96-1.266 11.187-3.352 15.678-6.257 4.492-2.906 8.048-6.704 10.669-11.394 2.62-4.689 3.93-10.486 3.93-17.391 0-5.006-.749-9.394-2.246-13.163a30.748 30.748 0 0 0-6.479-10.055c-2.821-2.935-6.205-5.567-10.149-7.898-3.945-2.33-8.394-4.531-13.347-6.602-3.628-1.497-6.881-2.949-9.761-4.359-2.879-1.41-5.327-2.848-7.342-4.316-2.016-1.467-3.571-3.021-4.665-4.661-1.094-1.64-1.641-3.495-1.641-5.567 0-1.899.489-3.61 1.468-5.135s2.362-2.834 4.147-3.927c1.785-1.094 3.973-1.942 6.565-2.547 2.591-.604 5.471-.906 8.638-.906 2.304 0 4.737.173 7.299.518 2.563.345 5.14.877 7.732 1.597a53.669 53.669 0 0 1 7.558 2.719 41.7 41.7 0 0 1 6.781 3.797v-25.807c-4.204-1.611-8.797-2.805-13.778-3.582-4.981-.777-10.697-1.165-17.147-1.165-6.565 0-12.784.705-18.658 2.115-5.874 1.409-11.043 3.61-15.506 6.602-4.463 2.993-7.99 6.805-10.582 11.437-2.591 4.632-3.887 10.17-3.887 16.615 0 8.228 2.375 15.248 7.127 21.06 4.751 5.811 11.963 10.731 21.638 14.759a291.458 291.458 0 0 1 10.625 4.575c3.283 1.496 6.119 3.049 8.509 4.66 2.39 1.611 4.276 3.366 5.658 5.265 1.382 1.899 2.073 4.057 2.073 6.474a9.901 9.901 0 0 1-1.296 4.963c-.863 1.524-2.174 2.848-3.93 3.97-1.756 1.122-3.945 1.999-6.565 2.632-2.62.633-5.687.95-9.2.95-5.989 0-11.92-1.05-17.794-3.151-5.875-2.1-11.317-5.25-16.327-9.451Zm-46.036-68.733H140V109H41v22.742h35.345V233h28.137V131.742Z"
fill="#FFF"
/>
</svg>
);
export { Typescript };

View File

@@ -0,0 +1,42 @@
"use client";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import * as React from "react";
import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
const TooltipArrow = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Arrow>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Arrow>
>(({ className, ...props }, ref) => (
<TooltipPrimitive.Arrow
ref={ref}
className={cn("fill-primary", className)}
{...props}
/>
));
TooltipArrow.displayName = TooltipPrimitive.Arrow.displayName;
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TooltipArrow };