From 2b225197b4c33017271ec8edd756b48a0063345f Mon Sep 17 00:00:00 2001
From: Ricardo
Date: Sat, 24 Jan 2026 12:13:34 +0100
Subject: [PATCH] Initial commit: Indiekit Eleventy theme
---
.gitignore | 26 +
404.njk | 8 +
README.md | 56 +
_data/blueskyFeed.js | 68 +
_data/cv.js | 190 +
_data/funkwhaleActivity.js | 123 +
_data/githubActivity.js | 228 +
_data/githubRepos.js | 48 +
_data/mastodonFeed.js | 96 +
_data/site.js | 66 +
_data/youtubeChannel.js | 206 +
_includes/components/blog-sidebar.njk | 184 +
.../components/funkwhale-stats-content.njk | 66 +
_includes/components/h-card.njk | 64 +
_includes/components/reply-context.njk | 63 +
_includes/components/sidebar.njk | 258 +
_includes/components/webmentions.njk | 158 +
_includes/layouts/base.njk | 219 +
_includes/layouts/home.njk | 208 +
_includes/layouts/post.njk | 123 +
about.njk | 68 +
articles.njk | 92 +
blog.njk | 130 +
bookmarks.njk | 29 +
categories-index.njk | 27 +
categories.njk | 69 +
css/tailwind.css | 317 ++
eleventy.config.js | 360 ++
feed-json.njk | 34 +
feed.njk | 31 +
funkwhale.njk | 268 +
github.njk | 266 +
images/default-avatar.svg | 5 +
images/og-default.png | Bin 0 -> 39412 bytes
images/rick.jpg | Bin 0 -> 152493 bytes
index.njk | 4 +
interactions.njk | 105 +
likes.njk | 47 +
notes.njk | 89 +
package-lock.json | 4514 +++++++++++++++++
package.json | 31 +
photos.njk | 23 +
postcss.config.js | 6 +
replies.njk | 51 +
reposts.njk | 53 +
tailwind.config.js | 79 +
youtube.njk | 262 +
47 files changed, 9418 insertions(+)
create mode 100644 .gitignore
create mode 100644 404.njk
create mode 100644 README.md
create mode 100644 _data/blueskyFeed.js
create mode 100644 _data/cv.js
create mode 100644 _data/funkwhaleActivity.js
create mode 100644 _data/githubActivity.js
create mode 100644 _data/githubRepos.js
create mode 100644 _data/mastodonFeed.js
create mode 100644 _data/site.js
create mode 100644 _data/youtubeChannel.js
create mode 100644 _includes/components/blog-sidebar.njk
create mode 100644 _includes/components/funkwhale-stats-content.njk
create mode 100644 _includes/components/h-card.njk
create mode 100644 _includes/components/reply-context.njk
create mode 100644 _includes/components/sidebar.njk
create mode 100644 _includes/components/webmentions.njk
create mode 100644 _includes/layouts/base.njk
create mode 100644 _includes/layouts/home.njk
create mode 100644 _includes/layouts/post.njk
create mode 100644 about.njk
create mode 100644 articles.njk
create mode 100644 blog.njk
create mode 100644 bookmarks.njk
create mode 100644 categories-index.njk
create mode 100644 categories.njk
create mode 100644 css/tailwind.css
create mode 100644 eleventy.config.js
create mode 100644 feed-json.njk
create mode 100644 feed.njk
create mode 100644 funkwhale.njk
create mode 100644 github.njk
create mode 100644 images/default-avatar.svg
create mode 100644 images/og-default.png
create mode 100644 images/rick.jpg
create mode 100644 index.njk
create mode 100644 interactions.njk
create mode 100644 likes.njk
create mode 100644 notes.njk
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 photos.njk
create mode 100644 postcss.config.js
create mode 100644 replies.njk
create mode 100644 reposts.njk
create mode 100644 tailwind.config.js
create mode 100644 youtube.njk
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea329ac
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+# Dependencies
+node_modules/
+
+# Build output
+_site/
+css/style.css
+
+# Cache
+.cache/
+
+# Content (symlinked at runtime)
+content/
+uploads/
+
+# Personal overrides (should be in parent repo)
+*.rmendes
+
+# OS files
+.DS_Store
+Thumbs.db
+
+# Editor
+.vscode/
+.idea/
+*.swp
+*.swo
diff --git a/404.njk b/404.njk
new file mode 100644
index 0000000..332ccc1
--- /dev/null
+++ b/404.njk
@@ -0,0 +1,8 @@
+---
+layout: layouts/base.njk
+title: Page Not Found
+permalink: /404.html
+---
+404 - Page Not Found
+Sorry, the page you're looking for doesn't exist.
+Go back to the homepage
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..db997e0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+# Indiekit Eleventy Theme
+
+A modern, responsive Eleventy theme designed for [Indiekit](https://getindiekit.com/)-powered IndieWeb blogs.
+
+## Features
+
+- **IndieWeb Ready**: Full h-card, h-entry, h-feed microformats support
+- **Dark Mode**: Automatic dark/light mode with manual toggle
+- **Responsive**: Mobile-first design with Tailwind CSS
+- **Social Integration**:
+ - Bluesky and Mastodon feeds in sidebar
+ - GitHub activity page
+ - Funkwhale listening history
+ - YouTube channel display
+- **Performance**: Optimized images, lazy loading, prefetching
+- **Accessible**: Semantic HTML, proper ARIA labels
+
+## Usage
+
+This theme is designed to be used as a git submodule in an Indiekit deployment:
+
+```bash
+git submodule add https://github.com/rmdes/indiekit-eleventy-theme.git eleventy-site
+```
+
+## Configuration
+
+The theme uses environment variables for configuration. See the data files in `_data/` for required variables:
+
+- `SITE_NAME`, `SITE_URL`, `SITE_DESCRIPTION`
+- `AUTHOR_NAME`, `AUTHOR_BIO`, `AUTHOR_AVATAR`
+- `GITHUB_USERNAME`, `BLUESKY_HANDLE`, `MASTODON_INSTANCE`
+- And more...
+
+## Directory Structure
+
+```
+├── _data/ # Eleventy data files (site config, API fetchers)
+├── _includes/ # Layouts and components
+├── css/ # Tailwind CSS source
+├── images/ # Static images
+├── *.njk # Page templates
+├── eleventy.config.js
+└── package.json
+```
+
+## Development
+
+```bash
+npm install
+npm run dev
+```
+
+## License
+
+MIT
diff --git a/_data/blueskyFeed.js b/_data/blueskyFeed.js
new file mode 100644
index 0000000..b0083bc
--- /dev/null
+++ b/_data/blueskyFeed.js
@@ -0,0 +1,68 @@
+/**
+ * Bluesky Feed Data
+ * Fetches recent posts from Bluesky using the AT Protocol API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+import { BskyAgent } from "@atproto/api";
+
+export default async function () {
+ const handle = process.env.BLUESKY_HANDLE || "";
+
+ try {
+ // Create agent and resolve handle to DID
+ const agent = new BskyAgent({ service: "https://bsky.social" });
+
+ // Get the author's feed using public API (no auth needed for public posts)
+ const feedUrl = `https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${handle}&limit=10`;
+
+ const response = await EleventyFetch(feedUrl, {
+ duration: "15m", // Cache for 15 minutes
+ type: "json",
+ fetchOptions: {
+ headers: {
+ Accept: "application/json",
+ },
+ },
+ });
+
+ if (!response.feed) {
+ console.log("No Bluesky feed found for handle:", handle);
+ return [];
+ }
+
+ // Transform the feed into a simpler format
+ return response.feed.map((item) => {
+ // Extract rkey from AT URI (at://did:plc:xxx/app.bsky.feed.post/rkey)
+ const rkey = item.post.uri.split("/").pop();
+ const postUrl = `https://bsky.app/profile/${item.post.author.handle}/post/${rkey}`;
+
+ return {
+ text: item.post.record.text,
+ createdAt: item.post.record.createdAt,
+ uri: item.post.uri,
+ url: postUrl,
+ cid: item.post.cid,
+ author: {
+ handle: item.post.author.handle,
+ displayName: item.post.author.displayName,
+ avatar: item.post.author.avatar,
+ },
+ likeCount: item.post.likeCount || 0,
+ repostCount: item.post.repostCount || 0,
+ replyCount: item.post.replyCount || 0,
+ // Extract any embedded links or images
+ embed: item.post.embed
+ ? {
+ type: item.post.embed.$type,
+ images: item.post.embed.images || [],
+ external: item.post.embed.external || null,
+ }
+ : null,
+ };
+ });
+ } catch (error) {
+ console.error("Error fetching Bluesky feed:", error.message);
+ return [];
+ }
+}
diff --git a/_data/cv.js b/_data/cv.js
new file mode 100644
index 0000000..576eac9
--- /dev/null
+++ b/_data/cv.js
@@ -0,0 +1,190 @@
+/**
+ * CV Data - Easy to update!
+ *
+ * To add a new experience: Add an entry to the `experience` array
+ * To add a new project: Add an entry to the `projects` array
+ * To update skills: Modify the `skills` object
+ */
+
+export default {
+ // Last updated date - automatically set to build time
+ lastUpdated: new Date().toISOString().split("T")[0],
+
+ // Work Experience - Add new positions at the TOP of the array
+ experience: [
+ {
+ title: "Middleware Engineer",
+ company: "FGTB-ABVV",
+ location: "Brussels",
+ startDate: "2023-11",
+ endDate: null, // null = present
+ type: "full-time",
+ description: "Technology Specialist focusing on IT infrastructure and application delivery",
+ highlights: [
+ "Strategic migration of Java applications from legacy IBM Datapowers and PureApp systems",
+ "Containerized application deployment on VMware Linux and OpenShift Kubernetes clusters",
+ "Mastering OpenShift, Kubernetes, and Docker technologies"
+ ]
+ },
+ {
+ title: "Solution Architect",
+ company: "OSINTukraine.com",
+ location: "Remote",
+ startDate: "2022-02",
+ endDate: null,
+ type: "volunteer",
+ description: "Open-source intelligence (OSINT) initiative for Ukraine conflict monitoring",
+ highlights: [
+ "Collection, archiving, translation, analysis and dissemination of critical information",
+ "Monitoring Russian Telegram channels with filtering, categorization, and archiving",
+ "Sub-projects: War crimes archive, Drones research, Location-related alerts system"
+ ]
+ },
+ {
+ title: "DevOps Training",
+ company: "BeCode",
+ location: "Brussels",
+ startDate: "2021-09",
+ endDate: "2022-03",
+ type: "training",
+ description: "7-month intensive DevOps specialization",
+ highlights: [
+ "Vagrant and Ansible infrastructure as code for WordPress, Nginx, Redis",
+ "Docker Swarm cluster management",
+ "GitLab CI/CD with SonarQube security audits",
+ "Jenkins pipelines, Python basics, Prometheus/Grafana monitoring"
+ ]
+ },
+ {
+ title: "CTO",
+ company: "DigitYser",
+ location: "Brussels",
+ startDate: "2018-10",
+ endDate: "2020-03",
+ type: "full-time",
+ description: "Digital flagship of tech communities in Brussels",
+ highlights: [
+ "Hosting infrastructure and automation",
+ "Integrations with digital marketing tools",
+ "Technical Event Management: Livestreaming, sound, video, photos"
+ ]
+ },
+ {
+ title: "Solution Architect",
+ company: "Armada.digital",
+ location: "Brussels",
+ startDate: "2016-05",
+ endDate: "2021-12",
+ type: "freelance",
+ description: "Consultancy to amplify visibility of good causes",
+ highlights: [
+ "Custom communication and collaboration solutions",
+ "Empowering individuals and ethical businesses"
+ ]
+ },
+ {
+ title: "FactChecking Platform",
+ company: "Journalistes Solidaires",
+ location: "Brussels",
+ startDate: "2020-03",
+ endDate: "2020-05",
+ type: "volunteer",
+ description: "Cloudron/Docker backend for factchecking workflow",
+ highlights: [
+ "WordPress with custom post types for COVID-19 disinformation monitoring"
+ ]
+ },
+ {
+ title: "Event Manager",
+ company: "European Data Innovation Hub",
+ location: "Brussels",
+ startDate: "2019-02",
+ endDate: "2020-03",
+ type: "full-time",
+ description: "Technical event organization and management"
+ },
+ {
+ title: "Technical Advisor",
+ company: "WomenPreneur-Initiative",
+ location: "Brussels",
+ startDate: "2019-01",
+ endDate: "2020-01",
+ type: "volunteer",
+ description: "Technical guidance for women-focused entrepreneurship initiative"
+ },
+ {
+ title: "Technical Advisor",
+ company: "Promote Ukraine",
+ location: "Brussels",
+ startDate: "2019-01",
+ endDate: "2020-01",
+ type: "freelance",
+ description: "Technical consulting for Ukraine advocacy organization"
+ }
+ ],
+
+ // Current/Recent Projects - Add new projects at the TOP
+ projects: [
+ {
+ name: "OSINT Intelligence Platform",
+ url: "https://osintukraine.com",
+ description: "Real-time monitoring and analysis platform for open-source intelligence",
+ technologies: ["Docker", "Telegram API", "Python", "PostgreSQL"],
+ status: "active"
+ },
+ {
+ name: "Indiekit Cloudron Package",
+ url: "https://github.com/rmdes/indiekit-cloudron",
+ description: "Cloudron-packaged IndieWeb publishing server with Eleventy frontend",
+ technologies: ["Node.js", "Eleventy", "Docker", "Cloudron"],
+ status: "active"
+ }
+ // Add more projects here as needed
+ ],
+
+ // Skills - Organized by category
+ skills: {
+ containers: ["OpenShift", "Kubernetes", "Docker", "Docker Swarm"],
+ automation: ["Ansible", "Vagrant", "GitLab CI/CD", "Jenkins", "GitHub Actions"],
+ monitoring: ["Prometheus", "Grafana", "OpenTelemetry"],
+ systems: ["Linux Administration", "System Administration", "VMware"],
+ hosting: ["Cloudron", "On-Premise", "Cloud Infrastructure"],
+ web: ["Nginx", "Redis", "WordPress", "TLS/SSL", "Eleventy"],
+ security: ["SonarQube", "Information Assurance", "OSINT"],
+ languages: ["Python", "Bash", "JavaScript", "Node.js"]
+ },
+
+ // Languages spoken
+ languages: [
+ { name: "Portuguese", level: "Native" },
+ { name: "French", level: "Fluent" },
+ { name: "English", level: "Fluent" },
+ { name: "Spanish", level: "Conversational" }
+ ],
+
+ // Education
+ education: [
+ {
+ degree: "DevOps Training",
+ institution: "BeCode",
+ location: "Brussels",
+ year: "2021-2022",
+ description: "7-month intensive DevOps specialization"
+ },
+ {
+ degree: "Bachelor's in Management Information Technology",
+ institution: "ISLA - Instituto Superior de Gestão e Tecnologia",
+ location: "Portugal",
+ year: "1998-2001",
+ description: "Curso Técnico Superior Profissional de Informática de Gestão"
+ }
+ ],
+
+ // Interests
+ interests: [
+ "Music Production (Ableton Live, Ableton Push 3)",
+ "IndieWeb & Decentralized Tech",
+ "Open Source Intelligence (OSINT)",
+ "Democracy & Digital Rights"
+ ]
+};
diff --git a/_data/funkwhaleActivity.js b/_data/funkwhaleActivity.js
new file mode 100644
index 0000000..316aeb4
--- /dev/null
+++ b/_data/funkwhaleActivity.js
@@ -0,0 +1,123 @@
+/**
+ * Funkwhale Activity Data
+ * Fetches from Indiekit's endpoint-funkwhale public API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+const FUNKWHALE_INSTANCE = process.env.FUNKWHALE_INSTANCE || "";
+
+/**
+ * Fetch from Indiekit's public Funkwhale API endpoint
+ */
+async function fetchFromIndiekit(endpoint) {
+ try {
+ const url = `${INDIEKIT_URL}/funkwhaleapi/api/${endpoint}`;
+ console.log(`[funkwhaleActivity] Fetching from Indiekit: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log(`[funkwhaleActivity] Indiekit ${endpoint} success`);
+ return data;
+ } catch (error) {
+ console.log(
+ `[funkwhaleActivity] Indiekit API unavailable for ${endpoint}: ${error.message}`
+ );
+ return null;
+ }
+}
+
+/**
+ * Format duration in seconds to human-readable string
+ */
+function formatDuration(seconds) {
+ if (!seconds || seconds < 0) return "0:00";
+
+ const hours = Math.floor(seconds / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+
+ if (hours > 24) {
+ const days = Math.floor(hours / 24);
+ return `${days}d`;
+ }
+
+ if (hours > 0) {
+ return `${hours}h ${minutes}m`;
+ }
+
+ return `${minutes}m`;
+}
+
+export default async function () {
+ try {
+ console.log("[funkwhaleActivity] Fetching Funkwhale data...");
+
+ // Fetch all data from Indiekit API
+ const [nowPlaying, listenings, favorites, stats] = await Promise.all([
+ fetchFromIndiekit("now-playing"),
+ fetchFromIndiekit("listenings"),
+ fetchFromIndiekit("favorites"),
+ fetchFromIndiekit("stats"),
+ ]);
+
+ // Check if we got data
+ const hasData = nowPlaying || listenings?.listenings?.length || stats?.summary;
+
+ if (!hasData) {
+ console.log("[funkwhaleActivity] No data available from Indiekit");
+ return {
+ nowPlaying: null,
+ listenings: [],
+ favorites: [],
+ stats: null,
+ instanceUrl: FUNKWHALE_INSTANCE,
+ source: "unavailable",
+ };
+ }
+
+ console.log("[funkwhaleActivity] Using Indiekit API data");
+
+ // Format stats with human-readable durations
+ let formattedStats = null;
+ if (stats?.summary) {
+ formattedStats = {
+ ...stats,
+ summary: {
+ all: {
+ ...stats.summary.all,
+ totalDurationFormatted: formatDuration(stats.summary.all?.totalDuration || 0),
+ },
+ month: {
+ ...stats.summary.month,
+ totalDurationFormatted: formatDuration(stats.summary.month?.totalDuration || 0),
+ },
+ week: {
+ ...stats.summary.week,
+ totalDurationFormatted: formatDuration(stats.summary.week?.totalDuration || 0),
+ },
+ },
+ };
+ }
+
+ return {
+ nowPlaying: nowPlaying || null,
+ listenings: listenings?.listenings || [],
+ favorites: favorites?.favorites || [],
+ stats: formattedStats,
+ instanceUrl: FUNKWHALE_INSTANCE,
+ source: "indiekit",
+ };
+ } catch (error) {
+ console.error("[funkwhaleActivity] Error:", error.message);
+ return {
+ nowPlaying: null,
+ listenings: [],
+ favorites: [],
+ stats: null,
+ instanceUrl: FUNKWHALE_INSTANCE,
+ source: "error",
+ };
+ }
+}
diff --git a/_data/githubActivity.js b/_data/githubActivity.js
new file mode 100644
index 0000000..f3f5f19
--- /dev/null
+++ b/_data/githubActivity.js
@@ -0,0 +1,228 @@
+/**
+ * GitHub Activity Data
+ * Fetches from Indiekit's endpoint-github public API
+ * Falls back to direct GitHub API if Indiekit is unavailable
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const GITHUB_USERNAME = process.env.GITHUB_USERNAME || "";
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+// Fallback featured repos if Indiekit API unavailable (from env: comma-separated)
+const FALLBACK_FEATURED_REPOS = process.env.GITHUB_FEATURED_REPOS?.split(",").filter(Boolean) || [];
+
+/**
+ * Fetch from Indiekit's public GitHub API endpoint
+ */
+async function fetchFromIndiekit(endpoint) {
+ try {
+ const url = `${INDIEKIT_URL}/githubapi/api/${endpoint}`;
+ console.log(`[githubActivity] Fetching from Indiekit: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log(`[githubActivity] Indiekit ${endpoint} success`);
+ return data;
+ } catch (error) {
+ console.log(
+ `[githubActivity] Indiekit API unavailable for ${endpoint}: ${error.message}`
+ );
+ return null;
+ }
+}
+
+/**
+ * Fetch from GitHub API directly
+ */
+async function fetchFromGitHub(endpoint) {
+ const url = `https://api.github.com${endpoint}`;
+ const headers = {
+ Accept: "application/vnd.github.v3+json",
+ "User-Agent": "Eleventy-Site",
+ };
+
+ if (process.env.GITHUB_TOKEN) {
+ headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
+ }
+
+ return await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ fetchOptions: { headers },
+ });
+}
+
+/**
+ * Truncate text with ellipsis
+ */
+function truncate(text, maxLength = 80) {
+ if (!text || text.length <= maxLength) return text || "";
+ return text.slice(0, maxLength - 1) + "...";
+}
+
+/**
+ * Extract commits from push events
+ */
+function extractCommits(events) {
+ if (!Array.isArray(events)) return [];
+
+ return events
+ .filter((event) => event.type === "PushEvent")
+ .flatMap((event) =>
+ (event.payload?.commits || []).map((commit) => ({
+ sha: commit.sha.slice(0, 7),
+ message: truncate(commit.message.split("\n")[0]),
+ url: `https://github.com/${event.repo.name}/commit/${commit.sha}`,
+ repo: event.repo.name,
+ repoUrl: `https://github.com/${event.repo.name}`,
+ date: event.created_at,
+ }))
+ )
+ .slice(0, 10);
+}
+
+/**
+ * Extract PRs/Issues from events
+ */
+function extractContributions(events) {
+ if (!Array.isArray(events)) return [];
+
+ return events
+ .filter(
+ (event) =>
+ (event.type === "PullRequestEvent" || event.type === "IssuesEvent") &&
+ event.payload?.action === "opened"
+ )
+ .map((event) => {
+ const item = event.payload.pull_request || event.payload.issue;
+ return {
+ type: event.type === "PullRequestEvent" ? "pr" : "issue",
+ title: truncate(item?.title),
+ url: item?.html_url,
+ repo: event.repo.name,
+ repoUrl: `https://github.com/${event.repo.name}`,
+ number: item?.number,
+ date: event.created_at,
+ };
+ })
+ .slice(0, 10);
+}
+
+/**
+ * Format starred repos
+ */
+function formatStarred(repos) {
+ if (!Array.isArray(repos)) return [];
+
+ return repos.map((repo) => ({
+ name: repo.full_name,
+ description: truncate(repo.description, 120),
+ url: repo.html_url,
+ stars: repo.stargazers_count,
+ language: repo.language,
+ topics: repo.topics?.slice(0, 5) || [],
+ }));
+}
+
+/**
+ * Fetch featured repos directly from GitHub (fallback)
+ */
+async function fetchFeaturedFromGitHub(repoList) {
+ const featured = [];
+
+ for (const repoFullName of repoList) {
+ try {
+ const repo = await fetchFromGitHub(`/repos/${repoFullName}`);
+ let commits = [];
+ try {
+ const commitsData = await fetchFromGitHub(
+ `/repos/${repoFullName}/commits?per_page=5`
+ );
+ commits = commitsData.map((c) => ({
+ sha: c.sha.slice(0, 7),
+ message: truncate(c.commit.message.split("\n")[0]),
+ url: c.html_url,
+ date: c.commit.author.date,
+ }));
+ } catch (e) {
+ console.log(`[githubActivity] Could not fetch commits for ${repoFullName}`);
+ }
+
+ featured.push({
+ fullName: repo.full_name,
+ name: repo.name,
+ description: repo.description,
+ url: repo.html_url,
+ stars: repo.stargazers_count,
+ forks: repo.forks_count,
+ language: repo.language,
+ isPrivate: repo.private,
+ commits,
+ });
+ } catch (error) {
+ console.log(`[githubActivity] Could not fetch ${repoFullName}: ${error.message}`);
+ }
+ }
+
+ return featured;
+}
+
+export default async function () {
+ try {
+ console.log("[githubActivity] Fetching GitHub data...");
+
+ // Try Indiekit public API first
+ const [indiekitStars, indiekitCommits, indiekitActivity, indiekitFeatured] =
+ await Promise.all([
+ fetchFromIndiekit("stars"),
+ fetchFromIndiekit("commits"),
+ fetchFromIndiekit("activity"),
+ fetchFromIndiekit("featured"),
+ ]);
+
+ // Check if Indiekit API is available
+ const hasIndiekitData =
+ indiekitStars?.stars ||
+ indiekitCommits?.commits ||
+ indiekitFeatured?.featured;
+
+ if (hasIndiekitData) {
+ console.log("[githubActivity] Using Indiekit API data");
+ return {
+ stars: indiekitStars?.stars || [],
+ commits: indiekitCommits?.commits || [],
+ activity: indiekitActivity?.activity || [],
+ featured: indiekitFeatured?.featured || [],
+ source: "indiekit",
+ };
+ }
+
+ // Fallback to direct GitHub API
+ console.log("[githubActivity] Falling back to GitHub API");
+
+ const [events, starred, featured] = await Promise.all([
+ fetchFromGitHub(`/users/${GITHUB_USERNAME}/events/public?per_page=50`),
+ fetchFromGitHub(`/users/${GITHUB_USERNAME}/starred?per_page=20&sort=created`),
+ fetchFeaturedFromGitHub(FALLBACK_FEATURED_REPOS),
+ ]);
+
+ return {
+ stars: formatStarred(starred || []),
+ commits: extractCommits(events || []),
+ contributions: extractContributions(events || []),
+ featured,
+ source: "github",
+ };
+ } catch (error) {
+ console.error("[githubActivity] Error:", error.message);
+ return {
+ stars: [],
+ commits: [],
+ contributions: [],
+ featured: [],
+ source: "error",
+ };
+ }
+}
diff --git a/_data/githubRepos.js b/_data/githubRepos.js
new file mode 100644
index 0000000..1b8fe5f
--- /dev/null
+++ b/_data/githubRepos.js
@@ -0,0 +1,48 @@
+/**
+ * GitHub Repos Data
+ * Fetches public repositories from GitHub API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+export default async function () {
+ const username = process.env.GITHUB_USERNAME || "";
+
+ try {
+ // Fetch public repos, sorted by updated date
+ const url = `https://api.github.com/users/${username}/repos?sort=updated&per_page=10&type=owner`;
+
+ const repos = await EleventyFetch(url, {
+ duration: "1h", // Cache for 1 hour
+ type: "json",
+ fetchOptions: {
+ headers: {
+ Accept: "application/vnd.github.v3+json",
+ "User-Agent": "Eleventy-Site",
+ },
+ },
+ });
+
+ // Filter and transform repos
+ return repos
+ .filter((repo) => !repo.fork && !repo.private) // Exclude forks and private repos
+ .map((repo) => ({
+ name: repo.name,
+ full_name: repo.full_name,
+ description: repo.description,
+ html_url: repo.html_url,
+ homepage: repo.homepage,
+ language: repo.language,
+ stargazers_count: repo.stargazers_count,
+ forks_count: repo.forks_count,
+ open_issues_count: repo.open_issues_count,
+ topics: repo.topics || [],
+ updated_at: repo.updated_at,
+ created_at: repo.created_at,
+ }))
+ .slice(0, 10); // Limit to 10 repos
+ } catch (error) {
+ console.error("Error fetching GitHub repos:", error.message);
+ return [];
+ }
+}
diff --git a/_data/mastodonFeed.js b/_data/mastodonFeed.js
new file mode 100644
index 0000000..0c79abf
--- /dev/null
+++ b/_data/mastodonFeed.js
@@ -0,0 +1,96 @@
+/**
+ * Mastodon Feed Data
+ * Fetches recent posts from Mastodon using the public API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+export default async function () {
+ const instance = process.env.MASTODON_INSTANCE?.replace("https://", "") || "";
+ const username = process.env.MASTODON_USER || "";
+
+ try {
+ // First, look up the account ID
+ const lookupUrl = `https://${instance}/api/v1/accounts/lookup?acct=${username}`;
+
+ const account = await EleventyFetch(lookupUrl, {
+ duration: "1h", // Cache account lookup for 1 hour
+ type: "json",
+ fetchOptions: {
+ headers: {
+ Accept: "application/json",
+ },
+ },
+ });
+
+ if (!account || !account.id) {
+ console.log("Mastodon account not found:", username);
+ return [];
+ }
+
+ // Fetch recent statuses (excluding replies and boosts for cleaner feed)
+ const statusesUrl = `https://${instance}/api/v1/accounts/${account.id}/statuses?limit=10&exclude_replies=true&exclude_reblogs=true`;
+
+ const statuses = await EleventyFetch(statusesUrl, {
+ duration: "15m", // Cache for 15 minutes
+ type: "json",
+ fetchOptions: {
+ headers: {
+ Accept: "application/json",
+ },
+ },
+ });
+
+ if (!statuses || !Array.isArray(statuses)) {
+ console.log("No Mastodon statuses found for:", username);
+ return [];
+ }
+
+ // Transform statuses into a simpler format
+ return statuses.map((status) => ({
+ id: status.id,
+ url: status.url,
+ text: stripHtml(status.content),
+ htmlContent: status.content,
+ createdAt: status.created_at,
+ author: {
+ username: status.account.username,
+ displayName: status.account.display_name || status.account.username,
+ avatar: status.account.avatar,
+ url: status.account.url,
+ },
+ favouritesCount: status.favourites_count || 0,
+ reblogsCount: status.reblogs_count || 0,
+ repliesCount: status.replies_count || 0,
+ // Media attachments
+ media: status.media_attachments
+ ? status.media_attachments.map((m) => ({
+ type: m.type,
+ url: m.url,
+ previewUrl: m.preview_url,
+ description: m.description,
+ }))
+ : [],
+ }));
+ } catch (error) {
+ console.error("Error fetching Mastodon feed:", error.message);
+ return [];
+ }
+}
+
+// Simple HTML stripper for plain text display
+function stripHtml(html) {
+ if (!html) return "";
+ return html
+ .replace(/ /gi, " ")
+ .replace(/<\/p>/gi, " ")
+ .replace(/<[^>]+>/g, "")
+ .replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/"/g, '"')
+ .replace(/'/g, "'")
+ .replace(/ /g, " ")
+ .replace(/\s+/g, " ")
+ .trim();
+}
diff --git a/_data/site.js b/_data/site.js
new file mode 100644
index 0000000..0f4b389
--- /dev/null
+++ b/_data/site.js
@@ -0,0 +1,66 @@
+/**
+ * Site configuration for Eleventy
+ *
+ * Configure via environment variables in Cloudron app settings.
+ * All values have sensible defaults for initial deployment.
+ */
+
+// Parse social links from env (format: "name|url|icon,name|url|icon")
+function parseSocialLinks(envVar) {
+ if (!envVar) return [];
+ return envVar.split(",").map((link) => {
+ const [name, url, icon] = link.split("|").map((s) => s.trim());
+ return { name, url, rel: "me", icon: icon || name.toLowerCase() };
+ });
+}
+
+// Default social links if none configured
+const defaultSocial = [
+ {
+ name: "GitHub",
+ url: "https://github.com/",
+ rel: "me",
+ icon: "github",
+ },
+];
+
+export default {
+ // Basic site info
+ name: process.env.SITE_NAME || "My IndieWeb Blog",
+ url: process.env.SITE_URL || "https://example.com",
+ me: process.env.SITE_URL || "https://example.com",
+ locale: process.env.SITE_LOCALE || "en",
+ description:
+ process.env.SITE_DESCRIPTION ||
+ "An IndieWeb-powered blog with Micropub support",
+
+ // Author info (shown in h-card, about page, etc.)
+ author: {
+ name: process.env.AUTHOR_NAME || "Blog Author",
+ url: process.env.SITE_URL || "https://example.com",
+ avatar: process.env.AUTHOR_AVATAR || "/images/default-avatar.svg",
+ title: process.env.AUTHOR_TITLE || "",
+ bio: process.env.AUTHOR_BIO || "Welcome to my IndieWeb blog.",
+ location: process.env.AUTHOR_LOCATION || "",
+ email: process.env.AUTHOR_EMAIL || "",
+ },
+
+ // Social links (for rel="me" and footer)
+ // Set SITE_SOCIAL env var as: "GitHub|https://github.com/user|github,Mastodon|https://mastodon.social/@user|mastodon"
+ social: parseSocialLinks(process.env.SITE_SOCIAL) || defaultSocial,
+
+ // Feed integrations (usernames for data fetching)
+ feeds: {
+ github: process.env.GITHUB_USERNAME || "",
+ bluesky: process.env.BLUESKY_HANDLE || "",
+ mastodon: {
+ instance: process.env.MASTODON_INSTANCE?.replace("https://", "") || "",
+ username: process.env.MASTODON_USER || "",
+ },
+ },
+
+ // Webmentions configuration
+ webmentions: {
+ domain: process.env.SITE_URL?.replace("https://", "").replace("http://", "") || "example.com",
+ },
+};
diff --git a/_data/youtubeChannel.js b/_data/youtubeChannel.js
new file mode 100644
index 0000000..7fbf461
--- /dev/null
+++ b/_data/youtubeChannel.js
@@ -0,0 +1,206 @@
+/**
+ * YouTube Channel Data
+ * Fetches from Indiekit's endpoint-youtube public API
+ * Supports single or multiple channels
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+/**
+ * Fetch from Indiekit's public YouTube API endpoint
+ */
+async function fetchFromIndiekit(endpoint) {
+ try {
+ const url = `${INDIEKIT_URL}/youtubeapi/api/${endpoint}`;
+ console.log(`[youtubeChannel] Fetching from Indiekit: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "5m",
+ type: "json",
+ });
+ console.log(`[youtubeChannel] Indiekit ${endpoint} success`);
+ return data;
+ } catch (error) {
+ console.log(
+ `[youtubeChannel] Indiekit API unavailable for ${endpoint}: ${error.message}`
+ );
+ return null;
+ }
+}
+
+/**
+ * Format large numbers with locale separators
+ */
+function formatNumber(num) {
+ if (!num) return "0";
+ return new Intl.NumberFormat().format(num);
+}
+
+/**
+ * Format view count with K/M suffix for compact display
+ */
+function formatViewCount(num) {
+ if (!num) return "0";
+ if (num >= 1000000) {
+ return (num / 1000000).toFixed(1).replace(/\.0$/, "") + "M";
+ }
+ if (num >= 1000) {
+ return (num / 1000).toFixed(1).replace(/\\.0$/, "") + "K";
+ }
+ return num.toString();
+}
+
+/**
+ * Format relative time from ISO date string
+ */
+function formatRelativeTime(dateString) {
+ if (!dateString) return "";
+ const date = new Date(dateString);
+ const now = new Date();
+ const diffMs = now - date;
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
+
+ if (diffDays === 0) return "Today";
+ if (diffDays === 1) return "Yesterday";
+ if (diffDays < 7) return `${diffDays} days ago`;
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
+ if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`;
+ return `${Math.floor(diffDays / 365)} years ago`;
+}
+
+/**
+ * Format channel data with computed fields
+ */
+function formatChannel(channel) {
+ if (!channel) return null;
+ return {
+ ...channel,
+ subscriberCountFormatted: formatNumber(channel.subscriberCount),
+ videoCountFormatted: formatNumber(channel.videoCount),
+ viewCountFormatted: formatNumber(channel.viewCount),
+ url: `https://www.youtube.com/channel/${channel.id}`,
+ };
+}
+
+/**
+ * Format video data with computed fields
+ */
+function formatVideo(video) {
+ return {
+ ...video,
+ viewCountFormatted: formatViewCount(video.viewCount),
+ relativeTime: formatRelativeTime(video.publishedAt),
+ };
+}
+
+export default async function () {
+ try {
+ console.log("[youtubeChannel] Fetching YouTube data...");
+
+ // Fetch all data from Indiekit API
+ const [channelData, videosData, liveData] = await Promise.all([
+ fetchFromIndiekit("channel"),
+ fetchFromIndiekit("videos"),
+ fetchFromIndiekit("live"),
+ ]);
+
+ // Check if we got data
+ const hasData =
+ channelData?.channel ||
+ channelData?.channels?.length ||
+ videosData?.videos?.length;
+
+ if (!hasData) {
+ console.log("[youtubeChannel] No data available from Indiekit");
+ return {
+ channel: null,
+ channels: [],
+ videos: [],
+ videosByChannel: {},
+ liveStatus: null,
+ liveStatuses: [],
+ isMultiChannel: false,
+ source: "unavailable",
+ };
+ }
+
+ console.log("[youtubeChannel] Using Indiekit API data");
+
+ // Determine if multi-channel mode
+ const isMultiChannel = !!(channelData?.channels && channelData.channels.length > 1);
+
+ // Format channels
+ let channels = [];
+ let channel = null;
+
+ if (isMultiChannel) {
+ channels = (channelData.channels || []).map(formatChannel).filter(Boolean);
+ channel = channels[0] || null;
+ } else {
+ channel = formatChannel(channelData?.channel);
+ channels = channel ? [channel] : [];
+ }
+
+ // Format videos
+ const videos = (videosData?.videos || []).map(formatVideo);
+
+ // Group videos by channel if multi-channel
+ let videosByChannel = {};
+ if (isMultiChannel && videosData?.videosByChannel) {
+ for (const [channelName, channelVideos] of Object.entries(videosData.videosByChannel)) {
+ videosByChannel[channelName] = (channelVideos || []).map(formatVideo);
+ }
+ } else if (channel) {
+ videosByChannel[channel.configName || channel.title] = videos;
+ }
+
+ // Format live status
+ let liveStatus = null;
+ let liveStatuses = [];
+
+ if (liveData) {
+ if (isMultiChannel && liveData.liveStatuses) {
+ liveStatuses = liveData.liveStatuses;
+ // Find first live or upcoming
+ const live = liveStatuses.find((s) => s.isLive);
+ const upcoming = liveStatuses.find((s) => s.isUpcoming && !s.isLive);
+ liveStatus = {
+ isLive: !!live,
+ isUpcoming: !live && !!upcoming,
+ stream: live?.stream || upcoming?.stream || null,
+ };
+ } else {
+ liveStatus = {
+ isLive: liveData.isLive || false,
+ isUpcoming: liveData.isUpcoming || false,
+ stream: liveData.stream || null,
+ };
+ liveStatuses = [{ ...liveStatus, channelConfigName: channel?.configName }];
+ }
+ }
+
+ return {
+ channel,
+ channels,
+ videos,
+ videosByChannel,
+ liveStatus,
+ liveStatuses,
+ isMultiChannel,
+ source: "indiekit",
+ };
+ } catch (error) {
+ console.error("[youtubeChannel] Error:", error.message);
+ return {
+ channel: null,
+ channels: [],
+ videos: [],
+ videosByChannel: {},
+ liveStatus: null,
+ liveStatuses: [],
+ isMultiChannel: false,
+ source: "error",
+ };
+ }
+}
diff --git a/_includes/components/blog-sidebar.njk b/_includes/components/blog-sidebar.njk
new file mode 100644
index 0000000..3ac588c
--- /dev/null
+++ b/_includes/components/blog-sidebar.njk
@@ -0,0 +1,184 @@
+{# Blog Sidebar - Shown on individual post pages #}
+{# Contains: Author compact card, Related posts, Categories, Recent posts #}
+
+{# Author Compact Card #}
+
+
+{# Post Navigation Widget - Previous/Next #}
+{% if previousPost or nextPost %}
+
+{% endif %}
+
+{# Table of Contents Widget (for articles with headings) #}
+{% if toc and toc.length %}
+
+{% endif %}
+
+{# Categories for This Post #}
+{% if category %}
+
+{% endif %}
+
+{# Recent Posts Widget #}
+{% if collections.posts %}
+
+{% endif %}
+
+{# Webmentions Widget (if this post has any) #}
+{% if webmentions and webmentions.length %}
+
+{% endif %}
+
+{# Share Widget #}
+
+
+{# Subscribe Widget #}
+
diff --git a/_includes/components/funkwhale-stats-content.njk b/_includes/components/funkwhale-stats-content.njk
new file mode 100644
index 0000000..12822e7
--- /dev/null
+++ b/_includes/components/funkwhale-stats-content.njk
@@ -0,0 +1,66 @@
+{# Stats Summary Cards #}
+{% if summary %}
+
+
+ {{ summary.totalPlays or 0 }}
+ Plays
+
+
+ {{ summary.uniqueTracks or 0 }}
+ Tracks
+
+
+ {{ summary.uniqueArtists or 0 }}
+ Artists
+
+
+ {{ summary.totalDurationFormatted or '0m' }}
+ Listened
+
+
+{% endif %}
+
+{# Top Artists #}
+{% if topArtists and topArtists.length %}
+
+
Top Artists
+
+ {% for artist in topArtists | head(5) %}
+
+ {{ loop.index }}
+ {{ artist.name }}
+ {{ artist.playCount }} plays
+
+ {% endfor %}
+
+
+{% endif %}
+
+{# Top Albums #}
+{% if topAlbums and topAlbums.length %}
+
+
Top Albums
+
+ {% for album in topAlbums | head(5) %}
+
+ {% if album.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
{{ album.title }}
+
{{ album.artist }}
+
{{ album.playCount }} plays
+
+ {% endfor %}
+
+
+{% endif %}
+
+{% if not summary and not topArtists and not topAlbums %}
+No statistics available for this period.
+{% endif %}
diff --git a/_includes/components/h-card.njk b/_includes/components/h-card.njk
new file mode 100644
index 0000000..fc97162
--- /dev/null
+++ b/_includes/components/h-card.njk
@@ -0,0 +1,64 @@
+{# h-card - IndieWeb identity microformat #}
+{# See: https://microformats.org/wiki/h-card #}
+
+
diff --git a/_includes/components/reply-context.njk b/_includes/components/reply-context.njk
new file mode 100644
index 0000000..9398caa
--- /dev/null
+++ b/_includes/components/reply-context.njk
@@ -0,0 +1,63 @@
+{# Reply Context Component #}
+{# Displays rich context for replies, likes, reposts, and bookmarks #}
+{# Uses h-cite microformat for citing external content #}
+
+{% if in_reply_to or like_of or repost_of or bookmark_of %}
+
+ {% if in_reply_to %}
+
+ {% endif %}
+
+ {% if like_of %}
+
+ {% endif %}
+
+ {% if repost_of %}
+
+
+
+
+
+ Reposted:
+
+
+ {{ repost_of }}
+
+
+ {% endif %}
+
+ {% if bookmark_of %}
+
+ {% endif %}
+
+{% endif %}
diff --git a/_includes/components/sidebar.njk b/_includes/components/sidebar.njk
new file mode 100644
index 0000000..79e1463
--- /dev/null
+++ b/_includes/components/sidebar.njk
@@ -0,0 +1,258 @@
+{# Sidebar Components #}
+{# Contains: Author card, Bluesky feed, GitHub repos, RSS feed #}
+
+{# Author Card Widget #}
+
+
+{# Social Feed Widget - Tabbed Bluesky/Mastodon #}
+{% if (blueskyFeed and blueskyFeed.length) or (mastodonFeed and mastodonFeed.length) %}
+
+{% endif %}
+
+{# GitHub Repos Widget #}
+{% if githubRepos and githubRepos.length %}
+
+{% endif %}
+
+{# Funkwhale Now Playing Widget #}
+{% if funkwhaleActivity and (funkwhaleActivity.nowPlaying or funkwhaleActivity.stats) %}
+
+{% endif %}
+
+{# Recent Posts Widget (for non-blog pages) #}
+{% if recentPosts and recentPosts.length %}
+
+{% endif %}
+
+{# Categories/Tags Widget #}
+{% if categories and categories.length %}
+
+{% endif %}
diff --git a/_includes/components/webmentions.njk b/_includes/components/webmentions.njk
new file mode 100644
index 0000000..5af3f1e
--- /dev/null
+++ b/_includes/components/webmentions.njk
@@ -0,0 +1,158 @@
+{# Webmentions Component #}
+{# Displays likes, reposts, and replies for a post #}
+
+{% set mentions = webmentions | webmentionsForUrl(page.url) %}
+
+{% if mentions.length %}
+
+
+ Webmentions ({{ mentions.length }})
+
+
+ {# Likes #}
+ {% set likes = mentions | webmentionsByType('likes') %}
+ {% if likes.length %}
+
+
+ {{ likes.length }} Like{% if likes.length != 1 %}s{% endif %}
+
+
+ {% for like in likes %}
+
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {# Reposts #}
+ {% set reposts = mentions | webmentionsByType('reposts') %}
+ {% if reposts.length %}
+
+
+ {{ reposts.length }} Repost{% if reposts.length != 1 %}s{% endif %}
+
+
+ {% for repost in reposts %}
+
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {# Replies #}
+ {% set replies = mentions | webmentionsByType('replies') %}
+ {% if replies.length %}
+
+
+ {{ replies.length }} Repl{% if replies.length != 1 %}ies{% else %}y{% endif %}
+
+
+ {% for reply in replies %}
+
+
+
+
+
+
+
+
+ {{ reply.content.html | safe if reply.content.html else reply.content.text }}
+
+
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {# Other mentions #}
+ {% set otherMentions = mentions | webmentionsByType('mentions') %}
+ {% if otherMentions.length %}
+
+
+ {{ otherMentions.length }} Mention{% if otherMentions.length != 1 %}s{% endif %}
+
+
+
+ {% endif %}
+
+{% endif %}
+
+{# Webmention send form #}
+
diff --git a/_includes/layouts/base.njk b/_includes/layouts/base.njk
new file mode 100644
index 0000000..d0fa075
--- /dev/null
+++ b/_includes/layouts/base.njk
@@ -0,0 +1,219 @@
+
+
+
+
+
+ {% if title %}{{ title }} - {% endif %}{{ site.name }}
+
+ {# OpenGraph meta tags #}
+ {% set ogTitle = title | default(site.name) %}
+ {% set ogDesc = description | default(content | ogDescription(200)) | default(site.description) %}
+ {% set contentImage = content | extractFirstImage %}
+
+
+
+
+
+
+ {% if photo %}
+
+ {% elif image %}
+
+ {% elif contentImage %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
+ {# Twitter Card meta tags #}
+ {% set hasImage = photo or image or contentImage %}
+
+
+
+ {% if photo %}
+
+ {% elif image %}
+
+ {% elif contentImage %}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {# IndieAuth rel="me" links for identity verification #}
+ {% for social in site.social %}
+
+ {% endfor %}
+
+
+
+
+
+
+ {% if withSidebar %}
+
+ {% elif withBlogSidebar %}
+
+ {% else %}
+ {{ content | safe }}
+ {% endif %}
+
+
+
+
+
+
diff --git a/_includes/layouts/home.njk b/_includes/layouts/home.njk
new file mode 100644
index 0000000..126c7f4
--- /dev/null
+++ b/_includes/layouts/home.njk
@@ -0,0 +1,208 @@
+---
+layout: layouts/base.njk
+withSidebar: true
+---
+
+{# Hero Section #}
+
+
+ {# Avatar #}
+
+
+ {# Introduction #}
+
+
+ {{ site.author.name }}
+
+
+ {{ site.author.title }}
+
+
+ {{ site.author.bio }}
+
+
+ {# Social Links #}
+
+
+
+
+
+{# Work Experience Timeline - only show if data exists #}
+{% if cv.experience and cv.experience.length %}
+
+ Experience
+
+
+ {% for job in cv.experience %}
+
+
+
+ {{ job.title }}
+
+ @ {{ job.company }}
+ {% if job.type != "full-time" %}
+ {{ job.type }}
+ {% endif %}
+
+
+
+ {{ job.startDate }} -
+ {% if job.endDate %}
+ {{ job.endDate }}
+ {% else %}
+ Present
+ {% endif %}
+ · {{ job.location }}
+
+
+ {% if job.description %}
+ {{ job.description }}
+ {% endif %}
+
+ {% if job.highlights %}
+
+ {% for highlight in job.highlights %}
+ {{ highlight }}
+ {% endfor %}
+
+ {% endif %}
+
+ {% endfor %}
+
+
+{% endif %}
+
+{# Projects Section - only show if data exists #}
+{% if cv.projects and cv.projects.length %}
+
+ Projects
+
+
+ {% for project in cv.projects %}
+
+
+ {% if project.url %}
+
+ {{ project.name }}
+
+ {% else %}
+ {{ project.name }}
+ {% endif %}
+
+
+ {{ project.description }}
+
+
+ {% for tech in project.technologies %}
+ {{ tech }}
+ {% endfor %}
+
+
+ {% if project.status == "active" %}
+ Active
+ {% endif %}
+
+ {% endfor %}
+
+
+{% endif %}
+
+{# Skills Section - only show if data exists #}
+{% if cv.skills and (cv.skills | dictsort | length) %}
+
+ Skills
+
+
+ {% for category, skills in cv.skills %}
+
+
+ {{ category }}
+
+
+ {% for skill in skills %}
+ {{ skill }}
+ {% endfor %}
+
+
+ {% endfor %}
+
+
+{% endif %}
+
+{# Education & Languages - only show if data exists #}
+{% if (cv.education and cv.education.length) or (cv.languages and cv.languages.length) %}
+
+ {# Education #}
+ {% if cv.education and cv.education.length %}
+
+
Education
+ {% for edu in cv.education %}
+
+ {{ edu.degree }}
+ {{ edu.institution }}
+ {{ edu.year }} · {{ edu.location }}
+
+ {% endfor %}
+
+ {% endif %}
+
+ {# Languages #}
+ {% if cv.languages and cv.languages.length %}
+
+
Languages
+
+ {% for lang in cv.languages %}
+
+ {{ lang.name }}
+ {{ lang.level }}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+{% endif %}
+
+{# Interests - only show if data exists #}
+{% if cv.interests and cv.interests.length %}
+
+ Interests
+
+ {% for interest in cv.interests %}
+ {{ interest }}
+ {% endfor %}
+
+
+{% endif %}
+
+{# Last Updated - only show if CV has content #}
+{% if cv.lastUpdated and (cv.experience.length or cv.projects.length) %}
+
+ Last updated: {{ cv.lastUpdated }}
+
+{% endif %}
diff --git a/_includes/layouts/post.njk b/_includes/layouts/post.njk
new file mode 100644
index 0000000..d7d7bcb
--- /dev/null
+++ b/_includes/layouts/post.njk
@@ -0,0 +1,123 @@
+---
+layout: layouts/base.njk
+withBlogSidebar: true
+---
+
+ {% if title %}
+ {{ title }}
+ {% endif %}
+
+
+
+ {{ date | dateDisplay }}
+
+ {% if category %}
+
+ {# Handle both string and array categories #}
+ {% if category is string %}
+ {{ category }}
+ {% else %}
+ {% for cat in category %}
+ {{ cat }}
+ {% endfor %}
+ {% endif %}
+
+ {% endif %}
+
+
+ {# Bridgy syndication content - controls what gets posted to social networks #}
+ {# Uses description/summary if available, otherwise first 280 chars of content #}
+ {% set bridgySummary = description or summary or (content | ogDescription(280)) %}
+ {% if bridgySummary %}
+ {{ bridgySummary }}
+ {% endif %}
+
+
+ {{ content | safe }}
+
+
+ {# Rich reply context with h-cite microformat #}
+ {% include "components/reply-context.njk" %}
+
+ {# Syndication Footer - shows where this post was also published #}
+ {% if syndication %}
+
+ {% endif %}
+
+ Permalink
+
+ {# Author h-card for IndieWeb authorship #}
+
+ {{ site.author.name }}
+
+
+
+ {# JSON-LD Structured Data for SEO #}
+ {% set postImage = photo or image or (content | extractFirstImage) %}
+ {% set postDesc = description | default(content | ogDescription(160)) %}
+
+
+
+{# Webmentions display - likes, reposts, replies #}
+{% include "components/webmentions.njk" %}
diff --git a/about.njk b/about.njk
new file mode 100644
index 0000000..b182335
--- /dev/null
+++ b/about.njk
@@ -0,0 +1,68 @@
+---
+layout: layouts/base.njk
+title: About
+permalink: /about/
+---
+
+
+
+
+
{{ site.author.bio }}
+
+
About This Site
+
+ This site is powered by Indiekit , an IndieWeb
+ server that supports Micropub, Webmentions, and other IndieWeb standards. It runs on
+ Cloudron for easy self-hosting.
+
+
+
IndieWeb
+
+ I'm part of the IndieWeb movement - owning my content
+ and identity online. You can interact with my posts through Webmentions - reply, like,
+ or repost from your own website and it will show up here.
+
+
+ {% if site.social.length > 0 %}
+
Connect
+
Find me on:
+
+ {% endif %}
+
+ {% if site.author.email %}
+
+ Or send me an email at
+ {{ site.author.email }}
+
+ {% endif %}
+
+
diff --git a/articles.njk b/articles.njk
new file mode 100644
index 0000000..a13cb40
--- /dev/null
+++ b/articles.njk
@@ -0,0 +1,92 @@
+---
+layout: layouts/base.njk
+title: Articles
+withSidebar: true
+pagination:
+ data: collections.articles
+ size: 20
+ alias: paginatedArticles
+permalink: "articles/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
+---
+
+
Articles
+
+ Long-form posts and essays.
+ ({{ collections.articles.length }} total)
+
+
+ {% if paginatedArticles.length > 0 %}
+
+ {% for post in paginatedArticles %}
+
+
+
+
+ {{ post.date | dateDisplay }}
+
+ {% if post.data.category %}
+
+ {% if post.data.category is string %}
+ {{ post.data.category }}
+ {% else %}
+ {% for cat in post.data.category %}
+ {{ cat }}
+ {% endfor %}
+ {% endif %}
+
+ {% endif %}
+
+
+ {{ post.templateContent | striptags | truncate(250) }}
+
+
+ Read more →
+
+
+ {% endfor %}
+
+
+ {# Pagination controls #}
+ {% if pagination.pages.length > 1 %}
+
+ {% endif %}
+
+ {% else %}
+
No articles yet.
+ {% endif %}
+
diff --git a/blog.njk b/blog.njk
new file mode 100644
index 0000000..0b8ca0b
--- /dev/null
+++ b/blog.njk
@@ -0,0 +1,130 @@
+---
+layout: layouts/base.njk
+title: Blog
+withSidebar: true
+pagination:
+ data: collections.posts
+ size: 20
+ alias: paginatedPosts
+permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
+---
+
+
Blog
+
+ All posts including articles and notes.
+ ({{ collections.posts.length }} total)
+
+
+ {% if paginatedPosts.length > 0 %}
+
+ {% for post in paginatedPosts %}
+
+ {# Article with title #}
+ {% if post.data.title %}
+
+
+ {{ post.templateContent | striptags | truncate(250) }}
+
+
+ Read more →
+
+
+ {# Note without title #}
+ {% else %}
+
+
+ {{ post.templateContent | safe }}
+
+
+ {% endif %}
+
+ {% endfor %}
+
+
+ {# Pagination controls #}
+ {% if pagination.pages.length > 1 %}
+
+ {% endif %}
+
+ {% else %}
+
No posts yet. Create your first post using a Micropub client!
+
Some popular Micropub clients:
+
+ Quill - Web-based
+ IndiePass - Mobile app
+ Micropublish - Web-based
+
+ {% endif %}
+
diff --git a/bookmarks.njk b/bookmarks.njk
new file mode 100644
index 0000000..070500d
--- /dev/null
+++ b/bookmarks.njk
@@ -0,0 +1,29 @@
+---
+layout: layouts/base.njk
+title: Bookmarks
+permalink: /bookmarks/
+---
+Bookmarks
+
+{% if collections.bookmarks.length > 0 %}
+
+ {% for post in collections.bookmarks %}
+
+ {% if post.data.title %}
+
+ {% endif %}
+
+
+ {{ post.date | dateDisplay }}
+
+
+ {% if post.data.bookmark_of %}
+ {{ post.data.bookmark_of }}
+ {% endif %}
+ {{ post.templateContent | safe }}
+
+ {% endfor %}
+
+{% else %}
+No bookmarks yet.
+{% endif %}
diff --git a/categories-index.njk b/categories-index.njk
new file mode 100644
index 0000000..1875dbd
--- /dev/null
+++ b/categories-index.njk
@@ -0,0 +1,27 @@
+---
+layout: layouts/base.njk
+title: Categories
+withSidebar: true
+permalink: categories/
+---
+
+
Categories
+
+ Browse posts by category.
+ ({{ collections.categories.length }} categories)
+
+
+ {% if collections.categories.length > 0 %}
+
+ {% for cat in collections.categories %}
+
+
+ {{ cat }}
+
+
+ {% endfor %}
+
+ {% else %}
+
No categories yet.
+ {% endif %}
+
diff --git a/categories.njk b/categories.njk
new file mode 100644
index 0000000..da037ad
--- /dev/null
+++ b/categories.njk
@@ -0,0 +1,69 @@
+---
+layout: layouts/base.njk
+withSidebar: true
+pagination:
+ data: collections.categories
+ size: 1
+ alias: category
+permalink: "categories/{{ category | slugify }}/"
+eleventyComputed:
+ title: "{{ category }}"
+---
+
+
{{ category }}
+
+ Posts tagged with "{{ category }}".
+
+
+ {% set categoryPosts = [] %}
+ {% for post in collections.posts %}
+ {% if post.data.category %}
+ {% if post.data.category is string %}
+ {% if post.data.category == category %}
+ {% set categoryPosts = (categoryPosts.push(post), categoryPosts) %}
+ {% endif %}
+ {% else %}
+ {% if category in post.data.category %}
+ {% set categoryPosts = (categoryPosts.push(post), categoryPosts) %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
+ {% endfor %}
+
+ {% if categoryPosts.length > 0 %}
+
{{ categoryPosts.length }} post{% if categoryPosts.length != 1 %}s{% endif %}
+
+ {% for post in categoryPosts %}
+
+
+
+
+ {{ post.date | dateDisplay }}
+
+ {% set postType = post.inputPath | replace("./content/", "") %}
+ {% set postType = postType.split("/")[0] %}
+ {{ postType }}
+
+
+ {{ post.templateContent | striptags | truncate(250) }}
+
+
+ View →
+
+
+ {% endfor %}
+
+ {% else %}
+
No posts found with this category.
+ {% endif %}
+
+
+
diff --git a/css/tailwind.css b/css/tailwind.css
new file mode 100644
index 0000000..8c9b8e6
--- /dev/null
+++ b/css/tailwind.css
@@ -0,0 +1,317 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* Accessibility utilities */
+@layer utilities {
+ .visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+ }
+
+ .skip-link {
+ @apply absolute -top-full left-0 z-50 bg-primary-600 text-white px-4 py-2;
+ }
+
+ .skip-link:focus {
+ @apply top-0;
+ }
+}
+
+/* Reduce motion for accessibility */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+}
+
+/* Dark mode body background */
+@layer base {
+ body {
+ @apply bg-white dark:bg-surface-950 text-surface-900 dark:text-surface-100;
+ }
+}
+
+/* Layout styles */
+@layer components {
+ /* Site header */
+ .site-header {
+ @apply bg-white dark:bg-surface-900 border-b border-surface-200 dark:border-surface-700 py-4 sticky top-0 z-50;
+ }
+
+ .header-container {
+ @apply flex items-center justify-between;
+ }
+
+ .site-title {
+ @apply text-xl font-bold text-surface-900 dark:text-white no-underline hover:text-primary-600 dark:hover:text-primary-400 transition-colors;
+ }
+
+ /* Header actions (nav + theme toggle) */
+ .header-actions {
+ @apply hidden md:flex items-center gap-4;
+ }
+
+ .site-nav {
+ @apply flex items-center gap-4;
+ }
+
+ .site-nav a {
+ @apply text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400 no-underline transition-colors py-2;
+ }
+
+ /* Mobile menu toggle button */
+ .menu-toggle {
+ @apply md:hidden p-2 rounded-lg text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors;
+ }
+
+ /* Mobile navigation dropdown */
+ .mobile-nav {
+ @apply md:hidden border-t border-surface-200 dark:border-surface-700 bg-white dark:bg-surface-900;
+ }
+
+ .mobile-nav a {
+ @apply block px-4 py-3 text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 hover:text-primary-600 dark:hover:text-primary-400 no-underline transition-colors border-b border-surface-100 dark:border-surface-800 last:border-b-0;
+ }
+
+ /* Theme toggle button */
+ .theme-toggle {
+ @apply p-2 rounded-lg text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors;
+ }
+
+ .theme-toggle .sun-icon {
+ @apply hidden;
+ }
+
+ .theme-toggle .moon-icon {
+ @apply block;
+ }
+
+ .dark .theme-toggle .sun-icon {
+ @apply block;
+ }
+
+ .dark .theme-toggle .moon-icon {
+ @apply hidden;
+ }
+
+ /* Container */
+ .container {
+ @apply max-w-5xl mx-auto px-4;
+ }
+
+ /* Site footer */
+ .site-footer {
+ @apply mt-12 py-8 border-t border-surface-200 dark:border-surface-700 text-center text-sm text-surface-500;
+ }
+
+ .site-footer a {
+ @apply text-primary-600 dark:text-primary-400 hover:underline;
+ }
+
+ /* Layout with sidebar - mobile-first with responsive grid */
+ .layout-with-sidebar {
+ @apply grid gap-6 md:gap-8 lg:grid-cols-3;
+ }
+
+ .main-content {
+ @apply lg:col-span-2 min-w-0; /* min-w-0 prevents flex/grid overflow */
+ }
+
+ .sidebar {
+ @apply space-y-6 lg:sticky lg:top-24 lg:self-start;
+ }
+
+ /* Main content area - adjust padding for mobile */
+ main.container {
+ @apply py-6 md:py-8;
+ }
+}
+
+/* Custom component styles */
+@layer components {
+ /* Post list */
+ .post-list {
+ @apply list-none p-0 m-0 space-y-6;
+ }
+
+ .post-list li {
+ @apply pb-6 border-b border-surface-200 dark:border-surface-700 last:border-0;
+ }
+
+ /* Post meta */
+ .post-meta {
+ @apply text-sm text-surface-600 dark:text-surface-400 flex flex-wrap gap-2 items-center;
+ }
+
+ /* Category tags */
+ .p-category {
+ @apply inline-block px-2 py-0.5 text-xs bg-primary-100 dark:bg-primary-900 text-primary-800 dark:text-primary-200 rounded;
+ }
+
+ /* Webmention styles */
+ .webmention-likes .avatar-row {
+ @apply flex flex-wrap gap-1;
+ }
+
+ .webmention-likes img {
+ @apply w-8 h-8 rounded-full;
+ }
+
+ /* GitHub components */
+ .repo-card {
+ @apply p-4 border border-surface-200 dark:border-surface-700 rounded-lg;
+ }
+
+ .repo-meta {
+ @apply flex gap-4 text-sm text-surface-600 dark:text-surface-400 mt-2;
+ }
+
+ /* Timeline for CV */
+ .timeline {
+ @apply relative pl-6 border-l-2 border-primary-500;
+ }
+
+ .timeline-item {
+ @apply relative pb-6 last:pb-0;
+ }
+
+ .timeline-item::before {
+ content: '';
+ @apply absolute -left-[calc(1.5rem+5px)] top-1.5 w-3 h-3 bg-primary-500 rounded-full;
+ }
+
+ /* Skills badges */
+ .skill-badge {
+ @apply inline-block px-3 py-1 text-sm bg-surface-100 dark:bg-surface-800 rounded-full;
+ }
+
+ /* Widget cards */
+ .widget {
+ @apply p-4 bg-surface-100 dark:bg-surface-800 rounded-lg;
+ }
+
+ .widget-title {
+ @apply font-bold text-lg mb-4;
+ }
+
+ /* Post cards */
+ .post-card {
+ @apply p-5 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm;
+ }
+
+ .post-header {
+ @apply flex flex-wrap items-center gap-2;
+ }
+
+ .post-footer {
+ @apply pt-3 border-t border-surface-100 dark:border-surface-700;
+ }
+
+ /* Pagination */
+ .pagination {
+ @apply mt-12 pt-8 border-t border-surface-200 dark:border-surface-700 flex flex-col sm:flex-row items-center justify-between gap-4;
+ }
+
+ .pagination-info {
+ @apply text-sm text-surface-600 dark:text-surface-400;
+ }
+
+ .pagination-links {
+ @apply flex items-center gap-2;
+ }
+
+ .pagination-link {
+ @apply inline-flex items-center gap-1 px-4 py-2 text-sm font-medium bg-surface-100 dark:bg-surface-800 rounded-lg hover:bg-surface-200 dark:hover:bg-surface-700 transition-colors;
+ }
+
+ .pagination-link.disabled {
+ @apply opacity-50 cursor-not-allowed hover:bg-surface-100 dark:hover:bg-surface-800;
+ }
+}
+
+/* Focus states */
+@layer base {
+ a:focus-visible,
+ button:focus-visible,
+ input:focus-visible,
+ textarea:focus-visible,
+ select:focus-visible {
+ @apply outline-2 outline-offset-2 outline-primary-500;
+ }
+}
+
+/* Video embeds */
+@layer components {
+ .video-embed {
+ @apply relative w-full aspect-video my-4;
+ }
+
+ .video-embed iframe {
+ @apply absolute inset-0 w-full h-full rounded-lg;
+ }
+}
+
+/* Performance: content-visibility for off-screen rendering optimization */
+@layer utilities {
+ .content-auto {
+ content-visibility: auto;
+ contain-intrinsic-size: auto 500px;
+ }
+}
+
+/* Apply content-visibility to images and post items for performance */
+@layer base {
+ /* Responsive typography */
+ html {
+ @apply text-base md:text-lg;
+ }
+
+ /* Prevent horizontal overflow */
+ body {
+ @apply overflow-x-hidden;
+ }
+
+ /* Images - prevent overflow and add content-visibility */
+ img {
+ @apply max-w-full h-auto;
+ content-visibility: auto;
+ }
+
+ /* Pre/code blocks - prevent overflow on mobile */
+ pre {
+ @apply overflow-x-auto max-w-full;
+ }
+
+ code {
+ @apply break-words;
+ }
+
+ /* Links in content - break long URLs */
+ .e-content a,
+ .prose a {
+ @apply break-words;
+ word-break: break-word;
+ }
+
+ article {
+ scroll-margin-top: 80px; /* Prevent header overlap when scrolling to anchors */
+ }
+
+ .post-list li {
+ content-visibility: auto;
+ contain-intrinsic-size: auto 200px;
+ }
+}
diff --git a/eleventy.config.js b/eleventy.config.js
new file mode 100644
index 0000000..1049ea1
--- /dev/null
+++ b/eleventy.config.js
@@ -0,0 +1,360 @@
+import pluginWebmentions from "@chrisburnell/eleventy-cache-webmentions";
+import pluginRss from "@11ty/eleventy-plugin-rss";
+import embedEverything from "eleventy-plugin-embed-everything";
+import { eleventyImageTransformPlugin } from "@11ty/eleventy-img";
+import sitemap from "@quasibit/eleventy-plugin-sitemap";
+import markdownIt from "markdown-it";
+import { minify } from "html-minifier-terser";
+import { createHash } from "crypto";
+import { readFileSync } from "fs";
+import { resolve, dirname } from "path";
+import { fileURLToPath } from "url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const siteUrl = process.env.SITE_URL || "https://example.com";
+
+export default function (eleventyConfig) {
+ // Ignore output directory (prevents re-processing generated files via symlink)
+ eleventyConfig.ignores.add("_site");
+ eleventyConfig.ignores.add("_site/**");
+
+ // Configure markdown-it with linkify enabled (auto-convert URLs to links)
+ const md = markdownIt({
+ html: true,
+ linkify: true, // Auto-convert URLs to clickable links
+ typographer: true,
+ });
+ eleventyConfig.setLibrary("md", md);
+
+ // RSS plugin for feed filters (dateToRfc822, absoluteUrl, etc.)
+ // Custom feed templates in feed.njk and feed-json.njk use these filters
+ eleventyConfig.addPlugin(pluginRss);
+
+ // JSON encode filter for JSON feed
+ eleventyConfig.addFilter("jsonEncode", (value) => {
+ return JSON.stringify(value);
+ });
+
+ // Alias dateToRfc822 (plugin provides dateToRfc2822)
+ eleventyConfig.addFilter("dateToRfc822", (date) => {
+ return pluginRss.dateToRfc2822(date);
+ });
+
+ // Embed Everything - auto-embed YouTube, Vimeo, Bluesky, Mastodon, etc.
+ eleventyConfig.addPlugin(embedEverything, {
+ use: ["youtube", "vimeo", "twitter", "mastodon", "bluesky", "spotify", "soundcloud"],
+ youtube: {
+ options: {
+ lite: false,
+ recommendSelfOnly: true,
+ },
+ },
+ mastodon: {
+ options: {
+ server: "mstdn.social",
+ },
+ },
+ });
+
+ // Custom transform to convert YouTube links to embeds
+ eleventyConfig.addTransform("youtube-link-to-embed", function (content, outputPath) {
+ if (!outputPath || !outputPath.endsWith(".html")) {
+ return content;
+ }
+ // Match tags where href contains youtube.com/watch or youtu.be
+ // Link text can be: URL, www.youtube..., youtube..., or youtube-related text
+ const youtubePattern = / ]+href="https?:\/\/(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]+)[^"]*"[^>]*>(?:https?:\/\/)?(?:www\.)?[^<]*(?:youtube|youtu\.be)[^<]*<\/a>/gi;
+
+ content = content.replace(youtubePattern, (match, videoId) => {
+ // Use standard YouTube iframe with exact oEmbed parameters
+ return `
VIDEO
`;
+ });
+
+ // Clean up empty
tags created by the replacement
+ content = content.replace(/\s*<\/p>/g, '');
+
+ return content;
+ });
+
+ // Image optimization - transforms tags automatically
+ eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
+ extensions: "html",
+ formats: ["webp", "jpeg"],
+ widths: ["auto"],
+ failOnError: false,
+ defaultAttributes: {
+ loading: "lazy",
+ decoding: "async",
+ sizes: "auto",
+ alt: "",
+ },
+ });
+
+ // Sitemap generation
+ eleventyConfig.addPlugin(sitemap, {
+ sitemap: {
+ hostname: siteUrl,
+ },
+ });
+
+ // HTML minification for production builds
+ eleventyConfig.addTransform("htmlmin", async function (content, outputPath) {
+ if (outputPath && outputPath.endsWith(".html")) {
+ return await minify(content, {
+ collapseWhitespace: true,
+ removeComments: true,
+ html5: true,
+ decodeEntities: true,
+ minifyCSS: true,
+ minifyJS: true,
+ });
+ }
+ return content;
+ });
+
+ // Copy static assets to output
+ eleventyConfig.addPassthroughCopy("css");
+ eleventyConfig.addPassthroughCopy("images");
+
+ // Watch for content changes
+ eleventyConfig.addWatchTarget("./content/");
+ eleventyConfig.addWatchTarget("./css/");
+
+ // Webmentions plugin configuration
+ const wmDomain = siteUrl.replace("https://", "").replace("http://", "");
+ eleventyConfig.addPlugin(pluginWebmentions, {
+ domain: siteUrl,
+ feed: `https://webmention.io/api/mentions.jf2?domain=${wmDomain}&token=${process.env.WEBMENTION_IO_TOKEN}`,
+ key: "children",
+ });
+
+ // Date formatting filter
+ eleventyConfig.addFilter("dateDisplay", (dateObj) => {
+ if (!dateObj) return "";
+ const date = new Date(dateObj);
+ return date.toLocaleDateString("en-GB", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ });
+ });
+
+ // ISO date filter
+ eleventyConfig.addFilter("isoDate", (dateObj) => {
+ if (!dateObj) return "";
+ return new Date(dateObj).toISOString();
+ });
+
+ // Truncate filter
+ eleventyConfig.addFilter("truncate", (str, len = 200) => {
+ if (!str) return "";
+ if (str.length <= len) return str;
+ return str.slice(0, len).trim() + "...";
+ });
+
+ // Clean excerpt for OpenGraph - strips HTML, decodes entities, removes extra whitespace
+ eleventyConfig.addFilter("ogDescription", (content, len = 200) => {
+ if (!content) return "";
+ // Strip HTML tags
+ let text = content.replace(/<[^>]+>/g, ' ');
+ // Decode common HTML entities
+ text = text.replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, "'")
+ .replace(/ /g, ' ');
+ // Remove extra whitespace
+ text = text.replace(/\s+/g, ' ').trim();
+ // Truncate
+ if (text.length > len) {
+ text = text.slice(0, len).trim() + "...";
+ }
+ return text;
+ });
+
+ // Extract first image from content for OpenGraph fallback
+ eleventyConfig.addFilter("extractFirstImage", (content) => {
+ if (!content) return null;
+ // Match tags and extract src attribute
+ const imgMatch = content.match(/ ]+src=["']([^"']+)["']/i);
+ if (imgMatch && imgMatch[1]) {
+ let src = imgMatch[1];
+ // Skip data URIs and external placeholder images
+ if (src.startsWith('data:')) return null;
+ // Return the src (will be made absolute in template)
+ return src;
+ }
+ return null;
+ });
+
+ // Head filter for arrays
+ eleventyConfig.addFilter("head", (array, n) => {
+ if (!Array.isArray(array) || n < 1) return array;
+ return array.slice(0, n);
+ });
+
+ // Slugify filter
+ eleventyConfig.addFilter("slugify", (str) => {
+ if (!str) return "";
+ return str
+ .toLowerCase()
+ .replace(/[^\w\s-]/g, "")
+ .replace(/[\s_-]+/g, "-")
+ .replace(/^-+|-+$/g, "");
+ });
+
+ // Hash filter for cache busting - generates MD5 hash of file content
+ eleventyConfig.addFilter("hash", (filePath) => {
+ try {
+ const fullPath = resolve(__dirname, filePath.startsWith("/") ? `.${filePath}` : filePath);
+ const content = readFileSync(fullPath);
+ return createHash("md5").update(content).digest("hex").slice(0, 8);
+ } catch {
+ // Return timestamp as fallback if file not found
+ return Date.now().toString(36);
+ }
+ });
+
+ // Date filter (for sidebar dates)
+ eleventyConfig.addFilter("date", (dateObj, format) => {
+ if (!dateObj) return "";
+ const date = new Date(dateObj);
+ const options = {};
+
+ if (format.includes("MMM")) options.month = "short";
+ if (format.includes("d")) options.day = "numeric";
+ if (format.includes("yyyy")) options.year = "numeric";
+
+ return date.toLocaleDateString("en-US", options);
+ });
+
+ // Webmention filters
+ eleventyConfig.addFilter("webmentionsForUrl", function (webmentions, url) {
+ if (!webmentions || !url) return [];
+ const absoluteUrl = url.startsWith("http")
+ ? url
+ : `${siteUrl}${url}`;
+ return webmentions.filter(
+ (wm) =>
+ wm["wm-target"] === absoluteUrl ||
+ wm["wm-target"] === absoluteUrl.replace(/\/$/, "")
+ );
+ });
+
+ eleventyConfig.addFilter("webmentionsByType", function (mentions, type) {
+ if (!mentions) return [];
+ const typeMap = {
+ likes: "like-of",
+ reposts: "repost-of",
+ bookmarks: "bookmark-of",
+ replies: "in-reply-to",
+ mentions: "mention-of",
+ };
+ const wmProperty = typeMap[type] || type;
+ return mentions.filter((m) => m["wm-property"] === wmProperty);
+ });
+
+ // Collections for different post types
+ // Note: content path is content/ due to symlink structure
+ // "posts" shows ALL content types combined
+ eleventyConfig.addCollection("posts", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/**/*.md")
+ .sort((a, b) => b.date - a.date);
+ });
+
+ eleventyConfig.addCollection("notes", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/notes/**/*.md")
+ .sort((a, b) => b.date - a.date);
+ });
+
+ eleventyConfig.addCollection("articles", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/articles/**/*.md")
+ .sort((a, b) => b.date - a.date);
+ });
+
+ eleventyConfig.addCollection("bookmarks", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/bookmarks/**/*.md")
+ .sort((a, b) => b.date - a.date);
+ });
+
+ eleventyConfig.addCollection("photos", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/photos/**/*.md")
+ .sort((a, b) => b.date - a.date);
+ });
+
+ eleventyConfig.addCollection("likes", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/likes/**/*.md")
+ .sort((a, b) => b.date - a.date);
+ });
+
+ // Replies collection - posts with in_reply_to property
+ eleventyConfig.addCollection("replies", function (collectionApi) {
+ return collectionApi
+ .getAll()
+ .filter((item) => item.data.in_reply_to)
+ .sort((a, b) => b.date - a.date);
+ });
+
+ // Reposts collection - posts with repost_of property
+ eleventyConfig.addCollection("reposts", function (collectionApi) {
+ return collectionApi
+ .getAll()
+ .filter((item) => item.data.repost_of)
+ .sort((a, b) => b.date - a.date);
+ });
+
+ // All content combined for homepage feed
+ eleventyConfig.addCollection("feed", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/**/*.md")
+ .sort((a, b) => b.date - a.date)
+ .slice(0, 20);
+ });
+
+ // Categories collection - deduplicated by slug to avoid duplicate permalinks
+ eleventyConfig.addCollection("categories", function (collectionApi) {
+ const categoryMap = new Map(); // slug -> original name (first seen)
+ const slugify = (str) => str.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
+
+ collectionApi.getAll().forEach((item) => {
+ if (item.data.category) {
+ const cats = Array.isArray(item.data.category) ? item.data.category : [item.data.category];
+ cats.forEach((cat) => {
+ if (cat && typeof cat === 'string' && cat.trim()) {
+ const slug = slugify(cat.trim());
+ if (slug && !categoryMap.has(slug)) {
+ categoryMap.set(slug, cat.trim());
+ }
+ }
+ });
+ }
+ });
+ return [...categoryMap.values()].sort();
+ });
+
+ // Recent posts for sidebar
+ eleventyConfig.addCollection("recentPosts", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/posts/**/*.md")
+ .sort((a, b) => b.date - a.date)
+ .slice(0, 5);
+ });
+
+ return {
+ dir: {
+ input: ".",
+ output: "_site",
+ includes: "_includes",
+ data: "_data",
+ },
+ markdownTemplateEngine: false, // Disable to avoid Nunjucks interpreting {{ in content
+ htmlTemplateEngine: "njk",
+ };
+}
diff --git a/feed-json.njk b/feed-json.njk
new file mode 100644
index 0000000..c84e79c
--- /dev/null
+++ b/feed-json.njk
@@ -0,0 +1,34 @@
+---
+permalink: /feed.json
+eleventyExcludeFromCollections: true
+---
+{
+ "version": "https://jsonfeed.org/version/1.1",
+ "title": "{{ site.name }}",
+ "home_page_url": "{{ site.url }}/",
+ "feed_url": "{{ site.url }}/feed.json",
+ "description": "{{ site.description }}",
+ "language": "{{ site.locale | default('en') }}",
+ "authors": [
+ {
+ "name": "{{ site.author | default('Ricardo Mendes') }}",
+ "url": "{{ site.url }}/"
+ }
+ ],
+ "items": [
+ {%- for post in collections.feed %}
+ {%- set absolutePostUrl = site.url + post.url %}
+ {%- set postImage = post.data.photo or post.data.image or (post.content | extractFirstImage) %}
+ {
+ "id": "{{ absolutePostUrl }}",
+ "url": "{{ absolutePostUrl }}",
+ "title": {{ post.data.title | default(post.content | striptags | truncate(80)) | jsonEncode | safe }},
+ "content_html": {{ post.content | htmlToAbsoluteUrls(absolutePostUrl) | jsonEncode | safe }},
+ "date_published": "{{ post.date | dateToRfc3339 }}"
+ {%- if postImage %},
+ "image": "{{ postImage | url | absoluteUrl(site.url) }}"
+ {%- endif %}
+ }{% if not loop.last %},{% endif %}
+ {%- endfor %}
+ ]
+}
diff --git a/feed.njk b/feed.njk
new file mode 100644
index 0000000..7582d2a
--- /dev/null
+++ b/feed.njk
@@ -0,0 +1,31 @@
+---
+permalink: /feed.xml
+eleventyExcludeFromCollections: true
+---
+
+
+
+ {{ site.name }}
+ {{ site.url }}/
+ {{ site.description }}
+ {{ site.locale | default('en') }}
+
+ {{ collections.feed | getNewestCollectionItemDate | dateToRfc822 }}
+ {%- for post in collections.feed %}
+ {%- set absolutePostUrl = site.url + post.url %}
+ {%- set postImage = post.data.photo or post.data.image or (post.content | extractFirstImage) %}
+ -
+
{{ post.data.title | default(post.content | striptags | truncate(80)) | escape }}
+ {{ absolutePostUrl }}
+ {{ absolutePostUrl }}
+ {{ post.date | dateToRfc822 }}
+ {{ post.content | htmlToAbsoluteUrls(absolutePostUrl) | escape }}
+ {%- if postImage %}
+ {%- set imageUrl = postImage | url | absoluteUrl(site.url) %}
+
+
+ {%- endif %}
+
+ {%- endfor %}
+
+
diff --git a/funkwhale.njk b/funkwhale.njk
new file mode 100644
index 0000000..2a121b4
--- /dev/null
+++ b/funkwhale.njk
@@ -0,0 +1,268 @@
+---
+layout: layouts/base.njk
+title: Funkwhale Listening Activity
+permalink: /funkwhale/
+withSidebar: true
+---
+
+
+
+ {# Now Playing / Recently Played Hero #}
+ {% if funkwhaleActivity.nowPlaying and funkwhaleActivity.nowPlaying.status %}
+
+
+
+ {% if funkwhaleActivity.nowPlaying.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if funkwhaleActivity.nowPlaying.status == 'now-playing' %}
+
+
+
+
+
+
+ Now Playing
+
+ {% else %}
+
+
+ Recently Played
+
+ {% endif %}
+
+
+
+ {% if funkwhaleActivity.nowPlaying.trackUrl %}
+
+ {{ funkwhaleActivity.nowPlaying.track }}
+
+ {% else %}
+ {{ funkwhaleActivity.nowPlaying.track }}
+ {% endif %}
+
+
{{ funkwhaleActivity.nowPlaying.artist }}
+ {% if funkwhaleActivity.nowPlaying.album %}
+
{{ funkwhaleActivity.nowPlaying.album }}
+ {% endif %}
+
{{ funkwhaleActivity.nowPlaying.relativeTime }}
+
+
+
+
+ {% endif %}
+
+ {# Stats Section with Tabs #}
+ {% if funkwhaleActivity.stats %}
+
+
+
+
+
+ Listening Statistics
+
+
+ {# Tab buttons #}
+
+
+ All Time
+
+
+ This Month
+
+
+ This Week
+
+
+ Trends
+
+
+
+ {# All Time Tab #}
+
+ {% set summary = funkwhaleActivity.stats.summary.all %}
+ {% set topArtists = funkwhaleActivity.stats.topArtists.all %}
+ {% set topAlbums = funkwhaleActivity.stats.topAlbums.all %}
+ {% include "components/funkwhale-stats-content.njk" %}
+
+
+ {# This Month Tab #}
+
+ {% set summary = funkwhaleActivity.stats.summary.month %}
+ {% set topArtists = funkwhaleActivity.stats.topArtists.month %}
+ {% set topAlbums = funkwhaleActivity.stats.topAlbums.month %}
+ {% include "components/funkwhale-stats-content.njk" %}
+
+
+ {# This Week Tab #}
+
+ {% set summary = funkwhaleActivity.stats.summary.week %}
+ {% set topArtists = funkwhaleActivity.stats.topArtists.week %}
+ {% set topAlbums = funkwhaleActivity.stats.topAlbums.week %}
+ {% include "components/funkwhale-stats-content.njk" %}
+
+
+ {# Trends Tab #}
+
+ {% if funkwhaleActivity.stats.trends and funkwhaleActivity.stats.trends.length %}
+
+
Daily Listening (Last 30 Days)
+
+ {% set maxCount = 1 %}
+ {% for day in funkwhaleActivity.stats.trends %}
+ {% if day.count > maxCount %}
+ {% set maxCount = day.count %}
+ {% endif %}
+ {% endfor %}
+ {% for day in funkwhaleActivity.stats.trends %}
+
+ {% endfor %}
+
+
+ {{ funkwhaleActivity.stats.trends[0].date }}
+ {{ funkwhaleActivity.stats.trends[funkwhaleActivity.stats.trends.length - 1].date }}
+
+
+ {% else %}
+
No trend data available yet.
+ {% endif %}
+
+
+ {% endif %}
+
+ {# Recent Listenings #}
+
+
+
+
+
+ Recent Listens
+
+
+ {% if funkwhaleActivity.listenings.length %}
+
+ {% for listening in funkwhaleActivity.listenings | head(15) %}
+
+ {% if listening.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if listening.trackUrl %}
+
+ {{ listening.track }}
+
+ {% else %}
+ {{ listening.track }}
+ {% endif %}
+
+
{{ listening.artist }}
+
+
+
+ {{ listening.relativeTime }}
+ {% if listening.duration %}
+ {{ listening.duration }}
+ {% endif %}
+
+
+ {% endfor %}
+
+ {% else %}
+ No recent listening history available.
+ {% endif %}
+
+
+ {# Favorites #}
+ {% if funkwhaleActivity.favorites.length %}
+
+
+
+
+
+ Favorite Tracks
+
+
+
+ {% for favorite in funkwhaleActivity.favorites | head(10) %}
+
+ {% if favorite.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if favorite.trackUrl %}
+
+ {{ favorite.track }}
+
+ {% else %}
+ {{ favorite.track }}
+ {% endif %}
+
+
{{ favorite.artist }}
+ {% if favorite.album %}
+
{{ favorite.album }}
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
diff --git a/github.njk b/github.njk
new file mode 100644
index 0000000..5d68cca
--- /dev/null
+++ b/github.njk
@@ -0,0 +1,266 @@
+---
+layout: layouts/base.njk
+title: GitHub Activity
+permalink: /github/
+withSidebar: true
+---
+
+
+
+ {# Featured Projects Section #}
+ {% if githubActivity.featured.length %}
+
+
+
+
+
+ Featured Projects
+
+
+
+ {% for repo in githubActivity.featured %}
+
+
+
+ {% if repo.isPrivate %}
+
Private
+ {% endif %}
+
+
+ {% if repo.description %}
+ {{ repo.description }}
+ {% endif %}
+
+
+ {% if repo.language %}
+
+
+ {{ repo.language }}
+
+ {% endif %}
+
+
+
+
+ {{ repo.stars }}
+
+ {% if repo.forks > 0 %}
+
+
+
+
+ {{ repo.forks }}
+
+ {% endif %}
+
+
+ {% if repo.commits and repo.commits.length %}
+
+
+ Recent commits ({{ repo.commits.length }})
+
+
+ {% for commit in repo.commits %}
+
+
+ {{ commit.sha }}
+
+ {{ commit.message }}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {# Starred Repos Section #}
+
+
+
+
+
+ Starred Repositories
+
+
+ {% if githubActivity.stars.length %}
+
+ {% for repo in githubActivity.stars | head(10) %}
+
+
+
+ {% if repo.description %}
+ {{ repo.description }}
+ {% endif %}
+
+
+ {% for topic in repo.topics %}
+
+ {{ topic }}
+
+ {% endfor %}
+
+
+
+ {% if repo.language %}
+
+
+ {{ repo.language }}
+
+ {% endif %}
+
+
+
+
+ {{ repo.stars }}
+
+
+
+ {% endfor %}
+
+ {% else %}
+ No starred repositories found.
+ {% endif %}
+
+
+ {# Recent Commits Section #}
+
+
+
+
+
+ Recent Commits
+
+
+ {% if githubActivity.commits.length %}
+
+ {% for commit in githubActivity.commits %}
+
+
+ {{ commit.sha }}
+
+
+
+ {% endfor %}
+
+ {% else %}
+ No recent commits found.
+ {% endif %}
+
+
+ {# Contributions Section #}
+ {% if githubActivity.contributions.length %}
+
+
+
+
+
+ Pull Requests & Issues
+
+
+
+ {% for item in githubActivity.contributions %}
+
+ {% if item.type == "pr" %}
+
PR
+ {% else %}
+
Issue
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {# My Repositories Section #}
+
+
+
+
+
+ My Repositories
+
+
+ {% if githubRepos.length %}
+
+ {% for repo in githubRepos | head(6) %}
+
+
+
+ {% if repo.description %}
+ {{ repo.description | truncate(100) }}
+ {% endif %}
+
+
+ {% if repo.language %}
+
+
+ {{ repo.language }}
+
+ {% endif %}
+
+
+
+
+ {{ repo.stargazers_count }}
+
+
+
+
+
+ {{ repo.forks_count }}
+
+
+
+ {% endfor %}
+
+
+
+ View all repositories →
+
+ {% else %}
+ No repositories found.
+ {% endif %}
+
+
diff --git a/images/default-avatar.svg b/images/default-avatar.svg
new file mode 100644
index 0000000..3b15f50
--- /dev/null
+++ b/images/default-avatar.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/images/og-default.png b/images/og-default.png
new file mode 100644
index 0000000000000000000000000000000000000000..bfaee94a7da84174fe53902a1912582cfe5f531e
GIT binary patch
literal 39412
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
literal 0
HcmV?d00001
diff --git a/images/rick.jpg b/images/rick.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f976d6d16964900a192ce37d14e2148b3de14ee6
GIT binary patch
literal 152493
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