diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..017ddc4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Normalise to LF in the repository. The CI runner is Linux; shell scripts inside +# the Gitea workflow break if they are checked out with CRLF. +* text=auto eol=lf + +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.webp binary +*.pdf binary diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..e6a46bd --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,101 @@ +name: Build & Deploy + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + inputs: + environment: + description: 'Deployment target' + required: true + default: 'development' + type: choice + options: + - development + - production + +env: + HUGO_VERSION: '0.164.0' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Hugo reads git info for lastmod + + - name: Install Hugo ${{ env.HUGO_VERSION }} + run: | + set -euo pipefail + curl -sSL -o /tmp/hugo.deb \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-amd64.deb" + sudo dpkg -i /tmp/hugo.deb + hugo version + + - name: Resolve target + id: target + run: | + set -euo pipefail + if [ "${{ github.event_name }}" = "workflow_dispatch" ] \ + && [ "${{ inputs.environment }}" = "production" ]; then + echo "base_url=https://motorradclub-giebelwald.de/" >> "$GITHUB_OUTPUT" + echo "deploy_path=${{ secrets.DEPLOY_PATH_PROD }}" >> "$GITHUB_OUTPUT" + echo "name=production" >> "$GITHUB_OUTPUT" + else + echo "base_url=https://mcg.luxick.de/" >> "$GITHUB_OUTPUT" + echo "deploy_path=${{ secrets.DEPLOY_PATH_DEV }}" >> "$GITHUB_OUTPUT" + echo "name=development" >> "$GITHUB_OUTPUT" + fi + + - name: Build + run: | + set -euo pipefail + hugo --gc --minify --baseURL "${{ steps.target.outputs.base_url }}" + + # Pull requests are built to catch breakage, but never published. + - name: Deploy to ${{ steps.target.outputs.name }} + if: github.event_name != 'pull_request' + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }} + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_PATH: ${{ steps.target.outputs.deploy_path }} + run: | + set -euo pipefail + + # rsync runs with --delete, so the destination becomes an exact mirror + # of public/. Refuse to run against an unset, root or shallow path. + case "${DEPLOY_PATH:-}" in + ""|"/"|"/root"|"/home"|"/var"|"/etc"|"/usr"|"/srv"|"/opt") + echo "::error::DEPLOY_PATH is unset or unsafe: '${DEPLOY_PATH:-}'" >&2 + exit 1 + ;; + esac + if [ "$(printf '%s' "$DEPLOY_PATH" | tr -cd '/' | wc -c)" -lt 2 ]; then + echo "::error::DEPLOY_PATH '$DEPLOY_PATH' is too shallow to delete into" >&2 + exit 1 + fi + if [ ! -f public/index.html ]; then + echo "::error::public/index.html missing - refusing to deploy an empty build" >&2 + exit 1 + fi + + sudo apt-get update -qq && sudo apt-get install -y -qq rsync openssh-client + + mkdir -p ~/.ssh + chmod 700 ~/.ssh + printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/id_deploy + chmod 600 ~/.ssh/id_deploy + printf '%s\n' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts + + rsync -az --delete --checksum \ + -e "ssh -i ~/.ssh/id_deploy -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes" \ + public/ "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/" + + rm -f ~/.ssh/id_deploy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a8cf0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Hugo build output +/public/ +/resources/_gen/ +/.hugo_build.lock + +# Migration scratch space: originals pulled off the old WordPress host. +# Only the files that get copied into content/, assets/ and static/ are committed. +/.cache/ + +__pycache__/ +*.py[cod] diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..90eac5a --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,113 @@ +# WordPress → Hugo Migration + +Record of the one-time import from the WordPress export at +`wordpress-export/motorradclubgiebelwaldev.WordPress.2026-07-28.xml` +(WordPress 7.0.2, HitMag theme, TablePress, `widgets_on_pages`). + +## Result + +| | | +|---|---| +| Content files | 26 (9 pages + 17 posts) | +| Media placed | 127 / 165 attachments | +| Internal links checked | 718, 0 broken | +| Stale `wp-content/` URLs | 0 | + +Re-verify at any time with: + +```sh +hugo --gc --minify +python tools/verify_migration.py +``` + +## URL changes + +Old permalinks were **not** preserved (deliberate — no redirects were required). + +| Old | New | +|---|---| +| `/2026/01/13/29-o-fahrt-zum-50-jubilaum/` | `/aktuelles/29-o-fahrt-zum-50-jubilaeum/` | +| `/2025/01/03/termin/` | `/aktuelles/28-o-fahrt-2025/` | +| `/2022/06/09/o-fahrt-2019/` | `/aktuelles/25-o-fahrt-2022/` | +| `/40-jahre-mcg/` | `/archiv/40-jahre-mcg/` | +| `/familenausfahrt-2019/` | `/archiv/familienausfahrt-2019/` | +| `/bilder-familienausfahrt/` | `/archiv/bilder-familienausfahrt/` | +| `/familienfahrt-2022/` | `/archiv/familienfahrt-2022/` | +| `/vergangene-o-fahren/` | `/archiv/vergangene-o-fahrten/` | +| `/aktuelles/`, `/termine/`, `/downloads/`, `/impressum/` | unchanged | + +Two post slugs were misleading in WordPress and were renamed to match their own +titles: `o-fahrt-2019` → `25-o-fahrt-2022`, `termin` → `28-o-fahrt-2025`. +Umlaut slugs were transliterated (`jubilaum` → `jubilaeum`). + +## What was dropped, and why + +| WP id | Item | Reason | +|---|---|---| +| 3 | "Privacy Policy" | Stock English WordPress template, still a draft, references an old bare IP address. Not usable. The Impressum already carries a `Datenschutz` section, which was migrated verbatim. | +| 12 | "Home" | Body was the placeholder *"Dies ist nur eine Demo Seite"*. The real homepage came from the HitMag magazine template plus widgets. | +| 410 | untitled draft post | Empty. | + +## Things the export did not contain + +The WXR format carries neither media binaries nor widgets. Both were recovered +from the live site before it is retired: + +- **Media** — `tools/fetch_media.py` downloads all 165 uploads, caps them at + 2000 px on the long edge and re-encodes JPEG at q82. Cached in `.cache/` + (gitignored); only files actually used are committed. +- **Widgets** — the homepage *Unsere Mitglieder* grid (20 portraits) and the + sidebar *In Erinnerung* block were WordPress widgets. `tools/build_data.py` + scrapes them off the rendered homepage into `data/members.yaml` and + `data/memoriam.yaml`. + + Note: the WordPress gallery widget **shuffles member order on every request**, + so the original order carried no meaning. Members are now sorted by name. + +## Structural changes + +- **Termine** — the six TablePress tables (`Termine 2020`–`2026`) became + `data/termine.yaml`. WordPress only ever embedded table id 7 (*Termine 2026*) + on one page; the other five were orphaned. The `/termine/` page now shows the + newest year expanded with earlier years in collapsible sections. +- **Aktuelles** — was a page containing `[widgets_on_pages id="1"]`. It is now a + real Hugo section with an RSS feed. +- **Categories** — `uncategorized` and `aktuelles` were noise and were dropped. + `ofahrt` became the tag `O-Fahrt`. + +## Data defects found in the export + +- Post 81 (*Ehrungen von Mitgliedern 2018*) references `wp-image-83`, but + attachment 83 no longer exists — the media record was deleted while the upload + survived. `tools/wp2hugo.py` recovers such references by matching the `src` + filename instead. +- Almost every page ends with an empty `

` paragraph; these are stripped. +- WordPress wrote emphasis with the whitespace *inside* the markers + (`Samstag `), which is not valid CommonMark emphasis. The + converter moves the whitespace outside. + +## Unused media (38 files, downloaded but not committed) + +These exist in the WordPress media library but appear nowhere on the live site. +They are still in `.cache/uploads/` if any are wanted later. + +- **Superseded member portraits (2019)** — `Bene_2`, `Burckhardt_2`, `Christian_2`, + `Christoph_2`, `Christopher_2`, `HeikeF`, `HeikeR`, `Henning`, `JensB`, + `Katrin_2`, `Marcel_2`, `Marvin_2`, `Rainer`, `Steffi_und_Sabine`, `Sven_2`, + `Timo_1`, `Timo_2`, `Udo`, `Uwe`, `Gruppenbildanja`. Replaced by the 2022 + `*_web.jpg` set. +- **Former members** — `Jannik_web.jpg`, `Timo_web.jpg` (present in the library, + removed from the live member grid). +- **Old theme assets** — `title.jpg`, `cropped-title.jpg`, `bg.jpeg`, + `Banner_small_web.jpg` (uncropped), and six `cropped-Emblem_transparent*.png` + logo crops. The site now uses `assets/img/logo.png` and `assets/img/banner.jpg`. +- **Orphans** — `SZ-Artikel.jpg`, `Banner.jpg` (2023), `1-1.jpeg`, `6-scaled.jpg`, + `IMG_20190209_205905-3.jpg`, `jahresfahrt-2019.jpg`. + +## Open items + +- `content/_index.md` carries a **placeholder intro** marked with a `TODO`. There + was no homepage text in WordPress to migrate — please replace it. +- No separate Datenschutzerklärung page exists. The site is fully static with no + cookies, forms or third-party embeds, and the Impressum contains a Datenschutz + section. Worth a legal review before the production cutover. diff --git a/README.md b/README.md new file mode 100644 index 0000000..73e6c7c --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +# motorradclub-giebelwald.de + +Static website of the Motorradclub Giebelwald e.V., built with [Hugo](https://gohugo.io). + +- Development: +- Production: + +Migrated from WordPress in July 2026 — see [MIGRATION.md](MIGRATION.md). + +## Requirements + +Hugo **0.164.0**. The *non-extended* build is sufficient: the site uses plain CSS, +no Sass. + +## Local development + +```sh +hugo server -D # http://localhost:1313 +hugo --gc --minify # production build into public/ +``` + +## Editing content + +Everything lives in Markdown under `content/`, edited via git (locally or in +Gitea's web editor). + +### Add a news post + +Create `content/aktuelles//index.md`: + +```markdown +--- +title: "30. O-Fahrt 2027" +date: 2027-01-15T10:00:00Z +tags: ["O-Fahrt"] +featured: "titelbild.jpg" +--- + +Text … + +{{< gallery "bild1.jpg" "bild2.jpg" >}} +``` + +Drop the images into the same folder. `featured` picks the teaser image; without +it the first image in the folder is used. + +### Add or change a Termin + +Edit `data/termine.yaml`. Newest year is shown expanded, older years collapse: + +```yaml +"2027": + - date: "06.02.2027\n18:30" + description: "Jahreshauptversammlung\nSängerheim Niederndorf" +``` + +### Members / In Erinnerung + +`data/members.yaml` and `data/memoriam.yaml`. Images go in `assets/img/`. + +### Downloads + +Put the PDF in `static/dokumente/` and link it from `content/downloads.md`: + +```markdown +{{< pdf "satzung.pdf" "Unsere Satzung" >}} +``` + +## Shortcodes + +| Shortcode | Purpose | +|---|---| +| `{{< gallery "a.jpg" "b.jpg" >}}` | Thumbnail grid; no arguments = all images in the bundle | +| `{{< figure "bild.jpg" "Bildunterschrift" >}}` | Single image with caption | +| `{{< pdf "datei.pdf" "Titel" >}}` | Download link with file size | + +## Deployment + +`.gitea/workflows/deploy.yml` builds with Hugo and rsyncs `public/` over SSH. + +- Push to `main` → deploys to **mcg.luxick.de** +- Manual *Run workflow* with `environment: production` → deploys to + **motorradclub-giebelwald.de** +- Pull requests are built but never published + +### Required secrets + +Set these in the Gitea repository settings: + +| Secret | Example | +|---|---| +| `SSH_PRIVATE_KEY` | Deploy key with write access to the webroot | +| `SSH_KNOWN_HOSTS` | Output of `ssh-keyscan ` | +| `DEPLOY_HOST` | `server.example.de` | +| `DEPLOY_USER` | `www-deploy` | +| `DEPLOY_PATH_DEV` | `/var/www/mcg-dev` | +| `DEPLOY_PATH_PROD` | `/var/www/mcg` | + +rsync runs with `--delete`, so each `DEPLOY_PATH_*` must point at a directory +owned solely by this site. The workflow refuses to deploy if the path is unset, +too shallow, or if the build produced no `index.html`. + +## Migration tooling + +`tools/` holds the one-shot WordPress importer, kept so the import stays +reproducible and reviewable. It is not part of the build. + +```sh +python tools/fetch_media.py # download uploads from the live WP host +python tools/build_data.py # termine / members / memoriam +python tools/wp2hugo.py # content bundles (won't overwrite; --force to do so) +python tools/verify_migration.py # inventory + link checks (run after `hugo`) +``` + +Requires Python 3.11+ and Pillow. diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..63dd219 --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,378 @@ +/* Motorradclub Giebelwald e.V. + Plain CSS on purpose: the project uses the non-extended Hugo binary, which + cannot compile Sass. */ + +:root { + --ink: #404040; /* carried over from the old HitMag custom CSS */ + --ink-soft: #6b6b6b; + --ink-faint: #8c8c8c; + --accent: #9c1c1c; + --accent-dark: #741414; + --bg: #ffffff; + --bg-alt: #f5f4f2; + --border: #e2e0dc; + + --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --measure: 68ch; + --radius: 6px; + --shadow: 0 1px 3px rgb(0 0 0 / 8%), 0 4px 12px rgb(0 0 0 / 6%); + + --space-1: 0.5rem; + --space-2: 1rem; + --space-3: 1.5rem; + --space-4: 2.5rem; + --space-5: 4rem; +} + +*, *::before, *::after { box-sizing: border-box; } + +html { -webkit-text-size-adjust: 100%; } + +body { + margin: 0; + font-family: var(--font); + font-size: 1.0625rem; + line-height: 1.65; + color: var(--ink); + background: var(--bg); +} + +img { max-width: 100%; height: auto; } + +a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; } +a:hover { color: var(--accent-dark); } + +h1, h2, h3, h4 { line-height: 1.25; font-weight: 700; } +h1 { font-size: clamp(1.75rem, 1.2rem + 2vw, 2.5rem); margin: 0 0 var(--space-2); } +h2 { font-size: clamp(1.35rem, 1.1rem + 1vw, 1.75rem); margin: var(--space-4) 0 var(--space-2); } +h3 { font-size: 1.2rem; margin: var(--space-3) 0 var(--space-1); } + +.container { + width: min(100% - 2rem, 68rem); + margin-inline: auto; +} + +.skip-link { + position: absolute; + left: -9999px; + background: var(--accent); + color: #fff; + padding: var(--space-1) var(--space-2); + z-index: 100; +} +.skip-link:focus { left: var(--space-2); top: var(--space-2); } + +:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; } + +/* ---------------------------------------------------------------- header */ + +.site-header { border-bottom: 1px solid var(--border); } + +.site-branding { + display: flex; + align-items: center; + gap: var(--space-2); + padding-block: var(--space-2); +} + +.site-logo img { display: block; width: 72px; height: auto; } + +.site-titles { min-width: 0; } + +.site-title { + margin: 0; + font-size: clamp(1.1rem, 0.9rem + 1.2vw, 1.6rem); + font-weight: 700; + letter-spacing: -0.01em; +} +.site-title a { color: var(--ink); text-decoration: none; } + +.site-tagline { + margin: 0; + color: var(--ink-faint); + font-size: 0.95rem; +} + +.site-banner { + display: block; + width: 100%; + max-height: 320px; + object-fit: cover; +} + +/* ------------------------------------------------------------------ nav */ + +.site-nav { + background: var(--ink); + position: sticky; + top: 0; + z-index: 20; +} + +.nav-toggle { position: absolute; opacity: 0; pointer-events: none; } + +.nav-toggle-label { + display: none; + padding: var(--space-2) 0; + color: #fff; + font-weight: 600; + cursor: pointer; +} + +.nav-list { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); + list-style: none; + margin: 0; + padding: 0; +} + +.nav-list a { + display: block; + padding: 0.85rem 0; + color: #fff; + text-decoration: none; + font-weight: 600; + font-size: 0.95rem; + border-bottom: 3px solid transparent; +} +.nav-list a:hover { color: #fff; border-bottom-color: rgb(255 255 255 / 45%); } +.nav-list a[aria-current="page"] { border-bottom-color: #fff; } + +@media (max-width: 640px) { + .nav-toggle-label { display: block; } + .nav-list { + display: none; + flex-direction: column; + gap: 0; + padding-bottom: var(--space-2); + } + .nav-toggle:checked ~ .nav-list { display: flex; } + .nav-list a { padding: 0.6rem 0; border-bottom: 0; } + .nav-list a[aria-current="page"] { text-decoration: underline; } + .nav-toggle:focus-visible ~ .nav-toggle-label { outline: 3px solid #fff; } +} + +/* ----------------------------------------------------------------- main */ + +.site-main { padding-block: var(--space-4) var(--space-5); } + +.page-header { margin-bottom: var(--space-3); } + +.page-meta { + margin: 0; + color: var(--ink-faint); + font-size: 0.9rem; +} + +.tag { + display: inline-block; + margin-left: var(--space-1); + padding: 0.1rem 0.5rem; + background: var(--bg-alt); + border-radius: 999px; + font-size: 0.8rem; +} + +.prose { max-width: var(--measure); } +.prose > * + * { margin-top: var(--space-2); } +.prose ul, .prose ol { padding-left: 1.4em; } +.prose hr { border: 0; border-top: 1px solid var(--border); margin-block: var(--space-4); } + +.content-figure { margin: var(--space-3) 0; } +.content-figure img { border-radius: var(--radius); display: block; } +.content-figure figcaption { + margin-top: var(--space-1); + color: var(--ink-faint); + font-size: 0.9rem; +} + +/* Gallery and post grid escape the reading measure. */ +.prose .gallery { max-width: none; } + +/* -------------------------------------------------------------- gallery */ + +.gallery { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); + gap: var(--space-1); + margin: var(--space-3) 0; +} + +.gallery-item { display: block; border-radius: var(--radius); overflow: hidden; } +.gallery-item img { + display: block; + width: 100%; + aspect-ratio: 4 / 3; + object-fit: cover; + transition: transform 180ms ease; +} +.gallery-item:hover img { transform: scale(1.04); } + +@media (prefers-reduced-motion: reduce) { + .gallery-item img { transition: none; } + .gallery-item:hover img { transform: none; } +} + +/* ---------------------------------------------------------- post grids */ + +.post-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); + gap: var(--space-3); + margin-top: var(--space-2); +} + +.post-card { + display: flex; + flex-direction: column; + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} +.post-card:hover { box-shadow: var(--shadow); } + +.post-card-image img { display: block; width: 100%; aspect-ratio: 8 / 5; object-fit: cover; } + +.post-card-body { padding: var(--space-2); } +.post-card-title { margin: 0 0 0.25rem; font-size: 1.1rem; } +.post-card-title a { color: var(--ink); text-decoration: none; } +.post-card-title a:hover { color: var(--accent); } +.post-card-meta { margin: 0 0 var(--space-1); color: var(--ink-faint); font-size: 0.85rem; } +.post-card-summary { margin: 0; color: var(--ink-soft); font-size: 0.95rem; } + +.more-link { margin-top: var(--space-2); font-weight: 600; } + +.page-list { padding-left: 1.2em; } +.page-list li { margin-bottom: var(--space-1); } + +.post-nav { + display: flex; + justify-content: space-between; + gap: var(--space-2); + margin-top: var(--space-4); + padding-top: var(--space-2); + border-top: 1px solid var(--border); + font-size: 0.95rem; +} +.post-nav .next { margin-left: auto; text-align: right; } + +/* ---------------------------------------------------------------- home */ + +.home-intro { max-width: var(--measure); } + +.member-grid, .memoriam-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(120px, 100%), 1fr)); + gap: var(--space-2); + list-style: none; + margin: var(--space-2) 0 0; + padding: 0; +} + +.member, .memoriam { text-align: center; } + +.member img, .memoriam img { + display: block; + width: 100%; + aspect-ratio: 1; + object-fit: cover; + border-radius: 50%; +} + +.member-name, .memoriam-name { + display: block; + margin-top: var(--space-1); + font-weight: 600; + font-size: 0.9rem; +} + +.memoriam-list { grid-template-columns: repeat(auto-fill, minmax(min(140px, 100%), 1fr)); } +.memoriam-date { display: block; color: var(--ink-faint); font-size: 0.85rem; } + +/* ------------------------------------------------------------- termine */ + +.termine { + width: 100%; + border-collapse: collapse; + margin: var(--space-2) 0; +} + +.termine th, .termine td { + padding: 0.7rem var(--space-1); + text-align: left; + vertical-align: top; + border-bottom: 1px solid var(--border); +} + +.termine th { + white-space: nowrap; + font-weight: 700; + width: 1%; + padding-right: var(--space-2); +} + +.termine tr:nth-child(odd) { background: var(--bg-alt); } + +.termine-past { + margin-top: var(--space-2); + border-top: 1px solid var(--border); + padding-top: var(--space-1); +} +.termine-past summary { + cursor: pointer; + font-weight: 600; + color: var(--ink-soft); + padding: var(--space-1) 0; +} + +@media (max-width: 520px) { + .termine th { white-space: normal; } +} + +/* --------------------------------------------------------------- files */ + +.pdf-link { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2); + margin-bottom: var(--space-1); + border: 1px solid var(--border); + border-radius: var(--radius); + text-decoration: none; + color: var(--ink); +} +.pdf-link:hover { border-color: var(--accent); color: var(--accent); } + +.pdf-icon { + flex: none; + padding: 0.2rem 0.45rem; + background: var(--accent); + color: #fff; + border-radius: 3px; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.05em; +} + +.pdf-label { font-weight: 600; } +.pdf-size { margin-left: auto; color: var(--ink-faint); font-size: 0.85rem; } + +/* -------------------------------------------------------------- footer */ + +.site-footer { + border-top: 1px solid var(--border); + background: var(--bg-alt); + padding-block: var(--space-3); + font-size: 0.9rem; + color: var(--ink-soft); +} + +.footer-contact { margin: 0 0 var(--space-1); } + +.footer-nav { display: flex; gap: var(--space-2); margin-bottom: var(--space-1); } + +.footer-copyright { margin: 0; color: var(--ink-faint); } diff --git a/assets/img/banner.jpg b/assets/img/banner.jpg new file mode 100644 index 0000000..ea934d4 Binary files /dev/null and b/assets/img/banner.jpg differ diff --git a/assets/img/benedikt-web.jpg b/assets/img/benedikt-web.jpg new file mode 100644 index 0000000..4a5c0ec Binary files /dev/null and b/assets/img/benedikt-web.jpg differ diff --git a/assets/img/bild-2.jpg b/assets/img/bild-2.jpg new file mode 100644 index 0000000..7d16ef4 Binary files /dev/null and b/assets/img/bild-2.jpg differ diff --git a/assets/img/burkhard-web.jpg b/assets/img/burkhard-web.jpg new file mode 100644 index 0000000..d683221 Binary files /dev/null and b/assets/img/burkhard-web.jpg differ diff --git a/assets/img/chris-web.jpg b/assets/img/chris-web.jpg new file mode 100644 index 0000000..2d7b4f2 Binary files /dev/null and b/assets/img/chris-web.jpg differ diff --git a/assets/img/christoph-web.jpg b/assets/img/christoph-web.jpg new file mode 100644 index 0000000..7415c2a Binary files /dev/null and b/assets/img/christoph-web.jpg differ diff --git a/assets/img/diuksan-web.jpg b/assets/img/diuksan-web.jpg new file mode 100644 index 0000000..43fa0ac Binary files /dev/null and b/assets/img/diuksan-web.jpg differ diff --git a/assets/img/harry-2.jpg b/assets/img/harry-2.jpg new file mode 100644 index 0000000..0938419 Binary files /dev/null and b/assets/img/harry-2.jpg differ diff --git a/assets/img/heike-web.jpg b/assets/img/heike-web.jpg new file mode 100644 index 0000000..a417e59 Binary files /dev/null and b/assets/img/heike-web.jpg differ diff --git a/assets/img/henning-web.jpg b/assets/img/henning-web.jpg new file mode 100644 index 0000000..23bbff8 Binary files /dev/null and b/assets/img/henning-web.jpg differ diff --git a/assets/img/jasmina-web.jpg b/assets/img/jasmina-web.jpg new file mode 100644 index 0000000..97f8c0b Binary files /dev/null and b/assets/img/jasmina-web.jpg differ diff --git a/assets/img/kai-2.jpg b/assets/img/kai-2.jpg new file mode 100644 index 0000000..e132162 Binary files /dev/null and b/assets/img/kai-2.jpg differ diff --git a/assets/img/katrin-web.jpg b/assets/img/katrin-web.jpg new file mode 100644 index 0000000..aa728d8 Binary files /dev/null and b/assets/img/katrin-web.jpg differ diff --git a/assets/img/logo.png b/assets/img/logo.png new file mode 100644 index 0000000..78746a8 Binary files /dev/null and b/assets/img/logo.png differ diff --git a/assets/img/lukas-2.jpg b/assets/img/lukas-2.jpg new file mode 100644 index 0000000..90b1a10 Binary files /dev/null and b/assets/img/lukas-2.jpg differ diff --git a/assets/img/lurchi-web.jpg b/assets/img/lurchi-web.jpg new file mode 100644 index 0000000..9714434 Binary files /dev/null and b/assets/img/lurchi-web.jpg differ diff --git a/assets/img/marcel-web.jpg b/assets/img/marcel-web.jpg new file mode 100644 index 0000000..7b5c7b6 Binary files /dev/null and b/assets/img/marcel-web.jpg differ diff --git a/assets/img/marvin-web.jpg b/assets/img/marvin-web.jpg new file mode 100644 index 0000000..a728e4b Binary files /dev/null and b/assets/img/marvin-web.jpg differ diff --git a/assets/img/p1030443.jpg b/assets/img/p1030443.jpg new file mode 100644 index 0000000..4ad5248 Binary files /dev/null and b/assets/img/p1030443.jpg differ diff --git a/assets/img/peter-web.jpg b/assets/img/peter-web.jpg new file mode 100644 index 0000000..6f88c53 Binary files /dev/null and b/assets/img/peter-web.jpg differ diff --git a/assets/img/ralf.jpg b/assets/img/ralf.jpg new file mode 100644 index 0000000..1820bc1 Binary files /dev/null and b/assets/img/ralf.jpg differ diff --git a/assets/img/sabine-web.jpg b/assets/img/sabine-web.jpg new file mode 100644 index 0000000..af60131 Binary files /dev/null and b/assets/img/sabine-web.jpg differ diff --git a/assets/img/silke.jpg b/assets/img/silke.jpg new file mode 100644 index 0000000..1981671 Binary files /dev/null and b/assets/img/silke.jpg differ diff --git a/assets/img/steffi-web.jpg b/assets/img/steffi-web.jpg new file mode 100644 index 0000000..8e35451 Binary files /dev/null and b/assets/img/steffi-web.jpg differ diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..f614803 --- /dev/null +++ b/content/_index.md @@ -0,0 +1,14 @@ +--- +title: "Motorradclub Giebelwald e.V." +--- + + + +Willkommen beim Motorradclub Giebelwald e.V. — gegründet 1976 im Siegerland. + +Wir treffen uns regelmäßig zum Fahren, zum Stammtisch und zu unserem jährlichen +Motorradtreffen mit Orientierungsfahrt an der Grillhütte in Niederndorf. +Alle Termine findet ihr unter [Termine](/termine/), Neuigkeiten unter +[Aktuelles](/aktuelles/). diff --git a/content/aktuelles/25-o-fahrt-2022/img-2846-web.jpg b/content/aktuelles/25-o-fahrt-2022/img-2846-web.jpg new file mode 100644 index 0000000..f81f645 Binary files /dev/null and b/content/aktuelles/25-o-fahrt-2022/img-2846-web.jpg differ diff --git a/content/aktuelles/25-o-fahrt-2022/index.md b/content/aktuelles/25-o-fahrt-2022/index.md new file mode 100644 index 0000000..b07bc00 --- /dev/null +++ b/content/aktuelles/25-o-fahrt-2022/index.md @@ -0,0 +1,37 @@ +--- +title: "25. O-Fahrt 2022" +date: 2022-06-09T16:08:00Z +lastmod: 2022-09-19T16:43:54Z +tags: ["O-Fahrt"] +featured: "logo.png" +--- + +![](img-2846-web.jpg) + +### Chronik 25 Jahre Orientierungsfahrt + +{{< pdf "25-jahre-o-fahrt.pdf" "25 Jahre O-Fahrt" >}} + +### Ergebnisse der Orientierungsfahrt + +{{< pdf "ergebnisliste-fahrer.pdf" "Ergebnisliste-Fahrer" >}} + +{{< pdf "ergebnisliste-sozius.pdf" "Ergebnisliste-Sozius-" >}} + +### Termin + +**01. - 03. Juli 2022** + +Das Treffen und der Start für die O-Fahrt finden an der Grillhütte in + +**57258 Freudenberg-Bühl statt.** + +**Anfahrt:** + +BAB 45 Abfahrt Freudenberg / bei Möbel Zimmermann rechts ab in Richtung Kreuztal bis Ortseingang Bühl fahren. Auf der Kuppe nach rechts abbiegen (Schild MCG) + +Die Orientierungsfahrt selbst findet am **Samstag** den **02. Juli** statt. + +Gestartet wird zwischen **10:00 Uhr und 12:00 Uhr** an der Grillhütte in Bühl. + +Wer sich bereits im Vorfeld anmelden möchte kann, das per Email an [vorstand@motorradclub-giebelwald.de](mailto:vorstand@motorradclub-giebelwald.de) machen. diff --git a/content/aktuelles/25-o-fahrt-2022/logo.png b/content/aktuelles/25-o-fahrt-2022/logo.png new file mode 100644 index 0000000..1131fe4 Binary files /dev/null and b/content/aktuelles/25-o-fahrt-2022/logo.png differ diff --git a/content/aktuelles/26-o-fahrt-2022/1-2.jpeg b/content/aktuelles/26-o-fahrt-2022/1-2.jpeg new file mode 100644 index 0000000..15508c4 Binary files /dev/null and b/content/aktuelles/26-o-fahrt-2022/1-2.jpeg differ diff --git a/content/aktuelles/26-o-fahrt-2022/4-1.jpeg b/content/aktuelles/26-o-fahrt-2022/4-1.jpeg new file mode 100644 index 0000000..6611178 Binary files /dev/null and b/content/aktuelles/26-o-fahrt-2022/4-1.jpeg differ diff --git a/content/aktuelles/26-o-fahrt-2022/5-1.jpeg b/content/aktuelles/26-o-fahrt-2022/5-1.jpeg new file mode 100644 index 0000000..35bd4b1 Binary files /dev/null and b/content/aktuelles/26-o-fahrt-2022/5-1.jpeg differ diff --git a/content/aktuelles/26-o-fahrt-2022/6-banner.jpg b/content/aktuelles/26-o-fahrt-2022/6-banner.jpg new file mode 100644 index 0000000..18d667d Binary files /dev/null and b/content/aktuelles/26-o-fahrt-2022/6-banner.jpg differ diff --git a/content/aktuelles/26-o-fahrt-2022/7-1.jpeg b/content/aktuelles/26-o-fahrt-2022/7-1.jpeg new file mode 100644 index 0000000..554e32e Binary files /dev/null and b/content/aktuelles/26-o-fahrt-2022/7-1.jpeg differ diff --git a/content/aktuelles/26-o-fahrt-2022/8.jpeg b/content/aktuelles/26-o-fahrt-2022/8.jpeg new file mode 100644 index 0000000..6d8981f Binary files /dev/null and b/content/aktuelles/26-o-fahrt-2022/8.jpeg differ diff --git a/content/aktuelles/26-o-fahrt-2022/emblem.png b/content/aktuelles/26-o-fahrt-2022/emblem.png new file mode 100644 index 0000000..b4b12be Binary files /dev/null and b/content/aktuelles/26-o-fahrt-2022/emblem.png differ diff --git a/content/aktuelles/26-o-fahrt-2022/index.md b/content/aktuelles/26-o-fahrt-2022/index.md new file mode 100644 index 0000000..b58b7ce --- /dev/null +++ b/content/aktuelles/26-o-fahrt-2022/index.md @@ -0,0 +1,23 @@ +--- +title: "26. O-Fahrt 2023" +date: 2023-07-01T18:14:00Z +lastmod: 2023-07-15T11:34:03Z +tags: ["O-Fahrt"] +featured: "emblem.png" +--- + +![](6-banner.jpg)Die Sieger der diesjährigen Orientierungsfahrt + +{{< gallery "4-1.jpeg" "1-2.jpeg" "7-1.jpeg" "8.jpeg" "5-1.jpeg" >}} + +#### Liste aller Starter und Platzierungen + +{{< pdf "ranking.pdf" "Ergebnisliste O-Fahrt 2023" >}} + +#### Das Roadbook der Tour zum selbst abfahren + +{{< pdf "roadbook-2023.pdf" "Roadbook 2023" >}} + +#### Chronik 25 Jahre Orientierungsfahrt + +{{< pdf "25-jahre-o-fahrt.pdf" "25 Jahre O-Fahrt" >}} diff --git a/content/aktuelles/27-o-fahrt-2024/index.md b/content/aktuelles/27-o-fahrt-2024/index.md new file mode 100644 index 0000000..b99a361 --- /dev/null +++ b/content/aktuelles/27-o-fahrt-2024/index.md @@ -0,0 +1,23 @@ +--- +title: "27. O-Fahrt 2024" +date: 2023-07-15T11:42:57Z +lastmod: 2024-06-12T08:09:03Z +--- + +Termin + +**05. - 07. Juli 2024** + +Das Treffen und der Start für die O-Fahrt finden an der Grillhütte in + +**57258 Freudenberg-Bühl statt.** + +**Anfahrt:** + +BAB 45 Abfahrt Freudenberg / bei Möbel Zimmermann rechts ab in Richtung Kreuztal bis Ortseingang Bühl fahren. Auf der Kuppe nach rechts abbiegen (Schild MCG) + +Die Orientierungsfahrt selbst findet am **Samstag** den **06. Juli** statt. + +Gestartet wird zwischen **10:00 Uhr und 12:00 Uhr** an der Grillhütte in Bühl. + +Wer sich bereits im Vorfeld anmelden möchte kann, das per Email an [vorstand@motorradclub-giebelwald.de](mailto:vorstand@motorradclub-giebelwald.de) machen. diff --git a/content/aktuelles/28-o-fahrt-2025/emblem.png b/content/aktuelles/28-o-fahrt-2025/emblem.png new file mode 100644 index 0000000..b4b12be Binary files /dev/null and b/content/aktuelles/28-o-fahrt-2025/emblem.png differ diff --git a/content/aktuelles/28-o-fahrt-2025/index.md b/content/aktuelles/28-o-fahrt-2025/index.md new file mode 100644 index 0000000..078cc28 --- /dev/null +++ b/content/aktuelles/28-o-fahrt-2025/index.md @@ -0,0 +1,23 @@ +--- +title: "28. O-Fahrt 2025" +date: 2025-01-03T16:49:54Z +lastmod: 2025-06-05T19:24:29Z +tags: ["O-Fahrt"] +featured: "emblem.png" +--- + +**27. – 29. Juni 2025** + +Das Treffen und der Start für die O-Fahrt finden an der Grillhütte in + +**57258 Freudenberg-Bühl statt.** + +**Anfahrt:** + +BAB 45 Abfahrt Freudenberg / bei Möbel Zimmermann rechts ab in Richtung Kreuztal bis Ortseingang Bühl fahren. Auf der Kuppe nach rechts abbiegen (Schild MCG) + +Die Orientierungsfahrt selbst findet am **Samstag** den **28. Juni** statt. + +Gestartet wird zwischen **10:00 Uhr und 12:00 Uhr** an der Grillhütte in Bühl. + +Wer sich bereits im Vorfeld anmelden möchte kann, das per Email an [vorstand@motorradclub-giebelwald.de](mailto:vorstand@motorradclub-giebelwald.de) machen. diff --git a/content/aktuelles/29-o-fahrt-zum-50-jubilaeum/image.png b/content/aktuelles/29-o-fahrt-zum-50-jubilaeum/image.png new file mode 100644 index 0000000..c5c66eb Binary files /dev/null and b/content/aktuelles/29-o-fahrt-zum-50-jubilaeum/image.png differ diff --git a/content/aktuelles/29-o-fahrt-zum-50-jubilaeum/index.md b/content/aktuelles/29-o-fahrt-zum-50-jubilaeum/index.md new file mode 100644 index 0000000..4e095f4 --- /dev/null +++ b/content/aktuelles/29-o-fahrt-zum-50-jubilaeum/index.md @@ -0,0 +1,22 @@ +--- +title: "29. O-Fahrt zum 50. Jubiläum" +date: 2026-01-13T10:05:22Z +lastmod: 2026-06-13T07:42:53Z +featured: "logo.png" +--- + +**10. – 12. Juli 2026** + +Das Treffen und der Start für die O-Fahrt finden an der Grillhütte in + +**57258 Freudenberg-Niederndorf statt.** + +![](image.png) + +[Google Maps Link](https://maps.app.goo.gl/LL2YBPYZmeE3nnUr9) + +Die Orientierungsfahrt selbst findet am **Samstag** den **11. Juli** statt. + +Gestartet wird zwischen **09:00 Uhr und 11:00 Uhr** an der Grillhütte in Niederndorf. + +Anmeldungen sind erst Samstag morgen möglich. Wer sich allerdings zur Planung schon vorher ankündigen möchte kannd das gerne per Email an [vorstand@motorradclub-giebelwald.de](mailto:vorstand@motorradclub-giebelwald.de) machen. diff --git a/content/aktuelles/29-o-fahrt-zum-50-jubilaeum/logo.png b/content/aktuelles/29-o-fahrt-zum-50-jubilaeum/logo.png new file mode 100644 index 0000000..1131fe4 Binary files /dev/null and b/content/aktuelles/29-o-fahrt-zum-50-jubilaeum/logo.png differ diff --git a/content/aktuelles/600-jahre-niederndorf/1.jpeg b/content/aktuelles/600-jahre-niederndorf/1.jpeg new file mode 100644 index 0000000..078e0c2 Binary files /dev/null and b/content/aktuelles/600-jahre-niederndorf/1.jpeg differ diff --git a/content/aktuelles/600-jahre-niederndorf/2.jpeg b/content/aktuelles/600-jahre-niederndorf/2.jpeg new file mode 100644 index 0000000..f64f592 Binary files /dev/null and b/content/aktuelles/600-jahre-niederndorf/2.jpeg differ diff --git a/content/aktuelles/600-jahre-niederndorf/3.jpeg b/content/aktuelles/600-jahre-niederndorf/3.jpeg new file mode 100644 index 0000000..dd927b2 Binary files /dev/null and b/content/aktuelles/600-jahre-niederndorf/3.jpeg differ diff --git a/content/aktuelles/600-jahre-niederndorf/4.jpeg b/content/aktuelles/600-jahre-niederndorf/4.jpeg new file mode 100644 index 0000000..0004b84 Binary files /dev/null and b/content/aktuelles/600-jahre-niederndorf/4.jpeg differ diff --git a/content/aktuelles/600-jahre-niederndorf/5.jpeg b/content/aktuelles/600-jahre-niederndorf/5.jpeg new file mode 100644 index 0000000..8c91e6a Binary files /dev/null and b/content/aktuelles/600-jahre-niederndorf/5.jpeg differ diff --git a/content/aktuelles/600-jahre-niederndorf/6.jpeg b/content/aktuelles/600-jahre-niederndorf/6.jpeg new file mode 100644 index 0000000..d7cd9fd Binary files /dev/null and b/content/aktuelles/600-jahre-niederndorf/6.jpeg differ diff --git a/content/aktuelles/600-jahre-niederndorf/7.jpeg b/content/aktuelles/600-jahre-niederndorf/7.jpeg new file mode 100644 index 0000000..078e0c2 Binary files /dev/null and b/content/aktuelles/600-jahre-niederndorf/7.jpeg differ diff --git a/content/aktuelles/600-jahre-niederndorf/banner.jpeg b/content/aktuelles/600-jahre-niederndorf/banner.jpeg new file mode 100644 index 0000000..1546b2f Binary files /dev/null and b/content/aktuelles/600-jahre-niederndorf/banner.jpeg differ diff --git a/content/aktuelles/600-jahre-niederndorf/index.md b/content/aktuelles/600-jahre-niederndorf/index.md new file mode 100644 index 0000000..bfd5a77 --- /dev/null +++ b/content/aktuelles/600-jahre-niederndorf/index.md @@ -0,0 +1,10 @@ +--- +title: "600 Jahre Niederndorf" +date: 2023-06-26T09:36:00Z +lastmod: 2023-07-02T09:42:32Z +featured: "banner.jpeg" +--- + +Am 18.06.2023 hat zur Feier des 600 jährigen bestehens von Niederndorf ein "stehender" Umzug an der Reithalle im Ort stattfgefunden. HIer war der Motorradclub natürlich auch vertreten + +{{< gallery "1.jpeg" "2.jpeg" "3.jpeg" "4.jpeg" "5.jpeg" "6.jpeg" "7.jpeg" >}} diff --git a/content/aktuelles/_index.md b/content/aktuelles/_index.md new file mode 100644 index 0000000..78144a0 --- /dev/null +++ b/content/aktuelles/_index.md @@ -0,0 +1,8 @@ +--- +title: "Aktuelles" +date: 2018-08-06T07:41:40Z +lastmod: 2020-06-28T08:19:29Z +--- + +Neuigkeiten aus dem Verein. + diff --git a/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-1.png b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-1.png new file mode 100644 index 0000000..eeeddb5 Binary files /dev/null and b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-1.png differ diff --git a/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-2.png b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-2.png new file mode 100644 index 0000000..f6a9123 Binary files /dev/null and b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-2.png differ diff --git a/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-3.png b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-3.png new file mode 100644 index 0000000..16367c6 Binary files /dev/null and b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-3.png differ diff --git a/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-4.png b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-4.png new file mode 100644 index 0000000..63bef4c Binary files /dev/null and b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-4.png differ diff --git a/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-5.png b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-5.png new file mode 100644 index 0000000..9ed6d59 Binary files /dev/null and b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image-5.png differ diff --git a/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image.png b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image.png new file mode 100644 index 0000000..c5c66eb Binary files /dev/null and b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/image.png differ diff --git a/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/index.md b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/index.md new file mode 100644 index 0000000..b291d6b --- /dev/null +++ b/content/aktuelles/biker-demo-gegen-fahrverbote-am-4-7-2020/index.md @@ -0,0 +1,19 @@ +--- +title: "Biker-Demo gegen Fahrverbote am 4.7.2020" +date: 2020-07-06T09:53:33Z +lastmod: 2020-07-06T09:54:16Z +featured: "image-4.png" +--- + +![](image.png) + +![](image-1.png)**Treffpunkt Siegen-Siegerlandhalle** + +![](image-2.png)**Um 8:15 starteten die Biker durch Siegen Richtung Littfeld** + +![](image-3.png)**Zur Demo auf dem Düsseldorfer Messegelände ging es über Landstraße in + mehreren Gruppen.** + +![](image-4.png)**Auf dem Messeparkplatz in Düsseldorf fanden sich etwa 10 000 Biker ein.** + +![](image-5.png) diff --git a/content/aktuelles/ehrungen-von-mitgliedern/20180317-203519.jpg b/content/aktuelles/ehrungen-von-mitgliedern/20180317-203519.jpg new file mode 100644 index 0000000..4e09f85 Binary files /dev/null and b/content/aktuelles/ehrungen-von-mitgliedern/20180317-203519.jpg differ diff --git a/content/aktuelles/ehrungen-von-mitgliedern/20180317-203949.jpg b/content/aktuelles/ehrungen-von-mitgliedern/20180317-203949.jpg new file mode 100644 index 0000000..bd2deff Binary files /dev/null and b/content/aktuelles/ehrungen-von-mitgliedern/20180317-203949.jpg differ diff --git a/content/aktuelles/ehrungen-von-mitgliedern/index.md b/content/aktuelles/ehrungen-von-mitgliedern/index.md new file mode 100644 index 0000000..453f2e9 --- /dev/null +++ b/content/aktuelles/ehrungen-von-mitgliedern/index.md @@ -0,0 +1,14 @@ +--- +title: "Ehrungen von Mitgliedern 2018" +date: 2018-08-07T08:24:48Z +lastmod: 2020-04-02T15:23:05Z +featured: "20180317-203519.jpg" +--- + +![](20180317-203519.jpg) + +von links: Roland für 40 Jahre; Burkhard für 30 Jahre; Jens und Jürgen für 25 Jahre + +![](20180317-203949.jpg) + +**Siegerehrung der Jahresfahrt 2017** diff --git a/content/aktuelles/grillen-nach-photoshooting/index.md b/content/aktuelles/grillen-nach-photoshooting/index.md new file mode 100644 index 0000000..753ab1d --- /dev/null +++ b/content/aktuelles/grillen-nach-photoshooting/index.md @@ -0,0 +1,10 @@ +--- +title: "Grillen nach Photoshooting" +date: 2022-05-16T13:58:00Z +lastmod: 2022-06-10T14:04:01Z +featured: "whatsapp-image-2022-05-26-at-17-47-06-1.jpeg" +--- + +Am 14.05.2022 Haben wir uns noch einmal in gemütlicher Runde zusammengefunden um am Abend die Reste des Season Openings zu vergrillen. + +![](whatsapp-image-2022-05-26-at-17-47-06.jpeg) diff --git a/content/aktuelles/grillen-nach-photoshooting/whatsapp-image-2022-05-26-at-17-47-06-1.jpeg b/content/aktuelles/grillen-nach-photoshooting/whatsapp-image-2022-05-26-at-17-47-06-1.jpeg new file mode 100644 index 0000000..255c8cc Binary files /dev/null and b/content/aktuelles/grillen-nach-photoshooting/whatsapp-image-2022-05-26-at-17-47-06-1.jpeg differ diff --git a/content/aktuelles/grillen-nach-photoshooting/whatsapp-image-2022-05-26-at-17-47-06.jpeg b/content/aktuelles/grillen-nach-photoshooting/whatsapp-image-2022-05-26-at-17-47-06.jpeg new file mode 100644 index 0000000..255c8cc Binary files /dev/null and b/content/aktuelles/grillen-nach-photoshooting/whatsapp-image-2022-05-26-at-17-47-06.jpeg differ diff --git a/content/aktuelles/kundgebung-gegen-fahrverbote/index.md b/content/aktuelles/kundgebung-gegen-fahrverbote/index.md new file mode 100644 index 0000000..f0b30f1 --- /dev/null +++ b/content/aktuelles/kundgebung-gegen-fahrverbote/index.md @@ -0,0 +1,22 @@ +--- +title: "Kundgebung gegen Fahrverbote" +date: 2020-06-30T14:49:16Z +lastmod: 2020-06-30T14:49:17Z +featured: "kundgebung-landtag-nrw.jpg" +--- + +![](kundgebung-siegen.png) + +Wenn Du Dich, ebenso wie wir, gegen die geplanten Fahrverbote für Motorradfahrer an Sonn- und Feiertagen wendest, dann unterstütze uns bei unserer Kundgebung am **04.07.2020** + +Wir starten an diesem Tag gemeinsam mit unseren Motorrädern + an der Siegerlandhalle (P&R-Parkplatz) und fahren in einem angemeldeten Korso durch + Siegen bis Kreuztal-Krombach + +Von dort kannst Du optional mit uns in Kleingruppen bis zur Hauptveranstaltung nach Düsseldorf fahren. + +Jedes Bike zählt!! Du zählst!! Hilf uns und mach mit!! + +### Weitere Infos + +[https://motorcycles.news/event/kundgebung-gegen-motorradfahrverbote-vor-dem-landtag-nrw/](https://motorcycles.news/event/kundgebung-gegen-motorradfahrverbote-vor-dem-landtag-nrw/) diff --git a/content/aktuelles/kundgebung-gegen-fahrverbote/kundgebung-landtag-nrw.jpg b/content/aktuelles/kundgebung-gegen-fahrverbote/kundgebung-landtag-nrw.jpg new file mode 100644 index 0000000..ac1526b Binary files /dev/null and b/content/aktuelles/kundgebung-gegen-fahrverbote/kundgebung-landtag-nrw.jpg differ diff --git a/content/aktuelles/kundgebung-gegen-fahrverbote/kundgebung-siegen.png b/content/aktuelles/kundgebung-gegen-fahrverbote/kundgebung-siegen.png new file mode 100644 index 0000000..4ca946e Binary files /dev/null and b/content/aktuelles/kundgebung-gegen-fahrverbote/kundgebung-siegen.png differ diff --git a/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0000.jpg b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0000.jpg new file mode 100644 index 0000000..0368090 Binary files /dev/null and b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0000.jpg differ diff --git a/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0001.jpg b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0001.jpg new file mode 100644 index 0000000..f96a25f Binary files /dev/null and b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0001.jpg differ diff --git a/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0002.jpg b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0002.jpg new file mode 100644 index 0000000..3f54989 Binary files /dev/null and b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0002.jpg differ diff --git a/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0003.jpg b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0003.jpg new file mode 100644 index 0000000..3647d84 Binary files /dev/null and b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0003.jpg differ diff --git a/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0004.jpg b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0004.jpg new file mode 100644 index 0000000..7fe9fb4 Binary files /dev/null and b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0004.jpg differ diff --git a/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0005.jpg b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0005.jpg new file mode 100644 index 0000000..2370dad Binary files /dev/null and b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0005.jpg differ diff --git a/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0006.jpg b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0006.jpg new file mode 100644 index 0000000..240ee2c Binary files /dev/null and b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0006.jpg differ diff --git a/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0007.jpg b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0007.jpg new file mode 100644 index 0000000..0fb401b Binary files /dev/null and b/content/aktuelles/mcg-auf-der-rennstrecke/img-20211104-wa0007.jpg differ diff --git a/content/aktuelles/mcg-auf-der-rennstrecke/index.md b/content/aktuelles/mcg-auf-der-rennstrecke/index.md new file mode 100644 index 0000000..509bee3 --- /dev/null +++ b/content/aktuelles/mcg-auf-der-rennstrecke/index.md @@ -0,0 +1,10 @@ +--- +title: "MCG auf der Rennstrecke" +date: 2021-08-10T14:00:00Z +lastmod: 2022-07-01T06:52:45Z +featured: "img-20211104-wa0000.jpg" +--- + +Unser erster Vorsitzender Christopher Lorscheiter zeigt auf der Rennstrecke was er kann + +{{< gallery "img-20211104-wa0000.jpg" "img-20211104-wa0001.jpg" "img-20211104-wa0002.jpg" "img-20211104-wa0003.jpg" "img-20211104-wa0004.jpg" "img-20211104-wa0005.jpg" "img-20211104-wa0006.jpg" "img-20211104-wa0007.jpg" >}} diff --git a/content/aktuelles/mitgliederehrungen-2019/img-20190209-205905-2.jpg b/content/aktuelles/mitgliederehrungen-2019/img-20190209-205905-2.jpg new file mode 100644 index 0000000..6766dfb Binary files /dev/null and b/content/aktuelles/mitgliederehrungen-2019/img-20190209-205905-2.jpg differ diff --git a/content/aktuelles/mitgliederehrungen-2019/img-20190209-210323.jpg b/content/aktuelles/mitgliederehrungen-2019/img-20190209-210323.jpg new file mode 100644 index 0000000..f16a2ba Binary files /dev/null and b/content/aktuelles/mitgliederehrungen-2019/img-20190209-210323.jpg differ diff --git a/content/aktuelles/mitgliederehrungen-2019/index.md b/content/aktuelles/mitgliederehrungen-2019/index.md new file mode 100644 index 0000000..d71dc18 --- /dev/null +++ b/content/aktuelles/mitgliederehrungen-2019/index.md @@ -0,0 +1,18 @@ +--- +title: "Mitgliederehrungen 2019" +date: 2019-03-08T15:12:25Z +lastmod: 2020-04-02T15:22:58Z +featured: "img-20190209-205905-2.jpg" +--- + +Auch dieses Jahr wurden wieder Mitglieder für Ihre langjährige Mitgliedschaft im Verein Geehrt + +![](img-20190209-205905-2.jpg)von links: Uwe und Steffi , Dirk L. und Jens G. (Für 10 Jahre) + +--- + +Den zweiten Platz der Jahresfahrt von 2018 Teilen sich dieses Jahr **Heike und Udo Ratgeber**. + +Der erste Platz geht an **Sven Rehm.** + +![](img-20190209-210323.jpg)von links: Heike R., Sven, Udo diff --git a/content/aktuelles/mitgliederehrungen-2020/10jahre.jpeg b/content/aktuelles/mitgliederehrungen-2020/10jahre.jpeg new file mode 100644 index 0000000..d12211c Binary files /dev/null and b/content/aktuelles/mitgliederehrungen-2020/10jahre.jpeg differ diff --git a/content/aktuelles/mitgliederehrungen-2020/index.md b/content/aktuelles/mitgliederehrungen-2020/index.md new file mode 100644 index 0000000..c64270b --- /dev/null +++ b/content/aktuelles/mitgliederehrungen-2020/index.md @@ -0,0 +1,18 @@ +--- +title: "Mitgliederehrungen 2020" +date: 2020-04-02T15:20:30Z +lastmod: 2020-04-02T15:22:41Z +featured: "10jahre.jpeg" +--- + +Auch dieses Jahr wurden wieder Mitglieder für Ihre langjährige Mitgliedschaft im Verein Geehrt + +![](10jahre.jpeg)von links nach rechts: Marcel Fries, Kai Dietzel, Peter Uebach + +Für langjährige Mitgliedschaft im Verein wurden dieses Jahr geehrt: + +Marcel Fries für **10 Jahre** Mitgliedschaft + +Kai Dietzel für **30 Jahre** Mitgliedschaft + +Peter Uebach für **25 Jahre** Mitgliedschaft diff --git a/content/aktuelles/mitgliederehrungen-2022/ehrungen1.jpg b/content/aktuelles/mitgliederehrungen-2022/ehrungen1.jpg new file mode 100644 index 0000000..f72af46 Binary files /dev/null and b/content/aktuelles/mitgliederehrungen-2022/ehrungen1.jpg differ diff --git a/content/aktuelles/mitgliederehrungen-2022/ehrungen2.jpg b/content/aktuelles/mitgliederehrungen-2022/ehrungen2.jpg new file mode 100644 index 0000000..bfe1669 Binary files /dev/null and b/content/aktuelles/mitgliederehrungen-2022/ehrungen2.jpg differ diff --git a/content/aktuelles/mitgliederehrungen-2022/index.md b/content/aktuelles/mitgliederehrungen-2022/index.md new file mode 100644 index 0000000..3d0b726 --- /dev/null +++ b/content/aktuelles/mitgliederehrungen-2022/index.md @@ -0,0 +1,16 @@ +--- +title: "Mitgliederehrungen 2022" +date: 2022-11-20T17:11:53Z +lastmod: 2022-11-20T17:12:27Z +featured: "ehrungen1.jpg" +--- + +Auf der Jahreshauptversammlung im November 2022 wurden zwei Mitglieder des Clubs für ihre langjährige Mitgliedschaft ausgezeichnet. + +![](ehrungen1.jpg) + +Christiane (links im Bild) wird für **30 Jahre** Mitgliedschaft im Verein geehrt + +![](ehrungen2.jpg) + +Marvin (links im Bild) wird für **10 Jahre** Mitgliedschaft im Verein geehrt diff --git a/content/aktuelles/regelmaessiger-stammtisch/index.md b/content/aktuelles/regelmaessiger-stammtisch/index.md new file mode 100644 index 0000000..02d84be --- /dev/null +++ b/content/aktuelles/regelmaessiger-stammtisch/index.md @@ -0,0 +1,9 @@ +--- +title: "Regelmäßger Stammtisch" +date: 2022-09-19T16:20:42Z +lastmod: 2022-09-19T16:20:43Z +--- + +Ab dem 5. Oktober wird ein regelmäßiger Stammtisch im Backes in Niederndorf stattfinden. + +Der Termin ist der **erste Mittwoch** des Monats um **19:00 Uhr** diff --git a/content/aktuelles/saison-abschlussgrillen/index.md b/content/aktuelles/saison-abschlussgrillen/index.md new file mode 100644 index 0000000..d2da384 --- /dev/null +++ b/content/aktuelles/saison-abschlussgrillen/index.md @@ -0,0 +1,10 @@ +--- +title: "Saison Abschlussgrillen" +date: 2022-09-19T16:23:35Z +--- + +Am 15. Oktober findet das Abschlussgrillen für die Saison 2022 statt. + +Wir Treffen uns um 19:00 Uhr bei Lurchi. + +Grillgut ist noch reichlich vorhanden, Getränke werden besorgt. diff --git a/content/aktuelles/vorankuendigung-mcg-wochenende-2024/index.md b/content/aktuelles/vorankuendigung-mcg-wochenende-2024/index.md new file mode 100644 index 0000000..13213ed --- /dev/null +++ b/content/aktuelles/vorankuendigung-mcg-wochenende-2024/index.md @@ -0,0 +1,15 @@ +--- +title: "Vorankündigung MCG Wochenende 2024" +date: 2023-03-11T10:03:48Z +featured: "whatsapp-image-2022-05-26-at-17-47-34.jpeg" +--- + +![](whatsapp-image-2022-05-26-at-17-47-34.jpeg) + +*Save the Date!* + +Das MCG Wochenende im Haus Marienmünster in Hövelhof wird nächstes Jahr an folgendem Datum stattfinden: + +**09.-12. Mai 2024** Wochenende Himmelfahrt + +Weitere Infos und Anmeldung folgen im laufe des Jahres diff --git a/content/aktuelles/vorankuendigung-mcg-wochenende-2024/whatsapp-image-2022-05-26-at-17-47-34.jpeg b/content/aktuelles/vorankuendigung-mcg-wochenende-2024/whatsapp-image-2022-05-26-at-17-47-34.jpeg new file mode 100644 index 0000000..85b8621 Binary files /dev/null and b/content/aktuelles/vorankuendigung-mcg-wochenende-2024/whatsapp-image-2022-05-26-at-17-47-34.jpeg differ diff --git a/content/archiv/40-jahre-mcg/40jmcg-01.jpg b/content/archiv/40-jahre-mcg/40jmcg-01.jpg new file mode 100644 index 0000000..5b9ce05 Binary files /dev/null and b/content/archiv/40-jahre-mcg/40jmcg-01.jpg differ diff --git a/content/archiv/40-jahre-mcg/40jmcg-02.jpg b/content/archiv/40-jahre-mcg/40jmcg-02.jpg new file mode 100644 index 0000000..73d0006 Binary files /dev/null and b/content/archiv/40-jahre-mcg/40jmcg-02.jpg differ diff --git a/content/archiv/40-jahre-mcg/40jmcg-03.jpg b/content/archiv/40-jahre-mcg/40jmcg-03.jpg new file mode 100644 index 0000000..2ae7d32 Binary files /dev/null and b/content/archiv/40-jahre-mcg/40jmcg-03.jpg differ diff --git a/content/archiv/40-jahre-mcg/40jmcg-05.jpg b/content/archiv/40-jahre-mcg/40jmcg-05.jpg new file mode 100644 index 0000000..813e4ad Binary files /dev/null and b/content/archiv/40-jahre-mcg/40jmcg-05.jpg differ diff --git a/content/archiv/40-jahre-mcg/40jmcg-06.jpg b/content/archiv/40-jahre-mcg/40jmcg-06.jpg new file mode 100644 index 0000000..5ecf148 Binary files /dev/null and b/content/archiv/40-jahre-mcg/40jmcg-06.jpg differ diff --git a/content/archiv/40-jahre-mcg/40jmcg-07.jpg b/content/archiv/40-jahre-mcg/40jmcg-07.jpg new file mode 100644 index 0000000..6d9a2c1 Binary files /dev/null and b/content/archiv/40-jahre-mcg/40jmcg-07.jpg differ diff --git a/content/archiv/40-jahre-mcg/40jmcg-08.jpg b/content/archiv/40-jahre-mcg/40jmcg-08.jpg new file mode 100644 index 0000000..34cd2a7 Binary files /dev/null and b/content/archiv/40-jahre-mcg/40jmcg-08.jpg differ diff --git a/content/archiv/40-jahre-mcg/40jmcg-09.jpg b/content/archiv/40-jahre-mcg/40jmcg-09.jpg new file mode 100644 index 0000000..0f4c275 Binary files /dev/null and b/content/archiv/40-jahre-mcg/40jmcg-09.jpg differ diff --git a/content/archiv/40-jahre-mcg/40jmch-04.jpg b/content/archiv/40-jahre-mcg/40jmch-04.jpg new file mode 100644 index 0000000..a8a07d2 Binary files /dev/null and b/content/archiv/40-jahre-mcg/40jmch-04.jpg differ diff --git a/content/archiv/40-jahre-mcg/index.md b/content/archiv/40-jahre-mcg/index.md new file mode 100644 index 0000000..89d1b9e --- /dev/null +++ b/content/archiv/40-jahre-mcg/index.md @@ -0,0 +1,9 @@ +--- +title: "40 Jahre MCG" +date: 2019-01-26T15:57:30Z +lastmod: 2019-01-26T16:00:05Z +--- + +Ein Paar Eindrücke von unser 40 Jahr Feier 2016 in Niederndorf + +{{< gallery "40jmch-04.jpg" "40jmcg-09.jpg" "40jmcg-08.jpg" "40jmcg-07.jpg" "40jmcg-06.jpg" "40jmcg-05.jpg" "40jmcg-03.jpg" "40jmcg-02.jpg" "40jmcg-01.jpg" >}} diff --git a/content/archiv/_index.md b/content/archiv/_index.md new file mode 100644 index 0000000..53077bd --- /dev/null +++ b/content/archiv/_index.md @@ -0,0 +1,5 @@ +--- +title: "Archiv" +--- + +Rückblicke auf vergangene Fahrten, Treffen und Jubiläen des MCG. diff --git a/content/archiv/bilder-familienausfahrt/20190601-154602.jpg b/content/archiv/bilder-familienausfahrt/20190601-154602.jpg new file mode 100644 index 0000000..01375d2 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-154602.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-160802.jpg b/content/archiv/bilder-familienausfahrt/20190601-160802.jpg new file mode 100644 index 0000000..3b4f422 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-160802.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-160809-1.jpg b/content/archiv/bilder-familienausfahrt/20190601-160809-1.jpg new file mode 100644 index 0000000..a3e7393 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-160809-1.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-160809.jpg b/content/archiv/bilder-familienausfahrt/20190601-160809.jpg new file mode 100644 index 0000000..a3e7393 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-160809.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-160817.jpg b/content/archiv/bilder-familienausfahrt/20190601-160817.jpg new file mode 100644 index 0000000..74d6d92 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-160817.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-160837.jpg b/content/archiv/bilder-familienausfahrt/20190601-160837.jpg new file mode 100644 index 0000000..3c6402f Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-160837.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-160842.jpg b/content/archiv/bilder-familienausfahrt/20190601-160842.jpg new file mode 100644 index 0000000..6099ca0 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-160842.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-160915-1.jpg b/content/archiv/bilder-familienausfahrt/20190601-160915-1.jpg new file mode 100644 index 0000000..6d21c46 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-160915-1.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-160915.jpg b/content/archiv/bilder-familienausfahrt/20190601-160915.jpg new file mode 100644 index 0000000..6d21c46 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-160915.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-160930.jpg b/content/archiv/bilder-familienausfahrt/20190601-160930.jpg new file mode 100644 index 0000000..4a763bf Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-160930.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-160932.jpg b/content/archiv/bilder-familienausfahrt/20190601-160932.jpg new file mode 100644 index 0000000..2d8e5ca Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-160932.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-181846.jpg b/content/archiv/bilder-familienausfahrt/20190601-181846.jpg new file mode 100644 index 0000000..2d23a60 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-181846.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-181854.jpg b/content/archiv/bilder-familienausfahrt/20190601-181854.jpg new file mode 100644 index 0000000..78b39a7 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-181854.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-181902.jpg b/content/archiv/bilder-familienausfahrt/20190601-181902.jpg new file mode 100644 index 0000000..c2850e7 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-181902.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-182158.jpg b/content/archiv/bilder-familienausfahrt/20190601-182158.jpg new file mode 100644 index 0000000..73f3e99 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-182158.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190601-182210.jpg b/content/archiv/bilder-familienausfahrt/20190601-182210.jpg new file mode 100644 index 0000000..1514a7d Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190601-182210.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190602-005454.jpg b/content/archiv/bilder-familienausfahrt/20190602-005454.jpg new file mode 100644 index 0000000..e66de97 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190602-005454.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/20190602-005501.jpg b/content/archiv/bilder-familienausfahrt/20190602-005501.jpg new file mode 100644 index 0000000..64c8c45 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/20190602-005501.jpg differ diff --git a/content/archiv/bilder-familienausfahrt/index.md b/content/archiv/bilder-familienausfahrt/index.md new file mode 100644 index 0000000..a1f3cd3 --- /dev/null +++ b/content/archiv/bilder-familienausfahrt/index.md @@ -0,0 +1,9 @@ +--- +title: "Bilder Familienausfahrt" +date: 2019-06-06T14:43:01Z +lastmod: 2019-06-06T15:15:53Z +--- + +{{< gallery "20190601-160915-1.jpg" "20190601-160809-1.jpg" "whatsapp-image-2019-06-04-at-10-55-19.jpeg" "whatsapp-image-2019-06-04-at-10-54-59.jpeg" "20190602-005501.jpg" "20190601-182210.jpg" "20190601-182158.jpg" "20190601-181902.jpg" "20190601-181854.jpg" "20190601-181846.jpg" "20190601-160932.jpg" "20190601-160930.jpg" "20190601-160915.jpg" "20190601-160842.jpg" "20190601-160837.jpg" "20190601-160817.jpg" "20190601-160809.jpg" "20190601-160802.jpg" "20190601-154602.jpg" >}} + +![](20190602-005454.jpg) diff --git a/content/archiv/bilder-familienausfahrt/whatsapp-image-2019-06-04-at-10-54-59.jpeg b/content/archiv/bilder-familienausfahrt/whatsapp-image-2019-06-04-at-10-54-59.jpeg new file mode 100644 index 0000000..3a86986 Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/whatsapp-image-2019-06-04-at-10-54-59.jpeg differ diff --git a/content/archiv/bilder-familienausfahrt/whatsapp-image-2019-06-04-at-10-55-19.jpeg b/content/archiv/bilder-familienausfahrt/whatsapp-image-2019-06-04-at-10-55-19.jpeg new file mode 100644 index 0000000..79e913c Binary files /dev/null and b/content/archiv/bilder-familienausfahrt/whatsapp-image-2019-06-04-at-10-55-19.jpeg differ diff --git a/content/archiv/familienausfahrt-2019/hoevelhof.jpg b/content/archiv/familienausfahrt-2019/hoevelhof.jpg new file mode 100644 index 0000000..f17e0c1 Binary files /dev/null and b/content/archiv/familienausfahrt-2019/hoevelhof.jpg differ diff --git a/content/archiv/familienausfahrt-2019/index.md b/content/archiv/familienausfahrt-2019/index.md new file mode 100644 index 0000000..008e643 --- /dev/null +++ b/content/archiv/familienausfahrt-2019/index.md @@ -0,0 +1,13 @@ +--- +title: "Familenausfahrt 2019" +date: 2019-01-26T16:06:07Z +lastmod: 2019-04-21T08:13:36Z +--- + +Die Familienausfahrt des MCG findet vom 30.5. - 2.6.2019 statt! + +Die Unkostenpauschale für 3 Nächte all incl. beträgt 40,00€ pro Person. Kinder und Jugendliche sind frei. Bettwäsche kann gebucht werden für 5,00€ pro Person. + +Eine verbindliche Buchung bitte bis zum 12.5.2019 bei Henning. + +![](hoevelhof.jpg) diff --git a/content/archiv/familienfahrt-2022/img-20220526-125036-small.jpg b/content/archiv/familienfahrt-2022/img-20220526-125036-small.jpg new file mode 100644 index 0000000..baf289b Binary files /dev/null and b/content/archiv/familienfahrt-2022/img-20220526-125036-small.jpg differ diff --git a/content/archiv/familienfahrt-2022/img-20220526-154321-small.jpg b/content/archiv/familienfahrt-2022/img-20220526-154321-small.jpg new file mode 100644 index 0000000..7dff39d Binary files /dev/null and b/content/archiv/familienfahrt-2022/img-20220526-154321-small.jpg differ diff --git a/content/archiv/familienfahrt-2022/img-20220527-115214-small.jpg b/content/archiv/familienfahrt-2022/img-20220527-115214-small.jpg new file mode 100644 index 0000000..2b17c81 Binary files /dev/null and b/content/archiv/familienfahrt-2022/img-20220527-115214-small.jpg differ diff --git a/content/archiv/familienfahrt-2022/img-20220527-121440-small.jpg b/content/archiv/familienfahrt-2022/img-20220527-121440-small.jpg new file mode 100644 index 0000000..caa15d6 Binary files /dev/null and b/content/archiv/familienfahrt-2022/img-20220527-121440-small.jpg differ diff --git a/content/archiv/familienfahrt-2022/img-20220528-170144-small.jpg b/content/archiv/familienfahrt-2022/img-20220528-170144-small.jpg new file mode 100644 index 0000000..e48ec09 Binary files /dev/null and b/content/archiv/familienfahrt-2022/img-20220528-170144-small.jpg differ diff --git a/content/archiv/familienfahrt-2022/img-20220528-225913-small.jpg b/content/archiv/familienfahrt-2022/img-20220528-225913-small.jpg new file mode 100644 index 0000000..42507e9 Binary files /dev/null and b/content/archiv/familienfahrt-2022/img-20220528-225913-small.jpg differ diff --git a/content/archiv/familienfahrt-2022/img-20220529-002530-small.jpg b/content/archiv/familienfahrt-2022/img-20220529-002530-small.jpg new file mode 100644 index 0000000..54832b9 Binary files /dev/null and b/content/archiv/familienfahrt-2022/img-20220529-002530-small.jpg differ diff --git a/content/archiv/familienfahrt-2022/index.md b/content/archiv/familienfahrt-2022/index.md new file mode 100644 index 0000000..5a7ef68 --- /dev/null +++ b/content/archiv/familienfahrt-2022/index.md @@ -0,0 +1,9 @@ +--- +title: "Familienfahrt 2022" +date: 2022-06-10T13:48:15Z +lastmod: 2022-06-10T13:57:46Z +--- + +Nach langer Coronapause konnten wir Christi-Himmelfahrt endlich wieder unsere Familienausfahrt ins Haus Marienmünster nach Hövelhof durführen. + +{{< gallery "img-20220526-125036-small.jpg" "img-20220526-154321-small.jpg" "img-20220527-115214-small.jpg" "img-20220527-121440-small.jpg" "img-20220528-170144-small.jpg" "img-20220528-225913-small.jpg" "img-20220529-002530-small.jpg" "whatsapp-image-2022-05-26-at-17-47-05.jpeg" "whatsapp-image-2022-05-26-at-17-47-07.jpeg" "whatsapp-image-2022-05-26-at-17-47-08-1.jpeg" "whatsapp-image-2022-05-26-at-17-47-34.jpeg" "whatsapp-image-2022-05-27-at-17-50-56.jpeg" "whatsapp-image-2022-05-27-at-17-50-57-1.jpeg" "whatsapp-image-2022-05-27-at-19-25-44.jpeg" "whatsapp-image-2022-05-27-at-19-25-45-1.jpeg" "whatsapp-image-2022-05-27-at-19-25-45.jpeg" "whatsapp-image-2022-05-28-at-23-00-10-1.jpeg" >}} diff --git a/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-05.jpeg b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-05.jpeg new file mode 100644 index 0000000..2009292 Binary files /dev/null and b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-05.jpeg differ diff --git a/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-07.jpeg b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-07.jpeg new file mode 100644 index 0000000..7a4c19f Binary files /dev/null and b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-07.jpeg differ diff --git a/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-08-1.jpeg b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-08-1.jpeg new file mode 100644 index 0000000..a72d8fd Binary files /dev/null and b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-08-1.jpeg differ diff --git a/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-34.jpeg b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-34.jpeg new file mode 100644 index 0000000..85b8621 Binary files /dev/null and b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-26-at-17-47-34.jpeg differ diff --git a/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-17-50-56.jpeg b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-17-50-56.jpeg new file mode 100644 index 0000000..4f12ead Binary files /dev/null and b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-17-50-56.jpeg differ diff --git a/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-17-50-57-1.jpeg b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-17-50-57-1.jpeg new file mode 100644 index 0000000..fb1140d Binary files /dev/null and b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-17-50-57-1.jpeg differ diff --git a/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-19-25-44.jpeg b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-19-25-44.jpeg new file mode 100644 index 0000000..62be8cf Binary files /dev/null and b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-19-25-44.jpeg differ diff --git a/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-19-25-45-1.jpeg b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-19-25-45-1.jpeg new file mode 100644 index 0000000..4963d54 Binary files /dev/null and b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-19-25-45-1.jpeg differ diff --git a/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-19-25-45.jpeg b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-19-25-45.jpeg new file mode 100644 index 0000000..9af7597 Binary files /dev/null and b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-27-at-19-25-45.jpeg differ diff --git a/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-28-at-23-00-10-1.jpeg b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-28-at-23-00-10-1.jpeg new file mode 100644 index 0000000..063a941 Binary files /dev/null and b/content/archiv/familienfahrt-2022/whatsapp-image-2022-05-28-at-23-00-10-1.jpeg differ diff --git a/content/archiv/vergangene-o-fahrten.md b/content/archiv/vergangene-o-fahrten.md new file mode 100644 index 0000000..3eb1c55 --- /dev/null +++ b/content/archiv/vergangene-o-fahrten.md @@ -0,0 +1,27 @@ +--- +title: "Vergangene O-Fahrten" +date: 2018-08-07T09:35:47Z +lastmod: 2019-01-26T16:32:36Z +--- + +### O-Fahrt 2018 + +**23. Orientierungsfahrt** + +**Termin:** + +- **Treffen vom 6.7. - 8.7.2018** +- **O- Fahrt am 7.7.2018 - Start von 10-12 Uhr** +- **WO ?** + +Das Treffen und der Start für die O-Fahrt finden an der Grillhütte in + +**57258 Freudenberg-Bühl statt.** + +**Anfahrt:** + +BAB 45 Abfahrt Freudenberg / bei Möbel Zimmermann rechts ab in Richtung Kreuztal bis Ortseingang Bühl fahren. Auf der Kuppe nach rechts abbiegen (Schild MCG) + +Das Startgeld für die O- Fahrt beträgt 10 Euro. + +--- diff --git a/content/downloads.md b/content/downloads.md new file mode 100644 index 0000000..a11a342 --- /dev/null +++ b/content/downloads.md @@ -0,0 +1,11 @@ +--- +title: "Downloads" +date: 2018-08-21T18:26:41Z +lastmod: 2023-05-15T15:12:02Z +--- + +{{< pdf "mitgliedsantrag.pdf" "Mitgliedsantrag" >}} + +{{< pdf "satzung.pdf" "Unsere Satzung" >}} + +{{< pdf "jahresfahrt-2023.pdf" "Jahresfahrt 2023" >}} diff --git a/content/impressum.md b/content/impressum.md new file mode 100644 index 0000000..1ba5a21 --- /dev/null +++ b/content/impressum.md @@ -0,0 +1,46 @@ +--- +title: "Impressum" +date: 2018-08-21T18:32:03Z +lastmod: 2025-06-26T16:26:56Z +--- + +Angaben gemäß § 5 TMG: + +Motorradclub Giebelwald e.V. + +Vertreten durch: + +Christopher Lohrscheiter - Vorsitzender +Marvin Schliefer - stellv. Vorsitzender + +Kontakt: + +E-Mail: vorstand@motorradclub-giebelwald.de + +Registereintrag: + +Eintragung im Vereinsregister. +Registergericht:Amtsgericht Siegen. +Registernummer: + +Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV: + +Marcel Fries, Feldstr. 6, 57258 Freudenberg, + +Haftungsausschluss: +Haftung für Inhalte + +Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen. Als Dienstanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach § 8 bis 10 TMG sind wir als Dienstanbieter jedoch nicht verpflichtet, Übermittelte oder gespeicherte fremde Informationen zu Überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen. +Haftung für Links + +Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend entfernen. +Urheberrecht + +Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet. Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen. +Datenschutz + +Die Nutzung unserer Webseite ist in der Regel ohne Angabe personenbezogener Daten möglich. Soweit auf unseren Seiten personenbezogene Daten (beispielsweise Name, Anschrift oder eMail-Adressen) erhoben werden, erfolgt dies, soweit möglich, stets auf freiwilliger Basis. Diese Daten werden ohne Ihre ausdrückliche Zustimmung nicht an Dritte weitergegeben. + +Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich. + +Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten durch Dritte zur Übersendung von nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit ausdrücklich widersprochen. Die Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-Mails, vor. diff --git a/content/termine.md b/content/termine.md new file mode 100644 index 0000000..bf94a4d --- /dev/null +++ b/content/termine.md @@ -0,0 +1,9 @@ +--- +title: "Termine" +date: 2018-08-06T14:36:53Z +lastmod: 2026-01-13T10:07:56Z +layout: "termine" +--- + +Unsere Vereinstermine. Änderungen werden hier bekannt gegeben. + diff --git a/data/members.yaml b/data/members.yaml new file mode 100644 index 0000000..74d5c92 --- /dev/null +++ b/data/members.yaml @@ -0,0 +1,43 @@ +# Mitglieder-Galerie der Startseite. +# Aus dem WordPress-Widget übernommen (Widgets sind nicht im XML-Export enthalten). + +- name: "Benedikt" + image: "img/benedikt-web.jpg" +- name: "Burkhard" + image: "img/burkhard-web.jpg" +- name: "Chris" + image: "img/chris-web.jpg" +- name: "Christoph" + image: "img/christoph-web.jpg" +- name: "Diuksan" + image: "img/diuksan-web.jpg" +- name: "Harry" + image: "img/harry-2.jpg" +- name: "Heike" + image: "img/heike-web.jpg" +- name: "Henning" + image: "img/henning-web.jpg" +- name: "Jasmina" + image: "img/jasmina-web.jpg" +- name: "Kai" + image: "img/kai-2.jpg" +- name: "Katrin" + image: "img/katrin-web.jpg" +- name: "Lukas" + image: "img/lukas-2.jpg" +- name: "Lurchi" + image: "img/lurchi-web.jpg" +- name: "Marcel" + image: "img/marcel-web.jpg" +- name: "Marvin" + image: "img/marvin-web.jpg" +- name: "Peter" + image: "img/peter-web.jpg" +- name: "Ralf" + image: "img/ralf.jpg" +- name: "Sabine" + image: "img/sabine-web.jpg" +- name: "Silke" + image: "img/silke.jpg" +- name: "Steffi" + image: "img/steffi-web.jpg" diff --git a/data/memoriam.yaml b/data/memoriam.yaml new file mode 100644 index 0000000..b45cd90 --- /dev/null +++ b/data/memoriam.yaml @@ -0,0 +1,8 @@ +# "In Erinnerung" – ebenfalls aus einem WordPress-Widget übernommen. + +- name: "Roland Groos" + died: "27.5.2020" + image: "img/bild-2.jpg" +- name: "Uwe Müller" + died: "28.3.2020" + image: "img/p1030443.jpg" diff --git a/data/termine.yaml b/data/termine.yaml new file mode 100644 index 0000000..871fbe5 --- /dev/null +++ b/data/termine.yaml @@ -0,0 +1,64 @@ +# Vereinstermine, ursprünglich TablePress-Tabellen aus WordPress. +# Neue Termine: einfach unter dem passenden Jahr ergänzen. + +"2026": + - date: "07.02.2026\n18:30" + description: "Jahreshauptversammlung\nSängerheim Niederndorf" + - date: "10. - 12.07.2026" + description: "Motoradtreffen Grillhütte Niederndorf" + - date: "11.07.2026\n09:00 - 11:00 Uhr" + description: "Start Orientierungsfahrt" + +"2025": + - date: "22.02.2025\n18:30" + description: "Jahreshauptversammlung\nSängerheim Niederndorf" + - date: "27. - 29.06.2025" + description: "Motoradtreffen Grillhütte Bühl" + - date: "28.6.2025\n10:00 - 12:00 Uhr" + description: "Start Orientierungsfahrt" + +"2023": + - date: "11.03" + description: "Jahreshauptversammlung im Sängerheim in Niederndorf" + - date: "07.07 - 09.07" + description: "Treffen und Orientierungsfahrt MCG" + - date: "02.09" + description: "O-Fahrt Motorradfreunden Hövelhof" + +"2022": + - date: "02. April" + description: "Season Opening MCG\nSportheim Niederndorf\n18:30 Uhr" + - date: "14. Mai" + description: "Photoshooting für Homepage um 14:00 Uhr\nParkplatz WURO" + - date: "26. - 29. Mai" + description: "MCG Familienfahrt nach Hövelhof" + - date: "24. - 26. Juni" + description: "MC Waldangelloch" + - date: "01. - 03. Juli" + description: "Motorradtreffen MCG\nFür Mehr Infos Hier Klicken" + - date: "05. Oktober" + description: "1. MCG Stammtisch im Backes in Niederndorf\nDanach immer der 1. Mittwoch des Monats.\nStart um 19:00 Uhr" + - date: "15. Oktober" + description: "Saison Abschlussgrillen bei Lurchi\n19:00 Uhr" + +"2021": + - date: "13. - 16.05.2021" + description: "Familenausfahrt zum Haus Marienmünster in Hövelhof-Liemke" + - date: "01. - 04.07.2021" + description: "Motorradtreffen und O-Fahrt des MCG in Bühl" + - date: "16. - 18.07.2021" + description: "Senne - Rally Motorradfreunde Hövelhof" + +"2020": + - date: "22.01.2020" + description: "Abgabe Jahresfahrt bis" + - date: "15.02.2020" + description: "JHV" + - date: "" + description: "Frauentour" + - date: "19.6. - 21.6.2020" + description: "MC Waldangelloch\n43. Spaßtour" + - date: "3.7 - 5.7.2020" + description: "25. O-Fahrt MCG mit Treffen in Bühl - Verschoben auf 2021" + - date: "29.08. -31.09.2020" + description: "Senne - Rally Motorradfreunde Hövelhof" diff --git a/hugo.toml b/hugo.toml new file mode 100644 index 0000000..e2ee013 --- /dev/null +++ b/hugo.toml @@ -0,0 +1,64 @@ +baseURL = 'https://mcg.luxick.de/' +title = 'Motorradclub Giebelwald e.V.' +locale = 'de-DE' +defaultContentLanguage = 'de' +enableRobotsTXT = true +enableGitInfo = false + +# Nothing here is dated in the future on purpose; keep drafts out of production. +buildDrafts = false +buildFuture = false + +[params] + description = 'Gegründet 1976' + tagline = 'Gegründet 1976' + email = 'vorstand@motorradclub-giebelwald.de' + logo = 'img/logo.png' + banner = 'img/banner.jpg' + dateFormat = '2. January 2006' + +[markup.goldmark.renderer] + # The imported WordPress content contains hand-written HTML (tables, links with + # target attributes) that must survive rendering. + unsafe = true + +[markup.tableOfContents] + startLevel = 2 + endLevel = 3 + +[taxonomies] + tag = 'tags' + +[menus] + [[menus.main]] + name = 'Aktuelles' + pageRef = '/aktuelles' + weight = 10 + [[menus.main]] + name = 'Termine' + pageRef = '/termine' + weight = 20 + [[menus.main]] + name = 'Downloads' + pageRef = '/downloads' + weight = 30 + [[menus.main]] + name = 'Archiv' + pageRef = '/archiv' + weight = 40 + [[menus.main]] + name = 'Impressum' + pageRef = '/impressum' + weight = 50 + +[outputs] + home = ['html', 'rss', 'sitemap'] + section = ['html', 'rss'] + +[services.rss] + limit = 20 + +[privacy] + # No third-party embeds on this site; keep it that way. + [privacy.youtube] + disable = true diff --git a/layouts/_markup/render-image.html b/layouts/_markup/render-image.html new file mode 100644 index 0000000..16c8f27 --- /dev/null +++ b/layouts/_markup/render-image.html @@ -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 -}} +
+ + {{ $alt }} + + {{- with $caption }}
{{ . }}
{{ end -}} +
+{{- else -}} + {{ $alt }} +{{- end -}} diff --git a/layouts/_partials/footer.html b/layouts/_partials/footer.html new file mode 100644 index 0000000..c1044c7 --- /dev/null +++ b/layouts/_partials/footer.html @@ -0,0 +1,13 @@ + diff --git a/layouts/_partials/func/featured-image.html b/layouts/_partials/func/featured-image.html new file mode 100644 index 0000000..261423a --- /dev/null +++ b/layouts/_partials/func/featured-image.html @@ -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 -}} diff --git a/layouts/_partials/head.html b/layouts/_partials/head.html new file mode 100644 index 0000000..cd95a9e --- /dev/null +++ b/layouts/_partials/head.html @@ -0,0 +1,32 @@ + + + + {{ if .IsHome }}{{ site.Title }}{{ else }}{{ .Title }} – {{ site.Title }}{{ end }} + + {{ $description := or .Description .Summary site.Params.description }} + + + + + + + + {{ with partial "func/featured-image.html" . }} + + {{ end }} + + {{ with resources.Get "css/main.css" | minify | fingerprint }} + + {{ end }} + + {{ with resources.Get site.Params.logo }} + {{ $icon := .Resize "180x180 png" }} + + + {{ end }} + + {{ with .OutputFormats.Get "rss" }} + + {{ end }} + + diff --git a/layouts/_partials/header.html b/layouts/_partials/header.html new file mode 100644 index 0000000..80c79ba --- /dev/null +++ b/layouts/_partials/header.html @@ -0,0 +1,38 @@ + diff --git a/layouts/_partials/post-card.html b/layouts/_partials/post-card.html new file mode 100644 index 0000000..90bc3fe --- /dev/null +++ b/layouts/_partials/post-card.html @@ -0,0 +1,17 @@ +{{- /* Teaser card for a news post. Context: the page. */ -}} +
+ {{ with partial "func/featured-image.html" . }} + {{ $thumb := .Fill "480x300 Center webp q80" }} + + {{ end }} +
+

{{ .Title }}

+

+ +

+

{{ .Summary | plainify | truncate 160 }}

+
+
diff --git a/layouts/_partials/termine-table.html b/layouts/_partials/termine-table.html new file mode 100644 index 0000000..066fdf9 --- /dev/null +++ b/layouts/_partials/termine-table.html @@ -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. */ -}} + + + {{ range . }} + + + + + {{ end }} + +
{{ .date | strings.Replace "\n" "
" | safeHTML }}
{{ .description | strings.Replace "\n" "
" | safeHTML }}
diff --git a/layouts/_shortcodes/figure.html b/layouts/_shortcodes/figure.html new file mode 100644 index 0000000..66d207b --- /dev/null +++ b/layouts/_shortcodes/figure.html @@ -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" }} +
+ + {{ $caption | plainify }} + + {{ with $caption }}
{{ . | markdownify }}
{{ end }} +
+{{- else -}} + {{- warnf "figure: %q not found in %s" $src .Page.Path -}} +{{- end -}} diff --git a/layouts/_shortcodes/gallery.html b/layouts/_shortcodes/gallery.html new file mode 100644 index 0000000..79972dc --- /dev/null +++ b/layouts/_shortcodes/gallery.html @@ -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 -}} + +{{- end -}} diff --git a/layouts/_shortcodes/pdf.html b/layouts/_shortcodes/pdf.html new file mode 100644 index 0000000..2ec45a5 --- /dev/null +++ b/layouts/_shortcodes/pdf.html @@ -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 -}} + + + {{ $label }} + {{ with $size }}{{ . }}{{ end }} + diff --git a/layouts/baseof.html b/layouts/baseof.html new file mode 100644 index 0000000..764fcb7 --- /dev/null +++ b/layouts/baseof.html @@ -0,0 +1,14 @@ + + +{{ partial "head.html" . }} + + + {{ partial "header.html" . }} +
+
+ {{ block "main" . }}{{ end }} +
+
+ {{ partial "footer.html" . }} + + diff --git a/layouts/home.html b/layouts/home.html new file mode 100644 index 0000000..07baf74 --- /dev/null +++ b/layouts/home.html @@ -0,0 +1,54 @@ +{{ define "main" }} +
+ {{ .Content }} +
+ + {{ with where site.RegularPages "Section" "aktuelles" }} +
+

Aktuelles

+
+ {{ range first 3 . }} + {{ partial "post-card.html" . }} + {{ end }} +
+ +
+ {{ end }} + + {{ with hugo.Data.members }} +
+

Unsere Mitglieder

+
    + {{ range . }} +
  • + {{ with resources.Get .image }} + {{ $p := .Fill "240x240 Center webp q80" }} + {{ $.Title }} + {{ end }} + {{ .name }} +
  • + {{ end }} +
+
+ {{ end }} + + {{ with hugo.Data.memoriam }} +
+

In Erinnerung

+
    + {{ range . }} +
  • + {{ with resources.Get .image }} + {{ $p := .Fill "200x200 Center webp q80" }} + + {{ end }} + {{ .name }} + ✝ {{ .died }} +
  • + {{ end }} +
+
+ {{ end }} +{{ end }} diff --git a/layouts/list.html b/layouts/list.html new file mode 100644 index 0000000..a6f3589 --- /dev/null +++ b/layouts/list.html @@ -0,0 +1,26 @@ +{{ define "main" }} +
+ + + {{ with .Content }}
{{ . }}
{{ end }} + + {{ $pages := .RegularPages }} + {{ if $pages }} + {{ if eq .Section "aktuelles" }} +
+ {{ range $pages }} + {{ partial "post-card.html" . }} + {{ end }} +
+ {{ else }} + + {{ end }} + {{ end }} +
+{{ end }} diff --git a/layouts/page.html b/layouts/page.html new file mode 100644 index 0000000..6c0f6af --- /dev/null +++ b/layouts/page.html @@ -0,0 +1,26 @@ +{{ define "main" }} +
+ + +
+ {{ .Content }} +
+ + {{ if eq .Section "aktuelles" }} + + {{ end }} +
+{{ end }} diff --git a/layouts/termine.html b/layouts/termine.html new file mode 100644 index 0000000..4b4fb1c --- /dev/null +++ b/layouts/termine.html @@ -0,0 +1,29 @@ +{{ define "main" }} +
+ + + {{ with .Content }}
{{ . }}
{{ 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 }} +
+

Termine {{ . }}

+ {{ partial "termine-table.html" $rows }} +
+ {{ else }} +
+ Termine {{ . }} + {{ partial "termine-table.html" $rows }} +
+ {{ end }} + {{ end }} +
+{{ end }} diff --git a/static/dokumente/25-jahre-o-fahrt.pdf b/static/dokumente/25-jahre-o-fahrt.pdf new file mode 100644 index 0000000..4ab979f Binary files /dev/null and b/static/dokumente/25-jahre-o-fahrt.pdf differ diff --git a/static/dokumente/ergebnisliste-fahrer.pdf b/static/dokumente/ergebnisliste-fahrer.pdf new file mode 100644 index 0000000..40d7e1d Binary files /dev/null and b/static/dokumente/ergebnisliste-fahrer.pdf differ diff --git a/static/dokumente/ergebnisliste-sozius.pdf b/static/dokumente/ergebnisliste-sozius.pdf new file mode 100644 index 0000000..e9dac15 Binary files /dev/null and b/static/dokumente/ergebnisliste-sozius.pdf differ diff --git a/static/dokumente/jahresfahrt-2019-1.pdf b/static/dokumente/jahresfahrt-2019-1.pdf new file mode 100644 index 0000000..2a2bd4f Binary files /dev/null and b/static/dokumente/jahresfahrt-2019-1.pdf differ diff --git a/static/dokumente/jahresfahrt-2019.pdf b/static/dokumente/jahresfahrt-2019.pdf new file mode 100644 index 0000000..2a2bd4f Binary files /dev/null and b/static/dokumente/jahresfahrt-2019.pdf differ diff --git a/static/dokumente/jahresfahrt-2020.pdf b/static/dokumente/jahresfahrt-2020.pdf new file mode 100644 index 0000000..003c5b5 Binary files /dev/null and b/static/dokumente/jahresfahrt-2020.pdf differ diff --git a/static/dokumente/jahresfahrt-2023.pdf b/static/dokumente/jahresfahrt-2023.pdf new file mode 100644 index 0000000..f8ceb7a Binary files /dev/null and b/static/dokumente/jahresfahrt-2023.pdf differ diff --git a/static/dokumente/kundgebung-siegen.pdf b/static/dokumente/kundgebung-siegen.pdf new file mode 100644 index 0000000..1985aa7 Binary files /dev/null and b/static/dokumente/kundgebung-siegen.pdf differ diff --git a/static/dokumente/mitgliedsantrag.pdf b/static/dokumente/mitgliedsantrag.pdf new file mode 100644 index 0000000..cdcf5cf Binary files /dev/null and b/static/dokumente/mitgliedsantrag.pdf differ diff --git a/static/dokumente/ranking.pdf b/static/dokumente/ranking.pdf new file mode 100644 index 0000000..22041ee Binary files /dev/null and b/static/dokumente/ranking.pdf differ diff --git a/static/dokumente/roadbook-2023.pdf b/static/dokumente/roadbook-2023.pdf new file mode 100644 index 0000000..2b9c511 Binary files /dev/null and b/static/dokumente/roadbook-2023.pdf differ diff --git a/static/dokumente/satzung.pdf b/static/dokumente/satzung.pdf new file mode 100644 index 0000000..d77cd71 Binary files /dev/null and b/static/dokumente/satzung.pdf differ diff --git a/tools/build_data.py b/tools/build_data.py new file mode 100644 index 0000000..c352752 --- /dev/null +++ b/tools/build_data.py @@ -0,0 +1,169 @@ +"""Generate data/*.yaml. + +Two sources: + +* ``data/termine.yaml`` comes from the six TablePress tables in the WXR export. +* ``data/members.yaml`` and ``data/memoriam.yaml`` come from the *live* homepage. + Those two blocks were WordPress widgets, and WXR does not export widgets at all, + so scraping the rendered page is the only way to recover them. + +Run once during the migration. After that the YAML files are the source of truth. +""" + +from __future__ import annotations + +import html +import json +import re +import sys +import urllib.request +from pathlib import Path + +from wxr import REPO_ROOT, clean_media_name, load_items + +LIVE_HOME = "https://motorradclub-giebelwald.de/" +USER_AGENT = "mcg-website-migration/1.0 (+https://mcg.luxick.de)" +DATA = REPO_ROOT / "data" + + +def yaml_str(value: str) -> str: + """Always double-quote; the cells contain colons, umlauts and inline HTML.""" + return '"' + value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + '"' + + +# --------------------------------------------------------------------------- termine + + +def build_termine() -> None: + items = load_items() + tables = [i for i in items.values() if i.post_type == "tablepress_table"] + + years: dict[int, list[dict[str, str]]] = {} + for table in tables: + match = re.search(r"(\d{4})", table.title) + if not match: + print(f"!! skipping table without a year in the title: {table.title}", + file=sys.stderr) + continue + year = int(match.group(1)) + + rows = [] + for row in json.loads(table.content): + date = (row[0] if len(row) > 0 else "").strip() + desc = (row[1] if len(row) > 1 else "").strip() + if not date and not desc: + continue # TablePress spacer rows + rows.append({"date": date, "description": desc}) + years[year] = rows + + lines = [ + "# Vereinstermine, ursprünglich TablePress-Tabellen aus WordPress.", + "# Neue Termine: einfach unter dem passenden Jahr ergänzen.", + "", + ] + for year in sorted(years, reverse=True): + lines.append(f'"{year}":') + if not years[year]: + lines[-1] += " []" + continue + for row in years[year]: + lines.append(f" - date: {yaml_str(row['date'])}") + lines.append(f" description: {yaml_str(row['description'])}") + lines.append("") + + (DATA / "termine.yaml").write_text("\n".join(lines).rstrip() + "\n", + encoding="utf-8") + total = sum(len(v) for v in years.values()) + print(f"termine.yaml: {len(years)} Jahre, {total} Termine") + + +# ------------------------------------------------------------------- widget scraping + + +def live_home() -> str: + req = urllib.request.Request(LIVE_HOME, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(req, timeout=60) as resp: + return resp.read().decode("utf-8", errors="replace") + + +GALLERY_ITEM = re.compile( + r"", + re.S, +) + + +def gallery_entries(fragment: str) -> list[tuple[str, str]]: + out = [] + for m in GALLERY_ITEM.finditer(fragment): + href = m.group("href") + caption = html.unescape(re.sub(r"<[^>]+>", "", m.group("caption") or "")).strip() + out.append((href, caption)) + return out + + +def image_ref(url: str) -> str: + """Map an uploads URL to the committed path under assets/img/.""" + return "img/" + clean_media_name(url.rsplit("/", 1)[-1]) + + +def build_widgets() -> None: + page = live_home() + + # Each widget renders as

NAME

followed by a WP + # gallery. Keep the trailing inside the group, otherwise the last + # entry of every gallery is silently dropped. + members_html = re.search( + r"Unsere Mitglieder(.*?)\s*", page, re.S + ) + memoriam_html = re.search( + r"In Erinnerung(.*?)\s*", page, re.S + ) + if not members_html or not memoriam_html: + raise SystemExit("could not locate the member/memoriam widgets on the live page") + + members = gallery_entries(members_html.group(1)) + memoriam = gallery_entries(memoriam_html.group(1)) + + # The WordPress gallery widget shuffles its order on every request, so the + # source order carries no meaning. Sort by name for a stable, readable list. + members.sort(key=lambda entry: entry[1].casefold()) + + lines = [ + "# Mitglieder-Galerie der Startseite.", + "# Aus dem WordPress-Widget übernommen (Widgets sind nicht im XML-Export enthalten).", + "", + ] + for url, name in members: + lines.append(f"- name: {yaml_str(name)}") + lines.append(f" image: {yaml_str(image_ref(url))}") + (DATA / "members.yaml").write_text("\n".join(lines) + "\n", encoding="utf-8") + print(f"members.yaml: {len(members)} Mitglieder") + + lines = [ + "# \"In Erinnerung\" – ebenfalls aus einem WordPress-Widget übernommen.", + "", + ] + for url, caption in memoriam: + # Captions look like "Roland Groos ✝ 27.5.2020". + name, _, died = caption.partition("✝") + lines.append(f"- name: {yaml_str(name.strip())}") + lines.append(f" died: {yaml_str(died.strip())}") + lines.append(f" image: {yaml_str(image_ref(url))}") + (DATA / "memoriam.yaml").write_text("\n".join(lines) + "\n", encoding="utf-8") + print(f"memoriam.yaml: {len(memoriam)} Einträge") + + return [image_ref(u) for u, _ in members] + [image_ref(u) for u, _ in memoriam] + + +def main() -> int: + DATA.mkdir(parents=True, exist_ok=True) + build_termine() + build_widgets() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/fetch_media.py b/tools/fetch_media.py new file mode 100644 index 0000000..1389ef0 --- /dev/null +++ b/tools/fetch_media.py @@ -0,0 +1,109 @@ +"""Download every attachment referenced by the WXR export into .cache/uploads/. + +The export only carries URLs, not binaries, so the files have to come off the live +WordPress host before it is retired. Images are capped at MAX_EDGE px on the long +side and re-encoded; PDFs are stored verbatim. + +Re-running is cheap: anything already in the cache is skipped. +""" + +from __future__ import annotations + +import sys +import urllib.error +import urllib.parse +import urllib.request +from io import BytesIO + +from PIL import Image + +from wxr import CACHE, attachments, cache_path, load_items + +MAX_EDGE = 2000 +JPEG_QUALITY = 82 +USER_AGENT = "mcg-website-migration/1.0 (+https://mcg.luxick.de)" + + +def fetch(url: str) -> bytes: + # A few uploads have umlauts in the filename (e.g. Hövelhof.jpg); the HTTP + # request line must be ASCII, so percent-encode the path before sending. + parts = urllib.parse.urlsplit(url) + safe = urllib.parse.urlunsplit( + parts._replace(path=urllib.parse.quote(parts.path, safe="/%")) + ) + req = urllib.request.Request(safe, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(req, timeout=60) as resp: + return resp.read() + + +def store_image(raw: bytes, dest) -> None: + img = Image.open(BytesIO(raw)) + img.load() + + # Honour the EXIF orientation now; the tag is dropped on save. + try: + from PIL import ImageOps + + img = ImageOps.exif_transpose(img) + except Exception: + pass + + if max(img.size) > MAX_EDGE: + img.thumbnail((MAX_EDGE, MAX_EDGE), Image.LANCZOS) + + suffix = dest.suffix.lower() + if suffix in (".jpg", ".jpeg"): + img.convert("RGB").save(dest, "JPEG", quality=JPEG_QUALITY, optimize=True, + progressive=True) + elif suffix == ".png": + img.save(dest, "PNG", optimize=True) + else: + img.save(dest) + + +def main() -> int: + items = load_items() + atts = attachments(items) + + skipped = downloaded = failed = 0 + for post_id, item in sorted(atts.items(), key=lambda kv: int(kv[0])): + url = item.attachment_url + if not url: + print(f"!! {post_id}: no attachment_url", file=sys.stderr) + failed += 1 + continue + + dest = cache_path(url) + if dest.exists() and dest.stat().st_size > 0: + skipped += 1 + continue + + dest.parent.mkdir(parents=True, exist_ok=True) + try: + raw = fetch(url) + except Exception as exc: # one bad upload must not abort the whole run + print(f"!! {post_id}: {url} -> {exc!r}", file=sys.stderr) + failed += 1 + continue + + try: + if dest.suffix.lower() == ".pdf": + dest.write_bytes(raw) + else: + store_image(raw, dest) + except Exception as exc: # corrupt/unsupported source, keep the original bytes + print(f"!! {post_id}: could not process {url} ({exc}); storing raw", + file=sys.stderr) + dest.write_bytes(raw) + + downloaded += 1 + print(f" {dest.relative_to(CACHE)} ({len(raw) // 1024} KiB -> " + f"{dest.stat().st_size // 1024} KiB)") + + print(f"\ndownloaded={downloaded} cached={skipped} failed={failed} " + f"total={len(atts)}") + return 1 if failed else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/html2md.py b/tools/html2md.py new file mode 100644 index 0000000..cee8dab --- /dev/null +++ b/tools/html2md.py @@ -0,0 +1,171 @@ +"""Minimal HTML -> Markdown converter for the imported WordPress content. + +Deliberately narrow: it only handles the tags that actually occur in this export +(paragraphs, headings, lists, emphasis, links, images, breaks, rules). Anything +unrecognised is passed through as raw HTML, which Goldmark renders because +``markup.goldmark.renderer.unsafe`` is on. +""" + +from __future__ import annotations + +import re +from html.parser import HTMLParser + +INLINE = {"strong", "b", "em", "i", "a", "br", "span", "code", "u", "s"} +BLOCK = {"p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "hr", + "blockquote", "figure", "figcaption", "div"} + +ESCAPE = re.compile(r"([\\`*_\[\]])") + + +class Converter(HTMLParser): + def __init__(self, resolve_image=None): + super().__init__(convert_charrefs=True) + self.out: list[tuple[str, str]] = [] + self.resolve_image = resolve_image or (lambda src, cls: src) + self._stack: list[str] = [] + self._list: list[str] = [] # 'ul' / 'ol' nesting + self._li_index: list[int] = [] + self._link: str | None = None + self._buf: list[str] = [] + + # -- helpers --------------------------------------------------------- + def _emit(self, text: str) -> None: + self._buf.append(text) + + def _flush_block(self, prefix: str = "", kind: str = "block") -> None: + text = "".join(self._buf).strip() + self._buf = [] + if text: + self.out.append((kind, prefix + text)) + + def _open_emphasis(self, marker: str) -> None: + # Emitted lazily: WordPress often writes " 57258 ...", + # and "** 57258**" is not valid emphasis in CommonMark. The marker is + # placed after any leading whitespace instead (see handle_data). + self._pending_emphasis = marker + + def _close_emphasis(self, marker: str) -> None: + if getattr(self, "_pending_emphasis", None) == marker: + self._pending_emphasis = None # empty + return + # Likewise move trailing whitespace out: "**Samstag **" -> "**Samstag** ". + trailing = "" + while self._buf and self._buf[-1] and self._buf[-1][-1] in " \t\n": + trailing = self._buf[-1][-1] + trailing + self._buf[-1] = self._buf[-1][:-1] + if not self._buf[-1]: + self._buf.pop() + if not self._buf: + return + self._emit(marker) + if trailing: + self._emit(trailing) + + # -- tags ------------------------------------------------------------ + def handle_starttag(self, tag, attrs): + a = dict(attrs) + if tag == "p": + self._flush_block() + elif tag in ("h1", "h2", "h3", "h4", "h5", "h6"): + self._flush_block() + self._pending_prefix = "#" * int(tag[1]) + " " + elif tag in ("ul", "ol"): + self._flush_block() + self._list.append(tag) + self._li_index.append(0) + elif tag == "li": + self._flush_block() + if self._list: + self._li_index[-1] += 1 + indent = " " * (len(self._list) - 1) + marker = ("- " if self._list[-1] == "ul" + else f"{self._li_index[-1]}. ") + self._pending_prefix = indent + marker + elif tag == "hr": + self._flush_block() + self.out.append(("block", "---")) + elif tag == "br": + self._emit(" \n") + elif tag in ("strong", "b"): + self._open_emphasis("**") + elif tag in ("em", "i"): + self._open_emphasis("*") + elif tag == "a": + self._link = a.get("href", "") + self._emit("[") + elif tag == "img": + src = self.resolve_image(a.get("src", ""), a.get("class", "")) + alt = (a.get("alt") or "").replace("]", "") + self._emit(f"![{alt}]({src})") + self._stack.append(tag) + + def handle_endtag(self, tag): + while self._stack and self._stack.pop() != tag: + pass + if tag == "p": + self._flush_block() + elif tag in ("h1", "h2", "h3", "h4", "h5", "h6", "li"): + prefix = getattr(self, "_pending_prefix", "") + self._pending_prefix = "" + self._flush_block(prefix, kind="li" if tag == "li" else "block") + elif tag in ("ul", "ol"): + self._flush_block() + if self._list: + self._list.pop() + self._li_index.pop() + elif tag in ("strong", "b"): + self._close_emphasis("**") + elif tag in ("em", "i"): + self._close_emphasis("*") + elif tag == "a": + self._emit(f"]({self._link or ''})") + self._link = None + + def handle_data(self, data): + if not data: + return + # Collapse WordPress' aggressive whitespace, but keep explicit breaks. + text = data.replace(" ", " ") + text = re.sub(r"[ \t]*\n[ \t]*", "\n", text) + + pending = getattr(self, "_pending_emphasis", None) + if pending: + if not text.strip(): + return # whitespace only; keep waiting for real content + lead = text[:len(text) - len(text.lstrip())] + if lead: + self._emit(lead) + text = text.lstrip() + self._emit(pending) + self._pending_emphasis = None + elif not self._buf and not text.strip(): + return + + self._emit(ESCAPE.sub(r"\\\1", text) if self._link is None + else text.replace("[", r"\[").replace("]", r"\]")) + + def result(self) -> str: + self._flush_block(getattr(self, "_pending_prefix", "")) + blocks = [(kind, text.strip()) for kind, text in self.out if text.strip()] + + out = "" + for index, (kind, text) in enumerate(blocks): + if index == 0: + out = text + continue + # Keep consecutive list items tight; a blank line between them makes + # Goldmark render a "loose" list, wrapping every item in

. + sep = "\n" if kind == "li" and blocks[index - 1][0] == "li" else "\n\n" + out += sep + text + return out + + +def html_to_markdown(html_text: str, resolve_image=None) -> str: + conv = Converter(resolve_image=resolve_image) + conv.feed(html_text) + conv.close() + text = conv.result() + # An escaped em-dash entity or stray double-escape looks worse than the source. + text = text.replace("\\_\\_", "__") + return re.sub(r"\n{3,}", "\n\n", text).strip() diff --git a/tools/verify_migration.py b/tools/verify_migration.py new file mode 100644 index 0000000..2d95dc7 --- /dev/null +++ b/tools/verify_migration.py @@ -0,0 +1,133 @@ +"""Post-migration checks. Run after `hugo --gc --minify`. + +1. Inventory - every published WordPress page/post has a content file. +2. Media - every attachment is either placed in the site or explicitly + accounted for as an unused media-library leftover. +3. Links - no internal href/src in public/ points at a missing file, and no + absolute wp-content URL survived the import. + +Exits non-zero if any check fails. +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path +from urllib.parse import unquote, urlsplit + +from wp2hugo import PAGE_MAP, SKIP, SLUG_FIXES +from wxr import REPO_ROOT, clean_media_name, load_items, slugify + +PUBLIC = REPO_ROOT / "public" +CONTENT = REPO_ROOT / "content" + +failures: list[str] = [] +notes: list[str] = [] + + +def check_inventory(items) -> None: + expected = 0 + for item in items.values(): + if item.post_id in SKIP or item.status != "publish": + continue + if item.post_type == "page": + mapping = PAGE_MAP.get(item.post_id) + if not mapping: + failures.append(f"page {item.post_id} ({item.slug}) is unmapped") + continue + path = CONTENT / mapping[0] + elif item.post_type == "post": + path = CONTENT / "aktuelles" / SLUG_FIXES.get( + item.slug, slugify(item.slug)) / "index.md" + else: + continue + expected += 1 + if not path.exists(): + failures.append(f"missing content file for {item.post_type} " + f"{item.post_id} ({item.slug}): {path}") + notes.append(f"inventory: {expected} published pages/posts accounted for") + + for post_id, reason in SKIP.items(): + notes.append(f" skipped {post_id}: {reason}") + + +def check_media(items) -> None: + placed = {p.name for p in CONTENT.rglob("*") if p.is_file() + and p.suffix.lower() != ".md"} + placed |= {p.name for p in (REPO_ROOT / "assets" / "img").rglob("*") + if p.is_file()} + placed |= {p.name for p in (REPO_ROOT / "static").rglob("*") if p.is_file()} + # assets/img/logo.png and banner.jpg are renamed copies. + placed |= {"Emblem_transparent.png", "cropped-Banner_small_web.jpg"} + + unused = [] + for item in items.values(): + if item.post_type != "attachment" or not item.attachment_url: + continue + original = item.attachment_url.rsplit("/", 1)[-1] + if original in placed or clean_media_name(original) in placed: + continue + unused.append(item.attachment_url) + + total = sum(1 for i in items.values() if i.post_type == "attachment") + notes.append(f"media: {total - len(unused)}/{total} attachments placed") + if unused: + notes.append(f" {len(unused)} media-library leftovers not shown on the " + f"live site (see MIGRATION.md)") + + +HREF = re.compile(r'(?:href|src)=["\']?([^"\'\s>]+)', re.I) + + +def check_links() -> None: + if not PUBLIC.exists(): + failures.append("public/ does not exist - run `hugo` first") + return + + checked = broken = 0 + for html in PUBLIC.rglob("*.html"): + text = html.read_text(encoding="utf-8", errors="replace") + + for stale in re.findall(r'https?://motorradclub-giebelwald\.de/wp-content/\S*', + text): + failures.append(f"{html.relative_to(PUBLIC)}: stale WordPress URL {stale}") + + for raw in HREF.findall(text): + url = urlsplit(raw) + if url.scheme or url.netloc or not url.path: + continue # external, mailto:, protocol-relative + if raw.startswith("#"): + continue + checked += 1 + path = unquote(url.path) + target = (PUBLIC / path.lstrip("/") if path.startswith("/") + else html.parent / path) + if target.is_dir(): + target = target / "index.html" + if not target.exists(): + broken += 1 + failures.append(f"{html.relative_to(PUBLIC)}: broken link -> {raw}") + + notes.append(f"links: {checked} internal references checked, {broken} broken") + + +def main() -> int: + items = load_items() + check_inventory(items) + check_media(items) + check_links() + + for note in notes: + print(note) + if failures: + print(f"\n{len(failures)} FAILURE(S):", file=sys.stderr) + for f in failures: + print(f" - {f}", file=sys.stderr) + return 1 + print("\nall checks passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/wp2hugo.py b/tools/wp2hugo.py new file mode 100644 index 0000000..aef4442 --- /dev/null +++ b/tools/wp2hugo.py @@ -0,0 +1,340 @@ +"""One-shot WordPress WXR -> Hugo importer. + +Run once during the migration: + + python tools/fetch_media.py # pull the binaries off the live host + python tools/build_data.py # termine / members / memoriam + python tools/wp2hugo.py # content bundles + assets + +Afterwards the generated Markdown in content/ is the source of truth and this +script is kept only so the import is reproducible and reviewable. +""" + +from __future__ import annotations + +import json +import re +import shutil +import sys +from dataclasses import dataclass, field +from pathlib import Path + +from html2md import html_to_markdown +from wxr import (REPO_ROOT, cache_path, clean_media_name, load_items, slugify) + +CONTENT = REPO_ROOT / "content" +ASSETS_IMG = REPO_ROOT / "assets" / "img" +STATIC_PDF = REPO_ROOT / "static" / "dokumente" + +# WordPress page id -> destination. Anything not listed is skipped. +PAGE_MAP = { + "13": ("aktuelles/_index.md", None), + "72": ("termine.md", "termine"), + "128": ("downloads.md", None), + "138": ("impressum.md", None), + "98": ("archiv/vergangene-o-fahrten.md", None), + "194": ("archiv/40-jahre-mcg/index.md", None), + "217": ("archiv/familienausfahrt-2019/index.md", None), + "287": ("archiv/bilder-familienausfahrt/index.md", None), + "473": ("archiv/familienfahrt-2022/index.md", None), +} + +# Deliberately dropped, with the reason recorded for the migration report. +SKIP = { + "3": "stock WordPress privacy-policy draft (English, references an old IP)", + "12": "WP home page body was the placeholder 'Dies ist nur eine Demo Seite'", + "410": "empty draft post", +} + +# Site chrome pulled out of the media library into assets/img/. +SITE_IMAGES = { + "Emblem_transparent.png": "logo.png", + "cropped-Banner_small_web.jpg": "banner.jpg", +} + +SLUG_FIXES = { + "29-o-fahrt-zum-50-jubilaum": "29-o-fahrt-zum-50-jubilaeum", + "regelmasger-stammtisch": "regelmaessiger-stammtisch", + "vorankundigung-mcg-wochenende-2024": "vorankuendigung-mcg-wochenende-2024", + "o-fahrt-2019": "25-o-fahrt-2022", # title says 25. O-Fahrt 2022; slug was stale + "termin": "28-o-fahrt-2025", # title says 28. O-Fahrt 2025 +} + + +@dataclass +class Target: + item: object + path: Path + bundle: Path | None + layout: str | None = None + images: dict[str, str] = field(default_factory=dict) # attachment id -> filename + + +# --------------------------------------------------------------------- helpers + + +def yaml_quote(value: str) -> str: + return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"' + + +def iso(date_gmt: str) -> str: + if not date_gmt or date_gmt.startswith("0000"): + return "" + return date_gmt.replace(" ", "T") + "Z" + + +def referenced_ids(item, by_filename: dict[str, str] | None = None) -> list[str]: + """Every attachment id an item points at, in document order. + + Some posts carry stale ``wp-image-NNN`` ids whose attachment record was + deleted from WordPress even though the upload itself survived (post 81 points + at id 83, which no longer exists). When ``by_filename`` is supplied, such + references are recovered from the ``src`` URL instead. + """ + ids: list[str] = [] + + def add(value): + if value and value not in ids: + ids.append(value) + + thumb = item.meta.get("_thumbnail_id") + add(thumb) + for m in re.finditer(r']*>', item.content): + tag = m.group(0) + cls = re.search(r'wp-image-(\d+)', tag) + if cls: + add(cls.group(1)) + if by_filename is not None: + src = re.search(r'src="([^"]+)"', tag) + if src: + add(by_filename.get(clean_media_name(src.group(1).rsplit("/", 1)[-1]))) + for m in re.finditer(r'', item.content, re.S): + try: + for gid in json.loads(m.group(1)).get("ids", []): + add(str(gid)) + except json.JSONDecodeError: + pass + for m in re.finditer(r'', item.content, re.S): + try: + add(str(json.loads(m.group(1)).get("id", ""))) + except json.JSONDecodeError: + pass + return [i for i in ids if i] + + +# ------------------------------------------------------------------ conversion + + +def convert_body(item, target: Target, attachments: dict, pdf_names: dict) -> str: + body = item.content + + # Galleries: replace the whole block with a shortcode listing bundle files. + def gallery_repl(match: re.Match) -> str: + try: + ids = [str(i) for i in json.loads(match.group(1)).get("ids", [])] + except json.JSONDecodeError: + return "" + files = [target.images[i] for i in ids if i in target.images] + if not files: + return "" + args = " ".join(f'"{f}"' for f in files) + return f"\n\n{{{{< gallery {args} >}}}}\n\n" + + body = re.sub( + r'.*?', + gallery_repl, body, flags=re.S) + + # File blocks: PDFs live in static/dokumente/, linked via the pdf shortcode. + def file_repl(match: re.Match) -> str: + block = match.group(0) + try: + attrs = json.loads(match.group(1)) + except json.JSONDecodeError: + return "" + att_id = str(attrs.get("id", "")) + href = attrs.get("href", "") + name = pdf_names.get(att_id) or clean_media_name(href.rsplit("/", 1)[-1]) + label = re.search(r'>([^<]+)', block) + label = label.group(1).strip() if label else name + return f'\n\n{{{{< pdf "{name}" "{label}" >}}}}\n\n' + + body = re.sub( + r'.*?', + file_repl, body, flags=re.S) + + # Shortcodes that have no Hugo equivalent. + body = re.sub(r'.*?', "", + body, flags=re.S) + body = re.sub(r'\[table id=\d+\s*/?\]', "", body) + body = re.sub(r'\[widgets_on_pages[^\]]*\]', "", body) + + # Remaining Gutenberg comments carry no information once converted. + body = re.sub(r'', "", body) + + # WordPress' editor leaves these around every translated paragraph. + body = re.sub(r'\s*', "", body) + body = re.sub(r'', "", body) + + def resolve_image(src: str, cls: str) -> str: + m = re.search(r'wp-image-(\d+)', cls or "") + if m and m.group(1) in target.images: + return target.images[m.group(1)] + # Fall back to matching on the original upload filename. + base = clean_media_name(src.rsplit("/", 1)[-1]) + for name in target.images.values(): + if name == base: + return name + return src + + return html_to_markdown(body, resolve_image=resolve_image) + + +def front_matter(item, target: Target, extra_tags: list[str]) -> str: + lines = ["---", f"title: {yaml_quote(item.title)}"] + if iso(item.date_gmt): + lines.append(f"date: {iso(item.date_gmt)}") + if iso(item.modified_gmt) and item.modified_gmt != item.date_gmt: + lines.append(f"lastmod: {iso(item.modified_gmt)}") + if extra_tags: + lines.append("tags: [" + ", ".join(yaml_quote(t) for t in extra_tags) + "]") + thumb = item.meta.get("_thumbnail_id") + if thumb and thumb in target.images: + lines.append(f"featured: {yaml_quote(target.images[thumb])}") + if target.layout: + lines.append(f"layout: {yaml_quote(target.layout)}") + lines.append("---") + return "\n".join(lines) + + +# ------------------------------------------------------------------------ main + + +def main() -> int: + items = load_items() + attachments = {k: v for k, v in items.items() if v.post_type == "attachment"} + + # Cleaned upload filename -> attachment id, for recovering stale wp-image ids. + by_filename: dict[str, str] = {} + for att_id, att in attachments.items(): + if att.attachment_url: + by_filename.setdefault( + clean_media_name(att.attachment_url.rsplit("/", 1)[-1]), att_id) + + targets: list[Target] = [] + + for item in items.values(): + if item.post_id in SKIP or item.status != "publish": + continue + + if item.post_type == "page": + mapping = PAGE_MAP.get(item.post_id) + if not mapping: + print(f"!! unmapped page {item.post_id} ({item.slug})", file=sys.stderr) + continue + rel, layout = mapping + path = CONTENT / rel + elif item.post_type == "post": + slug = SLUG_FIXES.get(item.slug, slugify(item.slug)) + path = CONTENT / "aktuelles" / slug / "index.md" + layout = None + else: + continue + + bundle = path.parent if path.name == "index.md" else None + targets.append(Target(item=item, path=path, bundle=bundle, layout=layout)) + + # ---- assign media ------------------------------------------------- + used: set[str] = set() + pdf_names: dict[str, str] = {} + + for target in targets: + for att_id in referenced_ids(target.item, by_filename): + att = attachments.get(att_id) + if not att or not att.attachment_url: + continue + src = cache_path(att.attachment_url) + if not src.exists(): + print(f"!! missing cached media for {att_id}: {att.attachment_url}", + file=sys.stderr) + continue + name = clean_media_name(att.attachment_url.rsplit("/", 1)[-1]) + + if src.suffix.lower() == ".pdf": + STATIC_PDF.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, STATIC_PDF / name) + pdf_names[att_id] = name + target.images[att_id] = name + else: + if target.bundle is None: + # Non-bundle page (termine.md, impressum.md, ...) -> shared assets. + ASSETS_IMG.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, ASSETS_IMG / name) + target.images[att_id] = f"/img/{name}" + else: + target.bundle.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, target.bundle / name) + target.images[att_id] = name + used.add(att_id) + + # Any PDF in the library that no page links to is still worth keeping around + # for the Downloads page; copy them all so nothing is lost. + for att_id, att in attachments.items(): + if not att.attachment_url.lower().endswith(".pdf"): + continue + src = cache_path(att.attachment_url) + if src.exists(): + STATIC_PDF.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, STATIC_PDF / clean_media_name( + att.attachment_url.rsplit("/", 1)[-1])) + used.add(att_id) + + # Site chrome + widget images (members / memoriam) into assets/img/. + ASSETS_IMG.mkdir(parents=True, exist_ok=True) + widget_images = set() + for yaml_file in ("members.yaml", "memoriam.yaml"): + text = (REPO_ROOT / "data" / yaml_file).read_text(encoding="utf-8") + widget_images.update(re.findall(r'image:\s*"img/([^"]+)"', text)) + + for att_id, att in attachments.items(): + original = att.attachment_url.rsplit("/", 1)[-1] + name = clean_media_name(original) + src = cache_path(att.attachment_url) + if not src.exists(): + continue + if original in SITE_IMAGES: + shutil.copy2(src, ASSETS_IMG / SITE_IMAGES[original]) + used.add(att_id) + elif name in widget_images: + shutil.copy2(src, ASSETS_IMG / name) + used.add(att_id) + + # ---- write content ------------------------------------------------ + # content/ is the source of truth once imported, so an accidental re-run must + # not throw away hand edits. Pass --force to regenerate anyway. + force = "--force" in sys.argv + written = kept = 0 + for target in targets: + item = target.item + target.path.parent.mkdir(parents=True, exist_ok=True) + if target.path.exists() and not force: + kept += 1 + continue + tags = ["O-Fahrt"] if any(nice == "ofahrt" for _, nice in item.categories) else [] + body = convert_body(item, target, attachments, pdf_names) + target.path.write_text( + front_matter(item, target, tags) + "\n\n" + body + "\n", + encoding="utf-8") + written += 1 + + print(f"wrote {written} content files" + + (f", kept {kept} existing (use --force to overwrite)" if kept else "")) + print(f"media used: {len(used)}/{len(attachments)} attachments") + unused = sorted(set(attachments) - used, key=int) + print(f"unreferenced attachments: {len(unused)}") + for att_id in unused: + print(f" - {attachments[att_id].attachment_url}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/wxr.py b/tools/wxr.py new file mode 100644 index 0000000..c4615b5 --- /dev/null +++ b/tools/wxr.py @@ -0,0 +1,126 @@ +"""Shared helpers for reading the WordPress WXR export.""" + +from __future__ import annotations + +import re +import unicodedata +import xml.etree.ElementTree as ET +from dataclasses import dataclass, field +from pathlib import Path + +NS = { + "wp": "http://wordpress.org/export/1.2/", + "content": "http://purl.org/rss/1.0/modules/content/", + "excerpt": "http://wordpress.org/export/1.2/excerpt/", + "dc": "http://purl.org/dc/elements/1.1/", +} + +REPO_ROOT = Path(__file__).resolve().parent.parent +EXPORT = REPO_ROOT / "wordpress-export" / "motorradclubgiebelwaldev.WordPress.2026-07-28.xml" +CACHE = REPO_ROOT / ".cache" / "uploads" + +# German transliteration first, so "Jubiläum" becomes "jubilaeum" and not "jubilaum". +UMLAUTS = { + "ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss", + "Ä": "Ae", "Ö": "Oe", "Ü": "Ue", +} + + +def slugify(value: str, *, keep_dots: bool = False) -> str: + for src, dst in UMLAUTS.items(): + value = value.replace(src, dst) + value = unicodedata.normalize("NFKD", value) + value = "".join(c for c in value if not unicodedata.combining(c)) + allowed = r"[^a-z0-9._-]+" if keep_dots else r"[^a-z0-9-]+" + value = re.sub(allowed, "-", value.lower()) + return re.sub(r"-{2,}", "-", value).strip("-") + + +def slugify_filename(name: str) -> str: + stem, _, ext = name.rpartition(".") + return f"{slugify(stem, keep_dots=False)}.{ext.lower()}" + + +def clean_media_name(name: str) -> str: + """Strip the bookkeeping suffixes WordPress bakes into upload filenames. + + ``Harry_2-e1654849333614.jpg`` -> ``harry-2.jpg`` + ``IMG_20220527_121440-small-scaled.jpg`` -> ``img-20220527-121440-small.jpg`` + """ + stem, _, ext = slugify_filename(name).rpartition(".") + stem = re.sub(r"-e\d{9,}$", "", stem) # post-crop revision id + stem = re.sub(r"-\d+x\d+$", "", stem) # generated size variant + stem = re.sub(r"-scaled$", "", stem) # WP "big image" downscale + return f"{stem or 'bild'}.{ext}" + + +def text(node: ET.Element | None, path: str, default: str = "") -> str: + if node is None: + return default + found = node.find(path, NS) + if found is None or found.text is None: + return default + return found.text + + +@dataclass +class Item: + element: ET.Element + post_id: str + post_type: str + status: str + title: str + slug: str + link: str + parent: str + date_gmt: str + modified_gmt: str + content: str + excerpt: str + attachment_url: str + meta: dict[str, str] = field(default_factory=dict) + categories: list[tuple[str, str]] = field(default_factory=list) + + +def load_items() -> dict[str, Item]: + channel = ET.parse(EXPORT).getroot().find("channel") + items: dict[str, Item] = {} + for el in channel.findall("item"): + meta = {} + for m in el.findall("wp:postmeta", NS): + key = text(m, "wp:meta_key") + meta[key] = text(m, "wp:meta_value") + cats = [ + (c.get("domain", ""), c.get("nicename", "")) + for c in el.findall("category") + ] + item = Item( + element=el, + post_id=text(el, "wp:post_id"), + post_type=text(el, "wp:post_type"), + status=text(el, "wp:status"), + title=(el.findtext("title") or "").strip(), + slug=text(el, "wp:post_name"), + link=(el.findtext("link") or "").strip(), + parent=text(el, "wp:post_parent"), + date_gmt=text(el, "wp:post_date_gmt"), + modified_gmt=text(el, "wp:post_modified_gmt"), + content=text(el, "content:encoded"), + excerpt=text(el, "excerpt:encoded"), + attachment_url=text(el, "wp:attachment_url"), + meta=meta, + categories=cats, + ) + items[item.post_id] = item + return items + + +def attachments(items: dict[str, Item]) -> dict[str, Item]: + return {k: v for k, v in items.items() if v.post_type == "attachment"} + + +def cache_path(url: str) -> Path: + """Local cache location mirroring the wp-content/uploads/YYYY/MM layout.""" + rel = url.split("/wp-content/uploads/", 1)[-1] + parts = rel.split("/") + return CACHE.joinpath(*parts[:-1], slugify_filename(parts[-1]))