feat: implement dynamic Eleventy tiling blog, modular CSS, and agent customizations

- Add project foundation README.md and root AGENTS.md for pair programming
- Configure .agents/ rules and skills for Eleventy tiling and Obsidian publishing
- Decompose monolithic styles into modular tokens, reset, layout, and component CSS
- Implement Nunjucks layout inheritance (base.njk, post.njk, page.njk) and reusable partials
- Configure Eleventy 3 with markdown-it-anchor, syntax highlighting, and dynamic collections
- Migrate sample prototypes into dynamic dispatches lander, gateway, and Markdown posts
- Add multi-stage Dockerfile and clean-URL nginx.conf for production serving
This commit is contained in:
venus
2026-09-13 00:49:27 -05:00
parent c0f67e75a3
commit d8fb8a3699
37 changed files with 4361 additions and 7 deletions
+123
View File
@@ -0,0 +1,123 @@
import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
import markdownIt from "markdown-it";
import markdownItAnchor from "markdown-it-anchor";
import { DateTime } from "luxon";
export default function (eleventyConfig) {
// 1. Static Asset Passthrough Copy
eleventyConfig.addPassthroughCopy({ "src/assets": "assets" });
// 2. Syntax Highlighting Plugin
eleventyConfig.addPlugin(syntaxHighlight);
// 3. Markdown-It Library Customization
const markdownOptions = {
html: true,
breaks: false,
linkify: true
};
const markdownLib = markdownIt(markdownOptions).use(markdownItAnchor, {
permalink: markdownItAnchor.permalink.headerLink({
safariReaderFix: true
}),
slugify: (s) =>
s
.trim()
.toLowerCase()
.replace(/[^a-z0-9 -]/g, "")
.replace(/\s+/g, "-")
.replace(/-+/g, "-")
});
eleventyConfig.setLibrary("md", markdownLib);
// 4. Custom Filters
// Format readable dates: SEP 12, 2026
eleventyConfig.addFilter("postDate", (dateObj) => {
if (!dateObj) return "";
return DateTime.fromJSDate(new Date(dateObj), { zone: "utc" })
.toFormat("LLL dd, yyyy")
.toUpperCase();
});
// ISO date format for <time datetime="...">
eleventyConfig.addFilter("isoDate", (dateObj) => {
if (!dateObj) return "";
return DateTime.fromJSDate(new Date(dateObj), { zone: "utc" }).toISODate();
});
// Limit array items
eleventyConfig.addFilter("limit", (arr, limit) => {
if (!Array.isArray(arr)) return [];
return arr.slice(0, limit);
});
// Filter out internal system tags
eleventyConfig.addFilter("filterTagList", (tags) => {
const internalTags = ["all", "nav", "post", "posts"];
return (tags || []).filter((tag) => !internalTags.includes(tag.toLowerCase()));
});
// Reading time estimator (defaults to 200 words/min if frontmatter lacks readingTime)
eleventyConfig.addFilter("readingTime", (text, frontmatterTime) => {
if (frontmatterTime) return frontmatterTime;
if (!text) return "1 MIN READ";
const wordCount = text.trim().split(/\s+/).length;
const minutes = Math.ceil(wordCount / 200);
return `${minutes} MIN READ`;
});
// Extract H2 and H3 headings with IDs for dynamic Table of Contents
eleventyConfig.addFilter("extractHeadings", (htmlContent) => {
if (!htmlContent) return [];
const headingRegex = /<h([23])\s+id="([^"]+)"[^>]*>(.*?)<\/h\1>/gis;
const headings = [];
let match;
while ((match = headingRegex.exec(htmlContent)) !== null) {
headings.push({
level: parseInt(match[1], 10),
id: match[2],
title: match[3].replace(/<[^>]+>/g, "").trim()
});
}
return headings;
});
// 5. Custom Collections
// Chronological blog posts collection
eleventyConfig.addCollection("posts", (collectionApi) => {
return collectionApi
.getFilteredByGlob("src/posts/*.md")
.filter((post) => !post.data.draft)
.sort((a, b) => b.date - a.date);
});
// Dynamic Tag List Collection across all posts
eleventyConfig.addCollection("tagList", (collectionApi) => {
const tagSet = new Set();
collectionApi.getFilteredByGlob("src/posts/*.md").forEach((item) => {
if ("tags" in item.data) {
let tags = item.data.tags;
if (typeof tags === "string") tags = [tags];
for (const tag of tags) {
const cleanTag = tag.replace(/^#/, "");
tagSet.add(cleanTag);
}
}
});
return [...tagSet].sort();
});
return {
dir: {
input: "src",
includes: "_includes",
data: "_data",
output: "_site"
},
templateFormats: ["njk", "md", "html"],
htmlTemplateEngine: "njk",
markdownTemplateEngine: "njk"
};
}