Consolidate all moonweb sites into single Eleventy config

- Single eleventy.config.js at root with computed pathPrefix per section
- Accent colors inlined in base.njk (no per-site theme.css needed)
- Hub pages output at root via explicit permalinks
- Per-section sitemaps with tag-based collections
- .eleventyignore excludes stefankoelle, timecapsule, markdown files
- Deleted: 5 per-site configs, merge script
- Simplified: package.json (single build/dev commands)
- Simplified: GitHub Actions workflow (no matrix, no merge job)
- Fixed: parent links for sub-site detail pages
This commit is contained in:
2026-09-11 12:31:47 +02:00
parent 10b05736c8
commit c77ad91e0e
58 changed files with 220 additions and 324 deletions
+17
View File
@@ -0,0 +1,17 @@
stefankoelle/
timecapsule/
scripts/
.tmp/
# Root markdown files (not templates)
/*.md
# Hub static redirect files (handled by passthrough copy)
hub/hardware.html
hub/users.html
hub/index2.html
hub/index3.html
hub/new.html
hub/links.html
hub/infos.html
hub/start.html
+16 -93
View File
@@ -21,12 +21,8 @@ env:
jobs:
build:
name: Build ${{ matrix.site }}
name: Build moonweb sites
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
site: [hub, infra, smarthome, code, retro]
steps:
- uses: actions/checkout@v7
@@ -38,118 +34,45 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Build ${{ matrix.site }}
run: npm run build:${{ matrix.site }}
- name: Validate build output
run: |
if [ ! -d "dist/${{ matrix.site }}" ]; then
echo "Error: dist/${{ matrix.site }} not found"
exit 1
fi
if [ -z "$(ls -A dist/${{ matrix.site }} 2>/dev/null)" ]; then
echo "Error: dist/${{ matrix.site }} is empty"
exit 1
fi
echo "Build output:"
find dist/${{ matrix.site }} -type f | head -20
- name: Upload build artifact
uses: actions/upload-artifact@v7
with:
name: dist-${{ matrix.site }}
path: dist/${{ matrix.site }}
retention-days: 7
build-timecapsule:
name: Build timecapsule
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install timecapsule dependencies
run: cd timecapsule && npm ci
- name: Build all moonweb sites
run: npm run build
- name: Build timecapsule
run: npm run build:timecapsule
- name: Validate build output
run: |
if [ ! -d "dist/timecapsule" ]; then
echo "Error: dist/timecapsule not found"
exit 1
fi
if [ -z "$(ls -A dist/timecapsule 2>/dev/null)" ]; then
echo "Error: dist/timecapsule is empty"
exit 1
fi
echo "Build output:"
find dist/timecapsule -type f | head -20
- name: Upload build artifact
uses: actions/upload-artifact@v7
with:
name: dist-timecapsule
path: dist/timecapsule
retention-days: 7
merge:
name: Merge all sites
needs: [build, build-timecapsule]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Download all artifacts
uses: actions/download-artifact@v8
with:
path: artifacts/
- name: Merge into dist/
run: |
mkdir -p dist
# Hub becomes root
cp -r artifacts/dist-hub/* dist/
# Subdirectories
for site in infra smarthome code retro timecapsule; do
if [ -d "artifacts/dist-$site" ]; then
cp -r "artifacts/dist-$site" "dist/$site"
fi
done
# Copy shared CSS to root (accessible from all subdirectories)
cp shared/base.css dist/shared-base.css
cp shared/favicon/hub.svg dist/favicon.svg
echo "Final structure:"
find dist -maxdepth 2 -type f | sort | head -40
echo "..."
echo "Total files:"
find dist -type f | wc -l
- name: Upload merged artifact
# Verify key directories exist
for dir in "" infra smarthome code retro timecapsule; do
if [ -n "$dir" ] && [ ! -d "dist/$dir" ]; then
echo "Error: dist/$dir not found"
exit 1
fi
done
- name: Upload build artifact
uses: actions/upload-artifact@v7
with:
name: dist-moonweb-merged
name: dist-moonweb
path: dist/
retention-days: 7
deploy:
name: Deploy to IONOS
needs: merge
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Download merged artifact
- name: Download build artifact
uses: actions/download-artifact@v8
with:
name: dist-moonweb-merged
name: dist-moonweb
path: dist/
- name: Deploy via SFTP
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Page Not Found"
section: "code"
tags: "code"
description: "The page you are looking for does not exist."
layout: base.njk
permalink: /404.html
permalink: /code/404.html
---
<div class="error-page">
<h1>404</h1>
-20
View File
@@ -1,20 +0,0 @@
module.exports = function (eleventyConfig) {
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org", pathPrefix: "/code" });
eleventyConfig.addFilter("date", (d) => d.toISOString());
eleventyConfig.addPassthroughCopy({ "shared/theme-code.css": "theme.css" });
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
eleventyConfig.addPassthroughCopy({ "shared/favicon/code.svg": "favicon.svg" });
eleventyConfig.addPassthroughCopy({ "code/robots.txt": "robots.txt" });
return {
dir: {
input: "code",
includes: "../shared/_includes",
output: "dist/code"
},
serverOptions: {
host: "0.0.0.0",
port: 8084
}
};
};
+1
View File
@@ -1,6 +1,7 @@
---
title: "Open Source Projects — GitHub Repos & Code | moonweb"
section: "code"
tags: "code"
description: "Curated catalog of my public GitHub projects — automation, dev tools, web apps, and firmware."
layout: base.njk
subcategories:
+2 -1
View File
@@ -1,5 +1,6 @@
---
permalink: /sitemap.xml
permalink: /code/sitemap.xml
sitemapCollection: "code"
eleventyExcludeFromCollections: true
---
{% include "sitemap.njk" %}
+93
View File
@@ -0,0 +1,93 @@
const SITES = {
hub: { pathPrefix: "" },
infra: { pathPrefix: "/infra" },
smarthome: { pathPrefix: "/smarthome" },
code: { pathPrefix: "/code" },
retro: { pathPrefix: "/retro" },
};
module.exports = function (eleventyConfig) {
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org" });
eleventyConfig.addFilter("date", (d) => d.toISOString());
// Computed pathPrefix based on page's section front matter
eleventyConfig.addGlobalData("eleventyComputed", {
pathPrefix: (data) => SITES[data.section]?.pathPrefix ?? "",
});
// Per-section collections (for sitemaps)
for (const section of Object.keys(SITES)) {
eleventyConfig.addCollection(section, (collectionsApi) =>
collectionsApi.getFilteredByTag(section)
);
}
// --- Passthrough copies ---
// Root-level shared assets
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
eleventyConfig.addPassthroughCopy({ "shared/favicon/hub.svg": "favicon.svg" });
// Per-section assets: favicon, robots.txt
for (const [section, cfg] of Object.entries(SITES)) {
const prefix = cfg.pathPrefix || ".";
eleventyConfig.addPassthroughCopy({
[`shared/favicon/${section}.svg`]: `${prefix}/favicon.svg`,
});
eleventyConfig.addPassthroughCopy({
[`${section}/robots.txt`]: `${prefix}/robots.txt`,
});
}
// Hub: profile photo
eleventyConfig.addPassthroughCopy({
"hub/stefan-koelle-foto.jpg": "stefan-koelle-foto.jpg",
});
// Hub: old www.moonweb.org redirects (.htm → .html)
const htmRedirects = [
"start",
"index2",
"index3",
"new",
"links",
"infos",
"hardware",
"users",
];
for (const name of htmRedirects) {
eleventyConfig.addPassthroughCopy({
[`hub/${name}.htm`]: `${name}.html`,
});
}
const htmSubdirs = [
"beginning",
"projects",
"products",
"acoustics",
"location",
"background",
"partners",
"contact",
"network",
"stefan",
"legal-notice",
];
for (const dir of htmSubdirs) {
eleventyConfig.addPassthroughCopy({
[`hub/${dir}/index.htm`]: `${dir}/index.html`,
});
}
return {
dir: {
input: ".",
includes: "shared/_includes",
output: "dist",
},
serverOptions: {
host: "0.0.0.0",
port: 8081,
},
};
};
+1
View File
@@ -1,6 +1,7 @@
---
title: "Page Not Found"
section: "hub"
tags: "hub"
description: "The page you are looking for does not exist."
layout: base.njk
permalink: /404.html
-46
View File
@@ -1,46 +0,0 @@
module.exports = function (eleventyConfig) {
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org" });
eleventyConfig.addFilter("date", (d) => d.toISOString());
eleventyConfig.addPassthroughCopy({ "shared/theme-hub.css": "theme.css" });
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
eleventyConfig.addPassthroughCopy({ "shared/favicon/hub.svg": "favicon.svg" });
eleventyConfig.addPassthroughCopy({ "hub/robots.txt": "robots.txt" });
eleventyConfig.addPassthroughCopy({ "hub/stefan-koelle-foto.jpg": "stefan-koelle-foto.jpg" });
// Old www.moonweb.org redirects (root-level HTML shortcuts)
// .htm files are excluded from Eleventy processing, renamed to .html on output
eleventyConfig.addPassthroughCopy({ "hub/start.htm": "start.html" });
eleventyConfig.addPassthroughCopy({ "hub/index2.htm": "index2.html" });
eleventyConfig.addPassthroughCopy({ "hub/index3.htm": "index3.html" });
eleventyConfig.addPassthroughCopy({ "hub/new.htm": "new.html" });
eleventyConfig.addPassthroughCopy({ "hub/links.htm": "links.html" });
eleventyConfig.addPassthroughCopy({ "hub/infos.htm": "infos.html" });
eleventyConfig.addPassthroughCopy({ "hub/hardware.htm": "hardware.html" });
eleventyConfig.addPassthroughCopy({ "hub/users.htm": "users.html" });
// Old www.moonweb.org redirects (content subdirectories)
// .htm files are excluded from Eleventy processing, renamed to .html on output
eleventyConfig.addPassthroughCopy({ "hub/beginning/index.htm": "beginning/index.html" });
eleventyConfig.addPassthroughCopy({ "hub/projects/index.htm": "projects/index.html" });
eleventyConfig.addPassthroughCopy({ "hub/products/index.htm": "products/index.html" });
eleventyConfig.addPassthroughCopy({ "hub/acoustics/index.htm": "acoustics/index.html" });
eleventyConfig.addPassthroughCopy({ "hub/location/index.htm": "location/index.html" });
eleventyConfig.addPassthroughCopy({ "hub/background/index.htm": "background/index.html" });
eleventyConfig.addPassthroughCopy({ "hub/partners/index.htm": "partners/index.html" });
eleventyConfig.addPassthroughCopy({ "hub/contact/index.htm": "contact/index.html" });
eleventyConfig.addPassthroughCopy({ "hub/network/index.htm": "network/index.html" });
eleventyConfig.addPassthroughCopy({ "hub/stefan/index.htm": "stefan/index.html" });
eleventyConfig.addPassthroughCopy({ "hub/legal-notice/index.htm": "legal-notice/index.html" });
return {
dir: {
input: "hub",
includes: "../shared/_includes",
output: "dist/hub"
},
serverOptions: {
host: "0.0.0.0",
port: 8081
}
};
};
+2
View File
@@ -1,6 +1,8 @@
---
title: "Legal Notice & Privacy Policy"
section: "hub"
tags: "hub"
permalink: /impressum/index.html
description: "Legal notice (Impressum) and privacy policy for moonweb.org."
layout: base.njk
---
+2
View File
@@ -1,6 +1,8 @@
---
title: "Stefan Koelle — Homelab, Smart Home, Code & Retro Hardware | moonweb"
section: "hub"
tags: "hub"
permalink: /index.html
description: "Stefan Koelle — Senior Software Developer & Architect based in Munich. Homelab projects, smart home automation, open-source code, and retro hardware."
layout: base.njk
ogImage: "/stefan-koelle-foto.jpg"
+1
View File
@@ -1,5 +1,6 @@
---
permalink: /sitemap.xml
sitemapCollection: "hub"
eleventyExcludeFromCollections: true
---
{% include "sitemap.njk" %}
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Page Not Found"
section: "infra"
tags: "infra"
description: "The page you are looking for does not exist."
layout: base.njk
permalink: /404.html
permalink: /infra/404.html
---
<div class="error-page">
<h1>404</h1>
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "Backup Strategy"
section: "infra"
parent: "/"
tags: "infra"
parent: "/infra/"
description: "Layered 3-2-1 backup strategy covering NAS snapshots, offsite copies, and cloud storage."
layout: base.njk
---
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "Xubuntu Dev VM"
section: "infra"
parent: "/"
tags: "infra"
parent: "/infra/"
description: "A disposable, daily-backed-up Xubuntu VM for AI-assisted, remote development via SSH and Chrome Remote Desktop."
layout: base.njk
---
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "Docker Container Strategy"
section: "infra"
parent: "/"
tags: "infra"
parent: "/infra/"
description: "How self-hosted services are organized across three Docker hosts on Proxmox and Synology, using Docker Compose as the single source of truth."
layout: base.njk
---
-20
View File
@@ -1,20 +0,0 @@
module.exports = function (eleventyConfig) {
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org", pathPrefix: "/infra" });
eleventyConfig.addFilter("date", (d) => d.toISOString());
eleventyConfig.addPassthroughCopy({ "shared/theme-infra.css": "theme.css" });
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
eleventyConfig.addPassthroughCopy({ "shared/favicon/infra.svg": "favicon.svg" });
eleventyConfig.addPassthroughCopy({ "infra/robots.txt": "robots.txt" });
return {
dir: {
input: "infra",
includes: "../shared/_includes",
output: "dist/infra"
},
serverOptions: {
host: "0.0.0.0",
port: 8082
}
};
};
+1
View File
@@ -1,6 +1,7 @@
---
title: "Homelab Infrastructure — Proxmox, Docker & Networking | moonweb"
section: "infra"
tags: "infra"
description: "Homelab infrastructure overview: Proxmox hosts, Synology NAS, Docker VMs and Setup-Guides."
layout: base.njk
sections:
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "LXC Container Strategy"
section: "infra"
parent: "/"
tags: "infra"
parent: "/infra/"
description: "Overview of the lightweight LXC containers running on the main Proxmox host, and why they are LXC instead of Docker."
layout: base.njk
---
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "Monitoring"
section: "infra"
parent: "/"
tags: "infra"
parent: "/infra/"
description: "Prometheus and Grafana monitoring stack for hosts, containers, and hardware metrics."
layout: base.njk
---
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "OpenWrt Multi-WAN Router"
section: "infra"
parent: "/"
tags: "infra"
parent: "/infra/"
description: "Multi-WAN OpenWrt router in the study with WiFi and Powerline failover, plus VLAN 189 for IoT isolation."
layout: base.njk
---
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "Proxmox Virtualization"
section: "infra"
parent: "/"
tags: "infra"
parent: "/infra/"
description: "What Proxmox VE is, the hardware it runs on in my homelab, its key advantages, and a short setup walkthrough."
layout: base.njk
---
+2 -1
View File
@@ -1,5 +1,6 @@
---
permalink: /sitemap.xml
permalink: /infra/sitemap.xml
sitemapCollection: "infra"
eleventyExcludeFromCollections: true
---
{% include "sitemap.njk" %}
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "Synology NAS"
section: "infra"
parent: "/"
tags: "infra"
parent: "/infra/"
description: "Model, storage setup and current role of the Synology NAS in the homelab after the late-2025 cleanup."
layout: base.njk
---
+3 -14
View File
@@ -4,24 +4,13 @@
"private": true,
"description": "Monorepo for hub/infra/smarthome/code/retro/timecapsule (Eleventy, static, deployed to IONOS SFTP)",
"scripts": {
"dev:hub": "npx @11ty/eleventy --config=hub/eleventy.config.js --serve --port=8081",
"dev:infra": "npx @11ty/eleventy --config=infra/eleventy.config.js --serve --port=8082",
"dev:smarthome": "npx @11ty/eleventy --config=smarthome/eleventy.config.js --serve --port=8083",
"dev:code": "npx @11ty/eleventy --config=code/eleventy.config.js --serve --port=8084",
"dev:retro": "npx @11ty/eleventy --config=retro/eleventy.config.js --serve --port=8085",
"dev": "npx @11ty/eleventy --serve --port=8081",
"dev:stefankoelle": "npx @11ty/eleventy --config=stefankoelle/eleventy.config.js --serve --port=8086",
"dev:timecapsule": "cd timecapsule && npx @11ty/eleventy --serve --port=8087",
"dev": "npm run dev:hub & npm run dev:infra & npm run dev:smarthome & npm run dev:code & npm run dev:retro & npm run dev:stefankoelle & npm run dev:timecapsule",
"build:hub": "npx @11ty/eleventy --config=hub/eleventy.config.js",
"build:infra": "npx @11ty/eleventy --config=infra/eleventy.config.js",
"build:smarthome": "npx @11ty/eleventy --config=smarthome/eleventy.config.js",
"build:code": "npx @11ty/eleventy --config=code/eleventy.config.js",
"build:retro": "npx @11ty/eleventy --config=retro/eleventy.config.js",
"build": "npx @11ty/eleventy",
"build:stefankoelle": "npx @11ty/eleventy --config=stefankoelle/eleventy.config.js",
"build:timecapsule": "cd timecapsule && npx @11ty/eleventy && mkdir -p ../../dist/timecapsule && cp -r _site/timecapsule/* ../../dist/timecapsule/",
"build": "npm run build:hub && npm run build:infra && npm run build:smarthome && npm run build:code && npm run build:retro && npm run build:timecapsule && npm run build:stefankoelle",
"build:moonweb": "npm run build:hub && npm run build:infra && npm run build:smarthome && npm run build:code && npm run build:retro && npm run build:timecapsule",
"merge:moonweb": "bash scripts/merge-moonweb.sh",
"build:moonweb": "npm run build && npm run build:timecapsule",
"pdf:cv": "bash stefankoelle/pdf/build-pdf.sh",
"prebuild": "bash prebuild.sh"
},
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Page Not Found"
section: "retro"
tags: "retro"
description: "The page you are looking for does not exist."
layout: base.njk
permalink: /404.html
permalink: /retro/404.html
---
<div class="error-page">
<h1>404</h1>
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Running AmigaOS in 2026: MiSTer FPGA vs Amiberry on Pi"
section: "retro"
tags: "retro"
description: "Two very different ways to get a fully working AmigaOS 3.2.3 desktop running today."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>Running AmigaOS in 2026: MiSTer FPGA vs Amiberry on Pi</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Vampire V4, A600GS or MiSTer: Amiga Hardware Isn't a Simple Speed Race"
section: "retro"
tags: "retro"
description: "Why raw benchmark numbers are actively misleading when comparing modern Amiga hardware options."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>Vampire V4, A600GS or MiSTer: Amiga Hardware Isn't a Simple Speed Race</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "The Atari Mega ST Fleet: Three Machines, Three Jobs"
section: "retro"
tags: "retro"
description: "How three Atari Mega ST computers ended up with completely different roles in a modern retro setup."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>The Atari Mega ST Fleet: Three Machines, Three Jobs</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "The 15kHz Problem: Finding a Monitor That Speaks Atari"
section: "retro"
tags: "retro"
description: "Why most modern monitors refuse to display Atari ST colour modes, and which ones actually work."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>The 15kHz Problem: Finding a Monitor That Speaks Atari</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Getting a 40-Year-Old Computer Online: Atari ST Networking and BBS Access"
section: "retro"
tags: "retro"
description: "Connecting an Atari ST to the modern internet and to dial-up-era bulletin board systems, warts and all."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>Getting a 40-Year-Old Computer Online: Atari ST Networking and BBS Access</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Batocera in the Living Room: One Box, Every Retro System"
section: "retro"
tags: "retro"
description: "Why the living room emulation station takes a completely different approach than the FPGA rigs upstairs."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>Batocera in the Living Room: One Box, Every Retro System</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "The 486 DX2-66: Rebuilding a DOS Era on MiSTer FPGA"
section: "retro"
tags: "retro"
description: "Recreating a Vobis-Highscreen 486 DX2-66 setup on MiSTer FPGA, down to the original DOS configuration."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>The 486 DX2-66: Rebuilding a DOS Era on MiSTer FPGA</h1>
<div class="detail-content">
-20
View File
@@ -1,20 +0,0 @@
module.exports = function (eleventyConfig) {
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org", pathPrefix: "/retro" });
eleventyConfig.addFilter("date", (d) => d.toISOString());
eleventyConfig.addPassthroughCopy({ "shared/theme-retro.css": "theme.css" });
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
eleventyConfig.addPassthroughCopy({ "shared/favicon/retro.svg": "favicon.svg" });
eleventyConfig.addPassthroughCopy({ "retro/robots.txt": "robots.txt" });
return {
dir: {
input: "retro",
includes: "../shared/_includes",
output: "dist/retro"
},
serverOptions: {
host: "0.0.0.0",
port: 8085
}
};
};
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Why an ET4000 Graphics Card and a Mega ST Don't Mix (Yet)"
section: "retro"
tags: "retro"
description: "A cautionary tale about pushing 1980s power supplies past their limits with a modern-ish graphics card."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>Why an ET4000 Graphics Card and a Mega ST Don't Mix (Yet)</h1>
<div class="detail-content">
+1
View File
@@ -1,6 +1,7 @@
---
title: "Retro Computing — Vintage Hardware & FPGA | moonweb"
section: "retro"
tags: "retro"
description: "Physical retro hardware collection — Raspberry Pi units, Pi 400, and vintage computing setups."
layout: base.njk
sections:
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Mini Arcades and Handhelds: Sorting the Real Deal from the Bootlegs"
section: "retro"
tags: "retro"
description: "A buyer's guide to compact retro gaming devices, and why the cheapest option is almost never the right one."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>Mini Arcades and Handhelds: Sorting the Real Deal from the Bootlegs</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "MiST and MiSTer: Two FPGA Boxes, Two Very Different Personalities"
section: "retro"
tags: "retro"
description: "Why the newer, more powerful MiSTer isn't automatically the better choice for every retro system."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>MiST and MiSTer: Two FPGA Boxes, Two Very Different Personalities</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "PiMiga 3 to 5: Chasing the Perfect Amiga-on-a-Pi Distro"
section: "retro"
tags: "retro"
description: "Three generations of PiMiga later, here's what actually changed — and why newer isn't always better."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>PiMiga 3 to 5: Chasing the Perfect Amiga-on-a-Pi Distro</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "The Retro Corner Today: A Snapshot of the Whole Setup"
section: "retro"
tags: "retro"
description: "A tour through every retro system currently active across the home, room by room."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>The Retro Corner Today: A Snapshot of the Whole Setup</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,5 +1,6 @@
---
permalink: /sitemap.xml
permalink: /retro/sitemap.xml
sitemapCollection: "retro"
eleventyExcludeFromCollections: true
---
{% include "sitemap.njk" %}
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "The A1200: What We Know Before It Ships"
section: "retro"
tags: "retro"
description: "A close look at Retro Games Ltd.'s upcoming Amiga 1200 console, based on everything public so far."
layout: base.njk
parent: "/"
parent: "/retro/"
---
<h1>The A1200: What We Know Before It Ships</h1>
<div class="detail-content">
-49
View File
@@ -1,49 +0,0 @@
#!/bin/bash
set -e
echo "Merging moonweb sites for deployment..."
# Clean previous merge
rm -rf dist/root
# Hub becomes root
echo " hub → dist/ (root)"
cp -r dist/hub dist/root
rm -rf dist/hub
# Copy timecapsule from its build output
if [ -d "timecapsule/_site/timecapsule" ]; then
echo " timecapsule → dist/root/timecapsule/"
cp -r timecapsule/_site/timecapsule dist/root/timecapsule
else
echo " WARNING: timecapsule/_site/timecapsule not found, skipping"
fi
# Subdirectories
for site in infra smarthome code retro; do
if [ -d "dist/$site" ]; then
echo " $site → dist/root/$site/"
cp -r "dist/$site" "dist/root/$site"
else
echo " WARNING: dist/$site not found, skipping"
fi
done
# Copy CSS to root (accessible from all subdirectories)
echo " Copying shared CSS to root..."
cp shared/base.css dist/root/shared-base.css
cp shared/favicon/hub.svg dist/root/favicon.svg
# Theme CSS files are already copied during build as theme.css
# The root theme is hub's theme
if [ -f "dist/root/theme.css" ]; then
echo " Root theme.css already in place"
fi
echo ""
echo "Merge complete. Final structure:"
find dist/root -maxdepth 2 -type f | sort | head -50
echo "..."
echo ""
echo "Total files:"
find dist/root -type f | wc -l
+2 -2
View File
@@ -20,8 +20,8 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lobster&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ site.pathPrefix | default('') }}/shared-base.css">
<link rel="stylesheet" href="{{ site.pathPrefix | default('') }}/theme.css">
<link rel="stylesheet" href="/shared-base.css">
<style>:root { --accent: {% if section == 'infra' %}#99333A{% elif section == 'smarthome' %}#1f8a8a{% elif section == 'code' %}#3E5098{% elif section == 'retro' %}#8a6d3b{% else %}#3b6ea5{% endif %}; }</style>
<script type="application/ld+json">
{
"@context": "https://schema.org",
+3 -2
View File
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for page in collections.all %}
{%- set sitemapPages = collections[sitemapCollection] if sitemapCollection else collections.all %}
{%- for page in sitemapPages %}
{%- if not page.url.includes("404") %}
<url>
<loc>{{ site.url }}{{ site.pathPrefix | default("") }}{{ page.url }}</loc>
<loc>{{ site.url }}{{ pathPrefix }}{{ page.url }}</loc>
<lastmod>{{ page.date | date }}</lastmod>
<changefreq>{% if page.url == "/" %}weekly{% else %}monthly{% endif %}</changefreq>
<priority>{% if page.url == "/" %}1.0{% elif parent %}0.8{% else %}0.7{% endif %}</priority>
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Page Not Found"
section: "smarthome"
tags: "smarthome"
description: "The page you are looking for does not exist."
layout: base.njk
permalink: /404.html
permalink: /smarthome/404.html
---
<div class="error-page">
<h1>404</h1>
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "Air Quality Dashboard"
section: "smarthome"
parent: "/"
tags: "smarthome"
parent: "/smarthome/"
description: "Real-time temperature and humidity monitoring across all rooms, powered by InfluxDB and Chart.js."
layout: base.njk
---
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "AirPlay Audio"
section: "smarthome"
parent: "/"
tags: "smarthome"
parent: "/smarthome/"
description: "Multi-room AirPlay receivers with software volume boost on Raspberry Pi devices."
layout: base.njk
---
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "Balkon Pi"
section: "smarthome"
parent: "/"
tags: "smarthome"
parent: "/smarthome/"
description: "Balcony sensor, lighting, and audio automation running on a Raspberry Pi Zero W."
layout: base.njk
---
-20
View File
@@ -1,20 +0,0 @@
module.exports = function (eleventyConfig) {
eleventyConfig.addGlobalData("site", { url: "https://www.moonweb.org", pathPrefix: "/smarthome" });
eleventyConfig.addFilter("date", (d) => d.toISOString());
eleventyConfig.addPassthroughCopy({ "shared/theme-smarthome.css": "theme.css" });
eleventyConfig.addPassthroughCopy({ "shared/base.css": "shared-base.css" });
eleventyConfig.addPassthroughCopy({ "shared/favicon/smarthome.svg": "favicon.svg" });
eleventyConfig.addPassthroughCopy({ "smarthome/robots.txt": "robots.txt" });
return {
dir: {
input: "smarthome",
includes: "../shared/_includes",
output: "dist/smarthome"
},
serverOptions: {
host: "0.0.0.0",
port: 8083
}
};
};
+2 -1
View File
@@ -1,9 +1,10 @@
---
title: "Home Control Buttons"
section: "smarthome"
tags: "smarthome"
description: "Overview of the central buttons dashboard used to control lighting, smart plugs, music, network devices and monitoring shortcuts around the apartment."
layout: base.njk
parent: "/"
parent: "/smarthome/"
---
<h1>Home Control Buttons</h1>
<div class="detail-content">
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "HomematicIP + MQTT"
section: "smarthome"
parent: "/"
tags: "smarthome"
parent: "/smarthome/"
description: "How HomematicIP room sensors are bridged into MQTT and stored in InfluxDB for dashboards."
layout: base.njk
---
+1
View File
@@ -1,6 +1,7 @@
---
title: "Smart Home Projects — Home Assistant, MQTT & IoT | moonweb"
section: "smarthome"
tags: "smarthome"
description: "Smart home projects: Home Assistant, MQTT sensors, AirPlay audio, OctoPrint, and energy monitoring."
layout: base.njk
sections:
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "Kids RFID MP3 Player"
section: "smarthome"
parent: "/"
tags: "smarthome"
parent: "/smarthome/"
description: "A custom Arduino-based RFID audio player for kids, inspired by the Hoerbert and Tonuino projects."
layout: base.njk
---
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "OctoPrint"
section: "smarthome"
parent: "/"
tags: "smarthome"
parent: "/smarthome/"
description: "Remote 3D printer control and camera streaming with OctoPrint on Raspberry Pi."
layout: base.njk
---
+2 -1
View File
@@ -1,5 +1,6 @@
---
permalink: /sitemap.xml
permalink: /smarthome/sitemap.xml
sitemapCollection: "smarthome"
eleventyExcludeFromCollections: true
---
{% include "sitemap.njk" %}
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "Tasmota & Energy Monitoring"
section: "smarthome"
parent: "/"
tags: "smarthome"
parent: "/smarthome/"
description: "Smart plugs and power monitoring across the flat with daily usage statistics."
layout: base.njk
---
+2 -1
View File
@@ -1,7 +1,8 @@
---
title: "YouTube Archiving: TubeArchivist & TubeSync"
section: "smarthome"
parent: "/"
tags: "smarthome"
parent: "/smarthome/"
description: "Self-hosted YouTube archiving and media management running on the Synology NAS, comparing TubeArchivist and TubeSync."
layout: base.njk
---