Migrate WordPress site to Hugo

Replaces the WordPress 7.0.2 / HitMag site with a static Hugo build,
deployed by Gitea Actions over rsync/SSH.

Content: 26 files (9 pages + 17 posts) as Markdown page bundles, plus
127 of 165 media attachments. The remaining 38 are media-library
leftovers that appear nowhere on the live site; they are listed in
MIGRATION.md.

The WXR export contains neither media binaries nor widgets, so both were
recovered from the live host before it is retired:
  - tools/fetch_media.py downloads all uploads, capping them at 2000px
  - tools/build_data.py scrapes the "Unsere Mitglieder" and
    "In Erinnerung" widgets into data/*.yaml

TablePress' six Termine tables became data/termine.yaml, rendered with
the newest year expanded and earlier years collapsed. Old permalinks are
not preserved (clean slugs, no redirects, as agreed).

Custom layouts, no third-party theme. Plain CSS, since the pinned Hugo
0.164.0 is the non-extended build and cannot compile Sass.

Verified: clean build with zero warnings, 718 internal links checked and
none broken, no surviving wp-content URLs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 19:20:11 +02:00
parent eccf30584a
commit bef5182545
188 changed files with 2770 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
{{- /* Markdown images inside a page bundle get resized and lazy-loaded.
External or non-resource images are passed through unchanged. */ -}}
{{- $src := .Destination -}}
{{- $alt := .Text | plainify -}}
{{- $caption := .Title -}}
{{- with .Page.Resources.GetMatch $src -}}
{{- $img := . -}}
{{- if gt .Width 1200 -}}{{- $img = .Resize "1200x webp q82" -}}{{- end -}}
<figure class="content-figure">
<a href="{{ .RelPermalink }}">
<img src="{{ $img.RelPermalink }}" width="{{ $img.Width }}" height="{{ $img.Height }}"
alt="{{ $alt }}" loading="lazy" decoding="async">
</a>
{{- with $caption }}<figcaption>{{ . }}</figcaption>{{ end -}}
</figure>
{{- else -}}
<img src="{{ $src | safeURL }}" alt="{{ $alt }}" loading="lazy" decoding="async">
{{- end -}}
+13
View File
@@ -0,0 +1,13 @@
<footer class="site-footer">
<div class="container">
<p class="footer-contact">
<strong>{{ site.Title }}</strong><br>
<a href="mailto:{{ site.Params.email }}">{{ site.Params.email }}</a>
</p>
<nav class="footer-nav" aria-label="Fußzeile">
<a href="{{ "/impressum/" | relURL }}">Impressum</a>
{{ with .OutputFormats.Get "rss" }}<a href="{{ .Permalink }}">RSS</a>{{ end }}
</nav>
<p class="footer-copyright">© {{ now.Year }} {{ site.Title }}</p>
</div>
</footer>
@@ -0,0 +1,15 @@
{{- /*
Resolve a page's featured image to a Resource.
Looks at the `featured` front-matter key first (set by the WordPress import),
then falls back to the first image in the page bundle.
Returns the Resource, or nothing.
*/ -}}
{{- $img := "" -}}
{{- with .Resources.GetMatch (printf "%s" (.Params.featured | default "")) -}}
{{- $img = . -}}
{{- else -}}
{{- with .Resources.ByType "image" -}}
{{- $img = index . 0 -}}
{{- end -}}
{{- end -}}
{{- return $img -}}
+32
View File
@@ -0,0 +1,32 @@
<head>
<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>
{{ $description := or .Description .Summary site.Params.description }}
<meta name="description" content="{{ $description | plainify | truncate 160 }}">
<meta property="og:site_name" content="{{ site.Title }}">
<meta property="og:title" content="{{ if .IsHome }}{{ site.Title }}{{ else }}{{ .Title }}{{ end }}">
<meta property="og:description" content="{{ $description | plainify | truncate 160 }}">
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}">
<meta property="og:url" content="{{ .Permalink }}">
{{ with partial "func/featured-image.html" . }}
<meta property="og:image" content="{{ .Permalink }}">
{{ end }}
{{ with resources.Get "css/main.css" | minify | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}">
{{ end }}
{{ with resources.Get site.Params.logo }}
{{ $icon := .Resize "180x180 png" }}
<link rel="icon" href="{{ $icon.RelPermalink }}" type="image/png">
<link rel="apple-touch-icon" href="{{ $icon.RelPermalink }}">
{{ end }}
{{ with .OutputFormats.Get "rss" }}
<link rel="alternate" type="application/rss+xml" href="{{ .Permalink }}" title="{{ site.Title }}">
{{ end }}
<link rel="canonical" href="{{ .Permalink }}">
</head>
+38
View File
@@ -0,0 +1,38 @@
<header class="site-header">
<div class="container site-branding">
<a class="site-logo" href="{{ "/" | relURL }}" rel="home">
{{ with resources.Get site.Params.logo }}
{{ $l := .Resize "160x png" }}
<img src="{{ $l.RelPermalink }}" width="{{ $l.Width }}" height="{{ $l.Height }}"
alt="Wappen des Motorradclub Giebelwald">
{{ end }}
</a>
<div class="site-titles">
<p class="site-title"><a href="{{ "/" | relURL }}" rel="home">{{ site.Title }}</a></p>
<p class="site-tagline">{{ site.Params.tagline }}</p>
</div>
</div>
{{ with resources.Get site.Params.banner }}
{{ $banner := .Resize "1600x webp q80" }}
<img class="site-banner" src="{{ $banner.RelPermalink }}"
width="{{ $banner.Width }}" height="{{ $banner.Height }}"
alt="" loading="eager" decoding="async">
{{ end }}
<nav class="site-nav" aria-label="Hauptnavigation">
<div class="container">
<input type="checkbox" id="nav-toggle" class="nav-toggle">
<label for="nav-toggle" class="nav-toggle-label">
<span aria-hidden="true"></span> Menü
</label>
<ul class="nav-list">
<li><a href="{{ "/" | relURL }}"{{ if .IsHome }} aria-current="page"{{ end }}>Start</a></li>
{{ range site.Menus.main }}
{{ $active := or ($.IsMenuCurrent "main" .) ($.HasMenuCurrent "main" .) }}
<li><a href="{{ .URL }}"{{ if $active }} aria-current="page"{{ end }}>{{ .Name }}</a></li>
{{ end }}
</ul>
</div>
</nav>
</header>
+17
View File
@@ -0,0 +1,17 @@
{{- /* Teaser card for a news post. Context: the page. */ -}}
<article class="post-card">
{{ with partial "func/featured-image.html" . }}
{{ $thumb := .Fill "480x300 Center webp q80" }}
<a class="post-card-image" href="{{ $.RelPermalink }}" tabindex="-1" aria-hidden="true">
<img src="{{ $thumb.RelPermalink }}" width="{{ $thumb.Width }}" height="{{ $thumb.Height }}"
alt="" loading="lazy" decoding="async">
</a>
{{ end }}
<div class="post-card-body">
<h3 class="post-card-title"><a href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
<p class="post-card-meta">
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date | time.Format site.Params.dateFormat }}</time>
</p>
<p class="post-card-summary">{{ .Summary | plainify | truncate 160 }}</p>
</div>
</article>
+12
View File
@@ -0,0 +1,12 @@
{{- /* Context: a slice of {date, description} rows from data/termine.yaml.
Cells may contain newlines (rendered as line breaks) and inline HTML. */ -}}
<table class="termine">
<tbody>
{{ range . }}
<tr>
<th scope="row">{{ .date | strings.Replace "\n" "<br>" | safeHTML }}</th>
<td>{{ .description | strings.Replace "\n" "<br>" | safeHTML }}</td>
</tr>
{{ end }}
</tbody>
</table>
+15
View File
@@ -0,0 +1,15 @@
{{- /* {{< figure "bild.jpg" "Bildunterschrift" >}} */ -}}
{{- $src := or (.Get "src") (.Get 0) -}}
{{- $caption := or (.Get "caption") (.Get 1) -}}
{{- with .Page.Resources.GetMatch $src -}}
{{- $img := .Resize "1200x webp q82" }}
<figure class="content-figure">
<a href="{{ .RelPermalink }}">
<img src="{{ $img.RelPermalink }}" width="{{ $img.Width }}" height="{{ $img.Height }}"
alt="{{ $caption | plainify }}" loading="lazy" decoding="async">
</a>
{{ with $caption }}<figcaption>{{ . | markdownify }}</figcaption>{{ end }}
</figure>
{{- else -}}
{{- warnf "figure: %q not found in %s" $src .Page.Path -}}
{{- end -}}
+32
View File
@@ -0,0 +1,32 @@
{{- /*
Responsive image grid.
{{< gallery "a.jpg" "b.jpg" >}} explicit, ordered
{{< gallery >}} every image resource of the page
Thumbnails link to the full-size original.
*/ -}}
{{- $page := .Page -}}
{{- $images := slice -}}
{{- if .Params -}}
{{- range .Params -}}
{{- with $page.Resources.GetMatch . -}}
{{- $images = $images | append . -}}
{{- else -}}
{{- warnf "gallery: %q not found in %s" . $page.Path -}}
{{- end -}}
{{- end -}}
{{- else -}}
{{- $images = $page.Resources.ByType "image" -}}
{{- end -}}
{{- if $images -}}
<div class="gallery">
{{- range $images }}
{{- $thumb := .Fill "400x300 Center webp q80" }}
{{- $full := .Resize "1600x webp q82" }}
<a class="gallery-item" href="{{ $full.RelPermalink }}">
<img src="{{ $thumb.RelPermalink }}" width="{{ $thumb.Width }}" height="{{ $thumb.Height }}"
alt="" loading="lazy" decoding="async">
</a>
{{- end }}
</div>
{{- end -}}
+15
View File
@@ -0,0 +1,15 @@
{{- /* {{< pdf "satzung.pdf" "Unsere Satzung" >}} — links a file in static/dokumente/. */ -}}
{{- $file := .Get 0 -}}
{{- $label := or (.Get 1) $file -}}
{{- $path := printf "static/dokumente/%s" $file -}}
{{- $size := "" -}}
{{- with (os.Stat $path) -}}
{{- $size = printf "%.1f MB" (div (float .Size) 1048576.0) -}}
{{- else -}}
{{- warnf "pdf shortcode: %s not found" $path -}}
{{- end -}}
<a class="pdf-link" href="{{ printf "/dokumente/%s" $file | relURL }}" download>
<span class="pdf-icon" aria-hidden="true">PDF</span>
<span class="pdf-label">{{ $label }}</span>
{{ with $size }}<span class="pdf-size">{{ . }}</span>{{ end }}
</a>
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="{{ site.Language.Locale | default "de-DE" }}">
{{ partial "head.html" . }}
<body>
<a class="skip-link" href="#main">Zum Inhalt springen</a>
{{ partial "header.html" . }}
<main id="main" class="site-main">
<div class="container">
{{ block "main" . }}{{ end }}
</div>
</main>
{{ partial "footer.html" . }}
</body>
</html>
+54
View File
@@ -0,0 +1,54 @@
{{ define "main" }}
<section class="home-intro">
{{ .Content }}
</section>
{{ with where site.RegularPages "Section" "aktuelles" }}
<section class="home-news">
<h2>Aktuelles</h2>
<div class="post-grid">
{{ range first 3 . }}
{{ partial "post-card.html" . }}
{{ end }}
</div>
<p class="more-link"><a href="{{ "/aktuelles/" | relURL }}">Alle Beiträge →</a></p>
</section>
{{ end }}
{{ with hugo.Data.members }}
<section class="home-members">
<h2>Unsere Mitglieder</h2>
<ul class="member-grid">
{{ range . }}
<li class="member">
{{ with resources.Get .image }}
{{ $p := .Fill "240x240 Center webp q80" }}
<img src="{{ $p.RelPermalink }}" width="{{ $p.Width }}" height="{{ $p.Height }}"
alt="{{ $.Title }}" loading="lazy" decoding="async">
{{ end }}
<span class="member-name">{{ .name }}</span>
</li>
{{ end }}
</ul>
</section>
{{ end }}
{{ with hugo.Data.memoriam }}
<section class="home-memoriam">
<h2>In Erinnerung</h2>
<ul class="memoriam-list">
{{ range . }}
<li class="memoriam">
{{ with resources.Get .image }}
{{ $p := .Fill "200x200 Center webp q80" }}
<img src="{{ $p.RelPermalink }}" width="{{ $p.Width }}" height="{{ $p.Height }}"
alt="" loading="lazy" decoding="async">
{{ end }}
<span class="memoriam-name">{{ .name }}</span>
<span class="memoriam-date">✝ {{ .died }}</span>
</li>
{{ end }}
</ul>
</section>
{{ end }}
{{ end }}
+26
View File
@@ -0,0 +1,26 @@
{{ define "main" }}
<div class="page">
<header class="page-header">
<h1>{{ .Title }}</h1>
</header>
{{ with .Content }}<div class="prose">{{ . }}</div>{{ end }}
{{ $pages := .RegularPages }}
{{ if $pages }}
{{ if eq .Section "aktuelles" }}
<div class="post-grid">
{{ range $pages }}
{{ partial "post-card.html" . }}
{{ end }}
</div>
{{ else }}
<ul class="page-list">
{{ range $pages.ByTitle }}
<li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end }}
</ul>
{{ end }}
{{ end }}
</div>
{{ end }}
+26
View File
@@ -0,0 +1,26 @@
{{ define "main" }}
<article class="page">
<header class="page-header">
<h1>{{ .Title }}</h1>
{{ if eq .Section "aktuelles" }}
<p class="page-meta">
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date | time.Format site.Params.dateFormat }}</time>
{{ with .Params.tags }}
<span class="tags">{{ range . }}<span class="tag">{{ . }}</span>{{ end }}</span>
{{ end }}
</p>
{{ end }}
</header>
<div class="prose">
{{ .Content }}
</div>
{{ if eq .Section "aktuelles" }}
<nav class="post-nav" aria-label="Weitere Beiträge">
{{ with .PrevInSection }}<a class="prev" href="{{ .RelPermalink }}">← {{ .Title }}</a>{{ end }}
{{ with .NextInSection }}<a class="next" href="{{ .RelPermalink }}">{{ .Title }} →</a>{{ end }}
</nav>
{{ end }}
</article>
{{ end }}
+29
View File
@@ -0,0 +1,29 @@
{{ define "main" }}
<div class="page">
<header class="page-header">
<h1>{{ .Title }}</h1>
</header>
{{ with .Content }}<div class="prose">{{ . }}</div>{{ end }}
{{ $years := slice }}
{{ range $year, $_ := hugo.Data.termine }}{{ $years = $years | append $year }}{{ end }}
{{ $years = sort $years "value" "desc" }}
{{ $current := index $years 0 }}
{{ range $years }}
{{ $rows := index hugo.Data.termine . }}
{{ if eq . $current }}
<section class="termine-current">
<h2>Termine {{ . }}</h2>
{{ partial "termine-table.html" $rows }}
</section>
{{ else }}
<details class="termine-past">
<summary>Termine {{ . }}</summary>
{{ partial "termine-table.html" $rows }}
</details>
{{ end }}
{{ end }}
</div>
{{ end }}