diff --git a/README.md b/README.md
index 13055ee..3fb9a0e 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
## About
-Astro portfolio with a static export for GitHub Pages. It fetches public, non-fork repositories from the GitHub GraphQL API at build time, resolves live GitHub Pages links through the REST API, and renders them in a terminal-themed UI.
+Astro portfolio with a static export for GitHub Pages. It fetches the repositories pinned on Jonathan's GitHub profile plus public, non-fork repositories from the GitHub GraphQL API at build time, resolves live GitHub Pages links through the REST API, and renders them in a terminal-themed UI.
The site includes a print-optimized resume page, SEO metadata, analytics, and a Konami code easter egg. The same shared data powers the on-page resume and the dedicated `/resume` route.
@@ -29,7 +29,8 @@ It is built to stay simple to deploy: build locally, export statically, and publ
## Features
-- Dynamic Workbench repository ledger from GitHub GraphQL API (public, non-fork repos)
+- Workbench major cards sourced from GitHub profile pinned repositories
+- Dynamic "Other GitHub repos" ledger from GitHub GraphQL API (public, non-fork repos)
- Live GitHub Pages links resolved at build time via GitHub REST API
- Terminal-themed dark UI with typing animations and scroll effects
- Print-optimized resume page with download support
diff --git a/src/components/Portfolio.tsx b/src/components/Portfolio.tsx
index ea0c45b..3758646 100644
--- a/src/components/Portfolio.tsx
+++ b/src/components/Portfolio.tsx
@@ -5,7 +5,6 @@ import {
AVAILABILITY,
ENGINEERING_PRINCIPLES,
EXPERIENCES,
- FEATURED_PROJECTS,
OPERATING_SIGNALS,
PROFILE,
SKILLS,
@@ -151,18 +150,18 @@ const SKILL_GROUPS: Array<{ key: keyof typeof SKILLS; label: string; path: strin
{ key: "frontend", label: "interface", path: "/stack/interface" },
];
-function projectLane(project: (typeof FEATURED_PROJECTS)[number]) {
- const text = `${project.name} ${project.tags.join(" ")}`.toLowerCase();
+function projectLane(project: GitHubRepo) {
+ const text = `${project.title} ${project.description} ${project.lang}`.toLowerCase();
if (text.includes("rinha") || text.includes("k6") || text.includes("performance")) return "load path";
- if (text.includes("clean") || text.includes(".net")) return "system design";
- if (text.includes("game") || text.includes("sdl") || text.includes("lynx")) return "runtime lab";
+ if (text.includes("clean") || text.includes(".net") || text.includes("blazor")) return "system design";
+ if (text.includes("game") || text.includes("sdl") || text.includes("lynx") || text.includes("mango")) return "runtime lab";
return "field note";
}
export default function Portfolio({ projects }: { projects: GitHubRepo[] }) {
const scrollProgress = useScrollProgress();
- const featuredSlugs = useMemo(() => new Set(FEATURED_PROJECTS.map((fp) => fp.slug)), []);
- const workbenchRepos = useMemo(() => projects.filter((project) => !featuredSlugs.has(project.title)), [featuredSlugs, projects]);
+ const pinnedRepos = useMemo(() => projects.filter((project) => project.pinned), [projects]);
+ const otherRepos = useMemo(() => projects.filter((project) => !project.pinned), [projects]);
const [termOpen, setTermOpen] = useState(false);
const [termInput, setTermInput] = useState("");
@@ -378,19 +377,24 @@ export default function Portfolio({ projects }: { projects: GitHubRepo[] }) {
Workbench
- {FEATURED_PROJECTS.map((project, index) => (
-
+ {pinnedRepos.map((project, index) => (
+
- {project.name}
- {project.description}
- {project.tags.map((tag) => {tag})}
+ {project.title}
+ {project.description || "Repository note pending."}
+
+ {project.stars > 0 && {project.stars} stars}
+ {project.lang && {project.lang}}
+ Pinned on GitHub
+
@@ -400,12 +404,12 @@ export default function Portfolio({ projects }: { projects: GitHubRepo[] }) {
public repository ledger
-
All non-fork GitHub work
-
Fetched at build time from GitHub, excluding this portfolio, profile metadata, collaborator repos, and forks. Pages links resolve from each repository's live GitHub Pages site.
+
Other GitHub repos
+
Fetched at build time from GitHub, excluding pinned Workbench repos, this portfolio, profile metadata, collaborator repos, and forks. Pages links resolve from each repository's live GitHub Pages site.
- {workbenchRepos.map((project, index) => (
+ {otherRepos.map((project, index) => (
{project.title}
@@ -420,7 +424,7 @@ export default function Portfolio({ projects }: { projects: GitHubRepo[] }) {
))}
-
+
View all repositories on GitHub
diff --git a/src/lib/github.ts b/src/lib/github.ts
index 8c9279c..0bb3614 100644
--- a/src/lib/github.ts
+++ b/src/lib/github.ts
@@ -8,24 +8,39 @@ export type GitHubRepo = {
homepageUrl?: string;
pagesUrl?: string;
updatedAt?: string;
+ pinned?: boolean;
};
const GITHUB_OWNER = "jonathanperis";
const EXCLUDE_REPOS = new Set(["jonathanperis.github.io", ".github", "jonathanperis"]);
const FALLBACK: GitHubRepo[] = [
- { title: "cpnucleo", description: "Modern .NET sample — clean architecture, testing, DI, and Docker containerization.", url: "https://github.com/jonathanperis/cpnucleo", lang: "C#", langColor: "#178600", stars: 8, homepageUrl: "https://jonathanperis.github.io/cpnucleo/", pagesUrl: "https://jonathanperis.github.io/cpnucleo/" },
- { title: "super-mango-editor", description: "A classic side-scrolling platformer built with C and SDL2 — playable in the browser via WebAssembly.", url: "https://github.com/jonathanperis/super-mango-editor", lang: "C", langColor: "#555555", stars: 0, homepageUrl: "https://jonathanperis.github.io/super-mango-editor/", pagesUrl: "https://jonathanperis.github.io/super-mango-editor/" },
- { title: "rinha4-back-end-dotnet", description: "Rinha de Backend 2025 implementation in .NET with docs and benchmark reports.", url: "https://github.com/jonathanperis/rinha4-back-end-dotnet", lang: "C#", langColor: "#178600", stars: 0, homepageUrl: "https://jonathanperis.github.io/rinha4-back-end-dotnet/", pagesUrl: "https://jonathanperis.github.io/rinha4-back-end-dotnet/" },
- { title: "rinha2-back-end-dotnet", description: "High-performance Rinha de Backend challenge in C# with PostgreSQL and Nginx.", url: "https://github.com/jonathanperis/rinha2-back-end-dotnet", lang: "C#", langColor: "#178600", stars: 3, homepageUrl: "https://jonathanperis.github.io/rinha2-back-end-dotnet/", pagesUrl: "https://jonathanperis.github.io/rinha2-back-end-dotnet/" },
- { title: "rinha2-back-end-k6", description: "K6 load testing suite for the Rinha de Backend challenge.", url: "https://github.com/jonathanperis/rinha2-back-end-k6", lang: "JavaScript", langColor: "#f1e05a", stars: 0, homepageUrl: "https://jonathanperis.github.io/rinha2-back-end-k6/", pagesUrl: "https://jonathanperis.github.io/rinha2-back-end-k6/" },
- { title: "blazor-mudblazor-starter", description: "Blazor + MudBlazor starter template with pre-configured components.", url: "https://github.com/jonathanperis/blazor-mudblazor-starter", lang: "HTML", langColor: "#e34c26", stars: 1, homepageUrl: "https://jonathanperis.github.io/blazor-mudblazor-starter/", pagesUrl: "https://jonathanperis.github.io/blazor-mudblazor-starter/" },
+ { title: "cpnucleo", description: "Modern .NET sample — clean architecture, testing, DI, and Docker containerization.", url: "https://github.com/jonathanperis/cpnucleo", lang: "C#", langColor: "#178600", stars: 8, homepageUrl: "https://jonathanperis.github.io/cpnucleo/", pagesUrl: "https://jonathanperis.github.io/cpnucleo/", pinned: true },
+ { title: "super-mango-editor", description: "A classic side-scrolling platformer built with C and SDL2 — playable in the browser via WebAssembly.", url: "https://github.com/jonathanperis/super-mango-editor", lang: "C", langColor: "#555555", stars: 0, homepageUrl: "https://jonathanperis.github.io/super-mango-editor/", pagesUrl: "https://jonathanperis.github.io/super-mango-editor/", pinned: true },
+ { title: "rinha2-back-end-dotnet", description: "High-performance Rinha de Backend challenge in C# with PostgreSQL and Nginx.", url: "https://github.com/jonathanperis/rinha2-back-end-dotnet", lang: "C#", langColor: "#178600", stars: 3, homepageUrl: "https://jonathanperis.github.io/rinha2-back-end-dotnet/", pagesUrl: "https://jonathanperis.github.io/rinha2-back-end-dotnet/", pinned: true },
+ { title: "rinha2-back-end-go", description: "Rinha de Backend in Go — high-performance with PostgreSQL and Nginx.", url: "https://github.com/jonathanperis/rinha2-back-end-go", lang: "PLpgSQL", langColor: "#336790", stars: 1, homepageUrl: "https://jonathanperis.github.io/rinha2-back-end-go/", pagesUrl: "https://jonathanperis.github.io/rinha2-back-end-go/", pinned: true },
+ { title: "blazor-mudblazor-starter", description: "Blazor + MudBlazor starter template with pre-configured components.", url: "https://github.com/jonathanperis/blazor-mudblazor-starter", lang: "HTML", langColor: "#e34c26", stars: 1, homepageUrl: "https://jonathanperis.github.io/blazor-mudblazor-starter/", pagesUrl: "https://jonathanperis.github.io/blazor-mudblazor-starter/", pinned: true },
+ { title: "speedy-bird-lynx", description: "Lynx motion/game experiment in C++.", url: "https://github.com/jonathanperis/speedy-bird-lynx", lang: "C++", langColor: "#f34b7d", stars: 0, homepageUrl: "https://jonathanperis.github.io/speedy-bird-lynx/", pagesUrl: "https://jonathanperis.github.io/speedy-bird-lynx/", pinned: true },
{ title: "rinha4-back-end-c", description: "Rinha de Backend 2025 C implementation with GitHub Pages documentation.", url: "https://github.com/jonathanperis/rinha4-back-end-c", lang: "C", langColor: "#555555", stars: 0, homepageUrl: "https://jonathanperis.github.io/rinha4-back-end-c/", pagesUrl: "https://jonathanperis.github.io/rinha4-back-end-c/" },
- { title: "rinha2-back-end-go", description: "Rinha de Backend in Go — high-performance with PostgreSQL and Nginx.", url: "https://github.com/jonathanperis/rinha2-back-end-go", lang: "PLpgSQL", langColor: "#336790", stars: 1, homepageUrl: "https://jonathanperis.github.io/rinha2-back-end-go/", pagesUrl: "https://jonathanperis.github.io/rinha2-back-end-go/" },
+ { title: "rinha2-back-end-k6", description: "K6 load testing suite for the Rinha de Backend challenge.", url: "https://github.com/jonathanperis/rinha2-back-end-k6", lang: "JavaScript", langColor: "#f1e05a", stars: 0, homepageUrl: "https://jonathanperis.github.io/rinha2-back-end-k6/", pagesUrl: "https://jonathanperis.github.io/rinha2-back-end-k6/" },
];
const QUERY = `{
user(login: "${GITHUB_OWNER}") {
+ pinnedItems(first: 100, types: REPOSITORY) {
+ nodes {
+ ... on Repository {
+ name
+ description
+ url
+ homepageUrl
+ stargazerCount
+ updatedAt
+ owner { login }
+ primaryLanguage { name color }
+ }
+ }
+ }
repositories(first: 100, privacy: PUBLIC, orderBy: { field: UPDATED_AT, direction: DESC }, isFork: false) {
nodes {
name
@@ -84,7 +99,7 @@ async function fetchPagesUrl(repoName: string, token: string): Promise {
}
const json = await res.json();
+ const pinnedNodes = json?.data?.user?.pinnedItems?.nodes;
const nodes = json?.data?.user?.repositories?.nodes;
if (!Array.isArray(nodes) || nodes.length === 0) {
@@ -130,9 +147,21 @@ export async function fetchRepos(): Promise {
return FALLBACK;
}
+ const pinnedOrder = new Map(
+ Array.isArray(pinnedNodes)
+ ? pinnedNodes
+ .filter((n: RepoNode) => n.owner.login === GITHUB_OWNER && !EXCLUDE_REPOS.has(n.name))
+ .map((n: RepoNode, index: number) => [n.name, index])
+ : [],
+ );
+
const repos = nodes
.filter((n: RepoNode) => n.owner.login === GITHUB_OWNER && !EXCLUDE_REPOS.has(n.name))
- .map(normalizeRepo);
+ .map((n: RepoNode) => normalizeRepo(n, pinnedOrder.has(n.name)))
+ .sort((a, b) => {
+ if (a.pinned && b.pinned) return (pinnedOrder.get(a.title) ?? 0) - (pinnedOrder.get(b.title) ?? 0);
+ return Number(b.pinned) - Number(a.pinned);
+ });
const pagesUrls = await Promise.all(repos.map((repo) => fetchPagesUrl(repo.title, token)));
diff --git a/wiki/dynamic_projects.md b/wiki/dynamic_projects.md
index aa8a52d..0993bf5 100644
--- a/wiki/dynamic_projects.md
+++ b/wiki/dynamic_projects.md
@@ -2,18 +2,25 @@
## How It Works
-The Workbench repository ledger is dynamic. During the Astro build, `src/lib/github.ts` fetches Jonathan's owned public, non-fork repositories from GitHub, excludes profile/portfolio metadata repos, and enriches each row with its live GitHub Pages URL when Pages is enabled.
+The Workbench repository data is dynamic. During the Astro build, `src/lib/github.ts` fetches Jonathan's GitHub profile pinned repositories plus owned public, non-fork repositories from GitHub, excludes profile/portfolio metadata repos, and enriches each entry with its live GitHub Pages URL when Pages is enabled.
-Featured project cards remain curated in `src/lib/data.ts` so the top of the Workbench can emphasize the strongest portfolio examples. The dynamic ledger lists the remaining repositories below those cards.
+The major Workbench cards are the repositories currently pinned on the `jonathanperis` GitHub profile. The ledger below them is labeled "Other GitHub repos" and lists the remaining owned public, non-fork repositories.
## Data Flow
1. `src/pages/index.astro` calls `fetchRepos()` from `src/lib/github.ts`.
-2. `github.ts` sends a GraphQL query to GitHub for public repositories ordered by recent update:
+2. `github.ts` sends a GraphQL query to GitHub for pinned repository items and public repositories ordered by recent update:
```graphql
{
user(login: "jonathanperis") {
+ pinnedItems(first: 100, types: REPOSITORY) {
+ nodes {
+ ... on Repository {
+ name
+ }
+ }
+ }
repositories(first: 100, privacy: PUBLIC, orderBy: { field: UPDATED_AT, direction: DESC }, isFork: false) {
nodes {
name
@@ -29,9 +36,10 @@ Featured project cards remain curated in `src/lib/data.ts` so the top of the Wor
}
```
-3. For each included repository, `github.ts` also checks the REST Pages endpoint: `GET /repos/jonathanperis/{repo}/pages`.
-4. The response is mapped to `GitHubRepo[]` and passed to the React `Portfolio` component.
-5. At build time, this data is baked into the static HTML.
+3. The mapper marks repos that appear in `pinnedItems` as `pinned: true` and preserves the GitHub profile pinned order for the major cards.
+4. For each included repository, `github.ts` also checks the REST Pages endpoint: `GET /repos/jonathanperis/{repo}/pages`.
+5. The response is mapped to `GitHubRepo[]` and passed to the React `Portfolio` component.
+6. At build time, this data is baked into the static HTML.
## Filtering
@@ -43,7 +51,7 @@ The code also excludes repositories that should not appear in the public Workben
- `.github` — organization/profile metadata
- `jonathanperis` — profile/readme metadata
-Featured project slugs from `FEATURED_PROJECTS` are removed from the ledger so they are not duplicated below the curated cards.
+Pinned repositories are removed from the "Other GitHub repos" ledger so they are not duplicated below the major Workbench cards.
## GitHub Pages Links
diff --git a/wiki/index.md b/wiki/index.md
index 028c475..2c30c11 100644
--- a/wiki/index.md
+++ b/wiki/index.md
@@ -8,7 +8,7 @@ Personal developer portfolio for **Jonathan Peris** — Software Engineer with 1
- Developer-themed dark UI with terminal aesthetic
- Typing role animation, scroll animations, progress bar
-- Dynamic Workbench repository ledger fetched at build time via GitHub GraphQL + Pages REST APIs
+- Dynamic Workbench pinned-repo cards and "Other GitHub repos" ledger fetched at build time via GitHub GraphQL + Pages REST APIs
- Print-optimized resume page generated from shared data (`/resume`)
- Interactive terminal easter egg (Konami code)
- SEO optimized: JSON-LD, sitemap, robots.txt, Open Graph, Twitter cards