-
+
+
+
© {new Date().getFullYear()} Tyler Koenig
-
+
diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx
index 0d625635..449dd74c 100644
--- a/src/components/Hero.tsx
+++ b/src/components/Hero.tsx
@@ -2,61 +2,74 @@ import Image from "next/image";
export default function Hero() {
return (
-
-
+
+ {/* Section header */}
+
+
+
-
-
-
-
- Tyler Koenig
-
-
- SOC Helpdesk I by day, building beyond the title
+
+
+
+ Tyler Koenig
+
+
+ SOC Helpdesk I · Homelab Operator
+
+
+
+
+ I write software and run infrastructure that goes well past what my
+ job title implies. Games, AI tooling, mobile apps, and a homelab
+ running 20+ self-hosted services on segmented VLANs. Continuously
+ learning by building things that actually work.
-
-
- I write software and run infrastructure that goes well past what my job
- title implies. Games, AI tooling, mobile apps, and a homelab running
- 20+ self-hosted services on segmented VLANs. Continuously learning
- by building things that actually work.
-
-
-
diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx
index b22978a1..5fe893ac 100644
--- a/src/components/Nav.tsx
+++ b/src/components/Nav.tsx
@@ -3,48 +3,45 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
-
const links = [
- { href: "/", label: "Home" },
- { href: "/homelab/", label: "Homelab" },
- { href: "/archive/", label: "Archive" },
+ { href: "/", label: "home" },
+ { href: "/homelab/", label: "homelab" },
+ { href: "/archive/", label: "archive" },
];
export default function Nav() {
const pathname = usePathname();
return (
-
-
+
+
tk
-
-
- {links.map(({ href, label }) => {
- const active = pathname === href || pathname === href.replace(/\/$/, "");
- return (
-
-
- {label}
-
-
- );
- })}
-
-
-
+
+ {links.map(({ href, label }) => {
+ const active =
+ pathname === href || pathname === href.replace(/\/$/, "");
+ return (
+
+
+ {label}
+
+
+ );
+ })}
+
);
diff --git a/src/components/ProjectCard.tsx b/src/components/ProjectCard.tsx
index 788fbaa3..cc42fcdf 100644
--- a/src/components/ProjectCard.tsx
+++ b/src/components/ProjectCard.tsx
@@ -2,67 +2,48 @@ import type { Project } from "@/data/projects";
type Props = {
project: Project;
- reversed?: boolean;
};
-export default function ProjectCard({ project, reversed = false }: Props) {
+export default function ProjectCard({ project }: Props) {
return (
-
- {/* Gradient image tile */}
-
-
+
-
-
- {/* Content */}
-
- {/* Animated accent bar */}
-
-
-
+ {project.title}
+
+
+ {project.stats && (
+
+ {project.stats}
+
+ )}
- {project.title}
+ ↗
- {project.stats && (
-
- {project.stats}
-
- )}
+
-
- {project.description}
-
+
+ {project.description}
+
-
- {project.tags.map((tag) => (
-
- {tag}
-
- ))}
-
+
+ {project.tags.map((tag) => (
+
+ {tag}
+
+ ))}
);
diff --git a/src/components/Skills.tsx b/src/components/Skills.tsx
index 05394a0f..f176c3e0 100644
--- a/src/components/Skills.tsx
+++ b/src/components/Skills.tsx
@@ -1,11 +1,13 @@
+import Widget from "@/components/Widget";
+
const skillGroups = [
{
label: "Languages",
skills: ["JavaScript", "TypeScript", "HTML", "CSS"],
},
{
- label: "Frontend & Mobile",
- skills: ["React", "React Native", "Expo", "Next.js", "Three.js", "Responsive Design"],
+ label: "Frontend",
+ skills: ["React", "React Native", "Expo", "Next.js", "Three.js"],
},
{
label: "Desktop & Tools",
@@ -21,34 +23,28 @@ const skillGroups = [
},
];
+const totalCount = skillGroups.reduce((n, g) => n + g.skills.length, 0);
+
export default function Skills() {
return (
-
-
- Skills
-
-
- {skillGroups.map(({ label, skills }) => (
-
-
+
+
+ {skillGroups.map(({ label, skills }, i) => (
+
+
{label}
-
- {skills.map((skill) => (
-
- {skill}
-
- ))}
-
+
+ {skills.join(" · ")}
+
))}
-
+
);
}
diff --git a/src/components/Widget.tsx b/src/components/Widget.tsx
new file mode 100644
index 00000000..c3e84071
--- /dev/null
+++ b/src/components/Widget.tsx
@@ -0,0 +1,39 @@
+type WidgetProps = {
+ title: string;
+ badge?: string | number;
+ meta?: string;
+ as?: "section" | "div" | "article";
+ className?: string;
+ children: React.ReactNode;
+};
+
+export default function Widget({
+ title,
+ badge,
+ meta,
+ as: Tag = "section",
+ className,
+ children,
+}: WidgetProps) {
+ return (
+
+
+
+ {title}
+
+ {meta && (
+
+ {meta}
+
+ )}
+
+ {badge !== undefined && (
+
+ {badge}
+
+ )}
+
+ {children}
+
+ );
+}
diff --git a/src/data/projects.ts b/src/data/projects.ts
index 4fa8ffde..8d6e3192 100644
--- a/src/data/projects.ts
+++ b/src/data/projects.ts
@@ -4,9 +4,9 @@ export type Project = {
description: string;
tags: string[];
githubUrl: string;
- gradient: string; // Tailwind gradient classes for placeholder image tile
tier: "featured" | "archive";
stats?: string;
+ year?: number;
};
export const projects: Project[] = [
@@ -18,7 +18,6 @@ export const projects: Project[] = [
"Offline-first mobile app for tracking golf rounds, managing your 14-club bag, and getting AI-powered club recommendations from a Smart Caddie. Covers 7 shot types per hole with full scorecard history.",
tags: ["React Native", "Expo", "Zustand", "AI", "Mobile"],
githubUrl: "https://github.com/lerko96/golf-book-mobile",
- gradient: "from-[var(--color-green-darkest)] via-[var(--color-bg)] to-[var(--color-bg-deep)]",
tier: "featured",
stats: "211 commits",
},
@@ -29,7 +28,6 @@ export const projects: Project[] = [
"Cross-platform desktop AI chat app for developers. Supports OpenAI, Anthropic Claude, and Google Gemini in a single interface with real-time cost tracking, conversation export, and automatic code explanation.",
tags: ["Electron", "Node.js", "OpenAI", "Claude", "Gemini"],
githubUrl: "https://github.com/lerko96/plaiground",
- gradient: "from-[var(--color-green-darker)] via-[var(--color-surface)] to-[var(--color-bg-deep)]",
tier: "featured",
},
{
@@ -39,7 +37,6 @@ export const projects: Project[] = [
"Web dashboard for tracking uptime across multiple services with 30-second polling, status history visualization, JWT-authenticated API, and Docker + nginx deployment.",
tags: ["React 18", "Vite", "Express", "SQLite", "Docker", "JWT"],
githubUrl: "https://github.com/lerko96/service-monitor",
- gradient: "from-[var(--color-bg)] via-[var(--color-green-darkest)] to-[var(--color-bg-deep)]",
tier: "featured",
},
{
@@ -49,7 +46,6 @@ export const projects: Project[] = [
"3D visualization platform for exploring and organizing thoughts using a radio-tuning metaphor. Filter ideas by frequency and bandwidth in an instanced Three.js scene with persistent local storage.",
tags: ["React", "TypeScript", "Three.js", "React Three Fiber", "Zustand"],
githubUrl: "https://github.com/lerko96/tht-1.2",
- gradient: "from-[var(--color-surface)] via-[var(--color-green-darkest)] to-[var(--color-bg-deep)]",
tier: "featured",
},
@@ -61,8 +57,8 @@ export const projects: Project[] = [
"Chrome extension (Manifest V3) that captures entire Twitter/X threads and exports them as HTML, Markdown, PDF, or image — with metadata preservation and preview before export.",
tags: ["Chrome Extension", "Manifest V3", "JavaScript", "jsPDF"],
githubUrl: "https://github.com/lerko96/twitter-thread-ext",
- gradient: "from-[var(--color-bg)] to-[var(--color-bg-deep)]",
tier: "archive",
+ year: 2023,
},
{
slug: "notes-app-1.0",
@@ -71,8 +67,8 @@ export const projects: Project[] = [
"Lightweight canvas drawing app with color picker, adjustable brush size, and PNG export. Runs in the browser, no dependencies.",
tags: ["HTML5 Canvas", "JavaScript", "CSS"],
githubUrl: "https://github.com/lerko96/notes-app-1.0",
- gradient: "from-[var(--color-bg)] to-[var(--color-bg-deep)]",
tier: "archive",
+ year: 2022,
},
{
slug: "were-hooked",
@@ -81,8 +77,8 @@ export const projects: Project[] = [
"Fishing location discovery app built as a team of 5 during bootcamp. Java/Spring MVC backend with Thymeleaf templates.",
tags: ["Java", "Spring", "Thymeleaf", "HTML", "CSS"],
githubUrl: "https://github.com/lerko96/were-hooked-repo",
- gradient: "from-[var(--color-bg)] to-[var(--color-bg-deep)]",
tier: "archive",
+ year: 2022,
},
{
slug: "mystery-educator",
@@ -91,8 +87,8 @@ export const projects: Project[] = [
"Single-page app mashup of the MET Museum and NASA public APIs. Built as a team of 4 during bootcamp.",
tags: ["JavaScript", "REST APIs", "HTML", "CSS"],
githubUrl: "https://github.com/lerko96/mystery-educator",
- gradient: "from-[var(--color-bg)] to-[var(--color-bg-deep)]",
tier: "archive",
+ year: 2022,
},
];