refactor: 移除未使用的代码和资源,优化项目结构

- 删除未使用的图片、SVG图标和组件
- 清理废弃的代码文件和依赖项
- 简化UI组件接口定义
- 更新部分文案为中文
- 移除博客相关功能
- 添加Telegram联系方式
This commit is contained in:
2026-01-22 14:52:45 +08:00
parent 426e9e0210
commit d3344e8961
54 changed files with 4882 additions and 2526 deletions

View File

@@ -0,0 +1,18 @@
{
"permissions": {
"allow": [
"WebFetch(domain:github.com)",
"WebFetch(domain:resume.liukersun.com)",
"Bash(npm run dev)",
"Bash(test:*)",
"Bash(for file in makeblock.png hit.png bytedance.png xiasha.png neau.png)",
"Bash(do if [ ! -f \"$file\" ])",
"Bash(then cp me.png \"$file\")",
"Bash(fi)",
"Bash(done)",
"Bash(ls:*)",
"Bash(npm uninstall:*)",
"Bash(npx knip)"
]
}
}

View File

@@ -1,34 +0,0 @@
import { defineCollection, defineConfig } from "@content-collections/core";
import { compileMDX } from "@content-collections/mdx";
import remarkGfm from "remark-gfm";
import { z } from "zod";
import { remarkCodeMeta } from "./src/lib/remark-code-meta";
const posts = defineCollection({
name: "posts",
directory: "content",
include: "**/*.mdx",
schema: z.object({
title: z.string(),
publishedAt: z.string(),
updatedAt: z.string().optional(),
author: z.string().optional(),
summary: z.string(),
image: z.string().optional(),
content: z.string(),
}),
transform: async (document, context) => {
const mdx = await compileMDX(context, document, {
remarkPlugins: [remarkGfm, remarkCodeMeta],
});
return {
...document,
mdx,
};
},
});
export default defineConfig({
collections: [posts],
});

View File

@@ -1,5 +1,3 @@
import { withContentCollections } from "@content-collections/next";
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
@@ -30,5 +28,4 @@ const nextConfig = {
},
};
// withContentCollections must be the outermost plugin
export default withContentCollections(nextConfig);
export default nextConfig;

View File

@@ -13,7 +13,6 @@
"lint:fix": "eslint --fix"
},
"dependencies": {
"@content-collections/mdx": "^0.2.2",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-icons": "^1.3.2",
@@ -23,7 +22,6 @@
"@tailwindcss/postcss": "^4.1.18",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"content-collections": "^0.2.1",
"lucide-react": "^0.562.0",
"motion": "^12.23.27",
"next": "16.1.1",
@@ -31,25 +29,19 @@
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-markdown": "^10.1.0",
"rehype-pretty-code": "^0.14.1",
"remark-gfm": "^4.0.1",
"shiki": "^3.20.0",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.18",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@content-collections/core": "^0.13.1",
"@content-collections/next": "^0.2.10",
"@tailwindcss/typography": "^0.5.19",
"@types/node": "^25.0.3",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"eslint": "^9.39.2",
"eslint-config-next": "16.1.1",
"knip": "^5.82.1",
"postcss": "^8.5.6",
"typescript": "^5.9.3",
"zod": "^4.3.5"
"typescript": "^5.9.3"
}
}

1
public/HIT.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 47 KiB

BIN
public/arch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

BIN
public/base.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

1
public/bytedance.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 7L10 9V37L5 39V7Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/><path d="M16 23L21 25V37L16 39V23Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/><path d="M27 21L32 19V35L27 33V21Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/><path d="M38 9L43 11V37L38 39V9Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 548 B

BIN
public/github.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="800" width="1200" viewBox="-5.805105 -9.675175 50.31091 58.05105"><g fill-rule="evenodd" fill="#0D0"><path d="M16.8477 19.3501c0-1.3794 1.1245-2.504 2.504-2.504s2.5041 1.1171 2.5041 2.504c0 1.3795-1.1246 2.5041-2.5041 2.5041-1.3795 0-2.504-1.1171-2.504-2.5041zm6.7461-3.7108l6.0053-3.4712c1.1846-.6823 1.4095-2.3017.4498-3.2838-1.8818-1.9268-4.2809-3.3438-6.9799-4.0335-1.3345-.3374-2.6315.6523-2.6315 2.0317v6.9425c.0075 1.6119 1.7544 2.624 3.1563 1.8143zm-5.3365-1.8143V6.89c0-1.372-1.297-2.369-2.6315-2.0317-2.6916.6898-5.0907 2.1067-6.98 4.0335-.9596.9747-.7347 2.5941.4499 3.2838l6.0052 3.4712c1.4095.8022 3.1564-.2099 3.1564-1.8218zm6.4309 7.3471l6.0128 3.4712c1.1845.6823 2.699.0675 3.0663-1.2445a14.9416 14.9416 0 00.5548-4.041c0-1.402-.1949-2.7515-.5548-4.041-.3673-1.3196-1.8818-1.9268-3.0663-1.2446l-6.0128 3.4712c-1.402.8022-1.402 2.819 0 3.6287zm-9.5736 1.8892l-6.0053 3.4712c-1.1845.6822-1.4094 2.3016-.4498 3.2838 1.8818 1.9268 4.2809 3.3438 6.9799 4.0335 1.3345.3374 2.6316-.6523 2.6316-2.0318v-6.9424c-.015-1.6119-1.7619-2.6165-3.1564-1.8143zm-1.0939-5.5253l-6.0128-3.4713c-1.1846-.6822-2.699-.0674-3.0664 1.2446a14.9421 14.9421 0 00-.5548 4.041c0 1.402.195 2.7515.5548 4.041.3674 1.3195 1.8818 1.9268 3.0664 1.2445l6.0128-3.4712c1.3944-.8022 1.3944-2.8189 0-3.6286zm6.4266 7.3471v6.9425c0 1.372 1.297 2.3691 2.6315 2.0317 2.6915-.6897 5.0906-2.1067 6.9799-4.0335.9597-.9746.7347-2.594-.4498-3.2838l-6.0053-3.4712c-1.4095-.8097-3.1563.1949-3.1563 1.8143z" fill-rule="nonzero"/><path d="M19.3503 38.7007C8.6668 38.7007 0 30.0414 0 19.3503 0 8.6668 8.6668 0 19.3503 0c10.6911 0 19.3579 8.6668 19.3504 19.3503 0 10.6836-8.6593 19.3504-19.3504 19.3504zm17.1619-19.3505c0-9.4615-7.6996-17.1537-17.1611-17.1537-9.4615 0-17.1537 7.6922-17.1537 17.1537 0 9.4615 7.6922 17.1611 17.1537 17.1611 9.4615 0 17.1611-7.6996 17.1611-17.1611z"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/makeblock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

After

Width:  |  Height:  |  Size: 902 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

1
public/neau.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="-18 0 292 292" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M223.774 57.34c-.201-1.46-1.48-2.268-2.537-2.357-1.055-.088-23.383-1.743-23.383-1.743s-15.507-15.395-17.209-17.099c-1.703-1.703-5.029-1.185-6.32-.805-.19.056-3.388 1.043-8.678 2.68-5.18-14.906-14.322-28.604-30.405-28.604-.444 0-.901.018-1.358.044C129.31 3.407 123.644.779 118.75.779c-37.465 0-55.364 46.835-60.976 70.635-14.558 4.511-24.9 7.718-26.221 8.133-8.126 2.549-8.383 2.805-9.45 10.462C21.3 95.806.038 260.235.038 260.235l165.678 31.042 89.77-19.42S223.973 58.8 223.775 57.34zM156.49 40.848l-14.019 4.339c.005-.988.01-1.96.01-3.023 0-9.264-1.286-16.723-3.349-22.636 8.287 1.04 13.806 10.469 17.358 21.32zm-27.638-19.483c2.304 5.773 3.802 14.058 3.802 25.238 0 .572-.005 1.095-.01 1.624-9.117 2.824-19.024 5.89-28.953 8.966 5.575-21.516 16.025-31.908 25.161-35.828zm-11.131-10.537c1.617 0 3.246.549 4.805 1.622-12.007 5.65-24.877 19.88-30.312 48.297l-22.886 7.088C75.694 46.16 90.81 10.828 117.72 10.828z" fill="#95BF46"/><path d="M221.237 54.983c-1.055-.088-23.383-1.743-23.383-1.743s-15.507-15.395-17.209-17.099c-.637-.634-1.496-.959-2.394-1.099l-12.527 256.233 89.762-19.418S223.972 58.8 223.774 57.34c-.201-1.46-1.48-2.268-2.537-2.357" fill="#5E8E3E"/><path d="M135.242 104.585l-11.069 32.926s-9.698-5.176-21.586-5.176c-17.428 0-18.305 10.937-18.305 13.693 0 15.038 39.2 20.8 39.2 56.024 0 27.713-17.577 45.558-41.277 45.558-28.44 0-42.984-17.7-42.984-17.7l7.615-25.16s14.95 12.835 27.565 12.835c8.243 0 11.596-6.49 11.596-11.232 0-19.616-32.16-20.491-32.16-52.724 0-27.129 19.472-53.382 58.778-53.382 15.145 0 22.627 4.338 22.627 4.338" fill="#FFF"/></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

BIN
public/yasha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,240 +0,0 @@
import { ImageResponse } from "next/og";
import { allPosts } from "content-collections";
import { DATA } from "@/data/resume";
export const runtime = "edge";
export const alt = "Blog Post";
export const size = {
width: 1200,
height: 630,
};
export const contentType = "image/png";
const getFontData = async () => {
try {
const [cabinetGrotesk, clashDisplay] = await Promise.all([
fetch(
new URL(
"../../../../public/fonts/CabinetGrotesk-Medium.ttf",
import.meta.url
)
).then((res) => res.arrayBuffer()),
fetch(
new URL(
"../../../../public/fonts/ClashDisplay-Semibold.ttf",
import.meta.url
)
).then((res) => res.arrayBuffer()),
]);
return { cabinetGrotesk, clashDisplay };
} catch (error) {
console.error("Failed to load fonts:", error);
return null;
}
};
const styles = {
outerWrapper: {
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
backgroundColor: "#ffffff",
position: "relative",
},
middleWrapper: {
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
backgroundColor: "#ffffff",
position: "relative",
padding: "40px",
},
wrapper: {
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
backgroundColor: "#fafafa",
position: "relative",
padding: "40px",
border: "1px solid #e5e5e5",
borderRadius: "12px",
},
imageSection: {
position: "absolute",
top: "40px",
left: "40px",
display: "flex",
alignItems: "center",
zIndex: "2",
},
mainContainer: {
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "flex-end",
height: "100%",
width: "100%",
position: "relative",
zIndex: "1",
},
image: {
width: "140px",
height: "140px",
borderRadius: "24px",
border: "4px solid #e5e5e5",
objectFit: "cover",
},
title: {
fontFamily: "Clash Display",
fontSize: "48px",
fontWeight: "600",
lineHeight: "1.1",
textAlign: "left",
color: "#000000",
marginBottom: "16px",
letterSpacing: "-0.02em",
maxWidth: "900px",
},
description: {
fontSize: "20px",
fontWeight: "400",
lineHeight: "1.5",
textAlign: "left",
maxWidth: "800px",
color: "#404040",
marginBottom: "16px",
textWrap: "balance",
},
date: {
fontSize: "16px",
fontWeight: "400",
lineHeight: "1.5",
textAlign: "left",
color: "#666666",
marginBottom: "32px",
},
} as const;
export default async function Image({
params,
}: {
params: Promise<{ slug: string }>;
}) {
try {
const fontData = await getFontData();
const { slug } = await params;
const post = allPosts.find((p) => p._meta.path.replace(/\.mdx$/, "") === slug);
const imageUrl = DATA.avatarUrl
? new URL(DATA.avatarUrl, DATA.url).toString()
: undefined;
if (!post) {
return new ImageResponse(
(
<div style={styles.outerWrapper}>
<div style={styles.middleWrapper}>
<div style={styles.wrapper}>
{imageUrl && (
<div style={styles.imageSection}>
<img src={imageUrl} alt="Blog Post" style={styles.image} />
</div>
)}
<div style={styles.mainContainer}>
<div style={styles.title}>Post Not Found</div>
</div>
</div>
</div>
</div>
),
{
...size,
fonts: fontData
? [
{
name: "Clash Display",
data: fontData.clashDisplay,
weight: 600,
style: "normal",
},
]
: undefined,
}
);
}
const title = post.title;
const description = post.summary || "";
const publishedDate = post.publishedAt
? new Date(post.publishedAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
timeZone: "UTC",
})
: "";
return new ImageResponse(
(
<div style={styles.outerWrapper}>
<div style={styles.middleWrapper}>
<div style={styles.wrapper}>
{imageUrl && (
<div style={styles.imageSection}>
<img src={imageUrl} alt={title} style={styles.image} />
</div>
)}
<div style={styles.mainContainer}>
<div style={styles.title}>{title}</div>
{description && (
<div style={styles.description}>{description}</div>
)}
{publishedDate && <div style={styles.date}>{publishedDate}</div>}
</div>
</div>
</div>
</div>
),
{
...size,
fonts: fontData
? [
{
name: "Cabinet Grotesk",
data: fontData.cabinetGrotesk,
weight: 400,
style: "normal",
},
{
name: "Cabinet Grotesk",
data: fontData.cabinetGrotesk,
weight: 700,
style: "normal",
},
{
name: "Clash Display",
data: fontData.clashDisplay,
weight: 600,
style: "normal",
},
]
: undefined,
}
);
} catch (error) {
console.error("Error generating OpenGraph image:", error);
return new Response(
`Failed to generate image: ${error instanceof Error ? error.message : "Unknown error"}`,
{
status: 500,
}
);
}
}

View File

@@ -1,193 +0,0 @@
import { allPosts } from "content-collections";
import { formatDate } from "@/lib/utils";
import { DATA } from "@/data/resume";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { MDXContent } from "@content-collections/mdx/react";
import { mdxComponents } from "@/mdx-components";
import Link from "next/link";
import { ChevronLeft, ChevronRight } from "lucide-react";
function getSortedPosts() {
return [...allPosts].sort((a, b) => {
if (new Date(a.publishedAt) > new Date(b.publishedAt)) {
return -1;
}
return 1;
});
}
export async function generateStaticParams() {
return allPosts.map((post) => ({
slug: post._meta.path.replace(/\.mdx$/, ""),
}));
}
export async function generateMetadata({
params,
}: {
params: Promise<{
slug: string;
}>;
}): Promise<Metadata | undefined> {
const { slug } = await params;
const post = allPosts.find((p) => p._meta.path.replace(/\.mdx$/, "") === slug);
if (!post) {
return undefined;
}
let {
title,
publishedAt: publishedTime,
summary: description,
image,
} = post;
return {
title,
description,
openGraph: {
title,
description,
type: "article",
publishedTime,
url: `${DATA.url}/blog/${slug}`,
...(image && {
images: [
{
url: `${DATA.url}${image}`,
},
],
}),
},
twitter: {
card: "summary_large_image",
title,
description,
...(image && {
images: [`${DATA.url}${image}`],
}),
},
};
}
export default async function Blog({
params,
}: {
params: Promise<{
slug: string;
}>;
}) {
const { slug } = await params;
const sortedPosts = getSortedPosts();
const currentIndex = sortedPosts.findIndex(
(p) => p._meta.path.replace(/\.mdx$/, "") === slug
);
const post = sortedPosts[currentIndex];
if (!post) {
notFound();
}
const previousPost = currentIndex > 0 ? sortedPosts[currentIndex - 1] : null;
const nextPost = currentIndex < sortedPosts.length - 1 ? sortedPosts[currentIndex + 1] : null;
const getSlug = (post: (typeof sortedPosts)[0]) =>
post._meta.path.replace(/\.mdx$/, "");
const jsonLdContent = JSON.stringify({
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.title,
datePublished: post.publishedAt,
dateModified: post.publishedAt,
description: post.summary,
image: post.image
? `${DATA.url}${post.image}`
: `${DATA.url}/blog/${slug}/opengraph-image`,
url: `${DATA.url}/blog/${slug}`,
author: {
"@type": "Person",
name: DATA.name,
},
}).replace(/</g, "\\u003c");
return (
<section id="blog">
<script
type="application/ld+json"
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: jsonLdContent,
}}
/>
<div className="flex justify-start gap-4 items-center">
<Link href="/blog" className="text-sm text-muted-foreground hover:text-foreground transition-colors border border-border rounded-lg px-2 py-1 inline-flex items-center gap-1 mb-6 group" aria-label="Back to Blog">
<ChevronLeft className="size-3 group-hover:-translate-x-px transition-transform" />
Back to Blog
</Link>
</div>
<div className="flex flex-col gap-4">
<h1 className="title font-semibold text-3xl md:text-4xl tracking-tighter leading-tight">
{post.title}
</h1>
<p className="text-sm text-muted-foreground">
{formatDate(post.publishedAt)}
</p>
</div>
<div className="my-6 flex w-full items-center">
<div
className="flex-1 h-px bg-border"
style={{
maskImage:
"linear-gradient(90deg, transparent, black 8%, black 92%, transparent)",
WebkitMaskImage:
"linear-gradient(90deg, transparent, black 8%, black 92%, transparent)",
}}
/>
</div>
<article className="prose max-w-full text-pretty font-sans leading-relaxed text-muted-foreground dark:prose-invert">
<MDXContent code={post.mdx} components={mdxComponents} />
</article>
<nav className="mt-12 pt-8 max-w-2xl">
<div className="flex flex-col sm:flex-row justify-between gap-4">
{previousPost ? (
<Link
href={`/blog/${getSlug(previousPost)}`}
className="group flex-1 flex flex-col gap-1 p-4 rounded-lg border border-border hover:bg-accent/50 transition-colors"
>
<span className="flex items-center gap-1 text-xs text-muted-foreground">
<ChevronLeft className="size-3" />
Previous
</span>
<span className="text-sm font-medium group-hover:text-foreground transition-colors whitespace-normal wrap-break-word">
{previousPost.title}
</span>
</Link>
) : (
<div className="hidden sm:block flex-1" />
)}
{nextPost ? (
<Link
href={`/blog/${getSlug(nextPost)}`}
className="group flex-1 flex flex-col gap-1 p-4 rounded-lg border border-border hover:bg-accent/50 transition-colors text-right"
>
<span className="flex items-center justify-end gap-1 text-xs text-muted-foreground">
Next
<ChevronRight className="size-3" />
</span>
<span className="text-sm font-medium group-hover:text-foreground transition-colors whitespace-normal wrap-break-word">
{nextPost.title}
</span>
</Link>
) : (
<div className="hidden sm:block flex-1" />
)}
</div>
</nav>
</section>
);
}

View File

@@ -1,174 +0,0 @@
import { ImageResponse } from "next/og";
import { DATA } from "@/data/resume";
export const runtime = "edge";
export const alt = "Blog";
export const size = {
width: 1200,
height: 630,
};
export const contentType = "image/png";
const getFontData = async () => {
try {
const [cabinetGrotesk, clashDisplay] = await Promise.all([
fetch(
new URL("../../../public/fonts/CabinetGrotesk-Medium.ttf", import.meta.url)
).then((res) => res.arrayBuffer()),
fetch(
new URL("../../../public/fonts/ClashDisplay-Semibold.ttf", import.meta.url)
).then((res) => res.arrayBuffer()),
]);
return { cabinetGrotesk, clashDisplay };
} catch (error) {
console.error("Failed to load fonts:", error);
return null;
}
};
const styles = {
outerWrapper: {
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
backgroundColor: "#ffffff",
position: "relative",
},
middleWrapper: {
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
backgroundColor: "#ffffff",
position: "relative",
padding: "40px",
},
wrapper: {
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
backgroundColor: "#fafafa",
position: "relative",
padding: "40px",
border: "1px solid #e5e5e5",
borderRadius: "12px",
},
imageSection: {
position: "absolute",
top: "40px",
left: "40px",
display: "flex",
alignItems: "center",
zIndex: "2",
},
mainContainer: {
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "flex-end",
height: "100%",
width: "100%",
position: "relative",
zIndex: "1",
},
image: {
width: "140px",
height: "140px",
borderRadius: "24px",
border: "4px solid #e5e5e5",
objectFit: "cover",
},
title: {
fontFamily: "Clash Display",
fontSize: "48px",
fontWeight: "600",
lineHeight: "1.1",
textAlign: "left",
color: "#000000",
marginBottom: "16px",
letterSpacing: "-0.02em",
maxWidth: "900px",
},
description: {
fontSize: "20px",
fontWeight: "400",
lineHeight: "1.5",
textAlign: "left",
maxWidth: "800px",
color: "#404040",
marginBottom: "32px",
textWrap: "balance",
},
} as const;
export default async function Image() {
try {
const fontData = await getFontData();
const title = "Blog";
const description = "Thoughts on software development, life, and more.";
const imageUrl = DATA.avatarUrl
? new URL(DATA.avatarUrl, DATA.url).toString()
: undefined;
return new ImageResponse(
(
<div style={styles.outerWrapper}>
<div style={styles.middleWrapper}>
<div style={styles.wrapper}>
{imageUrl && (
<div style={styles.imageSection}>
<img src={imageUrl} alt="Blog" style={styles.image} />
</div>
)}
<div style={styles.mainContainer}>
<div style={styles.title}>{title}</div>
{description && (
<div style={styles.description}>{description}</div>
)}
</div>
</div>
</div>
</div>
),
{
...size,
fonts: fontData
? [
{
name: "Cabinet Grotesk",
data: fontData.cabinetGrotesk,
weight: 400,
style: "normal",
},
{
name: "Cabinet Grotesk",
data: fontData.cabinetGrotesk,
weight: 700,
style: "normal",
},
{
name: "Clash Display",
data: fontData.clashDisplay,
weight: 600,
style: "normal",
},
]
: undefined,
}
);
} catch (error) {
console.error("Error generating OpenGraph image:", error);
return new Response(
`Failed to generate image: ${error instanceof Error ? error.message : "Unknown error"}`,
{
status: 500,
}
);
}
}

View File

@@ -1,141 +0,0 @@
import BlurFade from "@/components/magicui/blur-fade";
import { allPosts } from "content-collections";
import Link from "next/link";
import type { Metadata } from "next";
import { paginate, normalizePage } from "@/lib/pagination";
import { ChevronRight } from "lucide-react";
export const metadata: Metadata = {
title: "Blog",
description: "Thoughts on software development, life, and more.",
openGraph: {
title: "Blog",
description: "Thoughts on software development, life, and more.",
},
twitter: {
card: "summary_large_image",
title: "Blog",
description: "Thoughts on software development, life, and more.",
},
};
const PAGE_SIZE = 5;
const BLUR_FADE_DELAY = 0.04;
export default async function BlogPage({
searchParams,
}: {
searchParams: Promise<{ page?: string }>;
}) {
const { page: pageParam } = await searchParams;
const posts = allPosts;
const sortedPosts = [...posts].sort((a, b) => {
if (new Date(a.publishedAt) > new Date(b.publishedAt)) {
return -1;
}
return 1;
});
const totalPages = Math.ceil(sortedPosts.length / PAGE_SIZE);
const currentPage = normalizePage(pageParam, totalPages);
const { items: paginatedPosts, pagination } = paginate(sortedPosts, {
page: currentPage,
pageSize: PAGE_SIZE,
});
return (
<section id="blog">
<BlurFade delay={BLUR_FADE_DELAY}>
<h1 className="text-2xl font-semibold tracking-tight mb-2">Blog <span className="ml-1 bg-card border border-border rounded-md px-2 py-1 text-muted-foreground text-sm">{sortedPosts.length} posts</span></h1>
<p className="text-sm text-muted-foreground mb-8">
My thoughts on software development, life, and more.
</p>
</BlurFade>
{paginatedPosts.length > 0 ? (
<>
<BlurFade delay={BLUR_FADE_DELAY * 2}>
<div className="flex flex-col gap-5">
{paginatedPosts.map((post, id) => {
const slug = post._meta.path.replace(/\.mdx$/, "");
const indexNumber = (pagination.page - 1) * PAGE_SIZE + id + 1;
return (
<BlurFade delay={BLUR_FADE_DELAY * 3 + id * 0.05} key={slug}>
<Link
className="flex items-start gap-x-2 group cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
href={`/blog/${slug}`}
>
<span className="text-xs font-mono tabular-nums font-medium mt-[5px]">
{String(indexNumber).padStart(2, "0")}.
</span>
<div className="flex flex-col gap-y-2 flex-1">
<p className="tracking-tight text-lg font-medium">
<span className="group-hover:text-foreground transition-colors">
{post.title}
<ChevronRight
className="ml-1 inline-block size-4 stroke-3 text-muted-foreground opacity-0 -translate-x-2 transition-all duration-200 group-hover:opacity-100 group-hover:translate-x-0"
aria-hidden
/>
</span>
</p>
<p className="text-xs text-muted-foreground">
{post.publishedAt}
</p>
</div>
</Link>
</BlurFade>
);
})}
</div>
</BlurFade>
{/* Pagination Controls */}
{pagination.totalPages > 1 && (
<BlurFade delay={BLUR_FADE_DELAY * 4}>
<div className="flex gap-3 flex-row items-center justify-between mt-8">
<div className="text-sm text-muted-foreground">
Page {pagination.page} of {pagination.totalPages}
</div>
<div className="flex gap-2 sm:justify-end">
{pagination.hasPreviousPage ? (
<Link
href={`/blog?page=${pagination.page - 1}`}
className="h-8 w-fit px-2 flex items-center justify-center text-sm border border-border rounded-lg hover:bg-accent/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
Previous
</Link>
) : (
<span className="h-8 w-fit px-2 flex items-center justify-center text-sm border border-border rounded-lg opacity-50 cursor-not-allowed">
Previous
</span>
)}
{pagination.hasNextPage ? (
<Link
href={`/blog?page=${pagination.page + 1}`}
className="h-8 w-fit px-2 flex items-center justify-center text-sm border border-border rounded-lg hover:bg-accent/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
Next
</Link>
) : (
<span className="h-8 w-fit px-2 flex items-center justify-center text-sm border border-border rounded-lg opacity-50 cursor-not-allowed">
Next
</span>
)}
</div>
</div>
</BlurFade>
)}
</>
) : (
<BlurFade delay={BLUR_FADE_DELAY * 2}>
<div className="flex flex-col items-center justify-center py-12 px-4 border border-border rounded-xl">
<p className="text-muted-foreground text-center">
No blog posts yet. Check back soon!
</p>
</div>
</BlurFade>
)}
</section>
);
}

View File

@@ -6,7 +6,6 @@ import { DATA } from "@/data/resume";
import Link from "next/link";
import Markdown from "react-markdown";
import ContactSection from "@/components/section/contact-section";
import HackathonsSection from "@/components/section/hackathons-section";
import ProjectsSection from "@/components/section/projects-section";
import WorkSection from "@/components/section/work-section";
import { ArrowUpRight } from "lucide-react";
@@ -24,7 +23,7 @@ export default function Page() {
delay={BLUR_FADE_DELAY}
className="text-3xl font-semibold tracking-tighter sm:text-4xl lg:text-5xl"
yOffset={8}
text={`Hi, I'm ${DATA.name.split(" ")[0]}`}
text={`Hi, I'm ${DATA.name.split(" ")[0]}👋`}
/>
<BlurFadeText
className="text-muted-foreground max-w-[600px] md:text-lg lg:text-xl"
@@ -44,7 +43,7 @@ export default function Page() {
<section id="about">
<div className="flex min-h-0 flex-col gap-y-4">
<BlurFade delay={BLUR_FADE_DELAY * 3}>
<h2 className="text-xl font-bold">About</h2>
<h2 className="text-xl font-bold"></h2>
</BlurFade>
<BlurFade delay={BLUR_FADE_DELAY * 4}>
<div className="prose max-w-full text-pretty font-sans leading-relaxed text-muted-foreground dark:prose-invert">
@@ -135,11 +134,6 @@ export default function Page() {
<ProjectsSection />
</BlurFade>
</section>
<section id="hackathons">
<BlurFade delay={BLUR_FADE_DELAY * 13}>
<HackathonsSection />
</BlurFade>
</section>
<section id="contact">
<BlurFade delay={BLUR_FADE_DELAY * 16}>
<ContactSection />

View File

@@ -1,6 +1,6 @@
import { GlobeIcon, MailIcon } from "lucide-react";
export type IconProps = React.HTMLAttributes<SVGElement>;
type IconProps = React.HTMLAttributes<SVGElement>;
export const Icons = {
globe: (props: IconProps) => <GlobeIcon {...props} />,
@@ -226,4 +226,22 @@ export const Icons = {
/>
</svg>
),
telegram: (props: IconProps) => (
< svg width="800px" height="800px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" >
<circle cx="16" cy="16" r="14" fill="url(#paint0_linear_87_7225)" />
<path d="M22.9866 10.2088C23.1112 9.40332 22.3454 8.76755 21.6292 9.082L7.36482 15.3448C6.85123 15.5703 6.8888 16.3483 7.42147 16.5179L10.3631 17.4547C10.9246 17.6335 11.5325 17.541 12.0228 17.2023L18.655 12.6203C18.855 12.4821 19.073 12.7665 18.9021 12.9426L14.1281 17.8646C13.665 18.3421 13.7569 19.1512 14.314 19.5005L19.659 22.8523C20.2585 23.2282 21.0297 22.8506 21.1418 22.1261L22.9866 10.2088Z" fill="white" />
<defs>
<linearGradient id="paint0_linear_87_7225" x1="16" y1="2" x2="16" y2="30" gradientUnits="userSpaceOnUse">
<stop offset="1" stopColor="#007DBB" />
</linearGradient>
</defs>
</svg >
),
Blog: (props: IconProps) => (
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
<title>cat</title>
<path d="M28.926 1.17l-2.182 3.608c-1.876-0.608-4.669-0.489-6.426 0l-2.102-3.557c-3.452 6.448-2.475 10.523 0.159 12.549-0.403 0.252-0.818 0.529-1.247 0.833-10.979-8.759-20.863 1.106-14.379 9.92h0.050c1.163 1.687 2.503 2.731 3.95 3.277 2.050 0.773 4.159 0.551 6.236 0.257s4.109-0.663 6.046-0.525c1.937 0.138 3.874 0.635 5.647 2.569 1.209 1.318 2.926-0.101 1.486-1.507-2.185-2.134-4.525-2.959-6.825-3.122s-4.505 0.293-6.502 0.576c-1.997 0.283-3.761 0.409-5.276-0.163-0.711-0.268-1.403-0.69-2.070-1.36h22.51c1.064-3.756 1.177-7.73-0.033-10.237 3.635-1.897 5.097-6.376 0.958-13.116zM22.176 10.872c-2.316 1.117-3.367 0.212-3.817-1.656 2.273-1.41 3.626-0.278 3.817 1.656zM25.067 10.872c0.191-1.934 1.544-3.067 3.817-1.656-0.45 1.868-1.502 2.774-3.817 1.656z"></path>
</svg>
),
};

View File

@@ -11,11 +11,6 @@ interface DockProps {
distance?: number;
}
interface DockIconProps {
className?: string;
children?: ReactNode;
}
const DEFAULT_MAGNIFICATION = 60;
const DEFAULT_DISTANCE = 100;
const BASE_SIZE = 40;
@@ -47,7 +42,12 @@ const Dock = ({ className, children, magnification = DEFAULT_MAGNIFICATION, dist
);
};
const DockIcon = ({ className, children }: DockIconProps) => {
interface DockIconInternalProps {
className?: string;
children?: ReactNode;
}
const DockIcon = ({ className, children }: DockIconInternalProps) => {
const ref = useRef<HTMLDivElement>(null);
const context = useContext(DockContext);
@@ -88,4 +88,3 @@ const DockIcon = ({ className, children }: DockIconProps) => {
};
export { Dock, DockIcon };
export type { DockProps, DockIconProps };

View File

@@ -1,110 +0,0 @@
"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

@@ -1,34 +0,0 @@
/* 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

@@ -6,7 +6,7 @@ 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>
<span className="text-background text-sm font-medium"></span>
</div>
<div className="absolute inset-0 top-0 left-0 right-0 h-1/2 rounded-xl overflow-hidden">
<FlickeringGrid
@@ -24,17 +24,23 @@ export default function ContactSection() {
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}
href={DATA.contact.social.Telegram.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
Telegram
</Link>{" "}
and I&apos;ll respond whenever I can. I will ignore all
soliciting.
{" "}
<Link
href={`mailto:${DATA.contact.email}`}
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"
>
{DATA.contact.email}
</Link>
</p>
</div>
</div>

View File

@@ -1,81 +0,0 @@
/* 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

@@ -15,7 +15,7 @@ export default function ProjectsSection() {
/>
<div className="border bg-primary z-10 rounded-xl px-4 py-1">
<span className="text-background text-sm font-medium">My Projects</span>
<span className="text-background text-sm font-medium"></span>
</div>
<div
className="flex-1 h-px bg-linear-to-l from-transparent from-5% via-border via-95% to-transparent"
@@ -23,12 +23,7 @@ export default function ProjectsSection() {
/>
</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>
<h2 className="text-3xl font-bold tracking-tighter sm:text-4xl"></h2>
</div>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 max-w-[800px] mx-auto auto-rows-fr">

View File

@@ -23,7 +23,7 @@ const badgeVariants = cva(
}
)
export interface BadgeProps
interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
@@ -33,4 +33,4 @@ function Badge({ className, variant, ...props }: BadgeProps) {
)
}
export { Badge, badgeVariants }
export { Badge }

View File

@@ -34,7 +34,7 @@ const buttonVariants = cva(
}
);
export interface ButtonProps
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
@@ -54,4 +54,4 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
);
Button.displayName = "Button";
export { Button, buttonVariants };
export { Button };

View File

@@ -1,86 +0,0 @@
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

@@ -1,20 +0,0 @@
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

@@ -1,58 +0,0 @@
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

@@ -1,57 +0,0 @@
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

@@ -1,42 +0,0 @@
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

@@ -1,42 +0,0 @@
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

@@ -1,77 +0,0 @@
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

@@ -1,74 +0,0 @@
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 };

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

@@ -1,79 +1,70 @@
import { Icons } from "@/components/icons";
import { HomeIcon, NotebookIcon } from "lucide-react";
import { HomeIcon } from "lucide-react";
import { ReactLight } from "@/components/ui/svgs/reactLight";
import { NextjsIconDark } from "@/components/ui/svgs/nextjsIconDark";
import { Typescript } from "@/components/ui/svgs/typescript";
import { Nodejs } from "@/components/ui/svgs/nodejs";
import { Python } from "@/components/ui/svgs/python";
import { Golang } from "@/components/ui/svgs/golang";
import { Postgresql } from "@/components/ui/svgs/postgresql";
import { Docker } from "@/components/ui/svgs/docker";
import { Kubernetes } from "@/components/ui/svgs/kubernetes";
import { Java } from "@/components/ui/svgs/java";
import { Csharp } from "@/components/ui/svgs/csharp";
import { title } from "node:process";
export const DATA = {
name: "Dillion Verma",
initials: "DV",
url: "https://dillion.io",
location: "San Francisco, CA",
locationLink: "https://www.google.com/maps/place/sanfrancisco",
name: "Evan",
initials: "ES",
url: "https://liukersun.com",
location: "China",
locationLink: "https://www.google.com/maps/place/china",
description:
"Software Engineer turned Entrepreneur. I love building things and helping people. Very active on Twitter.",
"全栈软件工程师6年开发经验。热爱技术活跃于Telegram。",
summary:
"At the end of 2022, I quit my job as a software engineer to go fulltime into building and scaling my own SaaS businesses. In the past, [I pursued a double degree in computer science and business](/#education), [interned at big tech companies in Silicon Valley](https://www.youtube.com/watch?v=d-LJ2e5qKdE), and [competed in over 21 hackathons for fun](/#hackathons). I also had the pleasure of being a part of the first ever in-person cohort of buildspace called [buildspace sf1](https://buildspace.so/sf1).",
"拥有6年软件开发经验的全栈工程师曾就职于字节跳动 RTC 实验室、亚厦集团和 MakeBlock。擅长前后端开发、自动化测试、数据处理和办公自动化。",
avatarUrl: "/me.png",
skills: [
{ name: "React", icon: ReactLight },
{ name: "Next.js", icon: NextjsIconDark },
{ name: "Typescript", icon: Typescript },
{ name: "Node.js", icon: Nodejs },
{ name: "Python", icon: Python },
{ name: "Go", icon: Golang },
{ name: "Postgres", icon: Postgresql },
{ name: "Docker", icon: Docker },
{ name: "Kubernetes", icon: Kubernetes },
{ name: "Java", icon: Java },
{ name: "C++", icon: Csharp },
{ name: "React", icon: ReactLight },
{ name: "JavaScript", icon: Typescript },
{ name: "Docker", icon: Docker },
{ name: "PostgreSQL", icon: Postgresql },
{ name: "Redis", icon: Csharp },
{ name: "Nginx", icon: Nodejs },
{ name: "Jenkins", icon: Golang },
],
navbar: [
{ href: "/", icon: HomeIcon, label: "Home" },
{ href: "/blog", icon: NotebookIcon, label: "Blog" },
],
contact: {
email: "hello@example.com",
tel: "+123456789",
email: "liukersun@gmail.com",
tel: "",
social: {
GitHub: {
name: "GitHub",
url: "https://dub.sh/dillion-github",
url: "https://github.com/LiukerSun",
icon: Icons.github,
navbar: true,
},
LinkedIn: {
name: "LinkedIn",
url: "https://dub.sh/dillion-linkedin",
icon: Icons.linkedin,
Blog: {
name: "Blog",
url: "https://blog.liukersun.com",
icon: Icons.Blog,
navbar: true,
},
X: {
name: "X",
url: "https://dub.sh/dillion-twitter",
icon: Icons.x,
Telegram: {
name: "Telegram",
url: "https://t.me/DrJhaha",
icon: Icons.telegram,
navbar: true,
},
Youtube: {
name: "Youtube",
url: "https://dub.sh/dillion-youtube",
icon: Icons.youtube,
navbar: true,
},
email: {
name: "Send Email",
url: "#",
url: "mailto:liukersun@gmail.com",
icon: Icons.email,
navbar: false,
@@ -83,619 +74,107 @@ export const DATA = {
work: [
{
company: "Atomic Finance",
href: "https://atomic.finance",
company: "MakeBlock",
href: "https://www.makeblock.com",
badges: [],
location: "Remote",
title: "Bitcoin Protocol Engineer",
logoUrl: "/atomic.png",
start: "May 2021",
end: "Oct 2022",
location: "中国",
title: "全栈开发工程师",
logoUrl: "/makeblock.png",
start: "2023",
end: "2025",
description:
"Implemented the Bitcoin discreet log contract (DLC) protocol specifications as an open source Typescript SDK. Dockerized all microservices and setup production kubernetes cluster. Architected a data lake using AWS S3 and Athena for historical backtesting of bitcoin trading strategies. Built a mobile app using react native and typescript.",
"负责全栈开发工作内部质检系统功能编写。参与训练AI模型识别产品状况优化流水线效率。",
},
{
company: "Shopify",
company: "哈尔滨工业大学重庆研究院",
badges: [],
href: "https://shopify.com",
location: "Remote",
title: "Software Engineer",
logoUrl: "/shopify.svg",
start: "January 2021",
end: "April 2021",
href: "#",
location: "重庆",
title: "项目经理 / 技术负责人",
logoUrl: "/hit.svg",
start: "2023",
end: "2023",
description:
"Implemented a custom Kubernetes controller in Go to automate the deployment of MySQL and ProxySQL custom resources in order to enable 2,000+ internal developers to instantly deploy their app databases to production. Wrote several scripts in Go to automate MySQL database failovers while maintaining master-slave replication topologies and keeping Zookeeper nodes consistent with changes.",
"担任项目经理和技术负责人,负责项目整体技术方向和架构设计。管理项目进度和团队协作,确保项目按时高质量交付。参与多个科研项目的技术选型和实施。",
},
{
company: "Nvidia",
href: "https://nvidia.com/",
company: "字节跳动",
href: "https://www.bytedance.com",
badges: [],
location: "Santa Clara, CA",
title: "Software Engineer",
logoUrl: "/nvidia.png",
start: "January 2020",
end: "April 2020",
location: "中国",
title: "测试开发工程师",
logoUrl: "/bytedance.svg",
start: "2021",
end: "2023",
description:
"Architected and wrote the entire MVP of the GeForce Now Cloud Gaming internal admin and A/B testing dashboard using React, Redux, TypeScript, and Python.",
"在 RTC 实验室负责音视频质量评估和弱网自动化测试。开发自动化测试工具和框架,提升测试效率。参与音视频通话质量优化,保障产品稳定性。使用 Python、Java 进行测试工具开发。",
},
{
company: "Splunk",
href: "https://splunk.com",
company: "夏厦集团",
href: "#",
badges: [],
location: "San Jose, CA",
title: "Software Engineer",
logoUrl: "/splunk.svg",
start: "January 2019",
end: "April 2019",
location: "中国",
title: "BIM 工程师",
logoUrl: "/yasha.png",
start: "2020",
end: "2021",
description:
"Co-developed a prototype iOS app with another intern in Swift for the new Splunk Phantom security orchestration product (later publicly demoed and launched at .conf annual conference in Las Vegas). Implemented a realtime service for the iOS app in Django (Python) and C++; serialized data using protobufs transmitted over gRPC resulting in an approximate 500% increase in data throughput.",
},
{
company: "Lime",
href: "https://li.me/",
badges: [],
location: "San Francisco, CA",
title: "Software Engineer",
logoUrl: "/lime.svg",
start: "January 2018",
end: "April 2018",
description:
"Proposed and implemented an internal ruby API for sending/receiving commands to scooters over LTE networks. Developed a fully automated bike firmware update system to handle asynchronous firmware updates of over 100,000+ scooters worldwide, and provide progress reports in real-time using React, Ruby on Rails, PostgreSQL and AWS EC2 saving hundreds of developer hours.",
},
{
company: "Mitre Media",
href: "https://mitremedia.com/",
badges: [],
location: "Toronto, ON",
title: "Software Engineer",
logoUrl: "/mitremedia.png",
start: "May 2017",
end: "August 2017",
description:
"Designed and implemented a robust password encryption and browser cookie storage system in Ruby on Rails. Leveraged the Yahoo finance API to develop the dividend.com equity screener",
"负责外立面 3D 建模,使用 Rhino、CAD、GrassHopper 等专业软件。开发 Python/Java/VBA 自动化工具,提升建模和数据处理效率。参与多个大型项目的 BIM 建模和优化工作。",
},
],
education: [
{
school: "Buildspace",
href: "https://buildspace.so",
degree: "s3, s4, sf1, s5",
logoUrl: "/buildspace.jpg",
start: "2023",
end: "2024",
},
{
school: "University of Waterloo",
href: "https://uwaterloo.ca",
degree: "Bachelor's Degree of Computer Science (BCS)",
logoUrl: "/waterloo.png",
school: "东北农业大学",
href: "https://www.neau.edu.cn",
degree: "学士学位",
logoUrl: "/neau.svg",
start: "2016",
end: "2021",
},
{
school: "Wilfrid Laurier University",
href: "https://wlu.ca",
degree: "Bachelor's Degree of Business Administration (BBA)",
logoUrl: "/laurier.png",
start: "2016",
end: "2021",
},
{
school: "International Baccalaureate",
href: "https://ibo.org",
degree: "IB Diploma",
logoUrl: "/ib.png",
start: "2012",
end: "2016",
end: "2020",
},
],
projects: [
{
title: "Chat Collect",
href: "https://chatcollect.com",
dates: "Jan 2024 - Feb 2024",
title: "个人博客",
href: "https://blog.liukersun.com",
dates: "2018 - 至今",
active: true,
description:
"With the release of the [OpenAI GPT Store](https://openai.com/blog/introducing-the-gpt-store), I decided to build a SaaS which allows users to collect email addresses from their GPT users. This is a great way to build an audience and monetize your GPT API usage.",
"个人技术博客,分享软件开发经验、技术学习笔记和项目实践。涵盖前端、后端、自动化测试等多个技术领域。",
technologies: [
"Next.js",
"Typescript",
"PostgreSQL",
"Prisma",
"TailwindCSS",
"Stripe",
"Shadcn UI",
"Magic UI",
"Blog",
"技术分享",
"开发经验",
],
links: [
{
type: "Website",
href: "https://chatcollect.com",
href: "https://blog.liukersun.com",
icon: <Icons.globe className="size-3" />,
},
],
image: "",
video:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/chat-collect.mp4",
image: "./arch.png",
video: "",
},
{
title: "Magic UI",
href: "https://magicui.design",
dates: "June 2023 - Present",
title: "github",
href: "https://github.com/LiukerSun",
dates: "2018 - 至今",
active: true,
description:
"Designed, developed and sold animated UI components for developers.",
technologies: [
"Next.js",
"Typescript",
"PostgreSQL",
"Prisma",
"TailwindCSS",
"Stripe",
"Shadcn UI",
"Magic UI",
],
description: "个人github仓库记录个人项目和学习代码。",
technologies: ["github", "项目管理", "学习代码"],
links: [
{
type: "Website",
href: "https://magicui.design",
icon: <Icons.globe className="size-3" />,
},
{
type: "Source",
href: "https://github.com/magicuidesign/magicui",
href: "https://github.com/LiukerSun",
icon: <Icons.github className="size-3" />,
image: "./github.png",
},
],
image: "",
video: "https://cdn.magicui.design/bento-grid.mp4",
},
{
title: "llm.report",
href: "https://llm.report",
dates: "April 2023 - September 2023",
active: true,
description:
"Developed an open-source logging and analytics platform for OpenAI: Log your ChatGPT API requests, analyze costs, and improve your prompts.",
technologies: [
"Next.js",
"Typescript",
"PostgreSQL",
"Prisma",
"TailwindCSS",
"Shadcn UI",
"Magic UI",
"Stripe",
"Cloudflare Workers",
],
links: [
{
type: "Website",
href: "https://llm.report",
icon: <Icons.globe className="size-3" />,
},
{
type: "Source",
href: "https://github.com/dillionverma/llm.report",
icon: <Icons.github className="size-3" />,
},
],
image: "",
video: "https://cdn.llm.report/openai-demo.mp4",
},
{
title: "Automatic Chat",
href: "https://automatic.chat",
dates: "April 2023 - March 2024",
active: true,
description:
"Developed an AI Customer Support Chatbot which automatically responds to customer support tickets using the latest GPT models.",
technologies: [
"Next.js",
"Typescript",
"PostgreSQL",
"Prisma",
"TailwindCSS",
"Shadcn UI",
"Magic UI",
"Stripe",
"Cloudflare Workers",
],
links: [
{
type: "Website",
href: "https://automatic.chat",
icon: <Icons.globe className="size-3" />,
},
],
image: "",
video:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/automatic-chat.mp4",
},
image: "./github.png",
video: "",
}
],
hackathons: [
{
title: "Hack Western 5",
dates: "November 23rd - 25th, 2018",
location: "London, Ontario",
description:
"Developed a mobile application which delivered bedtime stories to children using augmented reality.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/hack-western.png",
mlh: "https://s3.amazonaws.com/logged-assets/trust-badge/2019/mlh-trust-badge-2019-white.svg",
links: [],
},
{
title: "Hack The North",
dates: "September 14th - 16th, 2018",
location: "Waterloo, Ontario",
description:
"Developed a mobile application which delivers university campus wide events in real time to all students.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/hack-the-north.png",
mlh: "https://s3.amazonaws.com/logged-assets/trust-badge/2019/mlh-trust-badge-2019-white.svg",
links: [],
},
{
title: "FirstNet Public Safety Hackathon",
dates: "March 23rd - 24th, 2018",
location: "San Francisco, California",
description:
"Developed a mobile application which communcicates a victims medical data from inside an ambulance to doctors at hospital.",
icon: "public",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/firstnet.png",
links: [],
},
{
title: "DeveloperWeek Hackathon",
dates: "February 3rd - 4th, 2018",
location: "San Francisco, California",
description:
"Developed a web application which aggregates social media data regarding cryptocurrencies and predicts future prices.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/developer-week.jpg",
links: [
{
title: "Github",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/cryptotrends/cryptotrends",
},
],
},
{
title: "HackDavis",
dates: "January 20th - 21st, 2018",
location: "Davis, California",
description:
"Developed a mobile application which allocates a daily carbon emission allowance to users to move towards a sustainable environment.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/hack-davis.png",
win: "Best Data Hack",
mlh: "https://s3.amazonaws.com/logged-assets/trust-badge/2018/white.svg",
links: [
{
title: "Devpost",
icon: <Icons.globe className="h-4 w-4" />,
href: "https://devpost.com/software/my6footprint",
},
{
title: "ML",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/Wallet6/my6footprint-machine-learning",
},
{
title: "iOS",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/Wallet6/CarbonWallet",
},
{
title: "Server",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/Wallet6/wallet6-server",
},
],
},
{
title: "ETH Waterloo",
dates: "October 13th - 15th, 2017",
location: "Waterloo, Ontario",
description:
"Developed a blockchain application for doctors and pharmacists to perform trustless transactions and prevent overdosage in patients.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/eth-waterloo.png",
links: [
{
title: "Organization",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/ethdocnet",
},
],
},
{
title: "Hack The North",
dates: "September 15th - 17th, 2017",
location: "Waterloo, Ontario",
description:
"Developed a virtual reality application allowing users to see themselves in third person.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/hack-the-north.png",
mlh: "https://s3.amazonaws.com/logged-assets/trust-badge/2017/white.svg",
links: [
{
title: "Streamer Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/justinmichaud/htn2017",
},
{
title: "Client Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/dillionverma/RTSPClient",
},
],
},
{
title: "Hack The 6ix",
dates: "August 26th - 27th, 2017",
location: "Toronto, Ontario",
description:
"Developed an open platform for people shipping items to same place to combine shipping costs and save money.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/hack-the-6ix.jpg",
mlh: "https://s3.amazonaws.com/logged-assets/trust-badge/2017/white.svg",
links: [
{
title: "Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/ShareShip/ShareShip",
},
{
title: "Site",
icon: <Icons.globe className="h-4 w-4" />,
href: "https://share-ship.herokuapp.com/",
},
],
},
{
title: "Stupid Hack Toronto",
dates: "July 23rd, 2017",
location: "Toronto, Ontario",
description:
"Developed a chrome extension which tracks which facebook profiles you have visited and immediately texts your girlfriend if you visited another girls page.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/stupid-hackathon.png",
links: [
{
title: "Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/nsagirlfriend/nsagirlfriend",
},
],
},
{
title: "Global AI Hackathon - Toronto",
dates: "June 23rd - 25th, 2017",
location: "Toronto, Ontario",
description:
"Developed a python library which can be imported to any python game and change difficulty of the game based on real time emotion of player. Uses OpenCV and webcam for facial recognition, and a custom Machine Learning Model trained on a [Kaggle Emotion Dataset](https://www.kaggle.com/c/challenges-in-representation-learning-facial-expression-recognition-challenge/leaderboard) using [Tensorflow](https://www.tensorflow.org/Tensorflow) and [Keras](https://keras.io/). This project recieved 1st place prize at the Global AI Hackathon - Toronto and was also invited to demo at [NextAI Canada](https://www.nextcanada.com/next-ai).",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/global-ai-hackathon.jpg",
win: "1st Place Winner",
links: [
{
title: "Article",
icon: <Icons.globe className="h-4 w-4" />,
href: "https://syncedreview.com/2017/06/26/global-ai-hackathon-in-toronto/",
},
{
title: "Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/TinySamosas/",
},
],
},
{
title: "McGill AI for Social Innovation Hackathon",
dates: "June 17th - 18th, 2017",
location: "Montreal, Quebec",
description:
"Developed realtime facial microexpression analyzer using AI",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/ai-for-social-good.jpg",
links: [],
},
{
title: "Open Source Circular Economy Days Hackathon",
dates: "June 10th, 2017",
location: "Toronto, Ontario",
description:
"Developed a custom admin interface for food waste startup <a href='http://genecis.co/'>Genecis</a> to manage their data and provide analytics.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/open-source-circular-economy-days.jpg",
win: "1st Place Winner",
links: [
{
title: "Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/dillionverma/genecis",
},
],
},
{
title: "Make School's Student App Competition 2017",
dates: "May 19th - 21st, 2017",
location: "International",
description: "Improved PocketDoc and submitted to online competition",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/make-school-hackathon.png",
win: "Top 10 Finalist | Honourable Mention",
links: [
{
title: "Medium Article",
icon: <Icons.globe className="h-4 w-4" />,
href: "https://medium.com/make-school/the-winners-of-make-schools-student-app-competition-2017-a6b0e72f190a",
},
{
title: "Devpost",
icon: <Icons.globe className="h-4 w-4" />,
href: "https://devpost.com/software/pocketdoc-react-native",
},
{
title: "YouTube",
icon: <Icons.youtube className="h-4 w-4" />,
href: "https://www.youtube.com/watch?v=XwFdn5Rmx68",
},
{
title: "Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/dillionverma/pocketdoc-react-native",
},
],
},
{
title: "HackMining",
dates: "May 12th - 14th, 2017",
location: "Toronto, Ontario",
description: "Developed neural network to optimize a mining process",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/hack-mining.png",
links: [],
},
{
title: "Waterloo Equithon",
dates: "May 5th - 7th, 2017",
location: "Waterloo, Ontario",
description:
"Developed Pocketdoc, an app in which you take a picture of a physical wound, and the app returns common solutions or cures to the injuries or diseases.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/waterloo-equithon.png",
links: [
{
title: "Devpost",
icon: <Icons.globe className="h-4 w-4" />,
href: "https://devpost.com/software/pocketdoc-react-native",
},
{
title: "YouTube",
icon: <Icons.youtube className="h-4 w-4" />,
href: "https://www.youtube.com/watch?v=XwFdn5Rmx68",
},
{
title: "Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/dillionverma/pocketdoc-react-native",
},
],
},
{
title: "SpaceApps Waterloo",
dates: "April 28th - 30th, 2017",
location: "Waterloo, Ontario",
description:
"Developed Earthwatch, a web application which allows users in a plane to virtually see important points of interest about the world below them. They can even choose to fly away from their route and then fly back if they choose. Special thanks to CesiumJS for providing open source world and plane models.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/space-apps.png",
links: [
{
title: "Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/dillionverma/earthwatch",
},
],
},
{
title: "MHacks 9",
dates: "March 24th - 26th, 2017",
location: "Ann Arbor, Michigan",
description:
"Developed Super Graphic Air Traffic, a VR website made to introduce people to the world of air traffic controlling. This project was built completely using THREE.js as well as a node backend server.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/mhacks-9.png",
mlh: "https://s3.amazonaws.com/logged-assets/trust-badge/2017/white.svg",
links: [
{
title: "Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/dillionverma/threejs-planes",
},
],
},
{
title: "StartHacks I",
dates: "March 4th - 5th, 2017",
location: "Waterloo, Ontario",
description:
"Developed at StartHacks 2017, Recipic is a mobile app which allows you to take pictures of ingredients around your house, and it will recognize those ingredients using ClarifAI image recognition API and return possible recipes to make. Recipic recieved 1st place at the hackathon for best pitch and hack.",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/starthacks.png",
win: "1st Place Winner",
mlh: "https://s3.amazonaws.com/logged-assets/trust-badge/2017/white.svg",
links: [
{
title: "Source (Mobile)",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/mattBlackDesign/recipic-ionic",
},
{
title: "Source (Server)",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/mattBlackDesign/recipic-rails",
},
],
},
{
title: "QHacks II",
dates: "February 3rd - 5th, 2017",
location: "Kingston, Ontario",
description:
"Developed a mobile game which enables city-wide manhunt with random lobbies",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/qhacks.png",
mlh: "https://s3.amazonaws.com/logged-assets/trust-badge/2017/white.svg",
links: [
{
title: "Source (Mobile)",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/dillionverma/human-huntr-react-native",
},
{
title: "Source (API)",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/mattBlackDesign/human-huntr-rails",
},
],
},
{
title: "Terrible Hacks V",
dates: "November 26th, 2016",
location: "Waterloo, Ontario",
description:
"Developed a mock of Windows 11 with interesting notifications and functionality",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/terrible-hacks-v.png",
links: [
{
title: "Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/justinmichaud/TerribleHacks2016-Windows11",
},
],
},
{
title: "Portal Hackathon",
dates: "October 29, 2016",
location: "Kingston, Ontario",
description:
"Developed an internal widget for uploading assignments using Waterloo's portal app",
image:
"https://pub-83c5db439b40468498f97946200806f7.r2.dev/hackline/portal-hackathon.png",
links: [
{
title: "Source",
icon: <Icons.github className="h-4 w-4" />,
href: "https://github.com/UWPortalSDK/crowmark",
},
],
},
// 如果有参加过的技术活动、黑客马拉松或竞赛,可以在这里添加
],
} as const;

View File

@@ -1,85 +0,0 @@
/**
* Pagination utilities for blog posts and other content collections
*/
export interface PaginationOptions {
page: number;
pageSize: number;
}
export interface PaginationResult<T> {
items: T[];
pagination: {
page: number;
pageSize: number;
totalItems: number;
totalPages: number;
hasNextPage: boolean;
hasPreviousPage: boolean;
};
}
/**
* Paginate an array of items
*/
export function paginate<T>(
items: T[],
options: PaginationOptions
): PaginationResult<T> {
const { page, pageSize } = options;
const totalItems = items.length;
const totalPages = Math.ceil(totalItems / pageSize);
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedItems = items.slice(startIndex, endIndex);
return {
items: paginatedItems,
pagination: {
page,
pageSize,
totalItems,
totalPages,
hasNextPage: page < totalPages,
hasPreviousPage: page > 1,
},
};
}
/**
* Get pagination metadata without slicing the array
*/
export function getPaginationMeta(
totalItems: number,
options: PaginationOptions
) {
const { page, pageSize } = options;
const totalPages = Math.ceil(totalItems / pageSize);
return {
page,
pageSize,
totalItems,
totalPages,
hasNextPage: page < totalPages,
hasPreviousPage: page > 1,
};
}
/**
* Validate and normalize page number
*/
export function normalizePage(page: number | string | undefined, maxPage: number): number {
if (typeof page === "string") {
const parsed = parseInt(page, 10);
if (isNaN(parsed) || parsed < 1) return 1;
return Math.min(parsed, maxPage);
}
if (typeof page === "number") {
if (page < 1) return 1;
return Math.min(page, maxPage);
}
return 1;
}

View File

@@ -1,31 +0,0 @@
export function remarkCodeMeta() {
return (tree: any) => {
const walk = (node: any) => {
if (!node || typeof node !== "object") return;
if (node.type === "code") {
const meta: string | undefined = node.meta;
if (meta) {
node.data ||= {};
node.data.hProperties ||= {};
node.data.hProperties["data-meta"] = meta;
const titleMatch = meta.match(/title="([^"]+)"/);
if (titleMatch?.[1]) {
node.data.hProperties["data-title"] = titleMatch[1];
}
}
}
const children = node.children;
if (Array.isArray(children)) {
for (const child of children) walk(child);
}
};
walk(tree);
};
}

View File

@@ -4,14 +4,3 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function formatDate(date: string | Date) {
// Use UTC to ensure consistent formatting between server and client
const dateObj = typeof date === "string" ? new Date(date) : date;
return dateObj.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
timeZone: "UTC",
});
}

View File

@@ -1,49 +0,0 @@
import { CodeBlock } from "@/components/mdx/code-block";
import { MediaContainer } from "@/components/mdx/media-container";
import type { ComponentProps } from "react";
type CodeProps = ComponentProps<"code"> & {
"data-language"?: string;
};
export const mdxComponents = {
MediaContainer,
pre: (props: ComponentProps<"pre">) => <CodeBlock {...props} />,
hr: (props: ComponentProps<"hr">) => (
<div className="my-10 flex w-full items-center" {...props}>
<div
className="flex-1 h-px bg-border"
style={{
maskImage:
"linear-gradient(90deg, transparent, black 8%, black 92%, transparent)",
WebkitMaskImage:
"linear-gradient(90deg, transparent, black 8%, black 92%, transparent)",
}}
/>
</div>
),
table: (props: ComponentProps<"table">) => (
<div className="my-6 border border-border rounded-xl overflow-hidden">
<div className="w-full overflow-x-auto">
<table
className="m-0! w-full min-w-full border-separate border-spacing-0"
{...props}
/>
</div>
</div>
),
code: ({ children, ...props }: CodeProps) => {
if (props["data-language"]) {
return <code {...props}>{children}</code>;
}
return (
<code
className="px-1.5 py-0.5 rounded-md bg-muted/60 dark:bg-muted/40 text-sm font-mono text-foreground/90"
{...props}
>
{children}
</code>
);
},
} as const;

4726
yarn.lock Normal file

File diff suppressed because it is too large Load Diff