Redesign homepage: Explore dropdown nav + paginated post list with summaries

- Replace Tags nav item with an Explore dropdown containing Tags, Site Index,
  and all tool subpages (Status, Flights, Volleyball Score, Pixel Art, ASCII Art)
- Limit homepage to 8 posts per page with pagination controls
- Show frontmatter description as post excerpt (more useful than auto-summary
  for Morning Brief posts with similar titles)
- Custom CSS for dropdown panel (hover + focus-within for keyboard/touch)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rock Campbell 2026-06-10 18:13:14 +00:00
parent f466701509
commit 9b37504988
5 changed files with 244 additions and 0 deletions

View file

@ -8,19 +8,69 @@ googleAnalytics = 'G-LMKPZJ98Y8'
contentDir = "content"
[pagination]
pagerSize = 8
[Params]
description = "Writer, thinker, technologist. Morning briefs, Stoic reflections, self-hosting, and systems thinking."
# Main nav: Posts as a direct link, then an Explore dropdown for everything else
[[menus.main]]
identifier = "posts"
name = "Posts"
url = "/posts/"
weight = 1
[[menus.main]]
identifier = "explore"
name = "Explore"
url = "/explore/"
weight = 2
# Explore dropdown children — blog navigation
[[menus.main]]
name = "Tags"
url = "/tags/"
parent = "explore"
weight = 1
[[menus.main]]
name = "Site Index"
url = "/explore/"
parent = "explore"
weight = 2
# Explore dropdown children — tools
[[menus.main]]
name = "Status Dashboard"
url = "/status"
parent = "explore"
weight = 3
[[menus.main]]
name = "Flights"
url = "/flights"
parent = "explore"
weight = 4
[[menus.main]]
name = "Volleyball Score"
url = "/volleyball-score"
parent = "explore"
weight = 5
[[menus.main]]
name = "Pixel Art"
url = "/pixel-art"
parent = "explore"
weight = 6
[[menus.main]]
name = "ASCII Art"
url = "/ascii-art"
parent = "explore"
weight = 7
[[menus.footer]]
name = "RSS"
url = "/index.xml"

42
layouts/index.html Normal file
View file

@ -0,0 +1,42 @@
{{ define "main" }}
{{- $pages := where .Site.RegularPages "Section" "posts" }}
{{- $paginator := .Paginate $pages }}
<ul class="post-list">
{{- range $paginator.Pages }}
<li class="post-list__entry">
<div class="post-list__meta">
<time class="post-list__date" datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "Jan 2, 2006" }}</time>
{{- with .Params.categories }}
<span class="post-list__category">{{ index . 0 | lower }}</span>
{{- end }}
</div>
<div class="post-list__body">
<a href="{{ .Permalink }}" class="post-list__title">{{ .Title }}</a>
{{- /* Use frontmatter description first (richer for Morning Brief posts),
fall back to auto-generated summary */}}
{{- $excerpt := or .Description (.Summary | plainify) }}
{{- if $excerpt }}
<p class="post-list__excerpt">{{ $excerpt | truncate 220 }}</p>
{{- end }}
</div>
</li>
{{- end }}
</ul>
{{- /* Pagination controls — only shown when there's more than one page */}}
{{- if gt $paginator.TotalPages 1 }}
<nav class="post-list-pagination" aria-label="Post navigation">
<span>
{{- if $paginator.HasPrev }}
<a href="{{ $paginator.Prev.URL | absURL }}">← Newer posts</a>
{{- end }}
</span>
<span>Page {{ $paginator.PageNumber }} of {{ $paginator.TotalPages }}</span>
<span>
{{- if $paginator.HasNext }}
<a href="{{ $paginator.Next.URL | absURL }}">Older posts →</a>
{{- end }}
</span>
</nav>
{{- end }}
{{ end }}

View file

@ -0,0 +1,23 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} — {{ .Site.Title }}{{ end }}</title>
{{ with .Description }}<meta name="description" content="{{ . }}">{{ end }}
{{ with .Params.summary }}<meta name="description" content="{{ . }}">{{ end }}
<meta name="generator" content="Hugo {{ hugo.Version }}">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;0,600;0,700;1,400&family=IBM+Plex+Mono:wght@400&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ "css/main.css" | relURL }}">
<link rel="stylesheet" href="{{ "css/custom.css" | relURL }}">
{{ with .OutputFormats.Get "rss" -}}
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink }}" title="{{ $.Site.Title }}">
{{- end }}
{{ with .Site.Params.googleAnalytics -}}
<script async src="https://www.googletagmanager.com/gtag/js?id={{ . }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ . }}');
</script>
{{- end }}

View file

@ -0,0 +1,37 @@
<header class="site-header">
<div class="site-wrapper">
<div class="site-header__inner">
<a href="{{ "/" | relURL }}" class="site-title">{{ .Site.Title }}</a>
{{- if .Site.Menus.main }}
<nav aria-label="Main navigation">
<ul class="site-nav">
{{- range .Site.Menus.main }}
{{- if not .Parent }}{{/* top-level items only */}}
{{- if .HasChildren }}
{{/* Explore dropdown — renders parent link + child list */}}
<li class="nav-has-dropdown">
<a href="{{ .URL }}"
{{- if $.IsMenuCurrent "main" . }} class="is-active"{{ end }}
aria-haspopup="true">{{ .Name }} <span class="nav-arrow" aria-hidden="true"></span></a>
<ul class="nav-dropdown" role="menu">
{{- range .Children }}
<li role="none">
<a href="{{ .URL }}" role="menuitem">{{ .Name }}</a>
</li>
{{- end }}
</ul>
</li>
{{- else }}
{{/* Regular nav item */}}
<li>
<a href="{{ .URL }}"{{ if $.IsMenuCurrent "main" . }} class="is-active"{{ end }}>{{ .Name }}</a>
</li>
{{- end }}
{{- end }}
{{- end }}
</ul>
</nav>
{{- end }}
</div>
</div>
</header>

92
static/css/custom.css Normal file
View file

@ -0,0 +1,92 @@
/* ── Explore dropdown nav ── */
/* Wrapper li needs relative positioning for the dropdown panel */
.nav-has-dropdown {
position: relative;
}
/* Show the dropdown on hover (desktop) and when focus is inside (keyboard/touch) */
.nav-has-dropdown:hover .nav-dropdown,
.nav-has-dropdown:focus-within .nav-dropdown {
display: block;
}
/* Dropdown panel */
.nav-dropdown {
display: none;
position: absolute;
top: calc(100% + 4px);
right: 0;
min-width: 180px;
list-style: none;
margin: 0;
padding: 6px 0;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 4px;
box-shadow: 0 4px 16px rgba(44, 36, 22, 0.10);
z-index: 200;
}
.nav-dropdown li {
display: block;
}
.nav-dropdown a {
display: block;
padding: 7px 16px;
font-size: 15px;
color: var(--color-secondary);
white-space: nowrap;
text-decoration: none;
transition: background 100ms ease, color 100ms ease;
}
.nav-dropdown a:hover {
background: var(--color-neutral);
color: var(--color-primary);
}
/* Subtle arrow nudge so it's clear it's a dropdown trigger */
.nav-arrow {
font-size: 11px;
opacity: 0.6;
vertical-align: middle;
}
/* ── Homepage post list improvements ── */
/* Slightly more breathing room between entries */
.post-list__entry {
padding-bottom: 1.4rem;
margin-bottom: 1.4rem;
}
/* Description/excerpt text under each post title */
.post-list__excerpt {
margin-top: 0.3rem;
font-size: 0.9rem;
color: var(--color-secondary);
line-height: 1.55;
}
/* Pagination controls at bottom of homepage */
.post-list-pagination {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1.5rem;
border-top: 1px solid var(--color-border);
font-size: 0.875rem;
color: var(--color-tertiary);
}
.post-list-pagination a {
color: var(--color-accent);
text-decoration: none;
font-weight: 500;
}
.post-list-pagination a:hover {
text-decoration: underline;
}