feat(design): nav anchor links, hidden services flag, drop badges
Add projects/journey/homelab anchor links to sticky nav for in-page navigation. Add hidden flag to services data — hidden entries count toward total but don't render. Remove badge counts from all widgets.
This commit is contained in:
@@ -2,13 +2,19 @@
|
|||||||
<nav class="max-w-[740px] mx-auto px-4ch h-11 flex items-center justify-between">
|
<nav class="max-w-[740px] mx-auto px-4ch h-11 flex items-center justify-between">
|
||||||
<a href="/" class="font-semibold">Tyler Koenig</a>
|
<a href="/" class="font-semibold">Tyler Koenig</a>
|
||||||
|
|
||||||
<button
|
<div class="flex items-center gap-2ch">
|
||||||
|
<a href="#projects" class="text-[var(--color-text-label)] hover:text-[var(--color-text)]">projects</a>
|
||||||
|
<a href="#journey" class="text-[var(--color-text-label)] hover:text-[var(--color-text)]">journey</a>
|
||||||
|
<a href="#homelab" class="text-[var(--color-text-label)] hover:text-[var(--color-text)]">homelab</a>
|
||||||
|
|
||||||
|
<button
|
||||||
data-theme-toggle
|
data-theme-toggle
|
||||||
aria-label="Switch to light mode"
|
aria-label="Switch to light mode"
|
||||||
class="text-[var(--color-text-label)] hover:text-[var(--color-text)] cursor-pointer"
|
class="text-[var(--color-text-label)] hover:text-[var(--color-text)] cursor-pointer"
|
||||||
>
|
>
|
||||||
light
|
light
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
+22
-19
@@ -2,6 +2,7 @@ export type Service = {
|
|||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
category: "infrastructure" | "security" | "monitoring" | "productivity" | "media";
|
category: "infrastructure" | "security" | "monitoring" | "productivity" | "media";
|
||||||
|
hidden?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const services: Service[] = [
|
export const services: Service[] = [
|
||||||
@@ -11,7 +12,7 @@ export const services: Service[] = [
|
|||||||
{ name: "Pi-hole", description: "Network-wide DNS + ad blocking", category: "infrastructure" },
|
{ name: "Pi-hole", description: "Network-wide DNS + ad blocking", category: "infrastructure" },
|
||||||
{ name: "WireGuard", description: "VPN — full LAN access for remote clients", category: "infrastructure" },
|
{ name: "WireGuard", description: "VPN — full LAN access for remote clients", category: "infrastructure" },
|
||||||
{ name: "mail relay", description: "Outbound SMTP relay for self-hosted service notifications", category: "infrastructure" },
|
{ name: "mail relay", description: "Outbound SMTP relay for self-hosted service notifications", category: "infrastructure" },
|
||||||
{ name: "gluetun", description: "VPN container routing download client traffic", category: "infrastructure" },
|
{ name: "gluetun", description: "VPN container routing download client traffic", category: "infrastructure", hidden: true },
|
||||||
{ name: "Home Assistant", description: "Smart home automation and device management", category: "infrastructure" },
|
{ name: "Home Assistant", description: "Smart home automation and device management", category: "infrastructure" },
|
||||||
|
|
||||||
// Security / Auth
|
// Security / Auth
|
||||||
@@ -23,34 +24,36 @@ export const services: Service[] = [
|
|||||||
{ name: "Grafana", description: "Dashboards and alerting across all hosts and services", category: "monitoring" },
|
{ name: "Grafana", description: "Dashboards and alerting across all hosts and services", category: "monitoring" },
|
||||||
{ name: "Beszel", description: "Lightweight container and host monitoring", category: "monitoring" },
|
{ name: "Beszel", description: "Lightweight container and host monitoring", category: "monitoring" },
|
||||||
{ name: "ntfy", description: "Self-hosted push notifications", category: "monitoring" },
|
{ name: "ntfy", description: "Self-hosted push notifications", category: "monitoring" },
|
||||||
|
{ name: "Uptime Kuma", description: "Self-hosted monitoring tool (GUI)", category: "monitoring" },
|
||||||
|
|
||||||
// Productivity
|
// Productivity
|
||||||
{ name: "Gitea", description: "Personal Git server", category: "productivity" },
|
{ name: "Gitea", description: "Personal Git server", category: "productivity" },
|
||||||
{ name: "Outline", description: "Team wiki and knowledge base", category: "productivity" },
|
{ name: "Outline", description: "Team wiki and knowledge base", category: "productivity" },
|
||||||
{ name: "Vikunja", description: "Task management", category: "productivity" },
|
|
||||||
{ name: "Actual Budget", description: "Personal budgeting", category: "productivity" },
|
{ name: "Actual Budget", description: "Personal budgeting", category: "productivity" },
|
||||||
{ name: "Ghostfolio", description: "Investment portfolio tracking", category: "productivity" },
|
|
||||||
{ name: "Hoarder", description: "Bookmark manager with tagging", category: "productivity" },
|
|
||||||
{ name: "FreshRSS", description: "RSS reader", category: "productivity" },
|
|
||||||
{ name: "Memos", description: "Quick notes and journal", category: "productivity" },
|
|
||||||
{ name: "Traggo", description: "Time tracking", category: "productivity" },
|
|
||||||
{ name: "Baikal", description: "CalDAV / CardDAV server", category: "productivity" },
|
{ name: "Baikal", description: "CalDAV / CardDAV server", category: "productivity" },
|
||||||
{ name: "Grist", description: "Spreadsheets and structured data", category: "productivity" },
|
{ name: "Grist", description: "Spreadsheets and structured data", category: "productivity" },
|
||||||
{ name: "Glance", description: "Self-hosted start page with feeds and service status", category: "productivity" },
|
{ name: "Glance", description: "Self-hosted start page with feeds and service status", category: "productivity" },
|
||||||
{ name: "Filebrowser", description: "Web-based file manager", category: "productivity" },
|
{ name: "Hoarder", description: "Bookmark manager with tagging", category: "productivity", hidden: true },
|
||||||
|
{ name: "FreshRSS", description: "RSS reader", category: "productivity", hidden: true },
|
||||||
|
{ name: "Memos", description: "Quick notes and journal", category: "productivity", hidden: true },
|
||||||
|
{ name: "Ghostfolio", description: "Investment portfolio tracking", category: "productivity", hidden: true },
|
||||||
|
{ name: "Vikunja", description: "Task management", category: "productivity", hidden: true },
|
||||||
|
{ name: "Traggo", description: "Time tracking", category: "productivity", hidden: true },
|
||||||
|
{ name: "Filebrowser", description: "Web-based file manager", category: "productivity", hidden: true },
|
||||||
|
|
||||||
// Media
|
// Media
|
||||||
{ name: "Plex", description: "Media streaming — movies, TV, music", category: "media" },
|
{ name: "Immich", description: "Open-source photo library", category: "media"},
|
||||||
{ name: "Jellyfin", description: "Open-source media streaming", category: "media" },
|
|
||||||
{ name: "Sonarr", description: "Automated TV show management", category: "media" },
|
|
||||||
{ name: "Radarr", description: "Automated movie management", category: "media" },
|
|
||||||
{ name: "Lidarr", description: "Automated music management", category: "media" },
|
|
||||||
{ name: "Prowlarr", description: "Indexer manager and proxy for the *arr stack", category: "media" },
|
|
||||||
{ name: "Bazarr", description: "Automatic subtitle download and management", category: "media" },
|
|
||||||
{ name: "nzbget", description: "Usenet downloader", category: "media" },
|
|
||||||
{ name: "qBittorrent", description: "Torrent client with web UI", category: "media" },
|
|
||||||
{ name: "Kavita", description: "Self-hosted manga and book reader", category: "media" },
|
{ name: "Kavita", description: "Self-hosted manga and book reader", category: "media" },
|
||||||
{ name: "Openshelf", description: "Book library with auto-ingest", category: "media" },
|
{ name: "Jellyfin", description: "Open-source media streaming", category: "media" },
|
||||||
|
{ name: "Prowlarr", description: "Indexer manager and proxy for the *arr stack", category: "media" },
|
||||||
|
{ name: "*Arr stack", description: "Automated media management for streaming", category: "media" },
|
||||||
|
{ name: "Bazarr", description: "Automatic subtitle download and management", category: "media" },
|
||||||
|
{ name: "Lidarr", description: "Automated music management", category: "media", hidden: true },
|
||||||
|
{ name: "Radarr", description: "Automated movie management", category: "media", hidden: true },
|
||||||
|
{ name: "Plex", description: "Media streaming — movies, TV, music", category: "media", hidden: true },
|
||||||
|
{ name: "nzbget", description: "Usenet downloader", category: "media", hidden: true },
|
||||||
|
{ name: "qBittorrent", description: "Torrent client with web UI", category: "media", hidden: true },
|
||||||
|
{ name: "Openshelf", description: "Book library with auto-ingest", category: "media", hidden: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const categoryOrder: Service["category"][] = [
|
export const categoryOrder: Service["category"][] = [
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ const adrs = [
|
|||||||
<Hero />
|
<Hero />
|
||||||
|
|
||||||
<section id="projects">
|
<section id="projects">
|
||||||
<Widget title="Projects" badge={featuredProjects.length} as="div">
|
<Widget title="Projects" as="div">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
{featuredProjects.map((project) => (
|
{featuredProjects.map((project) => (
|
||||||
<ProjectCard project={project} />
|
<ProjectCard project={project} />
|
||||||
@@ -113,7 +113,7 @@ const adrs = [
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Widget title="Overview" badge={glanceStats.length} as="div">
|
<Widget title="Overview" as="div">
|
||||||
<dl class="flex flex-col">
|
<dl class="flex flex-col">
|
||||||
{glanceStats.map(({ label, value }) => (
|
{glanceStats.map(({ label, value }) => (
|
||||||
<div class="flex gap-2ch py-qtr-lh">
|
<div class="flex gap-2ch py-qtr-lh">
|
||||||
@@ -157,10 +157,10 @@ const adrs = [
|
|||||||
</div>
|
</div>
|
||||||
</Widget>
|
</Widget>
|
||||||
|
|
||||||
<Widget title="Services" badge={services.length} as="div">
|
<Widget title="Services" as="div">
|
||||||
<div class="flex flex-col gap-2lh">
|
<div class="flex flex-col gap-2lh">
|
||||||
{categoryOrder.map((cat) => {
|
{categoryOrder.map((cat) => {
|
||||||
const catServices = services.filter((s) => s.category === cat);
|
const catServices = services.filter((s) => s.category === cat && !s.hidden);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-[var(--color-text-dim)] font-semibold mb-half-lh">
|
<h3 class="text-[var(--color-text-dim)] font-semibold mb-half-lh">
|
||||||
@@ -183,8 +183,7 @@ const adrs = [
|
|||||||
<Widget
|
<Widget
|
||||||
title="ADRs"
|
title="ADRs"
|
||||||
meta="why things are configured the way they are"
|
meta="why things are configured the way they are"
|
||||||
badge={adrs.length}
|
as="div"
|
||||||
as="div"
|
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-2lh">
|
<div class="flex flex-col gap-2lh">
|
||||||
{adrs.map((adr) => (
|
{adrs.map((adr) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user