feat: add syntax highlighting for code blocks

Integrates @11ty/eleventy-plugin-syntaxhighlight (PrismJS) for
build-time syntax highlighting of fenced code blocks. Includes
a custom GitHub-inspired theme with dark mode support (.dark class).

All existing articles with triple-backtick code blocks will
automatically get highlighting on next Eleventy rebuild.

Also fixes overflow-x: hidden on .prose/.e-content that was
clipping scrollable code blocks — changed to overflow-x: clip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ricardo
2026-02-11 10:21:31 +01:00
parent 2b3e51042c
commit ddbc983505
6 changed files with 343 additions and 12 deletions

View File

@@ -54,6 +54,7 @@
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="stylesheet" href="/css/style.css?v={{ '/css/style.css' | hash }}">
<link rel="stylesheet" href="/css/prism-theme.css?v={{ '/css/prism-theme.css' | hash }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lite-youtube-embed@0.3.2/src/lite-yt-embed.min.css">
<script src="https://cdn.jsdelivr.net/npm/lite-youtube-embed@0.3.2/src/lite-yt-embed.min.js" defer></script>
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>

201
css/prism-theme.css Normal file
View File

@@ -0,0 +1,201 @@
/* Syntax Highlighting — PrismJS theme for indiekit-eleventy-theme
Light mode: clean, high-contrast colors
Dark mode: scoped under .dark (Tailwind darkMode: "class") */
/* ── Base code block styling ── */
code[class*="language-"],
pre[class*="language-"] {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.875em;
line-height: 1.7;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
tab-size: 2;
hyphens: none;
}
pre[class*="language-"] {
padding: 1.25em;
margin: 1.5em 0;
overflow: auto;
border-radius: 0.5rem;
}
:not(pre) > code[class*="language-"] {
padding: 0.2em 0.4em;
border-radius: 0.25rem;
white-space: normal;
}
/* ── Light Mode ── */
code[class*="language-"],
pre[class*="language-"] {
color: #24292e;
}
pre[class*="language-"] {
background: #f6f8fa;
border: 1px solid #e1e4e8;
}
:not(pre) > code[class*="language-"] {
background: #f6f8fa;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #6a737d;
}
.token.punctuation {
color: #24292e;
}
.token.namespace {
opacity: 0.7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #005cc5;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #032f62;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #d73a49;
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #d73a49;
}
.token.function,
.token.class-name {
color: #6f42c1;
}
.token.regex,
.token.important,
.token.variable {
color: #e36209;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
/* Line highlighting */
.highlight-line-active {
background-color: #fffbdd;
display: inline-block;
width: calc(100% + 2.5em);
margin-left: -1.25em;
padding-left: 1.25em;
}
/* ── Dark Mode ── */
.dark code[class*="language-"],
.dark pre[class*="language-"] {
color: #e1e4e8;
}
.dark pre[class*="language-"] {
background: #161b22;
border-color: #30363d;
}
.dark :not(pre) > code[class*="language-"] {
background: #161b22;
}
.dark .token.comment,
.dark .token.prolog,
.dark .token.doctype,
.dark .token.cdata {
color: #8b949e;
}
.dark .token.punctuation {
color: #e1e4e8;
}
.dark .token.property,
.dark .token.tag,
.dark .token.boolean,
.dark .token.number,
.dark .token.constant,
.dark .token.symbol,
.dark .token.deleted {
color: #79c0ff;
}
.dark .token.selector,
.dark .token.attr-name,
.dark .token.string,
.dark .token.char,
.dark .token.builtin,
.dark .token.inserted {
color: #a5d6ff;
}
.dark .token.operator,
.dark .token.entity,
.dark .token.url,
.dark .language-css .token.string,
.dark .style .token.string {
color: #ff7b72;
}
.dark .token.atrule,
.dark .token.attr-value,
.dark .token.keyword {
color: #ff7b72;
}
.dark .token.function,
.dark .token.class-name {
color: #d2a8ff;
}
.dark .token.regex,
.dark .token.important,
.dark .token.variable {
color: #ffa657;
}
.dark .highlight-line-active {
background-color: rgba(56, 139, 253, 0.15);
}

View File

@@ -441,6 +441,11 @@
@apply break-words;
}
pre code {
word-break: normal;
overflow-wrap: normal;
}
/* Links in content - break long URLs */
.e-content a,
.prose a {
@@ -448,10 +453,10 @@
word-break: break-word;
}
/* Content containers - prevent horizontal overflow */
/* Content containers - clip horizontal overflow but allow pre blocks to scroll */
.e-content,
.prose {
@apply overflow-x-hidden;
overflow-x: clip;
max-width: 100%;
}

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 syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
import { minify } from "html-minifier-terser";
import { createHash } from "crypto";
import { execFileSync } from "child_process";
@@ -49,6 +50,9 @@ export default function (eleventyConfig) {
});
eleventyConfig.setLibrary("md", md);
// Syntax highlighting for fenced code blocks (```lang)
eleventyConfig.addPlugin(syntaxHighlight);
// RSS plugin for feed filters (dateToRfc822, absoluteUrl, etc.)
// Custom feed templates in feed.njk and feed-json.njk use these filters
eleventyConfig.addPlugin(pluginRss);

123
package-lock.json generated
View File

@@ -1,23 +1,25 @@
{
"name": "rmendes-eleventy-site",
"name": "indiekit-eleventy-theme",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rmendes-eleventy-site",
"name": "indiekit-eleventy-theme",
"version": "1.0.0",
"dependencies": {
"@11ty/eleventy": "^3.0.0",
"@11ty/eleventy-fetch": "^4.0.1",
"@11ty/eleventy-img": "^6.0.0",
"@11ty/eleventy-plugin-rss": "^2.0.2",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2",
"@atproto/api": "^0.12.0",
"@chrisburnell/eleventy-cache-webmentions": "^2.2.7",
"@quasibit/eleventy-plugin-sitemap": "^2.2.0",
"eleventy-plugin-embed-everything": "^1.21.0",
"html-minifier-terser": "^7.0.0",
"markdown-it": "^14.0.0",
"pagefind": "^1.3.0",
"rss-parser": "^3.13.0"
},
"devDependencies": {
@@ -226,6 +228,19 @@
"url": "https://opencollective.com/11ty"
}
},
"node_modules/@11ty/eleventy-plugin-syntaxhighlight": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-syntaxhighlight/-/eleventy-plugin-syntaxhighlight-5.0.2.tgz",
"integrity": "sha512-T6xVVRDJuHlrFMHbUiZkHjj5o1IlLzZW+1IL9eUsyXFU7rY2ztcYhZew/64vmceFFpQwzuSfxQOXxTJYmKkQ+A==",
"license": "MIT",
"dependencies": {
"prismjs": "^1.30.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/11ty"
}
},
"node_modules/@11ty/eleventy-utils": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-2.0.7.tgz",
@@ -894,6 +909,84 @@
"node": ">= 8"
}
},
"node_modules/@pagefind/darwin-arm64": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz",
"integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@pagefind/darwin-x64": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz",
"integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@pagefind/freebsd-x64": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz",
"integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@pagefind/linux-arm64": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz",
"integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@pagefind/linux-x64": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz",
"integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@pagefind/windows-x64": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz",
"integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@quasibit/eleventy-plugin-sitemap": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@quasibit/eleventy-plugin-sitemap/-/eleventy-plugin-sitemap-2.2.0.tgz",
@@ -3079,6 +3172,23 @@
"node": ">=8"
}
},
"node_modules/pagefind": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz",
"integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==",
"license": "MIT",
"bin": {
"pagefind": "lib/runner/bin.cjs"
},
"optionalDependencies": {
"@pagefind/darwin-arm64": "1.4.0",
"@pagefind/darwin-x64": "1.4.0",
"@pagefind/freebsd-x64": "1.4.0",
"@pagefind/linux-arm64": "1.4.0",
"@pagefind/linux-x64": "1.4.0",
"@pagefind/windows-x64": "1.4.0"
}
},
"node_modules/param-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
@@ -3478,6 +3588,15 @@
"node": ">= 0.8"
}
},
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",

View File

@@ -10,23 +10,24 @@
},
"dependencies": {
"@11ty/eleventy": "^3.0.0",
"html-minifier-terser": "^7.0.0",
"markdown-it": "^14.0.0",
"@11ty/eleventy-fetch": "^4.0.1",
"@11ty/eleventy-img": "^6.0.0",
"@11ty/eleventy-plugin-rss": "^2.0.2",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2",
"@atproto/api": "^0.12.0",
"@chrisburnell/eleventy-cache-webmentions": "^2.2.7",
"@quasibit/eleventy-plugin-sitemap": "^2.2.0",
"@atproto/api": "^0.12.0",
"eleventy-plugin-embed-everything": "^1.21.0",
"rss-parser": "^3.13.0",
"pagefind": "^1.3.0"
"html-minifier-terser": "^7.0.0",
"markdown-it": "^14.0.0",
"pagefind": "^1.3.0",
"rss-parser": "^3.13.0"
},
"devDependencies": {
"tailwindcss": "^3.4.0",
"@tailwindcss/typography": "^0.5.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.0",
"postcss-cli": "^11.0.0",
"autoprefixer": "^10.4.0",
"@tailwindcss/typography": "^0.5.0"
"tailwindcss": "^3.4.0"
}
}