feat: add zachleat.com-inspired theme enhancements

- Add time-difference web component for relative dates
- Add @zachleat/table-saw for responsive tables
- Add webmention facepile styling with bookmarks support
- Add OG image thumbnails to post navigation
- Add @11ty/is-land for lazy widget hydration
- Wrap sidebar widgets in is-land for deferred loading
- Lazy-load webmention avatars with is-land
- Add @zachleat/filter-container for blog archive filtering
- Add posting frequency sparkline to blog header
- Inline critical CSS and defer full stylesheet loading
This commit is contained in:
Ricardo
2026-02-18 11:16:33 +01:00
parent e5b0fd7dc6
commit c3eb04570c
16 changed files with 368 additions and 65 deletions

View File

@@ -244,6 +244,13 @@ export default function (eleventyConfig) {
eleventyConfig.addPassthroughCopy("favicon.ico");
eleventyConfig.addPassthroughCopy({ ".cache/og": "og" });
// Copy vendor web components from node_modules
eleventyConfig.addPassthroughCopy({
"node_modules/@zachleat/table-saw/table-saw.js": "js/table-saw.js",
"node_modules/@11ty/is-land/is-land.js": "js/is-land.js",
"node_modules/@zachleat/filter-container/filter-container.js": "js/filter-container.js",
});
// Watch for content changes
eleventyConfig.addWatchTarget("./content/");
eleventyConfig.addWatchTarget("./css/");
@@ -352,6 +359,16 @@ export default function (eleventyConfig) {
return existsSync(ogPath);
});
// Inline file contents (for critical CSS inlining)
eleventyConfig.addFilter("inlineFile", (filePath) => {
try {
const fullPath = resolve(__dirname, filePath.startsWith("/") ? `.${filePath}` : filePath);
return readFileSync(fullPath, "utf-8");
} catch {
return "";
}
});
// Current timestamp filter (for client-side JS buildtime)
eleventyConfig.addFilter("timestamp", () => Date.now());
@@ -426,6 +443,22 @@ export default function (eleventyConfig) {
: null;
});
// Posting frequency — compute posts-per-month for last 12 months (for sparkline)
eleventyConfig.addFilter("postingFrequency", (posts) => {
if (!Array.isArray(posts) || posts.length === 0) return "";
const now = new Date();
const counts = new Array(12).fill(0);
for (const post of posts) {
const postDate = new Date(post.date || post.data?.date);
if (isNaN(postDate.getTime())) continue;
const monthsAgo = (now.getFullYear() - postDate.getFullYear()) * 12 + (now.getMonth() - postDate.getMonth());
if (monthsAgo >= 0 && monthsAgo < 12) {
counts[11 - monthsAgo]++;
}
}
return counts.join(",");
});
// Collections for different post types
// Note: content path is content/ due to symlink structure
// "posts" shows ALL content types combined