2026-03-28 04:55:20 +00:00
|
|
|
const Image = require("@11ty/eleventy-img");
|
|
|
|
|
const path = require("path");
|
2026-06-28 01:20:01 +00:00
|
|
|
const markdownItKatex = require("@vscode/markdown-it-katex").default;
|
2026-03-28 04:55:20 +00:00
|
|
|
|
|
|
|
|
function imageShortcode(src, alt, sizes = "100vw") {
|
|
|
|
|
const inputPath = path.join("img", src);
|
|
|
|
|
const options = {
|
|
|
|
|
widths: [480, 800, 1200],
|
|
|
|
|
formats: ["avif", "webp", "jpeg"],
|
|
|
|
|
outputDir: "_site/img",
|
|
|
|
|
urlPath: "/img/",
|
|
|
|
|
filenameFormat: function (id, src, width, format) {
|
|
|
|
|
const name = path.basename(src, path.extname(src));
|
|
|
|
|
return `${name}-${width}.${format}`;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Image(inputPath, options);
|
|
|
|
|
const metadata = Image.statsSync(inputPath, options);
|
|
|
|
|
|
|
|
|
|
return Image.generateHTML(metadata, {
|
|
|
|
|
alt,
|
|
|
|
|
sizes,
|
|
|
|
|
loading: "lazy",
|
|
|
|
|
decoding: "async",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function heroImageShortcode(src, alt) {
|
|
|
|
|
const inputPath = path.join("img", src);
|
|
|
|
|
const options = {
|
|
|
|
|
widths: [800, 1200, 1600],
|
|
|
|
|
formats: ["avif", "webp", "jpeg"],
|
|
|
|
|
outputDir: "_site/img",
|
|
|
|
|
urlPath: "/img/",
|
|
|
|
|
filenameFormat: function (id, src, width, format) {
|
|
|
|
|
const name = path.basename(src, path.extname(src));
|
|
|
|
|
return `${name}-${width}.${format}`;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Image(inputPath, options);
|
|
|
|
|
const metadata = Image.statsSync(inputPath, options);
|
|
|
|
|
|
|
|
|
|
return Image.generateHTML(metadata, {
|
|
|
|
|
alt,
|
|
|
|
|
sizes: "(max-width: 960px) 100vw, 960px",
|
|
|
|
|
loading: "eager",
|
|
|
|
|
decoding: "async",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 04:10:09 +00:00
|
|
|
module.exports = function (eleventyConfig) {
|
|
|
|
|
eleventyConfig.addPassthroughCopy("css");
|
2026-03-28 04:55:20 +00:00
|
|
|
eleventyConfig.addPassthroughCopy("img");
|
2026-03-28 05:07:32 +00:00
|
|
|
eleventyConfig.addPassthroughCopy("favicon.svg");
|
2026-03-28 04:55:20 +00:00
|
|
|
|
2026-07-19 00:12:25 +00:00
|
|
|
// A snapshot copy of ~/.claude/CLAUDE.md, published verbatim at /CLAUDE.md
|
|
|
|
|
// (linked from the footer). Keep it out of template processing so it ships
|
|
|
|
|
// as raw markdown instead of being rendered to HTML.
|
|
|
|
|
eleventyConfig.addPassthroughCopy({ "assets/CLAUDE.md": "CLAUDE.md" });
|
|
|
|
|
eleventyConfig.ignores.add("assets/CLAUDE.md");
|
|
|
|
|
|
2026-06-28 01:20:01 +00:00
|
|
|
// KaTeX: render $$...$$ at build time into static markup. Ship katex.min.css
|
|
|
|
|
// plus its fonts/ as a sibling so the stylesheet's url(fonts/...) resolves.
|
|
|
|
|
eleventyConfig.addPassthroughCopy({
|
|
|
|
|
"node_modules/katex/dist/katex.min.css": "css/katex.min.css",
|
|
|
|
|
"node_modules/katex/dist/fonts": "css/fonts",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
eleventyConfig.amendLibrary("md", (md) => {
|
|
|
|
|
md.use(markdownItKatex, { throwOnError: false, errorColor: "#cc0000" });
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-28 04:55:20 +00:00
|
|
|
eleventyConfig.addShortcode("image", imageShortcode);
|
|
|
|
|
eleventyConfig.addShortcode("heroImage", heroImageShortcode);
|
2026-03-28 04:10:09 +00:00
|
|
|
|
2026-08-02 22:45:19 +00:00
|
|
|
// A "column" is anything tagged `column`, wherever it lives on disk.
|
|
|
|
|
// Newest first.
|
|
|
|
|
//
|
|
|
|
|
// Drafts carry the `column` tag too — their front matter is complete so that
|
|
|
|
|
// promoting one is a plain `git mv` — so they must be filtered out here by
|
|
|
|
|
// the `draft` flag that drafts/drafts.11tydata.js sets. This collection
|
|
|
|
|
// feeds the homepage and the sidebar archive; a draft in it would be listed
|
|
|
|
|
// sitewide.
|
2026-03-28 04:10:09 +00:00
|
|
|
eleventyConfig.addCollection("columns", function (collectionApi) {
|
|
|
|
|
return collectionApi
|
2026-06-29 03:05:41 +00:00
|
|
|
.getFilteredByTag("column")
|
2026-08-02 22:45:19 +00:00
|
|
|
.filter((item) => !item.data.draft)
|
|
|
|
|
.sort((a, b) => b.date - a.date);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Drafts, newest first, for the /drafts/ index. The collection is populated
|
|
|
|
|
// in both run modes, but the only page that reads it lives under drafts/ and
|
|
|
|
|
// so is itself unwritten during `yarn build`.
|
|
|
|
|
eleventyConfig.addCollection("drafts", function (collectionApi) {
|
|
|
|
|
return collectionApi
|
|
|
|
|
.getAll()
|
|
|
|
|
.filter((item) => item.data.draft)
|
2026-03-28 04:10:09 +00:00
|
|
|
.sort((a, b) => b.date - a.date);
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-29 03:05:41 +00:00
|
|
|
// The subject tags actually in use across columns — everything except the
|
|
|
|
|
// `column` type marker and Eleventy's reserved `all`. Sorted for stable order.
|
|
|
|
|
eleventyConfig.addCollection("subjects", function (collectionApi) {
|
|
|
|
|
const reserved = new Set(["all", "column"]);
|
|
|
|
|
const subjects = new Set();
|
|
|
|
|
for (const item of collectionApi.getFilteredByTag("column")) {
|
2026-08-02 22:45:19 +00:00
|
|
|
if (item.data.draft) continue;
|
2026-06-29 03:05:41 +00:00
|
|
|
for (const tag of item.data.tags || []) {
|
|
|
|
|
if (!reserved.has(tag)) subjects.add(tag);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return [...subjects].sort();
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-09 02:12:21 +00:00
|
|
|
eleventyConfig.addFilter("inlineMarkdown", function (text) {
|
|
|
|
|
return text.replace(/~~(.+?)~~/g, "<del>$1</del>");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
eleventyConfig.addFilter("stripMarkdown", function (text) {
|
|
|
|
|
return text.replace(/~~(.+?)~~/g, "$1");
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-28 04:10:09 +00:00
|
|
|
eleventyConfig.addFilter("year", function (date) {
|
|
|
|
|
return new Date(date).getFullYear();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
eleventyConfig.addFilter("isoDate", function (date) {
|
|
|
|
|
return new Date(date).toISOString().split("T")[0];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
eleventyConfig.addFilter("readableDate", function (date) {
|
|
|
|
|
return new Date(date).toLocaleDateString("en-US", {
|
|
|
|
|
year: "numeric",
|
|
|
|
|
month: "long",
|
|
|
|
|
day: "numeric",
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
dir: {
|
|
|
|
|
input: ".",
|
|
|
|
|
includes: "_includes",
|
|
|
|
|
output: "_site",
|
|
|
|
|
},
|
|
|
|
|
markdownTemplateEngine: "njk",
|
|
|
|
|
htmlTemplateEngine: "njk",
|
|
|
|
|
};
|
|
|
|
|
};
|