diff --git a/src/app.html b/src/app.html
index 7d708d5..caa6bce 100644
--- a/src/app.html
+++ b/src/app.html
@@ -4,7 +4,7 @@
-
Kevin Riehl — Software Engineer (React, Node.js, TypeScript) | Portfolio
+ Kevin Riehl — Lead Software Engineer (React, Node.js, WebRTC) | Portfolio
%sveltekit.head%
diff --git a/src/lib/components/NodeModal.svelte b/src/lib/components/NodeModal.svelte
index ca3873d..6777124 100644
--- a/src/lib/components/NodeModal.svelte
+++ b/src/lib/components/NodeModal.svelte
@@ -143,10 +143,15 @@
{/if}
-
- {#if node.category === 'projects' && node.status}
+
+ {#if node.category === 'projects' && (node.status || node.downloads)}
- {node.status}
+ {#if node.status}
+ {node.status}
+ {/if}
+ {#if node.downloads}
+ ↓ {node.downloads}
+ {/if}
{/if}
@@ -471,6 +476,18 @@
transform: translateY(0);
}
+ .downloads-badge {
+ display: inline-block;
+ padding: 0.2rem 0.6rem;
+ border: 1px solid rgba(255, 215, 0, 0.35);
+ border-radius: 12px;
+ background: rgba(255, 215, 0, 0.08);
+ color: var(--color-gold);
+ font-size: 0.75rem;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ }
+
.project-status {
margin: 0.75rem 0;
}
diff --git a/src/lib/components/SeoContent.svelte b/src/lib/components/SeoContent.svelte
index 1f2c215..3295241 100644
--- a/src/lib/components/SeoContent.svelte
+++ b/src/lib/components/SeoContent.svelte
@@ -79,6 +79,9 @@
{#if project.techStack?.length}
Tech: {project.techStack.join(', ')}
{/if}
+ {#if project.downloads}
+ {project.downloads}
+ {/if}
{#if project.projectUrl}- Live site
{/if}
{#if project.repoUrl}- Source code
{/if}
diff --git a/src/lib/data.ts b/src/lib/data.ts
index 36c15ce..9d50553 100644
--- a/src/lib/data.ts
+++ b/src/lib/data.ts
@@ -88,9 +88,9 @@ export const treeNodes: TreeNode[] = [
size: 'x-large',
icon: '',
fullName: 'Kevin Riehl',
- jobTitle: 'Software Development - Lead Associate',
+ jobTitle: 'Lead Software Engineer',
aboutMe:
- 'Intermediate Software Engineer with 5+ years of experience architecting full-stack applications using React, Node.js (Hapi), and SQL. Expertise includes modernizing legacy codebases, implementing rigorous testing standards, and leading frontend architecture improvements. Passionate about leveraging modern tools like ShadCN and Docker to streamline development workflows.',
+ "Software engineer with 5+ years building full-stack applications for Defense Health Agency programs. I've architected a WebRTC telehealth platform, shipped real-time gesture recognition that made chat usable for speech-impaired patients, and built the deployment tooling that unblocked a team after an air-gapped infrastructure migration. React, Node.js, NestJS, TypeScript, SQL.",
location: 'Kent, WA',
email: 'kevinriehl@gmail.com',
linkedIn: 'https://www.linkedin.com/in/kevinriehl',
@@ -134,7 +134,7 @@ export const treeNodes: TreeNode[] = [
{
id: 'exp-job1',
label: 'Software Engineer',
- description: 'Junior developer at the start of my career.',
+ description: 'Refactored legacy PHP endpoints onto Hapi.js and ran a MySQL to MSSQL migration.',
x: 420,
y: -280,
parentId: 'exp-contract',
@@ -155,7 +155,7 @@ export const treeNodes: TreeNode[] = [
{
id: 'exp-job2',
label: 'Software Engineer',
- description: 'Growing my skills and taking on more responsibility.',
+ description: 'Built a React + Hapi.js analytics search engine over complex event datasets.',
x: 500,
y: -350,
parentId: 'exp-job1',
@@ -177,7 +177,7 @@ export const treeNodes: TreeNode[] = [
{
id: 'exp-job3',
label: 'Intermediate Software Engineer',
- description: 'Stepping into a more senior role.',
+ description: 'Web Bluetooth biometric monitoring, plus a team-wide move to ShadCN and Docker.',
x: 560,
y: -430,
parentId: 'exp-job2',
@@ -199,7 +199,8 @@ export const treeNodes: TreeNode[] = [
{
id: 'exp-job4',
label: 'Intermediate Software Engineer',
- description: 'Where one journey ends, another begins.',
+ description:
+ 'WebRTC telehealth platform and real-time gesture recognition for speech-impaired users.',
x: 600,
y: -510,
parentId: 'exp-job3',
@@ -221,7 +222,8 @@ export const treeNodes: TreeNode[] = [
{
id: 'exp-job5',
label: 'Lead Associate',
- description: 'Where I am now.',
+ description:
+ 'Air-gapped deployment automation, a NestJS audit interceptor, and 90%+ test coverage.',
x: 620,
y: -590,
parentId: 'exp-job4',
@@ -233,9 +235,9 @@ export const treeNodes: TreeNode[] = [
startDate: 'January 2026',
endDate: 'Present',
highlights: [
- 'Proactively engineered a suite of automation tools to streamline code deployment workflows to an air-gapped Git instance following a critical infrastructure migration, eliminating manual bottlenecks and restoring team development velocity.',
- 'Designed and implemented a custom NestJS API interceptor to dynamically detect and audit specific user activities, providing robust, real-time daily activity tracking for compliance and analytics.',
- 'Spearheaded a rigorous testing initiative to elevate code quality, systematically architecting and executing unit tests to scale platform test coverage from a legacy deficit to a resilient 90%+ baseline.'
+ 'Proactively engineered a suite of automation tools to streamline code deployment workflows to an air-gapped Git instance following a critical infrastructure migration, eliminating manual bottlenecks and restoring team development velocity',
+ 'Designed and implemented a custom NestJS API interceptor to dynamically detect and audit specific user activities, providing robust, real-time daily activity tracking for compliance and analytics',
+ 'Spearheaded a rigorous testing initiative to elevate code quality, systematically architecting and executing unit tests to scale platform test coverage from a legacy deficit to a resilient 90%+ baseline'
]
},
@@ -306,6 +308,8 @@ export const treeNodes: TreeNode[] = [
techStack: ['Python', 'REST API', 'PyPI', 'GitHub Actions', 'Unit Testing'],
repoUrl: 'https://github.com/TehRiehlDeal/TVDB-API',
pypiUrl: 'https://pypi.org/project/tvdbAPI/',
+ // Lifetime PyPI downloads, checked 2026-08-09 (pypistats.org).
+ downloads: '2,277 downloads',
status: 'maintained'
},
{
@@ -322,6 +326,8 @@ export const treeNodes: TreeNode[] = [
techStack: ['Python', 'REST API', 'PyPI', 'GitHub Actions', 'Unit Testing'],
repoUrl: 'https://github.com/TehRiehlDeal/tmdbAPI',
pypiUrl: 'https://pypi.org/project/tmdbAPI/',
+ // Lifetime PyPI downloads, checked 2026-08-09 (pypistats.org).
+ downloads: '784 downloads',
status: 'maintained'
},
{
@@ -629,7 +635,7 @@ export const treeNodes: TreeNode[] = [
proficiency: 'intermediate'
},
{
- id: 'tool-vscod',
+ id: 'tool-vscode',
label: 'VSCode',
description:
'Powerful, lightweight code editor by Microsoft with built-in Git support, debugging, and a vast extension ecosystem.',
@@ -920,8 +926,8 @@ export const treeNodes: TreeNode[] = [
},
{
id: 'edu-degree',
- label: 'University',
- description: "Bachelor's degree in Computer Science.",
+ label: 'Green River College',
+ description: "Bachelor's of Applied Science in IT and Software Development.",
x: -300,
y: 80,
parentId: 'edu-branch',
diff --git a/src/lib/utils/stats.test.ts b/src/lib/utils/stats.test.ts
index b748a0a..cab98f4 100644
--- a/src/lib/utils/stats.test.ts
+++ b/src/lib/utils/stats.test.ts
@@ -4,7 +4,8 @@ import {
BRANCH_LABELS,
aggregateSkills,
countByProficiency,
- getPlayerClass
+ getPlayerClass,
+ calculatePlayerLevel
} from './stats';
import { treeNodes } from '$lib/data';
@@ -55,3 +56,19 @@ describe('getPlayerClass', () => {
expect(getPlayerClass()).toBe(origin?.jobTitle);
});
});
+
+describe('calculatePlayerLevel', () => {
+ it('derives the level from the earliest experience start date', () => {
+ // exp-job1 starts October 2020. Previously this date was hardcoded in
+ // stats.ts as well, so the two could drift apart silently.
+ const earliest = new Date('October 1, 2020').getTime();
+ const expected = Math.floor((Date.now() - earliest) / (1000 * 60 * 60 * 24 * 365.25));
+ expect(calculatePlayerLevel()).toBe(expected);
+ });
+
+ it('returns a plausible, non-negative level', () => {
+ const level = calculatePlayerLevel();
+ expect(level).toBeGreaterThanOrEqual(5);
+ expect(level).toBeLessThan(60);
+ });
+});
diff --git a/src/lib/utils/stats.ts b/src/lib/utils/stats.ts
index 315f2fc..aa38613 100644
--- a/src/lib/utils/stats.ts
+++ b/src/lib/utils/stats.ts
@@ -2,7 +2,7 @@
// STATS UTILS — pure aggregation for the RPG-style character panel.
// ==========================================================================
-import { treeNodes, type SkillNode } from '$lib/data';
+import { treeNodes, type SkillNode, type ExperienceNode } from '$lib/data';
/** Skill branch ids and their display labels (single source of truth). */
export const BRANCH_IDS = [
@@ -29,9 +29,31 @@ export const PROFICIENCY_ORDER: Record = {
beginner: 3
};
-/** Character "level" derived from years since the first job started. */
+/**
+ * Parse a "Month YYYY" string (the format used by every startDate in data.ts)
+ * into a timestamp. Built explicitly rather than handed straight to `new Date`:
+ * "October 2020" alone is not a format the spec requires engines to accept.
+ */
+function parseMonthYear(value: string): number | null {
+ const [month, year] = value.trim().split(/\s+/);
+ if (!month || !year) return null;
+ const parsed = new Date(`${month} 1, ${year}`).getTime();
+ return Number.isNaN(parsed) ? null : parsed;
+}
+
+/**
+ * Character "level" derived from years since the first job started.
+ *
+ * The start date is read from the experience nodes rather than hardcoded — it
+ * previously duplicated exp-job1.startDate, so the two could silently drift.
+ */
export function calculatePlayerLevel(): number {
- const earliestStart = new Date('2020-10-01'); // First job
+ const starts = treeNodes
+ .filter((n): n is ExperienceNode => n.category === 'experience' && !!n.startDate)
+ .map((n) => parseMonthYear(n.startDate as string))
+ .filter((t): t is number => t !== null);
+
+ const earliestStart = new Date(starts.length ? Math.min(...starts) : Date.now());
const now = new Date();
const millisPerYear = 1000 * 60 * 60 * 24 * 365.25;
const years = (now.getTime() - earliestStart.getTime()) / millisPerYear;
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 83f8943..bc786e8 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -159,7 +159,7 @@
@@ -190,11 +190,11 @@