feat: add heading anchors for direct linking to article sections

Uses markdown-it-anchor to generate linkable IDs on h2-h4 headings.
Headings become clickable links with a subtle # indicator on hover.
Includes scroll-margin-top so anchored headings don't hide behind
the sticky header.
This commit is contained in:
Ricardo
2026-02-17 19:09:09 +01:00
parent ed24ac47bd
commit 4e2f579e71
4 changed files with 68 additions and 0 deletions
+25
View File
@@ -468,6 +468,31 @@
scroll-margin-top: 80px; /* Prevent header overlap when scrolling to anchors */
}
/* Heading anchors — generated by markdown-it-anchor */
.prose h2[id],
.prose h3[id],
.prose h4[id] {
scroll-margin-top: 80px;
}
.prose :is(h2, h3, h4) > a.header-anchor {
color: inherit;
text-decoration: none;
}
.prose :is(h2, h3, h4) > a.header-anchor:hover {
text-decoration: underline;
text-decoration-color: var(--tw-prose-links, currentColor);
text-underline-offset: 4px;
}
.prose :is(h2, h3, h4) > a.header-anchor::after {
content: " #";
opacity: 0;
font-weight: normal;
transition: opacity 0.15s;
}
.prose :is(h2, h3, h4):hover > a.header-anchor::after {
opacity: 0.4;
}
.post-list li {
content-visibility: auto;
contain-intrinsic-size: auto 200px;
+6
View File
@@ -4,6 +4,7 @@ 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 markdownItAnchor from "markdown-it-anchor";
import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
import { minify } from "html-minifier-terser";
import { createHash } from "crypto";
@@ -48,6 +49,11 @@ export default function (eleventyConfig) {
linkify: true, // Auto-convert URLs to clickable links
typographer: true,
});
md.use(markdownItAnchor, {
permalink: markdownItAnchor.permalink.headerLink(),
slugify: (s) => s.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, ""),
level: [2, 3, 4],
});
eleventyConfig.setLibrary("md", md);
// Syntax highlighting for fenced code blocks (```lang)
+36
View File
@@ -19,6 +19,7 @@
"eleventy-plugin-embed-everything": "^1.21.0",
"html-minifier-terser": "^7.0.0",
"markdown-it": "^14.0.0",
"markdown-it-anchor": "^9.2.0",
"pagefind": "^1.3.0",
"rss-parser": "^3.13.0"
},
@@ -1053,6 +1054,31 @@
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
}
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT",
"peer": true
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT",
"peer": true
},
"node_modules/@types/node": {
"version": "14.18.63",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
@@ -2812,6 +2838,16 @@
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it-anchor": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-9.2.0.tgz",
"integrity": "sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==",
"license": "Unlicense",
"peerDependencies": {
"@types/markdown-it": "*",
"markdown-it": "*"
}
},
"node_modules/markdown-it/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+1
View File
@@ -20,6 +20,7 @@
"eleventy-plugin-embed-everything": "^1.21.0",
"html-minifier-terser": "^7.0.0",
"markdown-it": "^14.0.0",
"markdown-it-anchor": "^9.2.0",
"pagefind": "^1.3.0",
"rss-parser": "^3.13.0"
},