Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions personal-site/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Link from 'next/link'
import { Button } from '@/components/Button'
import { Card } from '@/components/Card'
import { Container } from '@/components/Container'
import { ProjectOutboundLinks } from '@/components/ProjectOutboundLinks'
import {
GitHubIcon,
LinkedInIcon,
Expand Down Expand Up @@ -120,21 +121,14 @@ function FeaturedProjectCard({ project }: { project: FeaturedProject }) {
return (
<Card as="article">
<Card.Eyebrow decorate>Project</Card.Eyebrow>
<Card.Title href={project.href}>{project.name}</Card.Title>
<Card.Title as="h3">{project.name}</Card.Title>
<Card.Description>{project.description}</Card.Description>
<div className="relative z-10 mt-4 flex flex-col gap-2 text-sm">
<p className="text-zinc-500 dark:text-zinc-400">{project.label}</p>
{project.extraLink ? (
<Link
href={project.extraLink.href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex w-fit font-medium text-teal-600 underline decoration-teal-500/30 underline-offset-2 hover:text-teal-700 dark:text-teal-400 dark:hover:text-teal-300"
>
{project.extraLink.label}
</Link>
) : null}
</div>
<ProjectOutboundLinks
github={project.github}
npm={project.npm}
live={project.live}
className="mt-4"
/>
</Card>
)
}
Expand Down Expand Up @@ -330,10 +324,11 @@ export default async function Home() {
Featured projects
</h2>
<p className="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
ClawQL, CoachellaPlus, and gRPC MCP Transport.
MCP stack, libraries, gateways, and product PWAs — use the icons for
GitHub, npm, or the live site when each exists.
</p>
</div>
<div className="mx-auto mt-10 grid max-w-2xl grid-cols-1 gap-8 lg:mx-0 lg:max-w-none lg:grid-cols-3">
<div className="mx-auto mt-10 grid max-w-2xl grid-cols-1 gap-8 sm:grid-cols-2 lg:mx-0 lg:max-w-none xl:grid-cols-3">
{featuredProjects.map((project) => (
<FeaturedProjectCard key={project.name} project={project} />
))}
Expand Down
36 changes: 12 additions & 24 deletions personal-site/src/app/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
import { type Metadata } from 'next'

import { Card } from '@/components/Card'
import { ProjectOutboundLinks } from '@/components/ProjectOutboundLinks'
import { SimpleLayout } from '@/components/SimpleLayout'
import { GitHubIcon } from '@/components/SocialIcons'
import { featuredRepos } from '@/lib/site'

function LinkIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...props}>
<path
d="M15.712 11.823a.75.75 0 1 0 1.06 1.06l-1.06-1.06Zm-4.95 1.768a.75.75 0 0 0 1.06-1.06l-1.06 1.06Zm-2.475-1.414a.75.75 0 1 0-1.06-1.06l1.06 1.06Zm4.95-1.768a.75.75 0 1 0-1.06 1.06l1.06-1.06Zm3.359.53-.884.884 1.06 1.06.885-.883-1.061-1.06Zm-4.95-2.12 1.414-1.415L12 6.344l-1.415 1.413 1.061 1.061Zm0 3.535a2.5 2.5 0 0 1 0-3.536l-1.06-1.06a4 4 0 0 0 0 5.656l1.06-1.06Zm4.95-4.95a2.5 2.5 0 0 1 0 3.535L17.656 12a4 4 0 0 0 0-5.657l-1.06 1.06Zm1.06-1.06a4 4 0 0 0-5.656 0l1.06 1.06a2.5 2.5 0 0 1 3.536 0l1.06-1.06Zm-7.07 7.07.176.177 1.06-1.06-.176-.177-1.06 1.06Zm-3.183-.353.884-.884-1.06-1.06-.884.883 1.06 1.06Zm4.95 2.121-1.414 1.414 1.06 1.06 1.415-1.413-1.06-1.061Zm0-3.536a2.5 2.5 0 0 1 0 3.536l1.06 1.06a4 4 0 0 0 0-5.656l-1.06 1.06Zm-4.95 4.95a2.5 2.5 0 0 1 0-3.535L6.344 12a4 4 0 0 0 0 5.656l1.06-1.06Zm-1.06 1.06a4 4 0 0 0 5.657 0l-1.061-1.06a2.5 2.5 0 0 1-3.535 0l-1.061 1.06Zm7.07-7.07-.176-.177-1.06 1.06.176.178 1.06-1.061Z"
fill="currentColor"
/>
</svg>
)
}

export const metadata: Metadata = {
title: 'Projects',
description:
Expand All @@ -25,31 +14,30 @@ export const metadata: Metadata = {
export default function Projects() {
return (
<SimpleLayout
title="Projects on GitHub"
intro="These repositories are pulled from my public GitHub profile and curated to highlight AI-adjacent work (MCP, agents, on-device ML) alongside platform and product code. Star and fork counts change over time — follow the links for the latest."
title="Projects"
intro="Curated to match my resume’s open-source section: MCP servers and transports, evolutionary-loop tooling, edge gateways, and product PWAs — plus a few extra public repos. Each card links to GitHub, npm, and/or a live site when available (icons below the title)."
>
<ul
role="list"
className="grid grid-cols-1 gap-x-12 gap-y-16 sm:grid-cols-2 lg:grid-cols-2"
>
{featuredRepos.map((project) => (
<Card as="li" key={project.name}>
<div className="relative z-10 flex h-12 w-12 items-center justify-center rounded-full bg-white shadow-md ring-1 shadow-zinc-800/5 ring-zinc-900/5 dark:border dark:border-zinc-700/50 dark:bg-zinc-800 dark:ring-0">
<GitHubIcon className="h-8 w-8 fill-zinc-700 dark:fill-zinc-200" />
</div>
{project.highlight ? (
<p className="relative z-10 mt-3 text-xs font-semibold uppercase tracking-wide text-teal-600 dark:text-teal-400">
<p className="relative z-10 mt-1 text-xs font-semibold uppercase tracking-wide text-teal-600 dark:text-teal-400">
AI / agents
</p>
) : null}
<h2 className="mt-2 text-base font-semibold text-zinc-800 dark:text-zinc-100">
<Card.Link href={project.href}>{project.name}</Card.Link>
<h2 className="relative z-10 mt-3 text-base font-semibold tracking-tight text-zinc-800 dark:text-zinc-100">
{project.name}
</h2>
<Card.Description>{project.description}</Card.Description>
<p className="relative z-10 mt-6 flex text-sm font-medium text-zinc-400 transition group-hover:text-teal-500 dark:text-zinc-200">
<LinkIcon className="h-6 w-6 flex-none" />
<span className="ml-2">{project.label}</span>
</p>
<ProjectOutboundLinks
github={project.github}
npm={project.npm}
live={project.live}
className="mt-6"
/>
</Card>
))}
</ul>
Expand Down
72 changes: 72 additions & 0 deletions personal-site/src/components/ProjectOutboundLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import Link from 'next/link'
import clsx from 'clsx'

import { GitHubIcon, GlobeIcon, NpmIcon } from '@/components/SocialIcons'

export type ProjectOutbound = {
github: string | null
npm: string | null
live: string | null
}

const base =
'relative z-10 flex h-10 w-10 items-center justify-center rounded-full border border-zinc-200/80 bg-white shadow-sm transition dark:border-zinc-600 dark:bg-zinc-800'

const hoverTeal =
'hover:border-teal-500/50 hover:text-teal-600 dark:hover:border-teal-500/50 dark:hover:text-teal-400'

/**
* Up to three outbound links (GitHub, npm, live site) with icons — only renders anchors that are set.
*/
export function ProjectOutboundLinks({
github,
npm,
live,
className,
}: ProjectOutbound & { className?: string }) {
return (
<div className={clsx('flex flex-wrap items-center gap-2', className)}>
{github ? (
<Link
href={github}
target="_blank"
rel="noopener noreferrer"
className={clsx(
base,
'text-zinc-600 dark:text-zinc-300',
hoverTeal,
'hover:[&_path]:fill-teal-600 dark:hover:[&_path]:fill-teal-400',
)}
aria-label="View source on GitHub"
>
<GitHubIcon className="h-5 w-5 fill-current" />
</Link>
) : null}
{npm ? (
<Link
href={npm}
target="_blank"
rel="noopener noreferrer"
className={clsx(
base,
'border-zinc-200/80 text-[#CB3837] hover:border-[#CB3837]/40 hover:text-[#a12c2c] dark:border-zinc-600',
)}
aria-label="View package on npm"
>
<NpmIcon className="h-5 w-5" />
</Link>
) : null}
{live ? (
<Link
href={live}
target="_blank"
rel="noopener noreferrer"
className={clsx(base, 'text-zinc-600 dark:text-zinc-300', hoverTeal)}
aria-label="Open live site or docs"
>
<GlobeIcon className="h-5 w-5" />
</Link>
) : null}
</div>
)
}
30 changes: 30 additions & 0 deletions personal-site/src/components/SocialIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,36 @@ export function GitHubIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
)
}

/** npm registry mark (simplified logo) */
export function NpmIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...props}>
<path
fill="currentColor"
d="M1.763 11.925C1.763 6.741 6.744 1.763 12.04 1.763c5.295 0 10.277 4.978 10.277 10.162 0 5.183-4.982 10.162-10.277 10.162-5.296 0-10.278-4.979-10.278-10.162zm4.879 8.046V2.758h7.355v17.213H6.642zm7.355 0h3.503V6.324h-3.503v13.647zm3.503-14.59h3.502V2.758H17.5v3.624zm0 3.624h3.502v10.023H17.5V6.324z"
/>
</svg>
)
}

/** Live site / docs (globe) */
export function GlobeIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
strokeWidth={1.5}
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
{...props}
>
<path d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .963-.15 1.89-.43 2.76" />
</svg>
)
}

export function LinkedInIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" {...props}>
Expand Down
Loading
Loading