rebuild portfolio: Next.js 16, React 19, Tailwind v4, homelab page, CI/CD
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s

This commit is contained in:
lerko96
2026-04-12 18:52:54 -04:00
parent 798027bb9d
commit 05a32492ac
61 changed files with 8078 additions and 19882 deletions

View File

@@ -1,35 +0,0 @@
import React from 'react';
const Footer = () => (
<footer>
<span class='copyright'>&copy; 2021 Tyler Koenig</span>
<div class='foot__links'>
<a
href='https://github.com/lerko96'
rel='noreferrer'
target='_blank'
title='github'
>
<i class='fa fa-github fa-2x' aria-hidden='true'></i>
</a>
<a
href='https://www.linkedin.com/in/tyler-koenig-72607a18b/'
rel='noreferrer'
target='_blank'
title='LinkedIn'
>
<i class='fa fa-linkedin-square fa-2x' aria-hidden='true'></i>
</a>
<a
href='mailto:tylerkng96@icloud.com'
rel='noreferrer'
target='_blank'
title='email'
>
<i class='fa fa-envelope-o fa-2x' aria-hidden='true'></i>
</a>
</div>
</footer>
);
export default Footer;

38
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,38 @@
export default function Footer() {
return (
<footer className="border-t border-[var(--color-grey-1)] py-8 mt-16">
<div className="max-w-5xl mx-auto px-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<p className="font-mono text-xs text-[var(--color-grey-2)] tracking-widest">
&copy; {new Date().getFullYear()} Tyler Koenig
</p>
<div className="flex items-center gap-5">
<a
href="https://github.com/lerko96"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub"
className="text-[var(--color-grey-2)] hover:text-[var(--color-green)] transition-colors"
>
<i className="fab fa-github text-lg" aria-hidden="true" />
</a>
<a
href="https://www.linkedin.com/in/tyler-koenig"
target="_blank"
rel="noopener noreferrer"
aria-label="LinkedIn"
className="text-[var(--color-grey-2)] hover:text-[var(--color-green)] transition-colors"
>
<i className="fab fa-linkedin text-lg" aria-hidden="true" />
</a>
<a
href="mailto:tylerkoenig96@gmail.com"
aria-label="Email"
className="text-[var(--color-grey-2)] hover:text-[var(--color-green)] transition-colors"
>
<i className="fas fa-envelope text-lg" aria-hidden="true" />
</a>
</div>
</div>
</footer>
);
}

64
src/components/Hero.tsx Normal file
View File

@@ -0,0 +1,64 @@
import Image from "next/image";
export default function Hero() {
return (
<section className="flex flex-col sm:flex-row items-center sm:items-start gap-8 mb-20">
<div className="shrink-0">
<Image
src="/images/headshot-tyler_koenig.png"
alt="Tyler Koenig"
width={120}
height={120}
className="rounded-full border-2 border-[var(--color-green-darker)]"
priority
/>
</div>
<div className="flex flex-col gap-4 text-center sm:text-left">
<div>
<h1 className="font-mono text-2xl font-bold text-[var(--color-text-light)] tracking-wide">
Tyler Koenig
</h1>
<p className="font-mono text-sm text-[var(--color-green)] tracking-widest uppercase mt-1">
SOC Helpdesk I by day, building beyond the title
</p>
</div>
<p className="text-[var(--color-grey-3)] text-sm leading-relaxed max-w-lg">
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.
</p>
<div className="flex items-center gap-5 justify-center sm:justify-start">
<a
href="https://github.com/lerko96"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub"
className="text-[var(--color-grey-2)] hover:text-[var(--color-green)] transition-colors"
>
<i className="fab fa-github text-xl" aria-hidden="true" />
</a>
<a
href="https://www.linkedin.com/in/tyler-koenig"
target="_blank"
rel="noopener noreferrer"
aria-label="LinkedIn"
className="text-[var(--color-grey-2)] hover:text-[var(--color-green)] transition-colors"
>
<i className="fab fa-linkedin text-xl" aria-hidden="true" />
</a>
<a
href="mailto:tylerkoenig96@gmail.com"
aria-label="Email"
className="text-[var(--color-grey-2)] hover:text-[var(--color-green)] transition-colors"
>
<i className="fas fa-envelope text-xl" aria-hidden="true" />
</a>
</div>
</div>
</section>
);
}

View File

@@ -1,42 +0,0 @@
import React from 'react';
const Nav = () => (
<header>
<nav>
<div id='logo'>
<a href='index.html'>tk</a>
</div>
<div id='nav__list'>
<ul>
{/* <li id='nav__contact'>
<a href='#contact' target='_self'>
CONTACT
</a>
</li> */}
<li>
<a href='#profile' target='_self'>
PROFILE
</a>
</li>
<li>
<a href='#projects' target='_self'>
PROJECTS
</a>
</li>
{/* <li>
<button onClick={() => setDarkMode(!darkMode)}>
Toggle Dark Mode
</button>
</li> */}
{/* <li>
<a href='#skills' target='_self'>
SKILLS
</a>
</li> */}
</ul>
</div>
</nav>
</header>
);
export default Nav;

51
src/components/Nav.tsx Normal file
View File

@@ -0,0 +1,51 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
const links = [
{ href: "/", label: "Home" },
{ href: "/homelab/", label: "Homelab" },
{ href: "/archive/", label: "Archive" },
];
export default function Nav() {
const pathname = usePathname();
return (
<header className="sticky top-0 z-50 bg-[var(--color-bg-deep)] border-b border-[var(--color-grey-1)]">
<nav className="max-w-5xl mx-auto px-6 h-14 flex items-center justify-between">
<Link
href="/"
className="font-mono text-xl font-bold text-[var(--color-green)] tracking-widest hover:opacity-80 transition-opacity"
>
tk
</Link>
<div className="flex items-center gap-6">
<ul className="flex gap-6">
{links.map(({ href, label }) => {
const active = pathname === href || pathname === href.replace(/\/$/, "");
return (
<li key={href}>
<Link
href={href}
aria-current={active ? "page" : undefined}
className={`text-xs font-mono tracking-widest uppercase transition-colors ${
active
? "text-[var(--color-green)]"
: "text-[var(--color-grey-3)] hover:text-[var(--color-grey-4)]"
}`}
>
{label}
</Link>
</li>
);
})}
</ul>
</div>
</nav>
</header>
);
}

View File

@@ -1,96 +0,0 @@
import React from 'react';
import headshot from '../images/headshot-tyler_koenig.png';
// function Greet() {
// return <p>Hello TK</p>
// }
const Profile = () => (
<section id='profile'>
<article class='profile__card'>
<div class='card__img'>
<img id='headshot' src={headshot} alt='tyler' />
</div>
<div class='card__bio'>
<div class='bio__name'>
<a href='index.html'>TYLER KOENIG</a>
</div>
<div class='bio__desc'>
<p>SOFTWARE DEVELOPER</p>
</div>
<div class='bio__contacts' id='contact'>
<a
href='https://www.linkedin.com/in/tyler-koenig-72607a18b/'
rel='noreferrer'
target='_blank'
title='LinkedIn'
>
<i
class='fa fa-linkedin-square fa-2x'
aria-hidden='true'
></i>
</a>
<a
href='https://github.com/lerko96'
rel='noreferrer'
target='_blank'
title='github'
>
<i class='fa fa-github fa-2x' aria-hidden='true'></i>
</a>
<a
href='mailto:tylerkng96@icloud.com'
rel='noreferrer'
target='_blank'
title='email'
>
<i
class='fa fa-envelope-o fa-2x'
aria-hidden='true'
></i>
</a>
</div>
</div>
</article>
<article class='profile__about'>
<h2>about</h2>
<p>
Full-Stack Java Developer, with a focus on Front-End
Development. I graduated with an Associate of Arts from Lorain
County Community College in Spring of 2018. I began building
HTML, CSS and JavaScript projects by the Summer of 2020 from
courses provided on Udemy. I received my Certificate in Software
Development from We Can Code IT in Fall of 2021. Thanks to the
courses I've taken, I have developed strong skills needed for
working in remote team environments, and ones that utilize Scrum
and Agile practices. My passion comes from seeing ideas be
brought to life. Let's get to work.
</p>
</article>
<article class='profile__skills' id='skills'>
<h2>skills</h2>
<ul>
<li>Java</li>
<li>Spring</li>
<li>MVC</li>
<li>JavaScript</li>
<li>JSON</li>
<li>Restful APIs</li>
<li>Test Driven Development</li>
<li>Relational Databases</li>
<li>Git</li>
<li>Agile/ Scrum</li>
<li>HTML</li>
<li>CSS</li>
<li>SCSS</li>
<li>React</li>
<li>Responsive Design</li>
<li>Thymeleaf</li>
{/* <li>Object Oriented Programming</li> */}
</ul>
</article>
</section>
);
export default Profile;

View File

@@ -0,0 +1,69 @@
import type { Project } from "@/data/projects";
type Props = {
project: Project;
reversed?: boolean;
};
export default function ProjectCard({ project, reversed = false }: Props) {
return (
<article className={`group flex flex-col ${reversed ? "sm:flex-row-reverse" : "sm:flex-row"} gap-6 mb-16`}>
{/* Gradient image tile */}
<a
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="shrink-0 sm:w-56 h-36 rounded-lg overflow-hidden"
aria-label={`View ${project.title} on GitHub`}
tabIndex={-1}
>
<div
className={`w-full h-full bg-gradient-to-br ${project.gradient} flex items-center justify-center transition-transform duration-300 group-hover:scale-105`}
>
<span className="font-mono text-xs text-[var(--color-green)] opacity-60 tracking-widest">
{project.slug}
</span>
</div>
</a>
{/* Content */}
<div className={`flex flex-col justify-center gap-3 ${reversed ? "sm:text-right sm:items-end" : ""}`}>
{/* Animated accent bar */}
<div
className={`h-0.5 w-8 bg-[var(--color-grey-1)] rounded-full transition-all duration-300 group-hover:w-16 group-hover:bg-[var(--color-green-darker)] ${reversed ? "self-end" : ""}`}
/>
<div>
<a
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="font-mono text-base font-semibold text-[var(--color-text-light)] hover:text-[var(--color-green)] transition-colors"
>
{project.title}
</a>
{project.stats && (
<span className="font-mono text-xs text-[var(--color-green)] ml-3 opacity-70">
{project.stats}
</span>
)}
</div>
<p className="text-sm text-[var(--color-grey-3)] leading-relaxed max-w-md">
{project.description}
</p>
<div className={`flex flex-wrap gap-2 ${reversed ? "justify-end" : ""}`}>
{project.tags.map((tag) => (
<span
key={tag}
className="font-mono text-xs px-2 py-0.5 border border-[var(--color-grey-1)] text-[var(--color-grey-2)] rounded"
>
{tag}
</span>
))}
</div>
</div>
</article>
);
}

View File

@@ -1,287 +0,0 @@
import React from 'react';
import wereHooked from '../images/were-hooked.png';
import donutClicker from '../images/donut-clicker.png';
import mysteryEducator from '../images/mystery-educator.png';
import trekkingSite from '../images/trek.png';
// import reviewSite from '../images/review-site.png';
const Projects = () => (
<section id='projects'>
<h2>projects</h2>
<article class='project'>
<a
class='project__img_link'
href='https://github.com/lerko96/were-hooked-repo'
rel='noreferrer'
target='_blank'
>
<img
src={wereHooked}
class='project__img'
alt='were-hooked-img'
/>
</a>
<div class='project__text'>
<h3 class='project__title'>
<a
href='https://github.com/lerko96/were-hooked-repo'
rel='noreferrer'
target='_blank'
>
We're Hooked
</a>
</h3>
<div class='project__subtitle_small'>
<time datetime='2021-08-20 12:00:00'>
<i class='fa fa-calendar mr-2'></i>Fri, August 20th 2021
</time>
</div>
<div class='project__bar'></div>
<div class='project__info'>
<p>
Designed, built and tested an MVC application that
allows users to discover fishing locations in Ohio, as
well as teach beginners how to get started fishing and
the current regulations that are needed to follow. This
project was built between a team of five in a completely
remote environment with the help of Github, Zoom and
Slack.
</p>
</div>
<ul class='project__tagbox'>
<li class='tag__item'>Java</li>
<li class='tag__item'>Spring</li>
<li class='tag__item'>JavaScript</li>
<li class='tag__item'>Restful API</li>
<li class='tag__item'>Thymeleaf</li>
<li class='tag__item'>HTML</li>
<li class='tag__item'>CSS</li>
<li class='tag__item'>Responsive Design</li>
{/* <li class='tag__item'>TDD</li> */}
{/* <li class='tag__item'>VS Code</li> */}
<li class='tag__item'>Git</li>
<li class='tag__item'>Agile</li>
<li class='tag__item'>Scrum</li>
<li class='tag__item'>Zoom</li>
<li class='tag__item'>Slack</li>
</ul>
</div>
</article>
<article class='project'>
<a
class='project__img_link'
href='https://github.com/lerko96/mystery-educator'
rel='noreferrer'
target='_blank'
>
<img class='project__img' src={mysteryEducator} alt='' />
</a>
<div class='project__text'>
<h3 class='project__title'>
<a
href='https://github.com/lerko96/mystery-educator'
rel='noreferrer'
target='_blank'
>
Mystery Educator
</a>
</h3>
<div class='project__subtitle_small'>
<time datetime='2021-07-23 12:00:00'>
<i class='fas fa-calendar-alt mr-2'></i>Fri, July 23rd
2021
</time>
</div>
<div class='project__bar'></div>
<div class='project__info'>
<p>
Designed, built and tested a single page application
that renders unique data from the MET Museum and NASA
APIs each time you visit. We also built a local backend
database to store historical information. This project
was built between a team of four in a completely remote
environemnt with the help of Github, Zoom, and Slack.
</p>
</div>
<ul class='project__tagbox'>
<li class='tag__item'>JavaScript</li>
<li class='tag__item'>Node.js</li>
<li class='tag__item'>Spring</li>
<li class='tag__item'>RESTful APIs</li>
<li class='tag__item'>Java</li>
<li class='tag__item'>HTML</li>
<li class='tag__item'>CSS</li>
<li class='tag__item'>Responsive Design</li>
{/* <li class='tag__item'>TDD</li> */}
{/* <li class='tag__item'>VS Code</li> */}
<li class='tag__item'>Git</li>
<li class='tag__item'>Agile</li>
<li class='tag__item'>Scrum</li>
<li class='tag__item'>Zoom</li>
<li class='tag__item'>Slack</li>
</ul>
</div>
</article>
<article class='project'>
<a
class='project__img_link'
href='https://github.com/lerko96/donut-clicker-lerko96'
rel='noreferrer'
target='_blank'
>
<img
class='project__img'
src={donutClicker}
alt='donut-clicker-img'
/>
</a>
<div class='project__text'>
<h3 class='project__title'>
<a
href='https://github.com/lerko96/donut-clicker-lerko96'
rel='noreferrer'
target='_blank'
>
Donut Clicker
</a>
</h3>
<div class='project__subtitle_small'>
<time datetime='2021-07-09 12:00:00'>
<i class='fas fa-calendar-alt mr-2'></i>Fri, July 9th
2021
</time>
</div>
<div class='project__bar'></div>
<div class='project__info'>
<p>
Designed, built and tested a single page application
that lets users make virtual donuts via clicking. Once
enough donuts are made you have the ability to purchase
upgrades such as auto-clickers and clicking-multipliers.
</p>
</div>
<ul class='project__tagbox'>
<li class='tag__item'>JavaScript</li>
<li class='tag__item'>Node.js</li>
<li class='tag__item'>HTML</li>
<li class='tag__item'>CSS</li>
<li class='tag__item'>Responsive Design</li>
<li class='tag__item'>TDD</li>
{/* <li class='tag__item'>VS Code</li> */}
<li class='tag__item'>Git</li>
</ul>
</div>
</article>
<article class='project'>
<a
class='project__img_link'
href='https://github.com/lerko96/trek'
rel='noreferrer'
target='_blank'
>
<img
class='project__img'
src={trekkingSite}
alt='trekking-img'
/>
</a>
<div class='project__text'>
<h3 class='project__title'>
<a
href='https://github.com/lerko96/trek'
rel='noreferrer'
target='_blank'
>
Trekking Site
</a>
</h3>
<div class='project__subtitle_small'>
<time datetime='2021-06-18 12:00:00'>
<i class='fas fa-calendar-alt mr-2'></i>Fri, June 18th
2021
</time>
</div>
<div class='project__bar'></div>
<div class='project__info'>
<p>
Designed, built and tested an MVC application that lets
users discover treks located by continent, region and
type. This project was built between a team of four in a
completely remote environemnt with the help of Github,
Zoom and Slack.
</p>
</div>
<ul class='project__tagbox'>
<li class='tag__item'>Java</li>
<li class='tag__item'>Spring</li>
{/* <li class='tag__item'>OOP</li> */}
<li class='tag__item'>HTML</li>
<li class='tag__item'>CSS</li>
<li class='tag__item'>Responsive Design</li>
<li class='tag__item'>TDD</li>
{/* <li class='tag__item'>IntelliJ</li> */}
<li class='tag__item'>Git</li>
<li class='tag__item'>Agile</li>
<li class='tag__item'>Scrum</li>
<li class='tag__item'>Zoom</li>
<li class='tag__item'>Slack</li>
</ul>
</div>
</article>
{/* <article class='project'>
<a
class='project__img_link'
href='https://github.com/lerko96/reviews-mvc'
rel='noreferrer'
target='_blank'
>
<img
class='project__img'
src={reviewSite}
alt='review-site-img'
/>
</a>
<div class='project__text'>
<h3 class='project__title'>
<a
href='https://github.com/lerko96/reviews-mvc'
rel='noreferrer'
target='_blank'
>
Movie Reviews
</a>
</h3>
<div class='project__subtitle_small'>
<time datetime='2021-06-04 12:00:00'>
<i class='fas fa-calendar-alt mr-2'></i>Fri, June 4th
2021
</time>
</div>
<div class='project__bar'></div>
<div class='project__info'>
<p>
Designed, built and tested a Java application for movie
reviews.
</p>
</div>
<ul class='project__tagbox'>
<li class='tag__item'>Java</li>
<li class='tag__item'>SpringJPA</li>
<li class='tag__item'>OOP</li>
<li class='tag__item'>Thymeleaf</li>
<li class='tag__item'>HTML</li>
<li class='tag__item'>CSS</li>
<li class='tag__item'>Responsive Design</li>
<li class='tag__item'>TDD</li>
<li class='tag__item'>IntelliJ</li>
<li class='tag__item'>Github</li>
</ul>
</div>
</article> */}
</section>
);
export default Projects;

54
src/components/Skills.tsx Normal file
View File

@@ -0,0 +1,54 @@
const skillGroups = [
{
label: "Languages",
skills: ["JavaScript", "TypeScript", "HTML", "CSS"],
},
{
label: "Frontend & Mobile",
skills: ["React", "React Native", "Expo", "Next.js", "Three.js", "Responsive Design"],
},
{
label: "Desktop & Tools",
skills: ["Electron", "Node.js", "REST APIs", "Git", "Docker", "TDD"],
},
{
label: "Infrastructure",
skills: ["Proxmox", "pfSense", "VLANs", "WireGuard", "Linux", "Caddy"],
},
{
label: "Practices",
skills: ["Agile / Scrum", "Relational Databases", "Self-hosting"],
},
];
export default function Skills() {
return (
<section className="mb-20" aria-labelledby="skills-heading">
<h2
id="skills-heading"
className="font-mono text-xs text-[var(--color-green)] tracking-widest uppercase mb-8"
>
Skills
</h2>
<div className="flex flex-col gap-5">
{skillGroups.map(({ label, skills }) => (
<div key={label} className="flex flex-col xs:flex-row gap-2 xs:items-start">
<span className="font-mono text-xs text-[var(--color-grey-2)] w-36 shrink-0 pt-0.5">
{label}
</span>
<div className="flex flex-wrap gap-2">
{skills.map((skill) => (
<span
key={skill}
className="text-xs font-mono px-3 py-1 border border-[var(--color-grey-1)] text-[var(--color-grey-3)] rounded hover:border-[var(--color-green-darker)] hover:text-[var(--color-grey-4)] transition-colors"
>
{skill}
</span>
))}
</div>
</div>
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,12 @@
// Server component — renders a blocking inline script that sets the dark class
// on <html> before React hydrates, preventing flash of wrong theme.
export default function ThemeScript() {
const script = `
(function() {
var stored = localStorage.getItem('lerko96-dark-mode');
var dark = stored === null ? true : stored === 'true';
if (dark) document.documentElement.classList.add('dark');
})();
`;
return <script dangerouslySetInnerHTML={{ __html: script }} />;
}