diff --git a/_data/blogrollStatus.js b/_data/blogrollStatus.js
new file mode 100644
index 0000000..aee1601
--- /dev/null
+++ b/_data/blogrollStatus.js
@@ -0,0 +1,34 @@
+/**
+ * Blogroll Status Data
+ * Checks if the blogroll API backend is available at build time.
+ * Used for conditional navigation — the blogroll page itself loads data client-side.
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+export default async function () {
+ try {
+ const url = `${INDIEKIT_URL}/blogrollapi/api/status`;
+ console.log(`[blogrollStatus] Checking API: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log("[blogrollStatus] API available");
+ return {
+ available: true,
+ source: "indiekit",
+ ...data,
+ };
+ } catch (error) {
+ console.log(
+ `[blogrollStatus] API unavailable: ${error.message}`
+ );
+ return {
+ available: false,
+ source: "unavailable",
+ };
+ }
+}
diff --git a/_data/cv.js b/_data/cv.js
index 576eac9..2dda1e2 100644
--- a/_data/cv.js
+++ b/_data/cv.js
@@ -1,190 +1,21 @@
/**
- * CV Data - Easy to update!
+ * CV Data — Empty defaults.
*
- * 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
+ * When the indiekit-endpoint-cv plugin is installed, it serves CV data
+ * via its API endpoint and the homepage plugin renders it.
+ *
+ * Without the plugin, users can edit this file directly:
+ * - Add entries to the `experience` array
+ * - Add entries to the `projects` array
+ * - 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"
- ]
+ lastUpdated: null,
+ experience: [],
+ projects: [],
+ skills: {},
+ languages: [],
+ education: [],
+ interests: [],
};
diff --git a/_data/enabledPostTypes.js b/_data/enabledPostTypes.js
new file mode 100644
index 0000000..989bc7a
--- /dev/null
+++ b/_data/enabledPostTypes.js
@@ -0,0 +1,50 @@
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+const CONTENT_DIR = process.env.CONTENT_DIR || "/data/content";
+
+// Standard post types for any Indiekit deployment
+const ALL_POST_TYPES = [
+ { type: "article", label: "Articles", path: "/articles/", createUrl: "/posts/create?type=article" },
+ { type: "note", label: "Notes", path: "/notes/", createUrl: "/posts/create?type=note" },
+ { type: "photo", label: "Photos", path: "/photos/", createUrl: "/posts/create?type=photo" },
+ { type: "bookmark", label: "Bookmarks", path: "/bookmarks/", createUrl: "/posts/create?type=bookmark" },
+ { type: "like", label: "Likes", path: "/likes/", createUrl: "/posts/create?type=like" },
+ { type: "reply", label: "Replies", path: "/replies/", createUrl: "/posts/create?type=reply" },
+ { type: "repost", label: "Reposts", path: "/reposts/", createUrl: "/posts/create?type=repost" },
+];
+
+/**
+ * Returns the list of enabled post types.
+ *
+ * Resolution order:
+ * 1. .indiekit/post-types.json in content dir (written by Indiekit or deployer)
+ * 2. POST_TYPES env var (comma-separated: "article,note,photo")
+ * 3. All standard post types (default)
+ */
+export default function () {
+ // 1. Try config file
+ try {
+ const configPath = resolve(CONTENT_DIR, ".indiekit", "post-types.json");
+ const raw = readFileSync(configPath, "utf8");
+ const types = JSON.parse(raw);
+ if (Array.isArray(types)) {
+ // Array of type strings: ["article", "note"]
+ return ALL_POST_TYPES.filter((pt) => types.includes(pt.type));
+ }
+ // Array of objects with at least { type }
+ return types;
+ } catch {
+ // File doesn't exist — fall through
+ }
+
+ // 2. Try env var
+ const envTypes = process.env.POST_TYPES;
+ if (envTypes) {
+ const types = envTypes.split(",").map((t) => t.trim().toLowerCase());
+ return ALL_POST_TYPES.filter((pt) => types.includes(pt.type));
+ }
+
+ // 3. Default — all standard types
+ return ALL_POST_TYPES;
+}
diff --git a/_data/homepageConfig.js b/_data/homepageConfig.js
new file mode 100644
index 0000000..2c2fc35
--- /dev/null
+++ b/_data/homepageConfig.js
@@ -0,0 +1,27 @@
+/**
+ * Homepage Configuration Data
+ * Reads config from indiekit-endpoint-homepage plugin (when installed).
+ * Falls back to null — home.njk then uses the default layout.
+ *
+ * Future: The homepage plugin will write a .indiekit/homepage.json file
+ * that Eleventy watches. On change, a rebuild picks up the new config,
+ * allowing layout changes without a Docker rebuild.
+ */
+
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+const CONTENT_DIR = process.env.CONTENT_DIR || "/data/content";
+
+export default function () {
+ try {
+ const configPath = resolve(CONTENT_DIR, ".indiekit", "homepage.json");
+ const raw = readFileSync(configPath, "utf8");
+ const config = JSON.parse(raw);
+ console.log("[homepageConfig] Loaded plugin config");
+ return config;
+ } catch {
+ // No homepage plugin config — this is the normal case for most deployments
+ return null;
+ }
+}
diff --git a/_data/podrollStatus.js b/_data/podrollStatus.js
new file mode 100644
index 0000000..cf117e5
--- /dev/null
+++ b/_data/podrollStatus.js
@@ -0,0 +1,34 @@
+/**
+ * Podroll Status Data
+ * Checks if the podroll API backend is available at build time.
+ * Used for conditional navigation — the podroll page itself loads data client-side.
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+export default async function () {
+ try {
+ const url = `${INDIEKIT_URL}/podrollapi/api/status`;
+ console.log(`[podrollStatus] Checking API: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log("[podrollStatus] API available");
+ return {
+ available: true,
+ source: "indiekit",
+ ...data,
+ };
+ } catch (error) {
+ console.log(
+ `[podrollStatus] API unavailable: ${error.message}`
+ );
+ return {
+ available: false,
+ source: "unavailable",
+ };
+ }
+}
diff --git a/_data/urlAliases.js b/_data/urlAliases.js
index cc016ca..1478a1f 100644
--- a/_data/urlAliases.js
+++ b/_data/urlAliases.js
@@ -4,9 +4,9 @@
* Maps new URLs to their old URLs so webmentions from previous
* URL structures can be displayed on current pages.
*
- * Sources:
- * - redirects.map.rmendes (micro.blog: /YYYY/MM/DD/slug.html → /notes/...)
- * - old-blog-redirects.map.rmendes (Known/WP: /YYYY/slug → /content/...)
+ * Place redirect map files in the parent directory of this theme:
+ * - redirects.map (e.g., micro.blog: /YYYY/MM/DD/slug.html → /notes/...)
+ * - old-blog-redirects.map (e.g., Known/WP: /YYYY/slug → /content/...)
*/
import { readFileSync, existsSync } from "fs";
@@ -94,13 +94,11 @@ function findFile(candidates) {
// Try multiple possible locations for each map type
const microblogMapPath = findFile([
resolve(pkgRoot, "redirects.map"),
- resolve(pkgRoot, "redirects.map.rmendes"),
resolve(__dirname, "../../redirects.map"),
]);
const knownMapPath = findFile([
resolve(pkgRoot, "old-blog-redirects.map"),
- resolve(pkgRoot, "old-blog-redirects.map.rmendes"),
resolve(__dirname, "../../old-blog-redirects.map"),
]);
diff --git a/_includes/components/empty-collection.njk b/_includes/components/empty-collection.njk
new file mode 100644
index 0000000..1e3000e
--- /dev/null
+++ b/_includes/components/empty-collection.njk
@@ -0,0 +1,27 @@
+{# Empty collection placeholder — encourages creating content #}
+{# Usage: {% include "components/empty-collection.njk" %} with postType set before include #}
+{% set typeInfo = null %}
+{% for pt in enabledPostTypes %}
+ {% if pt.type == postType %}{% set typeInfo = pt %}{% endif %}
+{% endfor %}
+
+
+
+
No {{ title | lower }} yet
+
+ This is where your {{ title | lower }} will appear once you start creating content.
+
+ {% if typeInfo %}
+
+
+
+
+ Create your first {{ postType }}
+
+ {% endif %}
+
diff --git a/_includes/components/sidebar.njk b/_includes/components/sidebar.njk
index 6d9d57d..0c503ea 100644
--- a/_includes/components/sidebar.njk
+++ b/_includes/components/sidebar.njk
@@ -1,300 +1,26 @@
-{# Sidebar Components #}
-{# Contains: Author card (via h-card component), Bluesky feed, GitHub repos, RSS feed #}
+{# Sidebar — composed from individual widget partials #}
+{# Each widget handles its own conditional display internally, #}
+{# except API-only widgets which need a data-source guard here. #}
-{# Author Card Widget - includes the canonical h-card component #}
-
- {% include "components/h-card.njk" %}
-
+{# Author Card (h-card) — always shown #}
+{% include "components/widgets/author-card.njk" %}
-{# Social Feed Widget - Tabbed Bluesky/Mastodon #}
-{% if (blueskyFeed and blueskyFeed.length) or (mastodonFeed and mastodonFeed.length) %}
-
+{# Recent Posts (for non-blog pages) #}
+{% include "components/widgets/recent-posts.njk" %}
+
+{# Blogroll — only when backend is available #}
+{% if blogrollStatus and blogrollStatus.source == "indiekit" %}
+{% include "components/widgets/blogroll.njk" %}
{% 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 %}
-
-{# Blogroll Widget - Dynamic loading from API #}
-
-
-
-
-{# Categories/Tags Widget #}
-{% if categories and categories.length %}
-
-{% endif %}
+{# Categories/Tags #}
+{% include "components/widgets/categories.njk" %}
diff --git a/_includes/components/widgets/author-card.njk b/_includes/components/widgets/author-card.njk
new file mode 100644
index 0000000..50a5048
--- /dev/null
+++ b/_includes/components/widgets/author-card.njk
@@ -0,0 +1,4 @@
+{# Author Card Widget - includes the canonical h-card component #}
+
+ {% include "components/h-card.njk" %}
+
diff --git a/_includes/components/widgets/blogroll.njk b/_includes/components/widgets/blogroll.njk
new file mode 100644
index 0000000..5e8222d
--- /dev/null
+++ b/_includes/components/widgets/blogroll.njk
@@ -0,0 +1,56 @@
+{# Blogroll Widget - Dynamic loading from API #}
+
+
+
diff --git a/_includes/components/widgets/categories.njk b/_includes/components/widgets/categories.njk
new file mode 100644
index 0000000..74ef98b
--- /dev/null
+++ b/_includes/components/widgets/categories.njk
@@ -0,0 +1,13 @@
+{# Categories/Tags Widget #}
+{% if categories and categories.length %}
+
+{% endif %}
diff --git a/_includes/components/widgets/funkwhale.njk b/_includes/components/widgets/funkwhale.njk
new file mode 100644
index 0000000..b513be5
--- /dev/null
+++ b/_includes/components/widgets/funkwhale.njk
@@ -0,0 +1,71 @@
+{# Funkwhale Now Playing Widget #}
+{% if funkwhaleActivity and (funkwhaleActivity.nowPlaying or funkwhaleActivity.stats) %}
+
+{% endif %}
diff --git a/_includes/components/widgets/github-repos.njk b/_includes/components/widgets/github-repos.njk
new file mode 100644
index 0000000..2907324
--- /dev/null
+++ b/_includes/components/widgets/github-repos.njk
@@ -0,0 +1,32 @@
+{# GitHub Repos Widget #}
+{% if githubRepos and githubRepos.length %}
+
+{% endif %}
diff --git a/_includes/components/widgets/recent-posts.njk b/_includes/components/widgets/recent-posts.njk
new file mode 100644
index 0000000..88bef43
--- /dev/null
+++ b/_includes/components/widgets/recent-posts.njk
@@ -0,0 +1,21 @@
+{# Recent Posts Widget (for non-blog pages) #}
+{% if recentPosts and recentPosts.length %}
+
+{% endif %}
diff --git a/_includes/components/widgets/social-activity.njk b/_includes/components/widgets/social-activity.njk
new file mode 100644
index 0000000..9439509
--- /dev/null
+++ b/_includes/components/widgets/social-activity.njk
@@ -0,0 +1,94 @@
+{# Social Feed Widget - Tabbed Bluesky/Mastodon #}
+{% if (blueskyFeed and blueskyFeed.length) or (mastodonFeed and mastodonFeed.length) %}
+
+{% endif %}
diff --git a/_includes/layouts/base.njk b/_includes/layouts/base.njk
index ff750ea..a5f4c73 100644
--- a/_includes/layouts/base.njk
+++ b/_includes/layouts/base.njk
@@ -114,7 +114,9 @@
Home
About
+ {% if collections.pages | selectattr("url", "equalto", "/now/") | list | length %}
Now
+ {% endif %}
{# Slash pages dropdown - all root pages in one menu #}
@@ -128,13 +130,24 @@
{% for item in collections.pages %}
/{{ item.fileSlug }}
{% endfor %}
+ {# Plugin pages — only show when their data source is configured #}
+ {% set hasPluginPages = (funkwhaleActivity and funkwhaleActivity.source == "indiekit") or
+ (githubActivity and githubActivity.source != "error") or
+ (lastfmActivity and lastfmActivity.source == "indiekit") or
+ (newsActivity and newsActivity.source == "indiekit") or
+ (youtubeChannel and youtubeChannel.source == "indiekit") or
+ (blogrollStatus and blogrollStatus.source == "indiekit") or
+ (podrollStatus and podrollStatus.source == "indiekit") %}
+ {% if hasPluginPages %}
-
/funkwhale
-
/github
-
/listening
-
/news
-
/podroll
-
/youtube
+ {% if blogrollStatus and blogrollStatus.source == "indiekit" %}
/blogroll {% endif %}
+ {% if funkwhaleActivity and funkwhaleActivity.source == "indiekit" %}
/funkwhale {% endif %}
+ {% if githubActivity and githubActivity.source != "error" %}
/github {% endif %}
+ {% if lastfmActivity and lastfmActivity.source == "indiekit" %}
/listening {% endif %}
+ {% if newsActivity and newsActivity.source == "indiekit" %}
/news {% endif %}
+ {% if podrollStatus and podrollStatus.source == "indiekit" %}
/podroll {% endif %}
+ {% if youtubeChannel and youtubeChannel.source == "indiekit" %}
/youtube {% endif %}
+ {% endif %}
{# Blog dropdown #}
@@ -147,13 +160,9 @@
Interactions
@@ -191,7 +200,9 @@
Home
About
+ {% if collections.pages | selectattr("url", "equalto", "/now/") | list | length %}
Now
+ {% endif %}
{# Slash pages section - all root pages in one menu #}
@@ -205,13 +216,17 @@
{% for item in collections.pages %}
/{{ item.fileSlug }}
{% endfor %}
+ {# Plugin pages — only show when configured #}
+ {% if hasPluginPages %}
- /funkwhale
- /github
- /listening
- /news
- /podroll
- /youtube
+ {% if blogrollStatus and blogrollStatus.source == "indiekit" %}/blogroll {% endif %}
+ {% if funkwhaleActivity and funkwhaleActivity.source == "indiekit" %}/funkwhale {% endif %}
+ {% if githubActivity and githubActivity.source != "error" %}/github {% endif %}
+ {% if lastfmActivity and lastfmActivity.source == "indiekit" %}/listening {% endif %}
+ {% if newsActivity and newsActivity.source == "indiekit" %}/news {% endif %}
+ {% if podrollStatus and podrollStatus.source == "indiekit" %}/podroll {% endif %}
+ {% if youtubeChannel and youtubeChannel.source == "indiekit" %}/youtube {% endif %}
+ {% endif %}
{# Blog section #}
@@ -224,13 +239,9 @@
Interactions
diff --git a/_includes/layouts/home.njk b/_includes/layouts/home.njk
index 73534c3..e0237e7 100644
--- a/_includes/layouts/home.njk
+++ b/_includes/layouts/home.njk
@@ -22,13 +22,17 @@ withSidebar: true
{{ site.author.title }}
+ {% if site.author.bio %}
- Hi, I geek around tech, information systems, democracy, justice, coercive groups (aka cults), and discernment.
+ {{ site.author.bio }}
+ {% endif %}
+ {% if site.description %}
- My blog serves as a repository for my thoughts, long-form writings (some still in draft), and a place where I bookmark interesting finds from the web. It's also my central hub for cross-posting to networks like Mastodon, Bluesky.
+ {{ site.description }}
Read more →
+ {% endif %}
{# Social Links #}
@@ -56,6 +60,25 @@ withSidebar: true
+{# Homepage content — three-tier fallback: #}
+{# 1. Plugin config (homepageConfig) — Phase 3, future #}
+{# 2. CV data — show experience/projects/skills #}
+{# 3. Default — show recent posts and activity #}
+
+{% set hasCvData = (cv.experience and cv.experience.length) or
+ (cv.projects and cv.projects.length) or
+ (cv.skills and (cv.skills | dictsort | length)) %}
+
+{# --- Tier 1: Plugin-driven layout (future) --- #}
+{% if homepageConfig and homepageConfig.sections %}
+{# Reserved for indiekit-endpoint-homepage plugin — will render configured sections here #}
+
+ Homepage plugin layout will render here.
+
+
+{# --- Tier 2: CV-based layout --- #}
+{% elif hasCvData %}
+
{# Work Experience Timeline - only show if data exists #}
{% if cv.experience and cv.experience.length %}
@@ -210,3 +233,83 @@ withSidebar: true
Last updated: {{ cv.lastUpdated }}
{% endif %}
+
+{# --- Tier 3: Default — recent activity when no CV and no plugin --- #}
+{% else %}
+
+{# Recent Posts #}
+{% if collections.posts and collections.posts.length %}
+
+ Recent Posts
+
+ {% for post in collections.posts | head(10) %}
+
+
+ {% if post.data.summary %}
+ {{ post.data.summary }}
+ {% endif %}
+
+
+ {{ (post.data.published or post.date) | date("MMM d, yyyy") }}
+
+ {% if post.data.postType %}
+ {{ post.data.postType }}
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ View all posts
+
+
+
+{% endif %}
+
+{# Getting Started — onboarding guide for new deployments #}
+
+ Getting Started
+
+
+
+
1
+
+
Create your first post
+
+ Sign in ,
+ then visit /create
+ to publish notes, articles, bookmarks, and photos.
+
+
+
+
+
+
2
+
+
Set up syndication
+
+ Cross-post to Mastodon, Bluesky, and LinkedIn automatically.
+ Add your credentials to the .env file and restart.
+
+
+
+
+
+
3
+
+
Enable interactions
+
+ Receive likes, replies, and reposts from across the web.
+ Register at webmention.io
+ and add the token to .env as WEBMENTION_IO_TOKEN.
+
+
+
+
+
+
+{% endif %} {# end three-tier fallback #}
diff --git a/articles.njk b/articles.njk
index b72f116..50b7d64 100644
--- a/articles.njk
+++ b/articles.njk
@@ -6,6 +6,7 @@ pagination:
data: collections.articles
size: 20
alias: paginatedArticles
+ generatePageOnEmptyData: true
permalink: "articles/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
---
@@ -87,6 +88,7 @@ permalink: "articles/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
{% endif %}
{% else %}
-
No articles yet.
+ {% set postType = "article" %}
+ {% include "components/empty-collection.njk" %}
{% endif %}
diff --git a/bookmarks.njk b/bookmarks.njk
index 41c888e..3ed42a1 100644
--- a/bookmarks.njk
+++ b/bookmarks.njk
@@ -6,6 +6,7 @@ pagination:
data: collections.bookmarks
size: 20
alias: paginatedBookmarks
+ generatePageOnEmptyData: true
permalink: "bookmarks/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
---
@@ -100,6 +101,7 @@ permalink: "bookmarks/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageN
{% endif %}
{% else %}
-
No bookmarks yet.
+ {% set postType = "bookmark" %}
+ {% include "components/empty-collection.njk" %}
{% endif %}
diff --git a/feed-json.njk b/feed-json.njk
index 43357c8..d45ed46 100644
--- a/feed-json.njk
+++ b/feed-json.njk
@@ -17,7 +17,7 @@ eleventyExcludeFromCollections: true
"language": "{{ site.locale | default('en') }}",
"authors": [
{
- "name": "{{ site.author | default('Ricardo Mendes') }}",
+ "name": "{{ site.author.name | default(site.name) }}",
"url": "{{ site.url }}/"
}
],
diff --git a/likes.njk b/likes.njk
index 1c7f299..7e25572 100644
--- a/likes.njk
+++ b/likes.njk
@@ -6,6 +6,7 @@ pagination:
data: collections.likes
size: 20
alias: paginatedLikes
+ generatePageOnEmptyData: true
permalink: "likes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
---
@@ -98,6 +99,7 @@ permalink: "likes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
{% endif %}
{% else %}
-
No likes yet.
+ {% set postType = "like" %}
+ {% include "components/empty-collection.njk" %}
{% endif %}
diff --git a/notes.njk b/notes.njk
index 65cb51d..0723f0d 100644
--- a/notes.njk
+++ b/notes.njk
@@ -6,6 +6,7 @@ pagination:
data: collections.notes
size: 20
alias: paginatedNotes
+ generatePageOnEmptyData: true
permalink: "notes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
---
@@ -84,6 +85,7 @@ permalink: "notes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
{% endif %}
{% else %}
-
No notes yet.
+ {% set postType = "note" %}
+ {% include "components/empty-collection.njk" %}
{% endif %}
diff --git a/package.json b/package.json
index 415b758..39ea7f8 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
- "name": "rmendes-eleventy-site",
+ "name": "indiekit-eleventy-theme",
"version": "1.0.0",
- "description": "Personal website powered by Indiekit and Eleventy",
+ "description": "Eleventy theme for Indiekit — IndieWeb-ready personal website",
"type": "module",
"scripts": {
"build": "eleventy",
diff --git a/photos.njk b/photos.njk
index 0f99923..9c9f53b 100644
--- a/photos.njk
+++ b/photos.njk
@@ -6,6 +6,7 @@ pagination:
data: collections.photos
size: 20
alias: paginatedPhotos
+ generatePageOnEmptyData: true
permalink: "photos/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
---
@@ -92,6 +93,7 @@ permalink: "photos/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumb
{% endif %}
{% else %}
-
No photos yet.
+ {% set postType = "photo" %}
+ {% include "components/empty-collection.njk" %}
{% endif %}
diff --git a/replies.njk b/replies.njk
index e1950ea..8eab057 100644
--- a/replies.njk
+++ b/replies.njk
@@ -6,6 +6,7 @@ pagination:
data: collections.replies
size: 20
alias: paginatedReplies
+ generatePageOnEmptyData: true
permalink: "replies/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
---
@@ -102,6 +103,7 @@ permalink: "replies/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
{% endif %}
{% else %}
-
No replies yet.
+ {% set postType = "reply" %}
+ {% include "components/empty-collection.njk" %}
{% endif %}
diff --git a/reposts.njk b/reposts.njk
index a16a4d2..7162d86 100644
--- a/reposts.njk
+++ b/reposts.njk
@@ -6,6 +6,7 @@ pagination:
data: collections.reposts
size: 20
alias: paginatedReposts
+ generatePageOnEmptyData: true
permalink: "reposts/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
---
@@ -104,6 +105,7 @@ permalink: "reposts/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
{% endif %}
{% else %}
-
No reposts yet.
+ {% set postType = "repost" %}
+ {% include "components/empty-collection.njk" %}
{% endif %}
diff --git a/slashes.njk b/slashes.njk
index df4444c..d57edf1 100644
--- a/slashes.njk
+++ b/slashes.njk
@@ -11,9 +11,9 @@ permalink: /slashes/
{# Dynamic pages (created via Indiekit) #}
- {% if collections.pages.length > 0 %}
Pages
+ {% if collections.pages.length > 0 %}
{% for page in collections.pages %}
@@ -37,21 +37,65 @@ permalink: /slashes/
{% endfor %}
+ {% else %}
+
+
+ No root pages yet. To create pages like /now, /uses, or /colophon, you need two plugins:
+
+
+ @rmdes/indiekit-post-type-page — registers the "page" post type with Indiekit, using root-level URL paths (/slug instead of /type/YYYY/MM/DD/slug)
+ @rmdes/indiekit-endpoint-posts — publishing UI that sends the h=page Micropub type so pages are created at root level
+
+
+ Once both plugins are installed, "Page" appears as a post type in the Indiekit admin UI, and pages are published directly at /slug.
+
+
+ {% endif %}
- {% endif %}
- {# Activity pages (from Indiekit plugins) #}
+ {# Activity pages — only show when their plugin backend is available #}
+ {% set hasActivityPages = (funkwhaleActivity and funkwhaleActivity.source == "indiekit") or
+ (githubActivity and githubActivity.source != "error") or
+ (lastfmActivity and lastfmActivity.source == "indiekit") or
+ (newsActivity and newsActivity.source == "indiekit") or
+ (youtubeChannel and youtubeChannel.source == "indiekit") or
+ (blogrollStatus and blogrollStatus.source == "indiekit") or
+ (podrollStatus and podrollStatus.source == "indiekit") %}
+ {% if hasActivityPages %}
Activity Feeds
+ {% if blogrollStatus and blogrollStatus.source == "indiekit" %}
+
+
+ Sites I follow
+
+ {% endif %}
+ {% if funkwhaleActivity and funkwhaleActivity.source == "indiekit" %}
+
+
+ Funkwhale activity
+
+ {% endif %}
+ {% if githubActivity and githubActivity.source != "error" %}
- My GitHub activity
+ GitHub activity
+ {% endif %}
+ {% if lastfmActivity and lastfmActivity.source == "indiekit" %}
Last.fm scrobbles
-
-
- My Funkwhale activity
-
-
-
- My YouTube channel
-
+ {% endif %}
+ {% if newsActivity and newsActivity.source == "indiekit" %}
RSS feed aggregator
+ {% endif %}
+ {% if podrollStatus and podrollStatus.source == "indiekit" %}
+
+
+ Podcasts I listen to
+
+ {% endif %}
+ {% if youtubeChannel and youtubeChannel.source == "indiekit" %}
+
+
+ YouTube channel
+
+ {% endif %}
+ {% endif %}
{# Inspiration section #}