diff --git a/indiekit.config.mjs b/indiekit.config.mjs
new file mode 100644
index 0000000..d4477c5
--- /dev/null
+++ b/indiekit.config.mjs
@@ -0,0 +1,75 @@
+export default {
+ url: "https://blog.giersig.eu",
+ // Debug-Level erhöhen
+ debug: "indiekit:*",
+ application: {
+ name: "Indiekit",
+ admin: {
+ username: "admin@blog.giersig.eu",
+ password: "accus3D!23"
+ }
+ },
+ "@indiekit/endpoint-auth": {
+ publicUrl: "https://blog.giersig.eu"
+ },
+ publication: {
+ me: "https://blog.giersig.eu",
+ postTypes: [
+ {
+ type: "article",
+ name: "Artikel",
+ post: {
+ path: "src/posts/{slug}.md",
+ url: "https://blog.giersig.eu/posts/{slug}/",
+ },
+ },
+ {
+ type: "note",
+ name: "Notiz",
+ post: {
+ path: "src/notes/{slug}.md",
+ url: "https://blog.giersig.eu/notes/{slug}/",
+ },
+ },
+ {
+ type: "bookmark",
+ name: "Lesezeichen",
+ post: {
+ path: "src/bookmarks/{slug}.md",
+ url: "https://blog.giersig.eu/bookmarks/{slug}/",
+ },
+ },
+ ],
+ },
+
+ secret: process.env.SECRET,
+ mongodbUrl: `mongodb://indiekit:${process.env.MONGO_PASSWORD}@10.100.0.20:27017/indiekit`,
+ plugins: [
+ "@indiekit/store-github",
+ "@rmdes/indiekit-endpoint-posts",
+ "@rmdes/indiekit-endpoint-auth",
+ "@rmdes/indiekit-endpoint-share",
+ "@rmdes/indiekit-endpoint-github",
+ "@rmdes/indiekit-endpoint-webmention-io",
+ "@rmdes/indiekit-endpoint-conversations",
+ // "@rmdes/indiekit-endpoint-activitypub",
+ ],
+ "@indiekit/store-github": {
+ user: "svemagie",
+ repo: "blog",
+ branch: "main",
+ },
+ "@rmdes/indiekit-endpoint-github": {
+ token: process.env.GITHUB_TOKEN,
+ user: "svemagie",
+ },
+ "@rmdes/indiekit-endpoint-webmention-io": {
+ token: process.env.WEBMENTION_IO_TOKEN,
+ },
+ "@rmdes/indiekit-endpoint-conversations": {
+ enabled: true,
+ },
+ "@rmdes/indiekit-endpoint-activitypub": {
+ username: "blog.giersig.eu",
+ },
+};
diff --git a/theme/_data/blogrollStatus.js b/theme/_data/blogrollStatus.js
new file mode 100644
index 0000000..aee1601
--- /dev/null
+++ b/theme/_data/blogrollStatus.js
@@ -0,0 +1,34 @@
+/**
+ * Blogroll Status Data
+ * Checks if the blogroll API backend is available at build time.
+ * Used for conditional navigation — the blogroll page itself loads data client-side.
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+export default async function () {
+ try {
+ const url = `${INDIEKIT_URL}/blogrollapi/api/status`;
+ console.log(`[blogrollStatus] Checking API: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log("[blogrollStatus] API available");
+ return {
+ available: true,
+ source: "indiekit",
+ ...data,
+ };
+ } catch (error) {
+ console.log(
+ `[blogrollStatus] API unavailable: ${error.message}`
+ );
+ return {
+ available: false,
+ source: "unavailable",
+ };
+ }
+}
diff --git a/theme/_data/blueskyFeed.js b/theme/_data/blueskyFeed.js
new file mode 100644
index 0000000..b0083bc
--- /dev/null
+++ b/theme/_data/blueskyFeed.js
@@ -0,0 +1,68 @@
+/**
+ * Bluesky Feed Data
+ * Fetches recent posts from Bluesky using the AT Protocol API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+import { BskyAgent } from "@atproto/api";
+
+export default async function () {
+ const handle = process.env.BLUESKY_HANDLE || "";
+
+ try {
+ // Create agent and resolve handle to DID
+ const agent = new BskyAgent({ service: "https://bsky.social" });
+
+ // Get the author's feed using public API (no auth needed for public posts)
+ const feedUrl = `https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${handle}&limit=10`;
+
+ const response = await EleventyFetch(feedUrl, {
+ duration: "15m", // Cache for 15 minutes
+ type: "json",
+ fetchOptions: {
+ headers: {
+ Accept: "application/json",
+ },
+ },
+ });
+
+ if (!response.feed) {
+ console.log("No Bluesky feed found for handle:", handle);
+ return [];
+ }
+
+ // Transform the feed into a simpler format
+ return response.feed.map((item) => {
+ // Extract rkey from AT URI (at://did:plc:xxx/app.bsky.feed.post/rkey)
+ const rkey = item.post.uri.split("/").pop();
+ const postUrl = `https://bsky.app/profile/${item.post.author.handle}/post/${rkey}`;
+
+ return {
+ text: item.post.record.text,
+ createdAt: item.post.record.createdAt,
+ uri: item.post.uri,
+ url: postUrl,
+ cid: item.post.cid,
+ author: {
+ handle: item.post.author.handle,
+ displayName: item.post.author.displayName,
+ avatar: item.post.author.avatar,
+ },
+ likeCount: item.post.likeCount || 0,
+ repostCount: item.post.repostCount || 0,
+ replyCount: item.post.replyCount || 0,
+ // Extract any embedded links or images
+ embed: item.post.embed
+ ? {
+ type: item.post.embed.$type,
+ images: item.post.embed.images || [],
+ external: item.post.embed.external || null,
+ }
+ : null,
+ };
+ });
+ } catch (error) {
+ console.error("Error fetching Bluesky feed:", error.message);
+ return [];
+ }
+}
diff --git a/theme/_data/conversationMentions.js b/theme/_data/conversationMentions.js
new file mode 100644
index 0000000..1652f37
--- /dev/null
+++ b/theme/_data/conversationMentions.js
@@ -0,0 +1,14 @@
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+export default async function () {
+ try {
+ const data = await EleventyFetch(
+ "http://127.0.0.1:8080/conversations/api/mentions?per-page=10000",
+ { duration: "15m", type: "json" }
+ );
+ return data.children || [];
+ } catch (e) {
+ console.log(`[conversationMentions] API unavailable: ${e.message}`);
+ return [];
+ }
+}
diff --git a/theme/_data/cv.js b/theme/_data/cv.js
new file mode 100644
index 0000000..a64d08f
--- /dev/null
+++ b/theme/_data/cv.js
@@ -0,0 +1,37 @@
+/**
+ * CV Data — reads from indiekit-endpoint-cv plugin data file.
+ *
+ * The CV plugin writes content/.indiekit/cv.json on every save
+ * and on startup. Eleventy reads that file here.
+ *
+ * Falls back to empty defaults if no plugin is installed.
+ */
+
+import { readFileSync } from "node:fs";
+import { resolve, dirname } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+export default function () {
+ try {
+ const cvPath = resolve(__dirname, "..", "content", ".indiekit", "cv.json");
+ const raw = readFileSync(cvPath, "utf8");
+ const data = JSON.parse(raw);
+ console.log("[cv] Loaded CV data from plugin");
+ return data;
+ } catch {
+ // No CV plugin data file — return empty defaults
+ return {
+ lastUpdated: null,
+ experience: [],
+ projects: [],
+ skills: {},
+ skillTypes: {},
+ languages: [],
+ education: [],
+ interests: {},
+ interestTypes: {},
+ };
+ }
+}
diff --git a/theme/_data/cvPageConfig.js b/theme/_data/cvPageConfig.js
new file mode 100644
index 0000000..456b388
--- /dev/null
+++ b/theme/_data/cvPageConfig.js
@@ -0,0 +1,29 @@
+/**
+ * CV Page Configuration Data
+ * Reads config from indiekit-endpoint-cv plugin CV page builder.
+ * Falls back to null — cv.njk then uses the default hardcoded layout.
+ *
+ * The CV plugin writes a .indiekit/cv-page.json file that Eleventy watches.
+ * On change, a rebuild picks up the new config, allowing layout changes
+ * without a Docker rebuild.
+ */
+
+import { readFileSync } from "node:fs";
+import { resolve, dirname } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+export default function () {
+ try {
+ // Resolve via the content/ symlink relative to the Eleventy project
+ const configPath = resolve(__dirname, "..", "content", ".indiekit", "cv-page.json");
+ const raw = readFileSync(configPath, "utf8");
+ const config = JSON.parse(raw);
+ console.log("[cvPageConfig] Loaded CV page builder config");
+ return config;
+ } catch {
+ // No CV page builder config — fall back to hardcoded layout in cv.njk
+ return null;
+ }
+}
diff --git a/theme/_data/eleventyComputed.js b/theme/_data/eleventyComputed.js
new file mode 100644
index 0000000..fa56636
--- /dev/null
+++ b/theme/_data/eleventyComputed.js
@@ -0,0 +1,52 @@
+/**
+ * Computed data resolved during the data cascade.
+ *
+ * Eleventy 3.x parallel rendering causes `page.url`, `page.fileSlug`,
+ * and `page.inputPath` to return values from OTHER pages being processed
+ * concurrently. This affects both templates and eleventyComputed functions.
+ *
+ * IMPORTANT: Only `permalink` is computed here, because it reads from the
+ * file's own frontmatter data (per-file, immune to race conditions).
+ * OG image lookups are done in templates using the `permalink` data value
+ * and Nunjucks filters (see base.njk).
+ *
+ * NEVER use `page.url`, `page.fileSlug`, or `page.inputPath` here.
+ *
+ * See: https://github.com/11ty/eleventy/issues/3183
+ */
+
+export default {
+ eleventyComputed: {
+ // Compute permalink from file path for posts without explicit frontmatter permalink.
+ // Pattern: content/{type}/{yyyy}-{MM}-{dd}-{slug}.md → /{type}/{yyyy}/{MM}/{dd}/{slug}/
+ permalink: (data) => {
+ // Convert stale /content/ permalinks from pre-beta.37 posts to canonical format
+ if (data.permalink && typeof data.permalink === "string") {
+ const contentMatch = data.permalink.match(
+ /^\/content\/([^/]+)\/(\d{4})-(\d{2})-(\d{2})-(.+?)\/?$/
+ );
+ if (contentMatch) {
+ const [, type, year, month, day, slug] = contentMatch;
+ return `/${type}/${year}/${month}/${day}/${slug}/`;
+ }
+ // Valid non-/content/ permalink — use as-is
+ return data.permalink;
+ }
+
+ // No frontmatter permalink — compute from file path
+ // NOTE: data.page.inputPath may be wrong due to parallel rendering,
+ // but posts without frontmatter permalink are rare (only pre-beta.37 edge cases)
+ const inputPath = data.page?.inputPath || "";
+ const match = inputPath.match(
+ /content\/([^/]+)\/(\d{4})-(\d{2})-(\d{2})-(.+)\.md$/
+ );
+ if (match) {
+ const [, type, year, month, day, slug] = match;
+ return `/${type}/${year}/${month}/${day}/${slug}/`;
+ }
+
+ // For non-matching files (pages, root files), let Eleventy decide
+ return data.permalink;
+ },
+ },
+};
diff --git a/theme/_data/enabledPostTypes.js b/theme/_data/enabledPostTypes.js
new file mode 100644
index 0000000..989bc7a
--- /dev/null
+++ b/theme/_data/enabledPostTypes.js
@@ -0,0 +1,50 @@
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+const CONTENT_DIR = process.env.CONTENT_DIR || "/data/content";
+
+// Standard post types for any Indiekit deployment
+const ALL_POST_TYPES = [
+ { type: "article", label: "Articles", path: "/articles/", createUrl: "/posts/create?type=article" },
+ { type: "note", label: "Notes", path: "/notes/", createUrl: "/posts/create?type=note" },
+ { type: "photo", label: "Photos", path: "/photos/", createUrl: "/posts/create?type=photo" },
+ { type: "bookmark", label: "Bookmarks", path: "/bookmarks/", createUrl: "/posts/create?type=bookmark" },
+ { type: "like", label: "Likes", path: "/likes/", createUrl: "/posts/create?type=like" },
+ { type: "reply", label: "Replies", path: "/replies/", createUrl: "/posts/create?type=reply" },
+ { type: "repost", label: "Reposts", path: "/reposts/", createUrl: "/posts/create?type=repost" },
+];
+
+/**
+ * Returns the list of enabled post types.
+ *
+ * Resolution order:
+ * 1. .indiekit/post-types.json in content dir (written by Indiekit or deployer)
+ * 2. POST_TYPES env var (comma-separated: "article,note,photo")
+ * 3. All standard post types (default)
+ */
+export default function () {
+ // 1. Try config file
+ try {
+ const configPath = resolve(CONTENT_DIR, ".indiekit", "post-types.json");
+ const raw = readFileSync(configPath, "utf8");
+ const types = JSON.parse(raw);
+ if (Array.isArray(types)) {
+ // Array of type strings: ["article", "note"]
+ return ALL_POST_TYPES.filter((pt) => types.includes(pt.type));
+ }
+ // Array of objects with at least { type }
+ return types;
+ } catch {
+ // File doesn't exist — fall through
+ }
+
+ // 2. Try env var
+ const envTypes = process.env.POST_TYPES;
+ if (envTypes) {
+ const types = envTypes.split(",").map((t) => t.trim().toLowerCase());
+ return ALL_POST_TYPES.filter((pt) => types.includes(pt.type));
+ }
+
+ // 3. Default — all standard types
+ return ALL_POST_TYPES;
+}
diff --git a/theme/_data/funkwhaleActivity.js b/theme/_data/funkwhaleActivity.js
new file mode 100644
index 0000000..316aeb4
--- /dev/null
+++ b/theme/_data/funkwhaleActivity.js
@@ -0,0 +1,123 @@
+/**
+ * Funkwhale Activity Data
+ * Fetches from Indiekit's endpoint-funkwhale public API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+const FUNKWHALE_INSTANCE = process.env.FUNKWHALE_INSTANCE || "";
+
+/**
+ * Fetch from Indiekit's public Funkwhale API endpoint
+ */
+async function fetchFromIndiekit(endpoint) {
+ try {
+ const url = `${INDIEKIT_URL}/funkwhaleapi/api/${endpoint}`;
+ console.log(`[funkwhaleActivity] Fetching from Indiekit: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log(`[funkwhaleActivity] Indiekit ${endpoint} success`);
+ return data;
+ } catch (error) {
+ console.log(
+ `[funkwhaleActivity] Indiekit API unavailable for ${endpoint}: ${error.message}`
+ );
+ return null;
+ }
+}
+
+/**
+ * Format duration in seconds to human-readable string
+ */
+function formatDuration(seconds) {
+ if (!seconds || seconds < 0) return "0:00";
+
+ const hours = Math.floor(seconds / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+
+ if (hours > 24) {
+ const days = Math.floor(hours / 24);
+ return `${days}d`;
+ }
+
+ if (hours > 0) {
+ return `${hours}h ${minutes}m`;
+ }
+
+ return `${minutes}m`;
+}
+
+export default async function () {
+ try {
+ console.log("[funkwhaleActivity] Fetching Funkwhale data...");
+
+ // Fetch all data from Indiekit API
+ const [nowPlaying, listenings, favorites, stats] = await Promise.all([
+ fetchFromIndiekit("now-playing"),
+ fetchFromIndiekit("listenings"),
+ fetchFromIndiekit("favorites"),
+ fetchFromIndiekit("stats"),
+ ]);
+
+ // Check if we got data
+ const hasData = nowPlaying || listenings?.listenings?.length || stats?.summary;
+
+ if (!hasData) {
+ console.log("[funkwhaleActivity] No data available from Indiekit");
+ return {
+ nowPlaying: null,
+ listenings: [],
+ favorites: [],
+ stats: null,
+ instanceUrl: FUNKWHALE_INSTANCE,
+ source: "unavailable",
+ };
+ }
+
+ console.log("[funkwhaleActivity] Using Indiekit API data");
+
+ // Format stats with human-readable durations
+ let formattedStats = null;
+ if (stats?.summary) {
+ formattedStats = {
+ ...stats,
+ summary: {
+ all: {
+ ...stats.summary.all,
+ totalDurationFormatted: formatDuration(stats.summary.all?.totalDuration || 0),
+ },
+ month: {
+ ...stats.summary.month,
+ totalDurationFormatted: formatDuration(stats.summary.month?.totalDuration || 0),
+ },
+ week: {
+ ...stats.summary.week,
+ totalDurationFormatted: formatDuration(stats.summary.week?.totalDuration || 0),
+ },
+ },
+ };
+ }
+
+ return {
+ nowPlaying: nowPlaying || null,
+ listenings: listenings?.listenings || [],
+ favorites: favorites?.favorites || [],
+ stats: formattedStats,
+ instanceUrl: FUNKWHALE_INSTANCE,
+ source: "indiekit",
+ };
+ } catch (error) {
+ console.error("[funkwhaleActivity] Error:", error.message);
+ return {
+ nowPlaying: null,
+ listenings: [],
+ favorites: [],
+ stats: null,
+ instanceUrl: FUNKWHALE_INSTANCE,
+ source: "error",
+ };
+ }
+}
diff --git a/theme/_data/githubActivity.js b/theme/_data/githubActivity.js
new file mode 100644
index 0000000..d8a19a7
--- /dev/null
+++ b/theme/_data/githubActivity.js
@@ -0,0 +1,284 @@
+/**
+ * GitHub Activity Data
+ * Fetches from Indiekit's endpoint-github public API
+ * Falls back to direct GitHub API if Indiekit is unavailable
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const GITHUB_USERNAME = process.env.GITHUB_USERNAME || "";
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+// Fallback featured repos if Indiekit API unavailable (from env: comma-separated)
+const FALLBACK_FEATURED_REPOS = process.env.GITHUB_FEATURED_REPOS?.split(",").filter(Boolean) || [];
+
+/**
+ * Fetch from Indiekit's public GitHub API endpoint
+ */
+async function fetchFromIndiekit(endpoint) {
+ try {
+ const url = `${INDIEKIT_URL}/githubapi/api/${endpoint}`;
+ console.log(`[githubActivity] Fetching from Indiekit: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log(`[githubActivity] Indiekit ${endpoint} success`);
+ return data;
+ } catch (error) {
+ console.log(
+ `[githubActivity] Indiekit API unavailable for ${endpoint}: ${error.message}`
+ );
+ return null;
+ }
+}
+
+/**
+ * Fetch from GitHub API directly
+ */
+async function fetchFromGitHub(endpoint) {
+ const url = `https://api.github.com${endpoint}`;
+ const headers = {
+ Accept: "application/vnd.github.v3+json",
+ "User-Agent": "Eleventy-Site",
+ };
+
+ if (process.env.GITHUB_TOKEN) {
+ headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
+ }
+
+ return await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ fetchOptions: { headers },
+ });
+}
+
+/**
+ * Truncate text with ellipsis
+ */
+function truncate(text, maxLength = 80) {
+ if (!text || text.length <= maxLength) return text || "";
+ return text.slice(0, maxLength - 1) + "...";
+}
+
+/**
+ * Extract commits from push events
+ */
+function extractCommits(events) {
+ if (!Array.isArray(events)) return [];
+
+ return events
+ .filter((event) => event.type === "PushEvent")
+ .flatMap((event) =>
+ (event.payload?.commits || []).map((commit) => ({
+ sha: commit.sha.slice(0, 7),
+ message: truncate(commit.message.split("\n")[0]),
+ url: `https://github.com/${event.repo.name}/commit/${commit.sha}`,
+ repo: event.repo.name,
+ repoUrl: `https://github.com/${event.repo.name}`,
+ date: event.created_at,
+ }))
+ )
+ .slice(0, 10);
+}
+
+/**
+ * Extract PRs/Issues from events
+ */
+function extractContributions(events) {
+ if (!Array.isArray(events)) return [];
+
+ return events
+ .filter(
+ (event) =>
+ (event.type === "PullRequestEvent" || event.type === "IssuesEvent") &&
+ event.payload?.action === "opened"
+ )
+ .map((event) => {
+ const item = event.payload.pull_request || event.payload.issue;
+ return {
+ type: event.type === "PullRequestEvent" ? "pr" : "issue",
+ title: truncate(item?.title),
+ url: item?.html_url,
+ repo: event.repo.name,
+ repoUrl: `https://github.com/${event.repo.name}`,
+ number: item?.number,
+ date: event.created_at,
+ };
+ })
+ .slice(0, 10);
+}
+
+/**
+ * Format starred repos
+ */
+function formatStarred(repos) {
+ if (!Array.isArray(repos)) return [];
+
+ return repos.map((repo) => ({
+ name: repo.full_name,
+ description: truncate(repo.description, 120),
+ url: repo.html_url,
+ stars: repo.stargazers_count,
+ language: repo.language,
+ topics: repo.topics?.slice(0, 5) || [],
+ }));
+}
+
+/**
+ * Fetch featured repos directly from GitHub (fallback)
+ */
+async function fetchFeaturedFromGitHub(repoList) {
+ const featured = [];
+
+ for (const repoFullName of repoList) {
+ try {
+ const repo = await fetchFromGitHub(`/repos/${repoFullName}`);
+ let commits = [];
+ try {
+ const commitsData = await fetchFromGitHub(
+ `/repos/${repoFullName}/commits?per_page=5`
+ );
+ commits = commitsData.map((c) => ({
+ sha: c.sha.slice(0, 7),
+ message: truncate(c.commit.message.split("\n")[0]),
+ url: c.html_url,
+ date: c.commit.author.date,
+ }));
+ } catch (e) {
+ console.log(`[githubActivity] Could not fetch commits for ${repoFullName}`);
+ }
+
+ featured.push({
+ fullName: repo.full_name,
+ name: repo.name,
+ description: repo.description,
+ url: repo.html_url,
+ stars: repo.stargazers_count,
+ forks: repo.forks_count,
+ language: repo.language,
+ isPrivate: repo.private,
+ commits,
+ });
+ } catch (error) {
+ console.log(`[githubActivity] Could not fetch ${repoFullName}: ${error.message}`);
+ }
+ }
+
+ return featured;
+}
+
+/**
+ * Fetch commits directly from user's recently pushed repos
+ * Fallback when events API doesn't include commit details
+ */
+async function fetchCommitsFromRepos(username, limit = 10) {
+ try {
+ const repos = await fetchFromGitHub(
+ `/users/${username}/repos?sort=pushed&per_page=5`
+ );
+
+ if (!Array.isArray(repos) || repos.length === 0) {
+ return [];
+ }
+
+ const allCommits = [];
+ for (const repo of repos.slice(0, 5)) {
+ try {
+ const repoCommits = await fetchFromGitHub(
+ `/repos/${repo.full_name}/commits?per_page=5`
+ );
+ for (const c of repoCommits) {
+ allCommits.push({
+ sha: c.sha.slice(0, 7),
+ message: truncate(c.commit?.message?.split("\n")[0]),
+ url: c.html_url,
+ repo: repo.full_name,
+ repoUrl: repo.html_url,
+ date: c.commit?.author?.date,
+ });
+ }
+ } catch {
+ // Skip repos we can't access
+ }
+ }
+
+ // Sort by date and limit
+ return allCommits
+ .sort((a, b) => new Date(b.date) - new Date(a.date))
+ .slice(0, limit);
+ } catch (error) {
+ console.log(`[githubActivity] Could not fetch commits from repos: ${error.message}`);
+ return [];
+ }
+}
+
+export default async function () {
+ try {
+ console.log("[githubActivity] Fetching GitHub data...");
+
+ // Try Indiekit public API first
+ const [indiekitStars, indiekitCommits, indiekitContributions, indiekitActivity, indiekitFeatured] =
+ await Promise.all([
+ fetchFromIndiekit("stars"),
+ fetchFromIndiekit("commits"),
+ fetchFromIndiekit("contributions"),
+ fetchFromIndiekit("activity"),
+ fetchFromIndiekit("featured"),
+ ]);
+
+ // Check if Indiekit API is available
+ const hasIndiekitData =
+ indiekitStars?.stars ||
+ indiekitCommits?.commits ||
+ indiekitFeatured?.featured;
+
+ if (hasIndiekitData) {
+ console.log("[githubActivity] Using Indiekit API data");
+ return {
+ stars: indiekitStars?.stars || [],
+ commits: indiekitCommits?.commits || [],
+ contributions: indiekitContributions?.contributions || [],
+ activity: indiekitActivity?.activity || [],
+ featured: indiekitFeatured?.featured || [],
+ source: "indiekit",
+ };
+ }
+
+ // Fallback to direct GitHub API
+ console.log("[githubActivity] Falling back to GitHub API");
+
+ const [events, starred, featured] = await Promise.all([
+ fetchFromGitHub(`/users/${GITHUB_USERNAME}/events/public?per_page=50`),
+ fetchFromGitHub(`/users/${GITHUB_USERNAME}/starred?per_page=20&sort=created`),
+ fetchFeaturedFromGitHub(FALLBACK_FEATURED_REPOS),
+ ]);
+
+ // Try to extract commits from events first
+ let commits = extractCommits(events || []);
+
+ // If events API didn't have commits, fetch directly from repos
+ if (commits.length === 0 && GITHUB_USERNAME) {
+ console.log("[githubActivity] Events API returned no commits, fetching from repos");
+ commits = await fetchCommitsFromRepos(GITHUB_USERNAME, 10);
+ }
+
+ return {
+ stars: formatStarred(starred || []),
+ commits,
+ contributions: extractContributions(events || []),
+ featured,
+ source: "github",
+ };
+ } catch (error) {
+ console.error("[githubActivity] Error:", error.message);
+ return {
+ stars: [],
+ commits: [],
+ contributions: [],
+ featured: [],
+ source: "error",
+ };
+ }
+}
diff --git a/theme/_data/githubRepos.js b/theme/_data/githubRepos.js
new file mode 100644
index 0000000..1b8fe5f
--- /dev/null
+++ b/theme/_data/githubRepos.js
@@ -0,0 +1,48 @@
+/**
+ * GitHub Repos Data
+ * Fetches public repositories from GitHub API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+export default async function () {
+ const username = process.env.GITHUB_USERNAME || "";
+
+ try {
+ // Fetch public repos, sorted by updated date
+ const url = `https://api.github.com/users/${username}/repos?sort=updated&per_page=10&type=owner`;
+
+ const repos = await EleventyFetch(url, {
+ duration: "1h", // Cache for 1 hour
+ type: "json",
+ fetchOptions: {
+ headers: {
+ Accept: "application/vnd.github.v3+json",
+ "User-Agent": "Eleventy-Site",
+ },
+ },
+ });
+
+ // Filter and transform repos
+ return repos
+ .filter((repo) => !repo.fork && !repo.private) // Exclude forks and private repos
+ .map((repo) => ({
+ name: repo.name,
+ full_name: repo.full_name,
+ description: repo.description,
+ html_url: repo.html_url,
+ homepage: repo.homepage,
+ language: repo.language,
+ stargazers_count: repo.stargazers_count,
+ forks_count: repo.forks_count,
+ open_issues_count: repo.open_issues_count,
+ topics: repo.topics || [],
+ updated_at: repo.updated_at,
+ created_at: repo.created_at,
+ }))
+ .slice(0, 10); // Limit to 10 repos
+ } catch (error) {
+ console.error("Error fetching GitHub repos:", error.message);
+ return [];
+ }
+}
diff --git a/theme/_data/githubStarred.js b/theme/_data/githubStarred.js
new file mode 100644
index 0000000..659d181
--- /dev/null
+++ b/theme/_data/githubStarred.js
@@ -0,0 +1,32 @@
+/**
+ * GitHub Starred Repos Metadata
+ * Fetches the starred API response (cached 15min) to extract totalCount.
+ * Only totalCount is passed to Eleventy's data cascade — the full star
+ * list is discarded after parsing, keeping build memory low.
+ * The starred page fetches all data client-side via Alpine.js.
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+export default async function () {
+ try {
+ const url = `${INDIEKIT_URL}/githubapi/api/starred/all`;
+ const response = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+
+ return {
+ totalCount: response.totalCount || 0,
+ buildDate: new Date().toISOString(),
+ };
+ } catch (error) {
+ console.log(`[githubStarred] Could not fetch starred count: ${error.message}`);
+ return {
+ totalCount: 0,
+ buildDate: new Date().toISOString(),
+ };
+ }
+}
diff --git a/theme/_data/homepageConfig.js b/theme/_data/homepageConfig.js
new file mode 100644
index 0000000..ed5b2a7
--- /dev/null
+++ b/theme/_data/homepageConfig.js
@@ -0,0 +1,29 @@
+/**
+ * Homepage Configuration Data
+ * Reads config from indiekit-endpoint-homepage plugin (when installed).
+ * Falls back to null — home.njk then uses the default layout.
+ *
+ * Future: The homepage plugin will write a .indiekit/homepage.json file
+ * that Eleventy watches. On change, a rebuild picks up the new config,
+ * allowing layout changes without a Docker rebuild.
+ */
+
+import { readFileSync } from "node:fs";
+import { resolve, dirname } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+export default function () {
+ try {
+ // Resolve via the content/ symlink relative to the Eleventy project
+ const configPath = resolve(__dirname, "..", "content", ".indiekit", "homepage.json");
+ const raw = readFileSync(configPath, "utf8");
+ const config = JSON.parse(raw);
+ console.log("[homepageConfig] Loaded plugin config");
+ return config;
+ } catch {
+ // No homepage plugin config — this is the normal case for most deployments
+ return null;
+ }
+}
diff --git a/theme/_data/lastfmActivity.js b/theme/_data/lastfmActivity.js
new file mode 100644
index 0000000..d7dd837
--- /dev/null
+++ b/theme/_data/lastfmActivity.js
@@ -0,0 +1,83 @@
+/**
+ * Last.fm Activity Data
+ * Fetches from Indiekit's endpoint-lastfm public API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+const LASTFM_USERNAME = process.env.LASTFM_USERNAME || "";
+
+/**
+ * Fetch from Indiekit's public Last.fm API endpoint
+ */
+async function fetchFromIndiekit(endpoint) {
+ try {
+ const url = `${INDIEKIT_URL}/lastfmapi/api/${endpoint}`;
+ console.log(`[lastfmActivity] Fetching from Indiekit: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log(`[lastfmActivity] Indiekit ${endpoint} success`);
+ return data;
+ } catch (error) {
+ console.log(
+ `[lastfmActivity] Indiekit API unavailable for ${endpoint}: ${error.message}`
+ );
+ return null;
+ }
+}
+
+export default async function () {
+ try {
+ console.log("[lastfmActivity] Fetching Last.fm data...");
+
+ // Fetch all data from Indiekit API
+ const [nowPlaying, scrobbles, loved, stats] = await Promise.all([
+ fetchFromIndiekit("now-playing"),
+ fetchFromIndiekit("scrobbles"),
+ fetchFromIndiekit("loved"),
+ fetchFromIndiekit("stats"),
+ ]);
+
+ // Check if we got data
+ const hasData = nowPlaying || scrobbles?.scrobbles?.length || stats?.summary;
+
+ if (!hasData) {
+ console.log("[lastfmActivity] No data available from Indiekit");
+ return {
+ nowPlaying: null,
+ scrobbles: [],
+ loved: [],
+ stats: null,
+ username: LASTFM_USERNAME,
+ profileUrl: LASTFM_USERNAME ? `https://www.last.fm/user/${LASTFM_USERNAME}` : null,
+ source: "unavailable",
+ };
+ }
+
+ console.log("[lastfmActivity] Using Indiekit API data");
+
+ return {
+ nowPlaying: nowPlaying || null,
+ scrobbles: scrobbles?.scrobbles || [],
+ loved: loved?.loved || [],
+ stats: stats || null,
+ username: LASTFM_USERNAME,
+ profileUrl: LASTFM_USERNAME ? `https://www.last.fm/user/${LASTFM_USERNAME}` : null,
+ source: "indiekit",
+ };
+ } catch (error) {
+ console.error("[lastfmActivity] Error:", error.message);
+ return {
+ nowPlaying: null,
+ scrobbles: [],
+ loved: [],
+ stats: null,
+ username: LASTFM_USERNAME,
+ profileUrl: null,
+ source: "error",
+ };
+ }
+}
diff --git a/theme/_data/mastodonFeed.js b/theme/_data/mastodonFeed.js
new file mode 100644
index 0000000..0c79abf
--- /dev/null
+++ b/theme/_data/mastodonFeed.js
@@ -0,0 +1,96 @@
+/**
+ * Mastodon Feed Data
+ * Fetches recent posts from Mastodon using the public API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+export default async function () {
+ const instance = process.env.MASTODON_INSTANCE?.replace("https://", "") || "";
+ const username = process.env.MASTODON_USER || "";
+
+ try {
+ // First, look up the account ID
+ const lookupUrl = `https://${instance}/api/v1/accounts/lookup?acct=${username}`;
+
+ const account = await EleventyFetch(lookupUrl, {
+ duration: "1h", // Cache account lookup for 1 hour
+ type: "json",
+ fetchOptions: {
+ headers: {
+ Accept: "application/json",
+ },
+ },
+ });
+
+ if (!account || !account.id) {
+ console.log("Mastodon account not found:", username);
+ return [];
+ }
+
+ // Fetch recent statuses (excluding replies and boosts for cleaner feed)
+ const statusesUrl = `https://${instance}/api/v1/accounts/${account.id}/statuses?limit=10&exclude_replies=true&exclude_reblogs=true`;
+
+ const statuses = await EleventyFetch(statusesUrl, {
+ duration: "15m", // Cache for 15 minutes
+ type: "json",
+ fetchOptions: {
+ headers: {
+ Accept: "application/json",
+ },
+ },
+ });
+
+ if (!statuses || !Array.isArray(statuses)) {
+ console.log("No Mastodon statuses found for:", username);
+ return [];
+ }
+
+ // Transform statuses into a simpler format
+ return statuses.map((status) => ({
+ id: status.id,
+ url: status.url,
+ text: stripHtml(status.content),
+ htmlContent: status.content,
+ createdAt: status.created_at,
+ author: {
+ username: status.account.username,
+ displayName: status.account.display_name || status.account.username,
+ avatar: status.account.avatar,
+ url: status.account.url,
+ },
+ favouritesCount: status.favourites_count || 0,
+ reblogsCount: status.reblogs_count || 0,
+ repliesCount: status.replies_count || 0,
+ // Media attachments
+ media: status.media_attachments
+ ? status.media_attachments.map((m) => ({
+ type: m.type,
+ url: m.url,
+ previewUrl: m.preview_url,
+ description: m.description,
+ }))
+ : [],
+ }));
+ } catch (error) {
+ console.error("Error fetching Mastodon feed:", error.message);
+ return [];
+ }
+}
+
+// Simple HTML stripper for plain text display
+function stripHtml(html) {
+ if (!html) return "";
+ return html
+ .replace(/ /gi, " ")
+ .replace(/<\/p>/gi, " ")
+ .replace(/<[^>]+>/g, "")
+ .replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/"/g, '"')
+ .replace(/'/g, "'")
+ .replace(/ /g, " ")
+ .replace(/\s+/g, " ")
+ .trim();
+}
diff --git a/theme/_data/newsActivity.js b/theme/_data/newsActivity.js
new file mode 100644
index 0000000..47499b8
--- /dev/null
+++ b/theme/_data/newsActivity.js
@@ -0,0 +1,99 @@
+/**
+ * News/RSS Activity Data
+ * Fetches from Indiekit's endpoint-rss public API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+/**
+ * Fetch from Indiekit's public RSS API endpoint
+ */
+async function fetchFromIndiekit(endpoint) {
+ try {
+ const url = `${INDIEKIT_URL}/rssapi/api/${endpoint}`;
+ console.log(`[newsActivity] Fetching from Indiekit: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log(`[newsActivity] Indiekit ${endpoint} success`);
+ return data;
+ } catch (error) {
+ console.log(
+ `[newsActivity] Indiekit API unavailable for ${endpoint}: ${error.message}`
+ );
+ return null;
+ }
+}
+
+export default async function () {
+ try {
+ console.log("[newsActivity] Fetching RSS feed data...");
+
+ // Fetch all data from Indiekit API
+ const [itemsRes, feedsRes, statusRes] = await Promise.all([
+ fetchFromIndiekit("items?limit=50"),
+ fetchFromIndiekit("feeds"),
+ fetchFromIndiekit("status"),
+ ]);
+
+ // Check if we got data
+ const hasData = itemsRes?.items?.length || feedsRes?.feeds?.length;
+
+ if (!hasData) {
+ console.log("[newsActivity] No data available from Indiekit");
+ return {
+ items: [],
+ feeds: [],
+ status: null,
+ lastUpdated: null,
+ source: "unavailable",
+ };
+ }
+
+ console.log(
+ `[newsActivity] Got ${itemsRes?.items?.length || 0} items from ${feedsRes?.feeds?.length || 0} feeds`
+ );
+
+ // Create a map of feed IDs to feed info for quick lookup
+ const feedMap = new Map();
+ for (const feed of feedsRes?.feeds || []) {
+ feedMap.set(feed.id, feed);
+ }
+
+ // Enhance items with additional feed info
+ const items = (itemsRes?.items || []).map((item) => {
+ const feed = feedMap.get(item.feedId);
+ return {
+ ...item,
+ feedInfo: feed
+ ? {
+ title: feed.title,
+ siteUrl: feed.siteUrl,
+ imageUrl: feed.imageUrl,
+ }
+ : null,
+ };
+ });
+
+ return {
+ items,
+ feeds: feedsRes?.feeds || [],
+ pagination: itemsRes?.pagination || null,
+ status: statusRes || null,
+ lastUpdated: statusRes?.lastSync || new Date().toISOString(),
+ source: "indiekit",
+ };
+ } catch (error) {
+ console.error("[newsActivity] Error:", error.message);
+ return {
+ items: [],
+ feeds: [],
+ status: null,
+ lastUpdated: null,
+ source: "error",
+ };
+ }
+}
diff --git a/theme/_data/podrollStatus.js b/theme/_data/podrollStatus.js
new file mode 100644
index 0000000..cf117e5
--- /dev/null
+++ b/theme/_data/podrollStatus.js
@@ -0,0 +1,34 @@
+/**
+ * Podroll Status Data
+ * Checks if the podroll API backend is available at build time.
+ * Used for conditional navigation — the podroll page itself loads data client-side.
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+export default async function () {
+ try {
+ const url = `${INDIEKIT_URL}/podrollapi/api/status`;
+ console.log(`[podrollStatus] Checking API: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log("[podrollStatus] API available");
+ return {
+ available: true,
+ source: "indiekit",
+ ...data,
+ };
+ } catch (error) {
+ console.log(
+ `[podrollStatus] API unavailable: ${error.message}`
+ );
+ return {
+ available: false,
+ source: "unavailable",
+ };
+ }
+}
diff --git a/theme/_data/recentComments.js b/theme/_data/recentComments.js
new file mode 100644
index 0000000..bdd1ede
--- /dev/null
+++ b/theme/_data/recentComments.js
@@ -0,0 +1,24 @@
+/**
+ * Recent Comments Data
+ * Fetches the 5 most recent comments at build time for the sidebar widget.
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+export default async function () {
+ try {
+ const url = `${INDIEKIT_URL}/comments/api/comments?limit=5`;
+ console.log(`[recentComments] Fetching: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log(`[recentComments] Got ${(data.children || []).length} comments`);
+ return data.children || [];
+ } catch (error) {
+ console.log(`[recentComments] Unavailable: ${error.message}`);
+ return [];
+ }
+}
diff --git a/theme/_data/site.js b/theme/_data/site.js
new file mode 100644
index 0000000..7d3d342
--- /dev/null
+++ b/theme/_data/site.js
@@ -0,0 +1,139 @@
+/**
+ * Site configuration for Eleventy
+ *
+ * Configure via environment variables in Cloudron app settings.
+ * All values have sensible defaults for initial deployment.
+ */
+
+// Parse social links from env (format: "name|url|icon,name|url|icon")
+function parseSocialLinks(envVar) {
+ if (!envVar) return [];
+ return envVar.split(",").map((link) => {
+ const [name, url, icon] = link.split("|").map((s) => s.trim());
+ // Bluesky requires "me atproto" for verification
+ const rel = url.includes("bsky.app") ? "me atproto" : "me";
+ return { name, url, rel, icon: icon || name.toLowerCase() };
+ });
+}
+
+// Get fediverse handle for fediverse:creator meta tag
+// Prefers the site's own ActivityPub identity over external Mastodon account
+function getFediverseCreator() {
+ // Primary: site's own ActivityPub actor (canonical fediverse identity)
+ const apHandle = process.env.ACTIVITYPUB_HANDLE;
+ if (apHandle) {
+ const domain = (process.env.SITE_URL || "https://example.com").replace(/^https?:\/\//, "");
+ return `@${apHandle}@${domain}`;
+ }
+ // Fallback: external Mastodon account (syndication target)
+ const instance = process.env.MASTODON_INSTANCE?.replace("https://", "") || "";
+ const user = process.env.MASTODON_USER || "";
+ if (instance && user) {
+ return `@${user}@${instance}`;
+ }
+ return "";
+}
+
+// Auto-generate social links from feed config when SITE_SOCIAL is not set
+function buildSocialFromFeeds() {
+ const links = [];
+ const github = process.env.GITHUB_USERNAME;
+ if (github) {
+ links.push({ name: "GitHub", url: `https://github.com/${github}`, rel: "me", icon: "github" });
+ }
+ const bskyHandle = process.env.BLUESKY_HANDLE;
+ if (bskyHandle) {
+ links.push({ name: "Bluesky", url: `https://bsky.app/profile/${bskyHandle}`, rel: "me atproto", icon: "bluesky" });
+ }
+ const mastoInstance = process.env.MASTODON_INSTANCE?.replace("https://", "");
+ const mastoUser = process.env.MASTODON_USER;
+ if (mastoInstance && mastoUser) {
+ links.push({ name: "Mastodon", url: `https://${mastoInstance}/@${mastoUser}`, rel: "me", icon: "mastodon" });
+ }
+ const linkedin = process.env.LINKEDIN_USERNAME;
+ if (linkedin) {
+ links.push({ name: "LinkedIn", url: `https://linkedin.com/in/${linkedin}`, rel: "me", icon: "linkedin" });
+ }
+ const apHandle = process.env.ACTIVITYPUB_HANDLE;
+ if (apHandle) {
+ const siteUrl = process.env.SITE_URL || "https://example.com";
+ links.push({ name: "ActivityPub", url: `${siteUrl}/activitypub/users/${apHandle}`, rel: "me", icon: "activitypub" });
+ }
+ return links;
+}
+
+// site.url: no trailing slash — used as URL base for path concatenation ({{ site.url }}/path)
+// site.me / site.author.url: trailing slash — Mastodon rel="me" requires exact match
+const siteUrlBase = (process.env.SITE_URL || "https://example.com").replace(/\/$/, "");
+const siteUrlWithSlash = siteUrlBase + "/";
+
+export default {
+ // Basic site info
+ name: process.env.SITE_NAME || "My IndieWeb Blog",
+ url: siteUrlBase,
+ me: siteUrlWithSlash,
+ locale: process.env.SITE_LOCALE || "en",
+ description:
+ process.env.SITE_DESCRIPTION ||
+ "An IndieWeb-powered blog with Micropub support",
+
+ // Author info (shown in h-card, about page, etc.)
+ author: {
+ name: process.env.AUTHOR_NAME || "Blog Author",
+ url: siteUrlWithSlash,
+ avatar: process.env.AUTHOR_AVATAR || "/images/default-avatar.svg",
+ title: process.env.AUTHOR_TITLE || "",
+ bio: process.env.AUTHOR_BIO || "Welcome to my IndieWeb blog.",
+ location: process.env.AUTHOR_LOCATION || "",
+ locality: process.env.AUTHOR_LOCALITY || "",
+ region: process.env.AUTHOR_REGION || "",
+ country: process.env.AUTHOR_COUNTRY || "",
+ org: process.env.AUTHOR_ORG || "",
+ pronoun: process.env.AUTHOR_PRONOUN || "",
+ categories: process.env.AUTHOR_CATEGORIES?.split(",").map(s => s.trim()) || [],
+ keyUrl: process.env.AUTHOR_KEY_URL || "",
+ email: process.env.AUTHOR_EMAIL || "",
+ },
+
+ // Social links (for rel="me" and h-card)
+ // Set SITE_SOCIAL env var as: "GitHub|https://github.com/user|github,Mastodon|https://mastodon.social/@user|mastodon"
+ // Falls back to auto-generating from feed config (GITHUB_USERNAME, BLUESKY_HANDLE, etc.)
+ social: parseSocialLinks(process.env.SITE_SOCIAL).length > 0
+ ? parseSocialLinks(process.env.SITE_SOCIAL)
+ : buildSocialFromFeeds(),
+
+ // Feed integrations (usernames for data fetching)
+ feeds: {
+ github: process.env.GITHUB_USERNAME || "",
+ bluesky: process.env.BLUESKY_HANDLE || "",
+ mastodon: {
+ instance: process.env.MASTODON_INSTANCE?.replace("https://", "") || "",
+ username: process.env.MASTODON_USER || "",
+ },
+ },
+
+ // Webmentions configuration
+ webmentions: {
+ domain: process.env.SITE_URL?.replace("https://", "").replace("http://", "") || "example.com",
+ },
+
+ // Fediverse creator for meta tag (e.g., @rick@rmendes.net)
+ fediverseCreator: getFediverseCreator(),
+
+ // Support/monetization configuration (used in _textcasting JSON Feed extension)
+ support: {
+ url: process.env.SUPPORT_URL || null,
+ stripe: process.env.SUPPORT_STRIPE_URL || null,
+ lightning: process.env.SUPPORT_LIGHTNING_ADDRESS || null,
+ paymentPointer: process.env.SUPPORT_PAYMENT_POINTER || null,
+ },
+
+ // Markdown for Agents — serve clean Markdown to AI agents
+ // Set MARKDOWN_AGENTS_ENABLED to "false" to disable entirely
+ markdownAgents: {
+ enabled: (process.env.MARKDOWN_AGENTS_ENABLED || "true").toLowerCase() === "true",
+ aiTrain: process.env.MARKDOWN_AGENTS_AI_TRAIN || "yes",
+ search: process.env.MARKDOWN_AGENTS_SEARCH || "yes",
+ aiInput: process.env.MARKDOWN_AGENTS_AI_INPUT || "yes",
+ },
+};
diff --git a/theme/_data/urlAliases.js b/theme/_data/urlAliases.js
new file mode 100644
index 0000000..1478a1f
--- /dev/null
+++ b/theme/_data/urlAliases.js
@@ -0,0 +1,155 @@
+/**
+ * URL Aliases for Webmention Recovery
+ *
+ * Maps new URLs to their old URLs so webmentions from previous
+ * URL structures can be displayed on current pages.
+ *
+ * Place redirect map files in the parent directory of this theme:
+ * - redirects.map (e.g., micro.blog: /YYYY/MM/DD/slug.html → /notes/...)
+ * - old-blog-redirects.map (e.g., Known/WP: /YYYY/slug → /content/...)
+ */
+
+import { readFileSync, existsSync } from "fs";
+import { resolve, dirname } from "path";
+import { fileURLToPath } from "url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const siteUrl = process.env.SITE_URL || "https://example.com";
+
+/**
+ * Parse a redirect map file into URL mappings
+ * Format: old_path new_path;
+ */
+function parseRedirectMap(filePath) {
+ const aliases = {};
+
+ if (!existsSync(filePath)) {
+ console.log(`[urlAliases] File not found: ${filePath}`);
+ return aliases;
+ }
+
+ try {
+ const content = readFileSync(filePath, "utf-8");
+ const lines = content.split("\n").filter((line) => {
+ const trimmed = line.trim();
+ return trimmed && !trimmed.startsWith("#");
+ });
+
+ for (const line of lines) {
+ // Format: /old/path /new/path;
+ const match = line.match(/^(\S+)\s+(\S+);?$/);
+ if (match) {
+ const [, oldPath, newPath] = match;
+ // Normalize paths (remove trailing slashes, ensure leading slash)
+ const normalizedNew = newPath.replace(/;$/, "").replace(/\/$/, "");
+ const normalizedOld = oldPath.replace(/\/$/, "");
+
+ // Map new URL → array of old URLs
+ if (!aliases[normalizedNew]) {
+ aliases[normalizedNew] = [];
+ }
+ aliases[normalizedNew].push(normalizedOld);
+ }
+ }
+ } catch (error) {
+ console.error(`[urlAliases] Error parsing ${filePath}:`, error.message);
+ }
+
+ return aliases;
+}
+
+/**
+ * Merge multiple alias maps
+ */
+function mergeAliases(...maps) {
+ const merged = {};
+ for (const map of maps) {
+ for (const [newUrl, oldUrls] of Object.entries(map)) {
+ if (!merged[newUrl]) {
+ merged[newUrl] = [];
+ }
+ merged[newUrl].push(...oldUrls);
+ }
+ }
+ return merged;
+}
+
+// Parse redirect maps from /app/pkg (Docker) or parent directory (local dev)
+// In Docker: eleventy-site is at /app/pkg/eleventy-site, maps are at /app/pkg/
+// In local dev: maps might be at ../
+const pkgRoot = resolve(__dirname, "../..");
+
+// Helper to find first existing file
+function findFile(candidates) {
+ for (const path of candidates) {
+ if (existsSync(path)) {
+ console.log(`[urlAliases] Found: ${path}`);
+ return path;
+ }
+ }
+ console.log(`[urlAliases] No file found in: ${candidates.join(", ")}`);
+ return null;
+}
+
+// Try multiple possible locations for each map type
+const microblogMapPath = findFile([
+ resolve(pkgRoot, "redirects.map"),
+ resolve(__dirname, "../../redirects.map"),
+]);
+
+const knownMapPath = findFile([
+ resolve(pkgRoot, "old-blog-redirects.map"),
+ resolve(__dirname, "../../old-blog-redirects.map"),
+]);
+
+const microblogAliases = microblogMapPath ? parseRedirectMap(microblogMapPath) : {};
+const knownAliases = knownMapPath ? parseRedirectMap(knownMapPath) : {};
+
+const allAliases = mergeAliases(microblogAliases, knownAliases);
+
+// Log summary
+const totalMappings = Object.keys(allAliases).length;
+const totalOldUrls = Object.values(allAliases).reduce((sum, urls) => sum + urls.length, 0);
+console.log(`[urlAliases] Loaded ${totalMappings} URL mappings with ${totalOldUrls} old URLs`);
+
+export default {
+ // The merged alias map: new URL → [old URLs]
+ aliases: allAliases,
+
+ // Site URL for building absolute URLs
+ siteUrl,
+
+ /**
+ * Get all URLs (old and new) that should be checked for webmentions
+ * @param {string} url - Current page URL (relative)
+ * @returns {string[]} - Array of absolute URLs to check
+ */
+ getAllUrls(url) {
+ const normalizedUrl = url.replace(/\/$/, "");
+ const urls = [
+ `${siteUrl}${url}`,
+ `${siteUrl}${normalizedUrl}`,
+ ];
+
+ // Add old URL variations
+ const oldUrls = allAliases[normalizedUrl] || [];
+ for (const oldUrl of oldUrls) {
+ urls.push(`${siteUrl}${oldUrl}`);
+ // Also try with trailing slash
+ urls.push(`${siteUrl}${oldUrl}/`);
+ }
+
+ // Deduplicate
+ return [...new Set(urls)];
+ },
+
+ /**
+ * Get just the old URLs for a given new URL
+ * @param {string} url - Current page URL (relative)
+ * @returns {string[]} - Array of old relative URLs
+ */
+ getOldUrls(url) {
+ const normalizedUrl = url.replace(/\/$/, "");
+ return allAliases[normalizedUrl] || [];
+ },
+};
diff --git a/theme/_data/youtubeChannel.js b/theme/_data/youtubeChannel.js
new file mode 100644
index 0000000..7fbf461
--- /dev/null
+++ b/theme/_data/youtubeChannel.js
@@ -0,0 +1,206 @@
+/**
+ * YouTube Channel Data
+ * Fetches from Indiekit's endpoint-youtube public API
+ * Supports single or multiple channels
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+
+/**
+ * Fetch from Indiekit's public YouTube API endpoint
+ */
+async function fetchFromIndiekit(endpoint) {
+ try {
+ const url = `${INDIEKIT_URL}/youtubeapi/api/${endpoint}`;
+ console.log(`[youtubeChannel] Fetching from Indiekit: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "5m",
+ type: "json",
+ });
+ console.log(`[youtubeChannel] Indiekit ${endpoint} success`);
+ return data;
+ } catch (error) {
+ console.log(
+ `[youtubeChannel] Indiekit API unavailable for ${endpoint}: ${error.message}`
+ );
+ return null;
+ }
+}
+
+/**
+ * Format large numbers with locale separators
+ */
+function formatNumber(num) {
+ if (!num) return "0";
+ return new Intl.NumberFormat().format(num);
+}
+
+/**
+ * Format view count with K/M suffix for compact display
+ */
+function formatViewCount(num) {
+ if (!num) return "0";
+ if (num >= 1000000) {
+ return (num / 1000000).toFixed(1).replace(/\.0$/, "") + "M";
+ }
+ if (num >= 1000) {
+ return (num / 1000).toFixed(1).replace(/\\.0$/, "") + "K";
+ }
+ return num.toString();
+}
+
+/**
+ * Format relative time from ISO date string
+ */
+function formatRelativeTime(dateString) {
+ if (!dateString) return "";
+ const date = new Date(dateString);
+ const now = new Date();
+ const diffMs = now - date;
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
+
+ if (diffDays === 0) return "Today";
+ if (diffDays === 1) return "Yesterday";
+ if (diffDays < 7) return `${diffDays} days ago`;
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
+ if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`;
+ return `${Math.floor(diffDays / 365)} years ago`;
+}
+
+/**
+ * Format channel data with computed fields
+ */
+function formatChannel(channel) {
+ if (!channel) return null;
+ return {
+ ...channel,
+ subscriberCountFormatted: formatNumber(channel.subscriberCount),
+ videoCountFormatted: formatNumber(channel.videoCount),
+ viewCountFormatted: formatNumber(channel.viewCount),
+ url: `https://www.youtube.com/channel/${channel.id}`,
+ };
+}
+
+/**
+ * Format video data with computed fields
+ */
+function formatVideo(video) {
+ return {
+ ...video,
+ viewCountFormatted: formatViewCount(video.viewCount),
+ relativeTime: formatRelativeTime(video.publishedAt),
+ };
+}
+
+export default async function () {
+ try {
+ console.log("[youtubeChannel] Fetching YouTube data...");
+
+ // Fetch all data from Indiekit API
+ const [channelData, videosData, liveData] = await Promise.all([
+ fetchFromIndiekit("channel"),
+ fetchFromIndiekit("videos"),
+ fetchFromIndiekit("live"),
+ ]);
+
+ // Check if we got data
+ const hasData =
+ channelData?.channel ||
+ channelData?.channels?.length ||
+ videosData?.videos?.length;
+
+ if (!hasData) {
+ console.log("[youtubeChannel] No data available from Indiekit");
+ return {
+ channel: null,
+ channels: [],
+ videos: [],
+ videosByChannel: {},
+ liveStatus: null,
+ liveStatuses: [],
+ isMultiChannel: false,
+ source: "unavailable",
+ };
+ }
+
+ console.log("[youtubeChannel] Using Indiekit API data");
+
+ // Determine if multi-channel mode
+ const isMultiChannel = !!(channelData?.channels && channelData.channels.length > 1);
+
+ // Format channels
+ let channels = [];
+ let channel = null;
+
+ if (isMultiChannel) {
+ channels = (channelData.channels || []).map(formatChannel).filter(Boolean);
+ channel = channels[0] || null;
+ } else {
+ channel = formatChannel(channelData?.channel);
+ channels = channel ? [channel] : [];
+ }
+
+ // Format videos
+ const videos = (videosData?.videos || []).map(formatVideo);
+
+ // Group videos by channel if multi-channel
+ let videosByChannel = {};
+ if (isMultiChannel && videosData?.videosByChannel) {
+ for (const [channelName, channelVideos] of Object.entries(videosData.videosByChannel)) {
+ videosByChannel[channelName] = (channelVideos || []).map(formatVideo);
+ }
+ } else if (channel) {
+ videosByChannel[channel.configName || channel.title] = videos;
+ }
+
+ // Format live status
+ let liveStatus = null;
+ let liveStatuses = [];
+
+ if (liveData) {
+ if (isMultiChannel && liveData.liveStatuses) {
+ liveStatuses = liveData.liveStatuses;
+ // Find first live or upcoming
+ const live = liveStatuses.find((s) => s.isLive);
+ const upcoming = liveStatuses.find((s) => s.isUpcoming && !s.isLive);
+ liveStatus = {
+ isLive: !!live,
+ isUpcoming: !live && !!upcoming,
+ stream: live?.stream || upcoming?.stream || null,
+ };
+ } else {
+ liveStatus = {
+ isLive: liveData.isLive || false,
+ isUpcoming: liveData.isUpcoming || false,
+ stream: liveData.stream || null,
+ };
+ liveStatuses = [{ ...liveStatus, channelConfigName: channel?.configName }];
+ }
+ }
+
+ return {
+ channel,
+ channels,
+ videos,
+ videosByChannel,
+ liveStatus,
+ liveStatuses,
+ isMultiChannel,
+ source: "indiekit",
+ };
+ } catch (error) {
+ console.error("[youtubeChannel] Error:", error.message);
+ return {
+ channel: null,
+ channels: [],
+ videos: [],
+ videosByChannel: {},
+ liveStatus: null,
+ liveStatuses: [],
+ isMultiChannel: false,
+ source: "error",
+ };
+ }
+}
diff --git a/theme/_includes/components/blog-sidebar.njk b/theme/_includes/components/blog-sidebar.njk
new file mode 100644
index 0000000..286f819
--- /dev/null
+++ b/theme/_includes/components/blog-sidebar.njk
@@ -0,0 +1,273 @@
+{# Blog Sidebar - Shown on individual post pages #}
+{# Data-driven when homepageConfig.blogPostSidebar is configured, otherwise falls back to default widgets #}
+{# Each widget is wrapped in a collapsible container with localStorage persistence #}
+{% from "components/icon.njk" import icon %}
+
+{% if homepageConfig and homepageConfig.blogPostSidebar and homepageConfig.blogPostSidebar.length %}
+ {# === Data-driven mode: render configured widgets === #}
+ {% for widget in homepageConfig.blogPostSidebar %}
+
+ {# Resolve widget title #}
+ {% if widget.type == "search" %}{% set widgetTitle = "Search" %}
+ {% elif widget.type == "social-activity" %}{% set widgetTitle = "Social Activity" %}
+ {% elif widget.type == "github-repos" %}{% set widgetTitle = "GitHub" %}
+ {% elif widget.type == "funkwhale" %}{% set widgetTitle = "Listening" %}
+ {% elif widget.type == "recent-posts" %}{% set widgetTitle = "Recent Posts" %}
+ {% elif widget.type == "blogroll" %}{% set widgetTitle = "Blogroll" %}
+ {% elif widget.type == "feedland" %}{% set widgetTitle = "FeedLand" %}
+ {% elif widget.type == "categories" %}{% set widgetTitle = "Categories" %}
+ {% elif widget.type == "webmentions" %}{% set widgetTitle = "Webmentions" %}
+ {% elif widget.type == "recent-comments" %}{% set widgetTitle = "Recent Comments" %}
+ {% elif widget.type == "fediverse-follow" %}{% set widgetTitle = "Fediverse" %}
+ {% elif widget.type == "author-card" %}{% set widgetTitle = "Author" %}
+ {% elif widget.type == "author-card-compact" %}{% set widgetTitle = "Author" %}
+ {% elif widget.type == "subscribe" %}{% set widgetTitle = "Subscribe" %}
+ {% elif widget.type == "toc" %}{% set widgetTitle = "Table of Contents" %}
+ {% elif widget.type == "post-categories" %}{% set widgetTitle = "Categories" %}
+ {% elif widget.type == "share" %}{% set widgetTitle = "Share" %}
+ {% elif widget.type == "custom-html" %}{% set widgetTitle = (widget.config.title if widget.config and widget.config.title) or "Custom" %}
+ {% else %}{% set widgetTitle = widget.type %}
+ {% endif %}
+
+ {# Resolve widget icon and accent border #}
+ {% if widget.type == "social-activity" %}
+ {% set widgetIcon = "globe" %}{% set widgetIconClass = "w-5 h-5 text-[#0085ff]" %}{% set widgetBorder = "border-l-[3px] border-l-[#0085ff]" %}
+ {% elif widget.type == "github-repos" %}
+ {% set widgetIcon = "github" %}{% set widgetIconClass = "w-5 h-5 text-surface-800 dark:text-surface-200" %}{% set widgetBorder = "border-l-[3px] border-l-surface-400 dark:border-l-surface-500" %}
+ {% elif widget.type == "funkwhale" %}
+ {% set widgetIcon = "headphones" %}{% set widgetIconClass = "w-5 h-5 text-purple-500" %}{% set widgetBorder = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
+ {% elif widget.type == "blogroll" %}
+ {% set widgetIcon = "book-open" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif widget.type == "feedland" %}
+ {% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif widget.type == "subscribe" %}
+ {% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-orange-500" %}{% set widgetBorder = "border-l-[3px] border-l-orange-400 dark:border-l-orange-500" %}
+ {% elif widget.type == "fediverse-follow" %}
+ {% set widgetIcon = "user-plus" %}{% set widgetIconClass = "w-5 h-5 text-[#a730b8]" %}{% set widgetBorder = "border-l-[3px] border-l-[#a730b8]" %}
+ {% elif widget.type == "author-card" or widget.type == "author-card-compact" %}
+ {% set widgetIcon = "user" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "recent-posts" %}
+ {% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "categories" or widget.type == "post-categories" %}
+ {% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "recent-comments" %}
+ {% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "search" %}
+ {% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "webmentions" %}
+ {% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "toc" %}
+ {% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "share" %}
+ {% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% else %}
+ {% set widgetIcon = "" %}{% set widgetIconClass = "" %}{% set widgetBorder = "" %}
+ {% endif %}
+
+ {% set widgetKey = "post-widget-" + widget.type + "-" + loop.index0 %}
+ {% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
+
+ {# Collapsible wrapper — Alpine.js handles toggle, localStorage persists state #}
+
+
+ {% endfor %}
+{% else %}
+ {# === Fallback: default blog post sidebar (backward compatibility) === #}
+ {# Each widget wrapped in collapsible container #}
+
+ {# Author Card Compact #}
+ {% set widgetKey = "post-fb-author-card-compact" %}
+
+
+ {# Table of Contents #}
+ {% set widgetKey = "post-fb-toc" %}
+
+
+ {# Post Categories #}
+ {% set widgetKey = "post-fb-post-categories" %}
+
+
+ {# Recent Posts #}
+ {% set widgetKey = "post-fb-recent-posts" %}
+
+
+ {# Webmentions #}
+ {% set widgetKey = "post-fb-webmentions" %}
+
+
+ {# Share #}
+ {% set widgetKey = "post-fb-share" %}
+
+
+ {# Subscribe #}
+ {% set widgetKey = "post-fb-subscribe" %}
+
+
+ {# Recent Comments #}
+ {% set widgetKey = "post-fb-recent-comments" %}
+
+{% endif %}
diff --git a/theme/_includes/components/comments.njk b/theme/_includes/components/comments.njk
new file mode 100644
index 0000000..ddf074c
--- /dev/null
+++ b/theme/_includes/components/comments.njk
@@ -0,0 +1,109 @@
+{# Comments section — shown on post pages before webmentions #}
+{# Collapsed when empty, auto-opens when comments exist #}
+{% set absoluteUrl = site.url + page.url %}
+
+
+
+
diff --git a/theme/_includes/components/cv-builder.njk b/theme/_includes/components/cv-builder.njk
new file mode 100644
index 0000000..3699f73
--- /dev/null
+++ b/theme/_includes/components/cv-builder.njk
@@ -0,0 +1,169 @@
+{#
+ CV Page Builder - renders configured layout, sections, and sidebar
+ from cvPageConfig (written by indiekit-endpoint-cv plugin)
+#}
+
+{% set layout = cvPageConfig.layout or "single-column" %}
+{% set hasSidebar = cvPageConfig.sidebar and cvPageConfig.sidebar.length %}
+
+{# CV identity — check cvPageConfig.identity first, fall back to site.author #}
+{% set cvId = cvPageConfig.identity if (cvPageConfig and cvPageConfig.identity) else {} %}
+{% set authorName = cvId.name or site.author.name %}
+{% set authorAvatar = cvId.avatar or site.author.avatar %}
+{% set authorTitle = cvId.title or site.author.title %}
+{% set authorBio = cvId.bio or site.author.bio %}
+{% set authorDescription = cvId.description or '' %}
+{% set socialLinks = cvId.social if (cvId.social and cvId.social.length) else site.social %}
+{% set cvLocality = cvId.locality or site.author.locality %}
+{% set cvCountry = cvId.country or site.author.country %}
+{% set cvOrg = cvId.org or site.author.org %}
+{% set cvUrl = cvId.url or '' %}
+{% set cvEmail = cvId.email or site.author.email %}
+{% set cvKeyUrl = cvId.keyUrl or site.author.keyUrl %}
+
+{# Hero — rendered at top when enabled (default: true) #}
+{% if cvPageConfig.hero.enabled != false %}
+
+
+
+
+
+ {{ authorName }}
+
+ {% if authorTitle %}
+
+ {{ authorTitle }}
+
+ {% endif %}
+ {% if authorBio %}
+
+ {{ authorBio }}
+
+ {% endif %}
+ {% if authorDescription %}
+
+
+ More about me ↓
+
+
+ {{ authorDescription }}
+
+
+ {% endif %}
+ {% from "components/social-icon.njk" import socialIcon, socialIconColorClass %}
+ {% if cvPageConfig.hero.showSocial != false and socialLinks %}
+
+ {% endif %}
+ {# Contact details — location, organization, website, email, PGP #}
+ {% if cvLocality or cvCountry or cvOrg or cvUrl or cvEmail or cvKeyUrl %}
+
+ {% if cvLocality or cvCountry %}
+
{% if cvLocality %}{{ cvLocality }}{% endif %}{% if cvLocality and cvCountry %}, {% endif %}{% if cvCountry %}{{ cvCountry }}{% endif %}
+ {% endif %}
+ {% if cvOrg %}
+
{{ cvOrg }}
+ {% endif %}
+ {% if cvUrl %}
+
{{ cvUrl | replace("https://", "") | replace("http://", "") }}
+ {% endif %}
+ {% if cvEmail %}
+
{{ cvEmail }}
+ {% endif %}
+ {% if cvKeyUrl %}
+
PGP Key
+ {% endif %}
+
+ {% endif %}
+
+
+
+{% endif %}
+
+{# Layout wrapper #}
+{% if layout == "single-column" %}
+
+ {# Single column — no sidebar, full width sections #}
+
+ {% for section in cvPageConfig.sections %}
+ {% include "components/homepage-section.njk" %}
+ {% endfor %}
+
+
+{% elif layout == "two-column" and hasSidebar %}
+
+ {# Two column — sections + sidebar #}
+
+
+{% elif layout == "full-width-hero" %}
+
+ {# Full width hero (already rendered above), then two-column below #}
+ {% if hasSidebar %}
+
+ {% else %}
+
+ {% for section in cvPageConfig.sections %}
+ {% include "components/homepage-section.njk" %}
+ {% endfor %}
+
+ {% endif %}
+
+{% else %}
+
+ {# Fallback — two-column without sidebar, or unknown layout #}
+
+ {% for section in cvPageConfig.sections %}
+ {% include "components/homepage-section.njk" %}
+ {% endfor %}
+
+
+{% endif %}
+
+{# Last Updated #}
+{% if cv.lastUpdated %}
+
+ Last updated: {{ cv.lastUpdated | date("PPP") }}
+
+{% endif %}
+
+{# Footer — rendered after the main layout, full width #}
+{% include "components/cv-footer.njk" %}
diff --git a/theme/_includes/components/cv-footer.njk b/theme/_includes/components/cv-footer.njk
new file mode 100644
index 0000000..c86957e
--- /dev/null
+++ b/theme/_includes/components/cv-footer.njk
@@ -0,0 +1,26 @@
+{# CV Page Builder Footer — renders footer items in a responsive 3-column grid #}
+{% if cvPageConfig.footer and cvPageConfig.footer.length %}
+
+{% endif %}
diff --git a/theme/_includes/components/cv-sidebar.njk b/theme/_includes/components/cv-sidebar.njk
new file mode 100644
index 0000000..d27f903
--- /dev/null
+++ b/theme/_includes/components/cv-sidebar.njk
@@ -0,0 +1,43 @@
+{# CV Page Builder Sidebar — renders widgets from cvPageConfig.sidebar #}
+{% if cvPageConfig.sidebar and cvPageConfig.sidebar.length %}
+ {% for widget in cvPageConfig.sidebar %}
+ {% if widget.type == "author-card" %}
+ {% include "components/widgets/author-card.njk" %}
+ {% elif widget.type == "social-activity" %}
+ {% include "components/widgets/social-activity.njk" %}
+ {% elif widget.type == "github-repos" %}
+ {% include "components/widgets/github-repos.njk" %}
+ {% elif widget.type == "funkwhale" %}
+ {% include "components/widgets/funkwhale.njk" %}
+ {% elif widget.type == "recent-posts" %}
+ {% include "components/widgets/recent-posts.njk" %}
+ {% elif widget.type == "blogroll" %}
+ {% include "components/widgets/blogroll.njk" %}
+ {% elif widget.type == "feedland" %}
+ {% include "components/widgets/feedland.njk" %}
+ {% elif widget.type == "categories" %}
+ {% include "components/widgets/categories.njk" %}
+ {% elif widget.type == "search" %}
+ {% include "components/widgets/search.njk" %}
+ {% elif widget.type == "webmentions" %}
+ {% include "components/widgets/webmentions.njk" %}
+ {% elif widget.type == "custom-html" %}
+ {# Custom content widget #}
+ {% set wConfig = widget.config or {} %}
+
+
+
+ {% else %}
+
+ {% endif %}
+ {% endfor %}
+{% endif %}
diff --git a/theme/_includes/components/empty-collection.njk b/theme/_includes/components/empty-collection.njk
new file mode 100644
index 0000000..8198853
--- /dev/null
+++ b/theme/_includes/components/empty-collection.njk
@@ -0,0 +1,27 @@
+{# Empty collection placeholder — encourages creating content #}
+{# Usage: {% include "components/empty-collection.njk" %} with postType set before include #}
+{% set typeInfo = null %}
+{% for pt in enabledPostTypes %}
+ {% if pt.type == postType %}{% set typeInfo = pt %}{% endif %}
+{% endfor %}
+
+
+
+
No {{ title | lower }} yet
+
+ This is where your {{ title | lower }} will appear once you start creating content.
+
+ {% if typeInfo %}
+
+
+
+
+ Create your first {{ postType }}
+
+ {% endif %}
+
diff --git a/theme/_includes/components/fediverse-modal.njk b/theme/_includes/components/fediverse-modal.njk
new file mode 100644
index 0000000..30699e0
--- /dev/null
+++ b/theme/_includes/components/fediverse-modal.njk
@@ -0,0 +1,81 @@
+{# Shared fediverse instance picker modal #}
+{# Used by post.njk (interact), fediverse-follow.njk (follow), share.njk (share) #}
+{# Requires: modalTitle, modalDescription variables set before include #}
+
+
+ {# Backdrop #}
+
+ {# Panel #}
+
+
{{ modalTitle }}
+
{{ modalDescription }}
+
+ {# Saved domains list #}
+
+
+
+
Use a different instance
+
+
+ Cancel
+
+
+
+
+
+ {# New domain input #}
+
+
+
+
+
+
+
+
+
+
+ Go
+
+
+
+
+
+
+
diff --git a/theme/_includes/components/funkwhale-stats-content.njk b/theme/_includes/components/funkwhale-stats-content.njk
new file mode 100644
index 0000000..b7123a2
--- /dev/null
+++ b/theme/_includes/components/funkwhale-stats-content.njk
@@ -0,0 +1,66 @@
+{# Stats Summary Cards #}
+{% if summary %}
+
+
+ {{ summary.totalPlays or 0 }}
+ Plays
+
+
+ {{ summary.uniqueTracks or 0 }}
+ Tracks
+
+
+ {{ summary.uniqueArtists or 0 }}
+ Artists
+
+
+ {{ summary.totalDurationFormatted or '0m' }}
+ Listened
+
+
+{% endif %}
+
+{# Top Artists #}
+{% if topArtists and topArtists.length %}
+
+
Top Artists
+
+ {% for artist in topArtists | head(5) %}
+
+ {{ loop.index }}
+ {{ artist.name }}
+ {{ artist.playCount }} plays
+
+ {% endfor %}
+
+
+{% endif %}
+
+{# Top Albums #}
+{% if topAlbums and topAlbums.length %}
+
+
Top Albums
+
+ {% for album in topAlbums | head(5) %}
+
+ {% if album.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
{{ album.title }}
+
{{ album.artist }}
+
{{ album.playCount }} plays
+
+ {% endfor %}
+
+
+{% endif %}
+
+{% if not summary and not topArtists and not topAlbums %}
+No statistics available for this period.
+{% endif %}
diff --git a/theme/_includes/components/h-card.njk b/theme/_includes/components/h-card.njk
new file mode 100644
index 0000000..8e1dc83
--- /dev/null
+++ b/theme/_includes/components/h-card.njk
@@ -0,0 +1,111 @@
+{# h-card - IndieWeb identity microformat #}
+{# See: https://microformats.org/wiki/h-card #}
+{#
+ This is the canonical h-card component for the site.
+ Include in sidebar widgets, author cards, etc.
+#}
+{% set id = homepageConfig.identity if (homepageConfig and homepageConfig.identity) else {} %}
+{% set authorName = id.name or site.author.name %}
+{% set authorAvatar = id.avatar or site.author.avatar %}
+{% set authorTitle = id.title or site.author.title %}
+{% set authorBio = id.bio or site.author.bio %}
+{% set authorUrl = id.url or site.author.url %}
+{% set authorPronoun = id.pronoun or site.author.pronoun %}
+{% set authorLocality = id.locality or site.author.locality %}
+{% set authorCountry = id.country or site.author.country %}
+{% set authorLocation = site.author.location %}
+{% set authorOrg = id.org or site.author.org %}
+{% set authorEmail = id.email or site.author.email %}
+{% set authorKeyUrl = id.keyUrl or site.author.keyUrl %}
+{% set authorCategories = id.categories if (id.categories and id.categories.length) else site.author.categories %}
+{% set socialLinks = id.social if (id.social and id.social.length) else site.social %}
+
+
+ {# Hidden u-photo for reliable microformat parsing (some parsers struggle with img inside links) #}
+
+
+
+
+
+
+
+
+ {{ authorName }}
+
+ {% if authorPronoun %}
+
({{ authorPronoun }})
+ {% endif %}
+
{{ authorTitle }}
+ {# Structured address #}
+
+ {% if authorLocality %}
+ {{ authorLocality }} {% if authorCountry %}, {% endif %}
+ {% endif %}
+ {% if authorCountry %}
+ {{ authorCountry }}
+ {% endif %}
+ {# Fallback to legacy location field #}
+ {% if not authorLocality and authorLocation %}
+ {{ authorLocation }}
+ {% endif %}
+
+
+
+
+ {# Bio #}
+
{{ authorBio }}
+
+ {# Organization #}
+ {% if authorOrg %}
+
+ {{ authorOrg }}
+
+ {% endif %}
+
+ {# Email and PGP Key #}
+
+ {% if authorEmail %}
+ {# Display text obfuscated to deter spam harvesters; href kept plain for browser compatibility #}
+
+ ✉️ {{ authorEmail | obfuscateEmail | safe }}
+
+ {% endif %}
+ {% if authorKeyUrl %}
+
+ 🔐 PGP Key
+
+ {% endif %}
+
+
+ {# Categories / Skills #}
+ {% if authorCategories and authorCategories.length %}
+
+ {% for category in authorCategories %}
+ {{ category }}
+ {% endfor %}
+
+ {% endif %}
+
+ {# Social links with rel="me" - critical for IndieWeb identity verification #}
+ {% from "components/social-icon.njk" import socialIcon %}
+ {% if socialLinks and socialLinks.length %}
+
+ {% for link in socialLinks %}
+
+ {{ socialIcon(link.icon, "w-5 h-5") }}
+
+ {% endfor %}
+
+ {% endif %}
+
diff --git a/theme/_includes/components/homepage-builder.njk b/theme/_includes/components/homepage-builder.njk
new file mode 100644
index 0000000..b0ba263
--- /dev/null
+++ b/theme/_includes/components/homepage-builder.njk
@@ -0,0 +1,86 @@
+{#
+ Homepage Builder - renders configured layout, sections, and sidebar
+ from homepageConfig (written by indiekit-endpoint-homepage plugin)
+#}
+
+{% set layout = homepageConfig.layout or "two-column" %}
+{% set hasSidebar = homepageConfig.sidebar and homepageConfig.sidebar.length %}
+
+{# Hero — rendered before layout wrapper when enabled #}
+{% if homepageConfig.hero and homepageConfig.hero.enabled %}
+ {% include "components/sections/hero.njk" %}
+{% endif %}
+
+{# Layout wrapper #}
+{% if layout == "single-column" %}
+
+ {# Single column — no sidebar, full width sections #}
+
+ {% for section in homepageConfig.sections %}
+ {% if section.type != "hero" %}
+ {% include "components/homepage-section.njk" %}
+ {% endif %}
+ {% endfor %}
+
+
+{% elif layout == "two-column" and hasSidebar %}
+
+ {# Two column — sections + sidebar #}
+
+
+{% elif layout == "full-width-hero" %}
+
+ {# Full width hero (already rendered above), then two-column below #}
+ {% if hasSidebar %}
+
+ {% else %}
+
+ {% for section in homepageConfig.sections %}
+ {% if section.type != "hero" %}
+ {% include "components/homepage-section.njk" %}
+ {% endif %}
+ {% endfor %}
+
+ {% endif %}
+
+{% else %}
+
+ {# Fallback — two-column without sidebar, or unknown layout #}
+
+ {% for section in homepageConfig.sections %}
+ {% if section.type != "hero" %}
+ {% include "components/homepage-section.njk" %}
+ {% endif %}
+ {% endfor %}
+
+
+{% endif %}
+
+{# Footer — rendered after the main layout, full width #}
+{% include "components/homepage-footer.njk" %}
diff --git a/theme/_includes/components/homepage-footer.njk b/theme/_includes/components/homepage-footer.njk
new file mode 100644
index 0000000..d6f917b
--- /dev/null
+++ b/theme/_includes/components/homepage-footer.njk
@@ -0,0 +1,26 @@
+{# Homepage Builder Footer — renders footer items in a responsive 3-column grid #}
+{% if homepageConfig.footer and homepageConfig.footer.length %}
+
+{% endif %}
diff --git a/theme/_includes/components/homepage-section.njk b/theme/_includes/components/homepage-section.njk
new file mode 100644
index 0000000..e7b8ee0
--- /dev/null
+++ b/theme/_includes/components/homepage-section.njk
@@ -0,0 +1,56 @@
+{# Homepage Section Dispatcher — maps section.type to the right partial #}
+{% if section.type == "featured-posts" %}
+ {% include "components/sections/featured-posts.njk" %}
+{% elif section.type == "recent-posts" %}
+ {% include "components/sections/recent-posts.njk" %}
+{% elif section.type == "custom-html" %}
+ {% include "components/sections/custom-html.njk" %}
+{% elif section.type == "cv-experience" %}
+ {% include "components/sections/cv-experience.njk" ignore missing %}
+{% elif section.type == "cv-projects" %}
+ {% include "components/sections/cv-projects.njk" ignore missing %}
+{% elif section.type == "cv-projects-personal" %}
+ {% include "components/sections/cv-projects-personal.njk" ignore missing %}
+{% elif section.type == "cv-projects-work" %}
+ {% include "components/sections/cv-projects-work.njk" ignore missing %}
+{% elif section.type == "cv-skills" %}
+ {% include "components/sections/cv-skills.njk" ignore missing %}
+{% elif section.type == "cv-education" %}
+ {% include "components/sections/cv-education.njk" ignore missing %}
+{% elif section.type == "cv-interests" %}
+ {% include "components/sections/cv-interests.njk" ignore missing %}
+{% elif section.type == "cv-experience-personal" %}
+ {% include "components/sections/cv-experience-personal.njk" ignore missing %}
+{% elif section.type == "cv-experience-work" %}
+ {% include "components/sections/cv-experience-work.njk" ignore missing %}
+{% elif section.type == "cv-education-personal" %}
+ {% include "components/sections/cv-education-personal.njk" ignore missing %}
+{% elif section.type == "cv-education-work" %}
+ {% include "components/sections/cv-education-work.njk" ignore missing %}
+{% elif section.type == "cv-skills-personal" %}
+ {% include "components/sections/cv-skills-personal.njk" ignore missing %}
+{% elif section.type == "cv-skills-work" %}
+ {% include "components/sections/cv-skills-work.njk" ignore missing %}
+{% elif section.type == "cv-interests-personal" %}
+ {% include "components/sections/cv-interests-personal.njk" ignore missing %}
+{% elif section.type == "cv-interests-work" %}
+ {% include "components/sections/cv-interests-work.njk" ignore missing %}
+{% elif section.type == "cv-languages" %}
+ {% include "components/sections/cv-languages.njk" ignore missing %}
+{% elif section.type == "blogroll" %}
+ {% include "components/sections/blogroll.njk" ignore missing %}
+{% elif section.type == "podroll" %}
+ {% include "components/sections/podroll.njk" ignore missing %}
+{% elif section.type == "github-activity" %}
+ {% include "components/sections/github-activity.njk" ignore missing %}
+{% elif section.type == "youtube" %}
+ {% include "components/sections/youtube.njk" ignore missing %}
+{% elif section.type == "funkwhale" %}
+ {% include "components/sections/funkwhale.njk" ignore missing %}
+{% elif section.type == "lastfm" %}
+ {% include "components/sections/lastfm.njk" ignore missing %}
+{% elif section.type == "posting-activity" %}
+ {% include "components/sections/posting-activity.njk" ignore missing %}
+{% else %}
+
+{% endif %}
diff --git a/theme/_includes/components/homepage-sidebar.njk b/theme/_includes/components/homepage-sidebar.njk
new file mode 100644
index 0000000..5fec890
--- /dev/null
+++ b/theme/_includes/components/homepage-sidebar.njk
@@ -0,0 +1,137 @@
+{# Homepage Builder Sidebar — renders widgets from homepageConfig.sidebar #}
+{# Each widget is wrapped in a collapsible container with localStorage persistence #}
+{% from "components/icon.njk" import icon %}
+
+{% if homepageConfig.sidebar and homepageConfig.sidebar.length %}
+ {% for widget in homepageConfig.sidebar %}
+
+ {# Resolve widget title #}
+ {% if widget.type == "search" %}{% set widgetTitle = "Search" %}
+ {% elif widget.type == "social-activity" %}{% set widgetTitle = "Social Activity" %}
+ {% elif widget.type == "github-repos" %}{% set widgetTitle = "GitHub" %}
+ {% elif widget.type == "funkwhale" %}{% set widgetTitle = "Listening" %}
+ {% elif widget.type == "recent-posts" %}{% set widgetTitle = "Recent Posts" %}
+ {% elif widget.type == "blogroll" %}{% set widgetTitle = "Blogroll" %}
+ {% elif widget.type == "feedland" %}{% set widgetTitle = "FeedLand" %}
+ {% elif widget.type == "categories" %}{% set widgetTitle = "Categories" %}
+ {% elif widget.type == "webmentions" %}{% set widgetTitle = "Webmentions" %}
+ {% elif widget.type == "recent-comments" %}{% set widgetTitle = "Recent Comments" %}
+ {% elif widget.type == "fediverse-follow" %}{% set widgetTitle = "Fediverse" %}
+ {% elif widget.type == "author-card" %}{% set widgetTitle = "Author" %}
+ {% elif widget.type == "custom-html" %}{% set widgetTitle = (widget.config.title if widget.config and widget.config.title) or "Custom" %}
+ {% else %}{% set widgetTitle = widget.type %}
+ {% endif %}
+
+ {# Resolve widget icon and accent border #}
+ {% if widget.type == "social-activity" %}
+ {% set widgetIcon = "globe" %}{% set widgetIconClass = "w-5 h-5 text-[#0085ff]" %}{% set widgetBorder = "border-l-[3px] border-l-[#0085ff]" %}
+ {% elif widget.type == "github-repos" %}
+ {% set widgetIcon = "github" %}{% set widgetIconClass = "w-5 h-5 text-surface-800 dark:text-surface-200" %}{% set widgetBorder = "border-l-[3px] border-l-surface-400 dark:border-l-surface-500" %}
+ {% elif widget.type == "funkwhale" %}
+ {% set widgetIcon = "headphones" %}{% set widgetIconClass = "w-5 h-5 text-purple-500" %}{% set widgetBorder = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
+ {% elif widget.type == "blogroll" %}
+ {% set widgetIcon = "book-open" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif widget.type == "feedland" %}
+ {% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif widget.type == "subscribe" %}
+ {% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-orange-500" %}{% set widgetBorder = "border-l-[3px] border-l-orange-400 dark:border-l-orange-500" %}
+ {% elif widget.type == "fediverse-follow" %}
+ {% set widgetIcon = "user-plus" %}{% set widgetIconClass = "w-5 h-5 text-[#a730b8]" %}{% set widgetBorder = "border-l-[3px] border-l-[#a730b8]" %}
+ {% elif widget.type == "author-card" %}
+ {% set widgetIcon = "user" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "recent-posts" %}
+ {% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "categories" %}
+ {% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "recent-comments" %}
+ {% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "search" %}
+ {% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "webmentions" %}
+ {% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% else %}
+ {% set widgetIcon = "" %}{% set widgetIconClass = "" %}{% set widgetBorder = "" %}
+ {% endif %}
+
+ {% set widgetKey = "widget-" + widget.type + "-" + loop.index0 %}
+ {% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
+
+ {# Collapsible wrapper — Alpine.js handles toggle, localStorage persists state #}
+
+
+ {% endfor %}
+{% endif %}
diff --git a/theme/_includes/components/icon.njk b/theme/_includes/components/icon.njk
new file mode 100644
index 0000000..d538ddd
--- /dev/null
+++ b/theme/_includes/components/icon.njk
@@ -0,0 +1,67 @@
+{#
+ Centralized UI icon macro
+ Usage: {% from "components/icon.njk" import icon %}
+ {{ icon("heart", "w-5 h-5 text-red-500") }}
+
+ All icons use stroke-width="2" unless they are filled icons.
+ Default size: w-5 h-5 (override via cssClass parameter)
+#}
+
+{% macro icon(name, cssClass) %}
+{% set cls = cssClass or "w-5 h-5" %}
+{%- if name == "heart" -%}
+
+{%- elif name == "bookmark" -%}
+
+{%- elif name == "repost" -%}
+
+{%- elif name == "reply" -%}
+
+{%- elif name == "camera" -%}
+
+{%- elif name == "article" -%}
+
+{%- elif name == "note" -%}
+
+{%- elif name == "music" -%}
+
+{%- elif name == "tag" -%}
+
+{%- elif name == "rss" -%}
+
+{%- elif name == "chat" -%}
+
+{%- elif name == "user" -%}
+
+{%- elif name == "search" -%}
+
+{%- elif name == "star" -%}
+
+{%- elif name == "external-link" -%}
+
+{%- elif name == "chevron-down" -%}
+
+{%- elif name == "chevron-right" -%}
+
+{%- elif name == "globe" -%}
+
+{%- elif name == "github" -%}
+
+{%- elif name == "list" -%}
+
+{%- elif name == "share" -%}
+
+{%- elif name == "book-open" -%}
+
+{%- elif name == "headphones" -%}
+
+{%- elif name == "mail" -%}
+
+{%- elif name == "podcast" -%}
+
+{%- elif name == "user-plus" -%}
+
+{%- else -%}
+
+{%- endif -%}
+{% endmacro %}
diff --git a/theme/_includes/components/post-navigation.njk b/theme/_includes/components/post-navigation.njk
new file mode 100644
index 0000000..0103704
--- /dev/null
+++ b/theme/_includes/components/post-navigation.njk
@@ -0,0 +1,103 @@
+{# Post Navigation - Previous/Next (image-first, inspired by zachleat.com) #}
+{% set _prevPost = collections.posts | previousInCollection(page) %}
+{% set _nextPost = collections.posts | nextInCollection(page) %}
+
+{% if _prevPost or _nextPost %}
+
+
+
+ {# ── Previous Post ── #}
+ {% if _prevPost %}
+ {% set _prevOgSlug = _prevPost.url | ogSlug %}
+ {% set _prevHasOg = _prevOgSlug | hasOgImage %}
+ {% set _prevTitle = _prevPost.data.title or _prevPost.data.name %}
+
+ {# Derive display text for non-article post types #}
+ {% set _likedUrl = _prevPost.data.likeOf or _prevPost.data.like_of %}
+ {% set _bookmarkedUrl = _prevPost.data.bookmarkOf or _prevPost.data.bookmark_of %}
+ {% set _repostedUrl = _prevPost.data.repostOf or _prevPost.data.repost_of %}
+ {% set _replyToUrl = _prevPost.data.inReplyTo or _prevPost.data.in_reply_to %}
+ {% if not _prevTitle %}
+ {% if _likedUrl %}
+ {% set _prevTitle = "Liked " + (_likedUrl | replace("https://", "") | truncate(40)) %}
+ {% elif _bookmarkedUrl %}
+ {% set _prevTitle = "Bookmarked " + (_bookmarkedUrl | replace("https://", "") | truncate(35)) %}
+ {% elif _repostedUrl %}
+ {% set _prevTitle = "Reposted " + (_repostedUrl | replace("https://", "") | truncate(35)) %}
+ {% elif _replyToUrl %}
+ {% set _prevTitle = "Reply to " + (_replyToUrl | replace("https://", "") | truncate(35)) %}
+ {% else %}
+ {% set _prevTitle = (_prevPost.templateContent | striptags | truncate(60)) or "Note" %}
+ {% endif %}
+ {% endif %}
+
+
+ {% if _prevHasOg %}
+
+
+ ← Previous
+
+ {% else %}
+
+ ← Previous
+
+ {{ _prevTitle }}
+
+ {{ _prevPost.date | dateDisplay }}
+
+ {% endif %}
+
+
+ {% else %}
+
+ {% endif %}
+
+ {# ── Next Post ── #}
+ {% if _nextPost %}
+ {% set _nextOgSlug = _nextPost.url | ogSlug %}
+ {% set _nextHasOg = _nextOgSlug | hasOgImage %}
+ {% set _nextTitle = _nextPost.data.title or _nextPost.data.name %}
+
+ {# Derive display text for non-article post types #}
+ {% set _likedUrl = _nextPost.data.likeOf or _nextPost.data.like_of %}
+ {% set _bookmarkedUrl = _nextPost.data.bookmarkOf or _nextPost.data.bookmark_of %}
+ {% set _repostedUrl = _nextPost.data.repostOf or _nextPost.data.repost_of %}
+ {% set _replyToUrl = _nextPost.data.inReplyTo or _nextPost.data.in_reply_to %}
+ {% if not _nextTitle %}
+ {% if _likedUrl %}
+ {% set _nextTitle = "Liked " + (_likedUrl | replace("https://", "") | truncate(40)) %}
+ {% elif _bookmarkedUrl %}
+ {% set _nextTitle = "Bookmarked " + (_bookmarkedUrl | replace("https://", "") | truncate(35)) %}
+ {% elif _repostedUrl %}
+ {% set _nextTitle = "Reposted " + (_repostedUrl | replace("https://", "") | truncate(35)) %}
+ {% elif _replyToUrl %}
+ {% set _nextTitle = "Reply to " + (_replyToUrl | replace("https://", "") | truncate(35)) %}
+ {% else %}
+ {% set _nextTitle = (_nextPost.templateContent | striptags | truncate(60)) or "Note" %}
+ {% endif %}
+ {% endif %}
+
+
+ {% if _nextHasOg %}
+
+
+ Next →
+
+ {% else %}
+
+ Next →
+
+ {{ _nextTitle }}
+
+ {{ _nextPost.date | dateDisplay }}
+
+ {% endif %}
+
+
+ {% else %}
+
+ {% endif %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/reply-context.njk b/theme/_includes/components/reply-context.njk
new file mode 100644
index 0000000..bfa2781
--- /dev/null
+++ b/theme/_includes/components/reply-context.njk
@@ -0,0 +1,74 @@
+{# Reply Context Component #}
+{# Displays rich context for replies, likes, reposts, and bookmarks #}
+{# Uses h-cite microformat for citing external content #}
+{# Includes unfurl card for rich link preview (OpenGraph metadata) #}
+
+{# Support both camelCase (Indiekit Eleventy preset) and underscore (legacy) property names #}
+{% set replyTo = inReplyTo or in_reply_to %}
+{% set likedUrl = likeOf or like_of %}
+{% set repostedUrl = repostOf or repost_of %}
+{% set bookmarkedUrl = bookmarkOf or bookmark_of %}
+
+{% if replyTo or likedUrl or repostedUrl or bookmarkedUrl %}
+
+ {% if replyTo %}
+
+ {% endif %}
+
+ {% if likedUrl %}
+
+ {% endif %}
+
+ {% if repostedUrl %}
+
+
+
+
+
+ Reposted:
+
+ {% unfurl repostedUrl %}
+
+ {{ repostedUrl }}
+
+
+ {% endif %}
+
+ {% if bookmarkedUrl %}
+
+ {% endif %}
+
+{% endif %}
diff --git a/theme/_includes/components/sections/custom-html.njk b/theme/_includes/components/sections/custom-html.njk
new file mode 100644
index 0000000..5045160
--- /dev/null
+++ b/theme/_includes/components/sections/custom-html.njk
@@ -0,0 +1,18 @@
+{#
+ Custom HTML Section - freeform HTML/markdown content block
+ Rendered by homepage-builder when custom-html section is configured
+#}
+
+{% set sectionConfig = section.config or {} %}
+
+
+ {% if sectionConfig.title %}
+
+ {{ sectionConfig.title }}
+
+ {% endif %}
+
+
+ {{ sectionConfig.content | safe }}
+
+
diff --git a/theme/_includes/components/sections/cv-education-personal.njk b/theme/_includes/components/sections/cv-education-personal.njk
new file mode 100644
index 0000000..fd937a7
--- /dev/null
+++ b/theme/_includes/components/sections/cv-education-personal.njk
@@ -0,0 +1,2 @@
+{% set filterType = "personal" %}
+{% include "components/sections/cv-education.njk" %}
diff --git a/theme/_includes/components/sections/cv-education-work.njk b/theme/_includes/components/sections/cv-education-work.njk
new file mode 100644
index 0000000..6d2e3ea
--- /dev/null
+++ b/theme/_includes/components/sections/cv-education-work.njk
@@ -0,0 +1,2 @@
+{% set filterType = "work" %}
+{% include "components/sections/cv-education.njk" %}
diff --git a/theme/_includes/components/sections/cv-education.njk b/theme/_includes/components/sections/cv-education.njk
new file mode 100644
index 0000000..3ff1ecf
--- /dev/null
+++ b/theme/_includes/components/sections/cv-education.njk
@@ -0,0 +1,88 @@
+{#
+ CV Education Section - collapsible education cards (accordion)
+ Data fetched from /cv/data.json via homepage plugin
+ Each card gets a distinct color via cycling palette
+#}
+
+{% set hasEducation = cv and cv.education and cv.education.length %}
+
+{% if hasEducation %}
+
+
+ {{ section.config.title or "Education" }}
+
+
+
+ {% for item in cv.education %}
+ {% if not filterType or item.educationType == filterType or not item.educationType %}
+ {% set ci = loop.index0 % 8 %}
+
+ {# Summary row — always visible, clickable #}
+
+
+
{{ item.degree }}
+
+ {{ item.institution }}{% if item.location %} · {{ item.location }}{% endif %}
+
+
+
+ {% if item.startDate %}
+
+ {{ item.startDate }}{% if item.endDate %} – {{ item.endDate }}{% else %} – Present{% endif %}
+
+ {% elif item.year %}
+
{{ item.year }}
+ {% endif %}
+
+
+
+
+
+
+ {# Detail section — collapsible #}
+
+ {% if item.startDate %}
+
+ {{ item.startDate }}{% if item.endDate %} – {{ item.endDate }}{% else %} – Present{% endif %}
+
+ {% elif item.year %}
+
{{ item.year }}
+ {% endif %}
+
+ {% if item.description %}
+
{{ item.description }}
+ {% endif %}
+
+
+ {% endif %}
+ {% endfor %}
+
+
+{% endif %}
diff --git a/theme/_includes/components/sections/cv-experience-personal.njk b/theme/_includes/components/sections/cv-experience-personal.njk
new file mode 100644
index 0000000..7d4c858
--- /dev/null
+++ b/theme/_includes/components/sections/cv-experience-personal.njk
@@ -0,0 +1,2 @@
+{% set filterType = "personal" %}
+{% include "components/sections/cv-experience.njk" %}
diff --git a/theme/_includes/components/sections/cv-experience-work.njk b/theme/_includes/components/sections/cv-experience-work.njk
new file mode 100644
index 0000000..57b91b3
--- /dev/null
+++ b/theme/_includes/components/sections/cv-experience-work.njk
@@ -0,0 +1,2 @@
+{% set filterType = "work" %}
+{% include "components/sections/cv-experience.njk" %}
diff --git a/theme/_includes/components/sections/cv-experience.njk b/theme/_includes/components/sections/cv-experience.njk
new file mode 100644
index 0000000..b9ad71d
--- /dev/null
+++ b/theme/_includes/components/sections/cv-experience.njk
@@ -0,0 +1,48 @@
+{#
+ CV Experience Section - work experience timeline
+ Data fetched from /cv/data.json via homepage plugin
+#}
+
+{% set sectionConfig = section.config or {} %}
+{% set maxItems = sectionConfig.maxItems or 10 %}
+{% set showHighlights = sectionConfig.showHighlights if sectionConfig.showHighlights is defined else true %}
+
+{% if cv and cv.experience and cv.experience.length %}
+
+
+ {{ sectionConfig.title or "Experience" }}
+
+
+
+ {% for item in cv.experience | head(maxItems) %}
+ {% if not filterType or item.experienceType == filterType or not item.experienceType %}
+
+
+
{{ item.title }}
+
+ {{ item.company }}{% if item.location %} · {{ item.location }}{% endif %}
+ {% if item.type %} · {{ item.type }} {% endif %}
+
+ {% if item.startDate %}
+
+ {{ item.startDate }}{% if item.endDate %} – {{ item.endDate }}{% else %} – Present{% endif %}
+
+ {% endif %}
+ {% if item.description %}
+
{{ item.description }}
+ {% endif %}
+ {% if showHighlights and item.highlights and item.highlights.length %}
+
+ {% for h in item.highlights %}
+
+ {{ h }}
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% endif %}
+ {% endfor %}
+
+
+{% endif %}
diff --git a/theme/_includes/components/sections/cv-interests-personal.njk b/theme/_includes/components/sections/cv-interests-personal.njk
new file mode 100644
index 0000000..0d053a3
--- /dev/null
+++ b/theme/_includes/components/sections/cv-interests-personal.njk
@@ -0,0 +1,2 @@
+{% set filterType = "personal" %}
+{% include "components/sections/cv-interests.njk" %}
diff --git a/theme/_includes/components/sections/cv-interests-work.njk b/theme/_includes/components/sections/cv-interests-work.njk
new file mode 100644
index 0000000..7cbe270
--- /dev/null
+++ b/theme/_includes/components/sections/cv-interests-work.njk
@@ -0,0 +1,2 @@
+{% set filterType = "work" %}
+{% include "components/sections/cv-interests.njk" %}
diff --git a/theme/_includes/components/sections/cv-interests.njk b/theme/_includes/components/sections/cv-interests.njk
new file mode 100644
index 0000000..997f791
--- /dev/null
+++ b/theme/_includes/components/sections/cv-interests.njk
@@ -0,0 +1,50 @@
+{#
+ CV Interests Section - interests grouped by category
+ Data fetched from /cv/data.json via homepage plugin
+ Each family gets a distinct color via cycling palette
+#}
+
+{% if cv and cv.interests and (cv.interests | dictsort | length) %}
+
+
+ {{ section.config.title or "Interests" }}
+
+
+
+ {% for category, items in cv.interests %}
+ {% if not filterType or (cv.interestTypes and cv.interestTypes[category] == filterType) or not cv.interestTypes or not cv.interestTypes[category] %}
+ {# Cycle through 8 distinct colors per family using loop.index0 #}
+ {% set ci = loop.index0 % 8 %}
+
+
+ {{ category }}
+
+
+ {% for interest in items %}
+ {% if ci == 0 %}
+
+ {% elif ci == 1 %}
+
+ {% elif ci == 2 %}
+
+ {% elif ci == 3 %}
+
+ {% elif ci == 4 %}
+
+ {% elif ci == 5 %}
+
+ {% elif ci == 6 %}
+
+ {% elif ci == 7 %}
+
+ {% endif %}
+ {{ interest }}
+
+ {% endfor %}
+
+
+ {% endif %}
+ {% endfor %}
+
+
+{% endif %}
diff --git a/theme/_includes/components/sections/cv-languages.njk b/theme/_includes/components/sections/cv-languages.njk
new file mode 100644
index 0000000..438905f
--- /dev/null
+++ b/theme/_includes/components/sections/cv-languages.njk
@@ -0,0 +1,21 @@
+{#
+ CV Languages Section
+ Data fetched from /cv/data.json via homepage plugin
+#}
+
+{% if cv and cv.languages and cv.languages.length %}
+
+
+ {{ section.config.title or "Languages" }}
+
+
+
+ {% for lang in cv.languages %}
+
+ {{ lang.name }}
+ {{ lang.level }}
+
+ {% endfor %}
+
+
+{% endif %}
diff --git a/theme/_includes/components/sections/cv-projects-personal.njk b/theme/_includes/components/sections/cv-projects-personal.njk
new file mode 100644
index 0000000..e3e6ab0
--- /dev/null
+++ b/theme/_includes/components/sections/cv-projects-personal.njk
@@ -0,0 +1,124 @@
+{#
+ CV Personal Projects Section - collapsible project cards (accordion)
+ Filters projects by projectType == "personal" (or unset)
+ Data fetched from /cv/data.json via homepage plugin
+#}
+
+{% set sectionConfig = section.config or {} %}
+{% set maxItems = sectionConfig.maxItems or 10 %}
+{% set showTechnologies = sectionConfig.showTechnologies if sectionConfig.showTechnologies is defined else true %}
+
+{% set personalProjects = [] %}
+{% if cv and cv.projects %}
+ {% for item in cv.projects %}
+ {% if item.projectType == "personal" or not item.projectType %}
+ {% set personalProjects = (personalProjects.push(item), personalProjects) %}
+ {% endif %}
+ {% endfor %}
+{% endif %}
+
+{% if personalProjects.length %}
+
+
+ {{ sectionConfig.title or "Personal Projects" }}
+
+
+
+ {% for item in personalProjects | head(maxItems) %}
+ {% set ci = loop.index0 % 8 %}
+
+ {# Summary row — always visible, clickable #}
+
+
+
+ {% if item.url %}
+ {{ item.name }}
+ {% else %}
+ {{ item.name }}
+ {% endif %}
+
+ {% if item.status %}
+
+ {{ item.status }}
+
+ {% endif %}
+
+
+ {% if item.startDate %}
+
+ {{ item.startDate }}{% if item.endDate %} – {{ item.endDate }}{% else %} – Present{% endif %}
+
+ {% endif %}
+
+
+
+
+
+
+ {# Detail section — collapsible #}
+
+ {% if item.startDate %}
+
+ {{ item.startDate }}{% if item.endDate %} – {{ item.endDate }}{% else %} – Present{% endif %}
+
+ {% endif %}
+
+ {% if item.description %}
+
{{ item.description }}
+ {% endif %}
+
+ {% if showTechnologies and item.technologies and item.technologies.length %}
+
+ {% for tech in item.technologies %}
+
+ {{ tech }}
+
+ {% endfor %}
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+{% endif %}
diff --git a/theme/_includes/components/sections/cv-projects-work.njk b/theme/_includes/components/sections/cv-projects-work.njk
new file mode 100644
index 0000000..47d7014
--- /dev/null
+++ b/theme/_includes/components/sections/cv-projects-work.njk
@@ -0,0 +1,124 @@
+{#
+ CV Work Projects Section - collapsible project cards (accordion)
+ Filters projects by projectType == "work"
+ Data fetched from /cv/data.json via homepage plugin
+#}
+
+{% set sectionConfig = section.config or {} %}
+{% set maxItems = sectionConfig.maxItems or 10 %}
+{% set showTechnologies = sectionConfig.showTechnologies if sectionConfig.showTechnologies is defined else true %}
+
+{% set workProjects = [] %}
+{% if cv and cv.projects %}
+ {% for item in cv.projects %}
+ {% if item.projectType == "work" %}
+ {% set workProjects = (workProjects.push(item), workProjects) %}
+ {% endif %}
+ {% endfor %}
+{% endif %}
+
+{% if workProjects.length %}
+
+
+ {{ sectionConfig.title or "Work Projects" }}
+
+
+
+ {% for item in workProjects | head(maxItems) %}
+ {% set ci = loop.index0 % 8 %}
+
+ {# Summary row — always visible, clickable #}
+
+
+
+ {% if item.url %}
+ {{ item.name }}
+ {% else %}
+ {{ item.name }}
+ {% endif %}
+
+ {% if item.status %}
+
+ {{ item.status }}
+
+ {% endif %}
+
+
+ {% if item.startDate %}
+
+ {{ item.startDate }}{% if item.endDate %} – {{ item.endDate }}{% else %} – Present{% endif %}
+
+ {% endif %}
+
+
+
+
+
+
+ {# Detail section — collapsible #}
+
+ {% if item.startDate %}
+
+ {{ item.startDate }}{% if item.endDate %} – {{ item.endDate }}{% else %} – Present{% endif %}
+
+ {% endif %}
+
+ {% if item.description %}
+
{{ item.description }}
+ {% endif %}
+
+ {% if showTechnologies and item.technologies and item.technologies.length %}
+
+ {% for tech in item.technologies %}
+
+ {{ tech }}
+
+ {% endfor %}
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+{% endif %}
diff --git a/theme/_includes/components/sections/cv-projects.njk b/theme/_includes/components/sections/cv-projects.njk
new file mode 100644
index 0000000..99cc152
--- /dev/null
+++ b/theme/_includes/components/sections/cv-projects.njk
@@ -0,0 +1,114 @@
+{#
+ CV Projects Section - collapsible project cards (accordion)
+ Data fetched from /cv/data.json via homepage plugin
+#}
+
+{% set sectionConfig = section.config or {} %}
+{% set maxItems = sectionConfig.maxItems or 10 %}
+{% set showTechnologies = sectionConfig.showTechnologies if sectionConfig.showTechnologies is defined else true %}
+
+{% if cv and cv.projects and cv.projects.length %}
+
+
+ {{ sectionConfig.title or "Projects" }}
+
+
+
+ {% for item in cv.projects | head(maxItems) %}
+ {% set ci = loop.index0 % 8 %}
+
+ {# Summary row — always visible, clickable #}
+
+
+
+ {% if item.url %}
+ {{ item.name }}
+ {% else %}
+ {{ item.name }}
+ {% endif %}
+
+ {% if item.status %}
+
+ {{ item.status }}
+
+ {% endif %}
+
+
+ {% if item.startDate %}
+
+ {{ item.startDate }}{% if item.endDate %} – {{ item.endDate }}{% else %} – Present{% endif %}
+
+ {% endif %}
+
+
+
+
+
+
+ {# Detail section — collapsible #}
+
+ {% if item.startDate %}
+
+ {{ item.startDate }}{% if item.endDate %} – {{ item.endDate }}{% else %} – Present{% endif %}
+
+ {% endif %}
+
+ {% if item.description %}
+
{{ item.description }}
+ {% endif %}
+
+ {% if showTechnologies and item.technologies and item.technologies.length %}
+
+ {% for tech in item.technologies %}
+
+ {{ tech }}
+
+ {% endfor %}
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+{% endif %}
diff --git a/theme/_includes/components/sections/cv-skills-personal.njk b/theme/_includes/components/sections/cv-skills-personal.njk
new file mode 100644
index 0000000..baa52be
--- /dev/null
+++ b/theme/_includes/components/sections/cv-skills-personal.njk
@@ -0,0 +1,2 @@
+{% set filterType = "personal" %}
+{% include "components/sections/cv-skills.njk" %}
diff --git a/theme/_includes/components/sections/cv-skills-work.njk b/theme/_includes/components/sections/cv-skills-work.njk
new file mode 100644
index 0000000..cc1332a
--- /dev/null
+++ b/theme/_includes/components/sections/cv-skills-work.njk
@@ -0,0 +1,2 @@
+{% set filterType = "work" %}
+{% include "components/sections/cv-skills.njk" %}
diff --git a/theme/_includes/components/sections/cv-skills.njk b/theme/_includes/components/sections/cv-skills.njk
new file mode 100644
index 0000000..6b55cde
--- /dev/null
+++ b/theme/_includes/components/sections/cv-skills.njk
@@ -0,0 +1,50 @@
+{#
+ CV Skills Section - skills grouped by category
+ Data fetched from /cv/data.json via homepage plugin
+ Each family gets a distinct color via cycling palette
+#}
+
+{% if cv and cv.skills and (cv.skills | dictsort | length) %}
+
+
+ {{ section.config.title or "Skills" }}
+
+
+
+ {% for category, items in cv.skills %}
+ {% if not filterType or (cv.skillTypes and cv.skillTypes[category] == filterType) or not cv.skillTypes or not cv.skillTypes[category] %}
+ {# Cycle through 8 distinct colors per family using loop.index0 #}
+ {% set ci = loop.index0 % 8 %}
+
+
+ {{ category }}
+
+
+ {% for skill in items %}
+ {% if ci == 0 %}
+
+ {% elif ci == 1 %}
+
+ {% elif ci == 2 %}
+
+ {% elif ci == 3 %}
+
+ {% elif ci == 4 %}
+
+ {% elif ci == 5 %}
+
+ {% elif ci == 6 %}
+
+ {% elif ci == 7 %}
+
+ {% endif %}
+ {{ skill }}
+
+ {% endfor %}
+
+
+ {% endif %}
+ {% endfor %}
+
+
+{% endif %}
diff --git a/theme/_includes/components/sections/featured-posts.njk b/theme/_includes/components/sections/featured-posts.njk
new file mode 100644
index 0000000..730fdbe
--- /dev/null
+++ b/theme/_includes/components/sections/featured-posts.njk
@@ -0,0 +1,259 @@
+{#
+ Featured Posts Section - displays curated posts with `featured: true` frontmatter
+ Rendered by homepage-builder when featured-posts section is configured
+ Supports type-aware rendering for articles, notes, likes, bookmarks, reposts, replies, photos
+#}
+
+{% set sectionConfig = section.config or {} %}
+{% set maxItems = sectionConfig.maxItems or 6 %}
+{% set showSummary = sectionConfig.showSummary if sectionConfig.showSummary is defined else true %}
+
+{% if collections.featuredPosts and collections.featuredPosts.length %}
+
+
+
+
+
+ {{ sectionConfig.title or "Featured" }}
+
+
+
+ {% for post in collections.featuredPosts | head(maxItems) %}
+ {# Detect post type from frontmatter properties #}
+ {% set likedUrl = post.data.likeOf or post.data.like_of %}
+ {% set bookmarkedUrl = post.data.bookmarkOf or post.data.bookmark_of %}
+ {% set repostedUrl = post.data.repostOf or post.data.repost_of %}
+ {% set replyToUrl = post.data.inReplyTo or post.data.in_reply_to %}
+ {% set hasPhotos = post.data.photo and post.data.photo.length %}
+
+ {# Determine border color by post type #}
+ {% set borderClass = "" %}
+ {% if likedUrl %}
+ {% set borderClass = "border-l-[3px] border-l-red-400 dark:border-l-red-500" %}
+ {% elif bookmarkedUrl %}
+ {% set borderClass = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif repostedUrl %}
+ {% set borderClass = "border-l-[3px] border-l-green-400 dark:border-l-green-500" %}
+ {% elif replyToUrl %}
+ {% set borderClass = "border-l-[3px] border-l-sky-400 dark:border-l-sky-500" %}
+ {% elif hasPhotos %}
+ {% set borderClass = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
+ {% else %}
+ {% set borderClass = "border-l-[3px] border-l-surface-300 dark:border-l-surface-600" %}
+ {% endif %}
+
+
+
+ {% if likedUrl %}
+ {# ── Like card ── #}
+
+
+
+
+ Liked
+
+ {{ post.date | dateDisplay }}
+
+
+ {{ likedUrl | unfurlCard | safe }}
+
+ {{ likedUrl }}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
Permalink
+
+
+
+ {% elif bookmarkedUrl %}
+ {# ── Bookmark card ── #}
+
+
+
+
+ Bookmarked
+
+ {{ post.date | dateDisplay }}
+
+
+ {% if post.data.title %}
+
+ {% endif %}
+ {{ bookmarkedUrl | unfurlCard | safe }}
+
+ {{ bookmarkedUrl }}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
Permalink
+
+
+
+ {% elif repostedUrl %}
+ {# ── Repost card ── #}
+
+
+
+
+ Reposted
+
+ {{ post.date | dateDisplay }}
+
+
+ {{ repostedUrl | unfurlCard | safe }}
+
+ {{ repostedUrl }}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
Permalink
+
+
+
+ {% elif replyToUrl %}
+ {# ── Reply card ── #}
+
+
+
+
+ In reply to
+
+ {{ post.date | dateDisplay }}
+
+
+ {{ replyToUrl | unfurlCard | safe }}
+
+ {{ replyToUrl }}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
Permalink
+
+
+
+ {% elif hasPhotos %}
+ {# ── Photo card ── #}
+
+
+
+
+ Photo
+
+ {{ post.date | dateDisplay }}
+
+
+
+ {% for img in post.data.photo | head(2) %}
+ {% set photoUrl = img.url %}
+ {% if photoUrl and photoUrl[0] != '/' and 'http' not in photoUrl %}
+ {% set photoUrl = '/' + photoUrl %}
+ {% endif %}
+
+
+
+ {% endfor %}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
Permalink
+
+
+
+ {% elif post.data.title %}
+ {# ── Article/Page card ── #}
+
+ {% if showSummary and post.templateContent %}
+
+ {{ post.templateContent | striptags | truncate(250) }}
+
+ {% endif %}
+
+
+ {{ post.date | dateDisplay }}
+
+ {% if post.data.postType %}
+
+ {{ post.data.postType }}
+
+ {% endif %}
+
+
+ {% else %}
+ {# ── Note card ── #}
+
+
+
+ {{ post.date | dateDisplay }}
+
+
+ {% if post.data.postType %}
+
+ {{ post.data.postType }}
+
+ {% endif %}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
+ Permalink
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ {% if collections.featuredPosts.length > maxItems %}
+
+ {% endif %}
+
+{% endif %}
diff --git a/theme/_includes/components/sections/hero.njk b/theme/_includes/components/sections/hero.njk
new file mode 100644
index 0000000..0366965
--- /dev/null
+++ b/theme/_includes/components/sections/hero.njk
@@ -0,0 +1,66 @@
+{#
+ Hero Section - author intro with avatar, name, title, bio
+ Rendered by homepage-builder when hero is enabled
+#}
+
+{% set heroConfig = homepageConfig.hero or {} %}
+{% set id = homepageConfig.identity if (homepageConfig and homepageConfig.identity) else {} %}
+{% set authorName = id.name or site.author.name %}
+{% set authorAvatar = id.avatar or site.author.avatar %}
+{% set authorTitle = id.title or site.author.title %}
+{% set authorBio = id.bio or site.author.bio %}
+{% set siteDescription = id.description or site.description %}
+{% set socialLinks = id.social if (id.social and id.social.length) else site.social %}
+
+
+
+ {# Avatar #}
+ {% if heroConfig.showAvatar != false %}
+
+ {% endif %}
+
+ {# Introduction #}
+
+
+ {{ authorName }}
+
+
+ {{ authorTitle }}
+
+ {% if authorBio %}
+
+ {{ authorBio }}
+
+ {% endif %}
+ {% if siteDescription %}
+
+ {{ siteDescription }}
+ Read more →
+
+ {% endif %}
+
+ {# Social Links #}
+ {% from "components/social-icon.njk" import socialIcon, socialIconColorClass %}
+ {% if heroConfig.showSocial != false and socialLinks %}
+
+ {% endif %}
+
+
+
diff --git a/theme/_includes/components/sections/posting-activity.njk b/theme/_includes/components/sections/posting-activity.njk
new file mode 100644
index 0000000..976dba5
--- /dev/null
+++ b/theme/_includes/components/sections/posting-activity.njk
@@ -0,0 +1,24 @@
+{# Posting Activity Section — configurable post-graph contribution grid #}
+{% set sectionConfig = section.config or {} %}
+{% set graphTitle = sectionConfig.title or "Posting Activity" %}
+
+{% if collections.posts and collections.posts.length %}
+
+
+ {{ graphTitle }}
+
+ {% set graphOptions = {} %}
+ {% if sectionConfig.years and sectionConfig.years.length %}
+ {% set graphOptions = { only: sectionConfig.years } %}
+ {% elif sectionConfig.limit %}
+ {% set graphOptions = { limit: sectionConfig.limit } %}
+ {% endif %}
+ {% postGraph collections.posts, graphOptions %}
+
+ View full history
+
+
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/sections/recent-posts.njk b/theme/_includes/components/sections/recent-posts.njk
new file mode 100644
index 0000000..6c89b14
--- /dev/null
+++ b/theme/_includes/components/sections/recent-posts.njk
@@ -0,0 +1,257 @@
+{#
+ Recent Posts Section - displays latest posts from any collection
+ Rendered by homepage-builder when recent-posts section is configured
+ Supports type-aware rendering for likes, bookmarks, reposts, replies, photos
+#}
+
+{% set sectionConfig = section.config or {} %}
+{% set maxItems = sectionConfig.maxItems or 5 %}
+{% set showSummary = sectionConfig.showSummary if sectionConfig.showSummary is defined else true %}
+
+{% if collections.posts and collections.posts.length %}
+
+
+ {{ sectionConfig.title or "Recent Posts" }}
+
+
+
+ {% for post in collections.posts | head(maxItems) %}
+ {# Detect post type from frontmatter properties #}
+ {% set likedUrl = post.data.likeOf or post.data.like_of %}
+ {% set bookmarkedUrl = post.data.bookmarkOf or post.data.bookmark_of %}
+ {% set repostedUrl = post.data.repostOf or post.data.repost_of %}
+ {% set replyToUrl = post.data.inReplyTo or post.data.in_reply_to %}
+ {% set hasPhotos = post.data.photo and post.data.photo.length %}
+
+ {# Determine border color by post type #}
+ {% set borderClass = "" %}
+ {% if likedUrl %}
+ {% set borderClass = "border-l-[3px] border-l-red-400 dark:border-l-red-500" %}
+ {% elif bookmarkedUrl %}
+ {% set borderClass = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif repostedUrl %}
+ {% set borderClass = "border-l-[3px] border-l-green-400 dark:border-l-green-500" %}
+ {% elif replyToUrl %}
+ {% set borderClass = "border-l-[3px] border-l-sky-400 dark:border-l-sky-500" %}
+ {% elif hasPhotos %}
+ {% set borderClass = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
+ {% else %}
+ {% set borderClass = "border-l-[3px] border-l-surface-300 dark:border-l-surface-600" %}
+ {% endif %}
+
+
+
+ {% if likedUrl %}
+ {# ── Like card ── #}
+
+
+
+
+ Liked
+
+ {{ post.date | dateDisplay }}
+
+
+ {{ likedUrl | unfurlCard | safe }}
+
+ {{ likedUrl }}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
Permalink
+
+
+
+ {% elif bookmarkedUrl %}
+ {# ── Bookmark card ── #}
+
+
+
+
+ Bookmarked
+
+ {{ post.date | dateDisplay }}
+
+
+ {% if post.data.title %}
+
+ {% endif %}
+ {{ bookmarkedUrl | unfurlCard | safe }}
+
+ {{ bookmarkedUrl }}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
Permalink
+
+
+
+ {% elif repostedUrl %}
+ {# ── Repost card ── #}
+
+
+
+
+ Reposted
+
+ {{ post.date | dateDisplay }}
+
+
+ {{ repostedUrl | unfurlCard | safe }}
+
+ {{ repostedUrl }}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
Permalink
+
+
+
+ {% elif replyToUrl %}
+ {# ── Reply card ── #}
+
+
+
+
+ In reply to
+
+ {{ post.date | dateDisplay }}
+
+
+ {{ replyToUrl | unfurlCard | safe }}
+
+ {{ replyToUrl }}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
Permalink
+
+
+
+ {% elif hasPhotos %}
+ {# ── Photo card ── #}
+
+
+
+
+ Photo
+
+ {{ post.date | dateDisplay }}
+
+
+
+ {% for img in post.data.photo | head(2) %}
+ {% set photoUrl = img.url %}
+ {% if photoUrl and photoUrl[0] != '/' and 'http' not in photoUrl %}
+ {% set photoUrl = '/' + photoUrl %}
+ {% endif %}
+
+
+
+ {% endfor %}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
Permalink
+
+
+
+ {% elif post.data.title %}
+ {# ── Article card ── #}
+
+ {% if showSummary and post.templateContent %}
+
+ {{ post.templateContent | striptags | truncate(250) }}
+
+ {% endif %}
+
+
+ {{ post.date | dateDisplay }}
+
+ {% if post.data.postType %}
+
+ {{ post.data.postType }}
+
+ {% endif %}
+
+
+ {% else %}
+ {# ── Note card ── #}
+
+
+
+ {{ post.date | dateDisplay }}
+
+
+ {% if post.data.postType %}
+
+ {{ post.data.postType }}
+
+ {% endif %}
+
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
+ Permalink
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ {% if sectionConfig.showViewAll != false %}
+
+ {{ sectionConfig.viewAllText or "View all posts" }}
+
+
+
+
+ {% endif %}
+
+{% endif %}
diff --git a/theme/_includes/components/sidebar.njk b/theme/_includes/components/sidebar.njk
new file mode 100644
index 0000000..a6378eb
--- /dev/null
+++ b/theme/_includes/components/sidebar.njk
@@ -0,0 +1,280 @@
+{# Sidebar — for blog listing pages (/blog/, /notes/, /articles/...) #}
+{# Data-driven when homepageConfig.blogListingSidebar is configured, otherwise falls back to default widgets #}
+{# Each widget is wrapped in a collapsible container with localStorage persistence #}
+{% from "components/icon.njk" import icon %}
+
+{% if homepageConfig and homepageConfig.blogListingSidebar and homepageConfig.blogListingSidebar.length %}
+ {# === Data-driven mode: render configured widgets === #}
+ {% for widget in homepageConfig.blogListingSidebar %}
+
+ {# Resolve widget title #}
+ {% if widget.type == "search" %}{% set widgetTitle = "Search" %}
+ {% elif widget.type == "social-activity" %}{% set widgetTitle = "Social Activity" %}
+ {% elif widget.type == "github-repos" %}{% set widgetTitle = "GitHub" %}
+ {% elif widget.type == "funkwhale" %}{% set widgetTitle = "Listening" %}
+ {% elif widget.type == "recent-posts" %}{% set widgetTitle = "Recent Posts" %}
+ {% elif widget.type == "blogroll" %}{% set widgetTitle = "Blogroll" %}
+ {% elif widget.type == "feedland" %}{% set widgetTitle = "FeedLand" %}
+ {% elif widget.type == "categories" %}{% set widgetTitle = "Categories" %}
+ {% elif widget.type == "webmentions" %}{% set widgetTitle = "Webmentions" %}
+ {% elif widget.type == "recent-comments" %}{% set widgetTitle = "Recent Comments" %}
+ {% elif widget.type == "fediverse-follow" %}{% set widgetTitle = "Fediverse" %}
+ {% elif widget.type == "author-card" %}{% set widgetTitle = "Author" %}
+ {% elif widget.type == "author-card-compact" %}{% set widgetTitle = "Author" %}
+ {% elif widget.type == "subscribe" %}{% set widgetTitle = "Subscribe" %}
+ {% elif widget.type == "custom-html" %}{% set widgetTitle = (widget.config.title if widget.config and widget.config.title) or "Custom" %}
+ {% else %}{% set widgetTitle = widget.type %}
+ {% endif %}
+
+ {# Resolve widget icon and accent border #}
+ {% if widget.type == "social-activity" %}
+ {% set widgetIcon = "globe" %}{% set widgetIconClass = "w-5 h-5 text-[#0085ff]" %}{% set widgetBorder = "border-l-[3px] border-l-[#0085ff]" %}
+ {% elif widget.type == "github-repos" %}
+ {% set widgetIcon = "github" %}{% set widgetIconClass = "w-5 h-5 text-surface-800 dark:text-surface-200" %}{% set widgetBorder = "border-l-[3px] border-l-surface-400 dark:border-l-surface-500" %}
+ {% elif widget.type == "funkwhale" %}
+ {% set widgetIcon = "headphones" %}{% set widgetIconClass = "w-5 h-5 text-purple-500" %}{% set widgetBorder = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
+ {% elif widget.type == "blogroll" %}
+ {% set widgetIcon = "book-open" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif widget.type == "feedland" %}
+ {% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif widget.type == "subscribe" %}
+ {% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-orange-500" %}{% set widgetBorder = "border-l-[3px] border-l-orange-400 dark:border-l-orange-500" %}
+ {% elif widget.type == "fediverse-follow" %}
+ {% set widgetIcon = "user-plus" %}{% set widgetIconClass = "w-5 h-5 text-[#a730b8]" %}{% set widgetBorder = "border-l-[3px] border-l-[#a730b8]" %}
+ {% elif widget.type == "author-card" or widget.type == "author-card-compact" %}
+ {% set widgetIcon = "user" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "recent-posts" %}
+ {% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "categories" %}
+ {% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "recent-comments" %}
+ {% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "search" %}
+ {% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% elif widget.type == "webmentions" %}
+ {% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
+ {% else %}
+ {% set widgetIcon = "" %}{% set widgetIconClass = "" %}{% set widgetBorder = "" %}
+ {% endif %}
+
+ {% set widgetKey = "listing-widget-" + widget.type + "-" + loop.index0 %}
+ {% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
+
+ {# Collapsible wrapper — Alpine.js handles toggle, localStorage persists state #}
+
+
+ {% endfor %}
+{% else %}
+ {# === Fallback: current hardcoded sidebar (backward compatibility) === #}
+ {# Each widget wrapped in collapsible container #}
+
+ {# Author Card (h-card) — always shown #}
+ {% set widgetKey = "listing-fb-author-card" %}
+
+
+ {# Social Activity — Bluesky/Mastodon feeds #}
+ {% set widgetKey = "listing-fb-social-activity" %}
+
+
+ {# GitHub Repos #}
+ {% set widgetKey = "listing-fb-github-repos" %}
+
+
+ {# Funkwhale — Now Playing / Listening Stats #}
+ {% set widgetKey = "listing-fb-funkwhale" %}
+
+
+ {# Recent Posts #}
+ {% set widgetKey = "listing-fb-recent-posts" %}
+
+
+ {# Blogroll — only when backend is available #}
+ {% if blogrollStatus and blogrollStatus.source == "indiekit" %}
+ {% set widgetKey = "listing-fb-blogroll" %}
+
+ {% endif %}
+
+ {# FeedLand — only when backend is available #}
+ {% if blogrollStatus and blogrollStatus.source == "indiekit" %}
+ {% set widgetKey = "listing-fb-feedland" %}
+
+ {% endif %}
+
+ {# Recent Comments #}
+ {% set widgetKey = "listing-fb-recent-comments" %}
+
+
+ {# Categories/Tags #}
+ {% set widgetKey = "listing-fb-categories" %}
+
+{% endif %}
diff --git a/theme/_includes/components/social-icon.njk b/theme/_includes/components/social-icon.njk
new file mode 100644
index 0000000..54638b2
--- /dev/null
+++ b/theme/_includes/components/social-icon.njk
@@ -0,0 +1,131 @@
+{#
+ Social Icon Macro
+ Usage: {% from "components/social-icon.njk" import socialIcon, socialIconColorClass %}
+ {{ socialIcon("github", "w-5 h-5") }}
+ {{ socialIcon("github", "w-5 h-5") }}
+
+ SVG paths sourced from Simple Icons (simpleicons.org) - CC0 1.0 Universal
+ All icons render at 24x24 viewBox with fill="currentColor"
+ Brand colors from official brand guidelines
+#}
+
+{# Returns Tailwind color classes for an icon's brand color (light + dark) #}
+{% macro socialIconColorClass(name) %}
+{%- if name == "activitypub" -%}text-[#f1027e]
+{%- elif name == "github" -%}text-[#181717] dark:text-[#e6edf3]
+{%- elif name == "gitlab" -%}text-[#FC6D26]
+{%- elif name == "forgejo" -%}text-[#609926]
+{%- elif name == "codeberg" -%}text-[#2185D0]
+{%- elif name == "mastodon" -%}text-[#6364FF]
+{%- elif name == "bluesky" -%}text-[#0085FF]
+{%- elif name == "pixelfed" -%}text-[#6C42C9]
+{%- elif name == "linkedin" -%}text-[#0A66C2]
+{%- elif name == "twitter" -%}text-[#000000] dark:text-[#e7e9ea]
+{%- elif name == "threads" -%}text-[#000000] dark:text-[#f5f5f5]
+{%- elif name == "youtube" -%}text-[#FF0000]
+{%- elif name == "twitch" -%}text-[#9146FF]
+{%- elif name == "spotify" -%}text-[#1DB954]
+{%- elif name == "bandcamp" -%}text-[#629aa9]
+{%- elif name == "soundcloud" -%}text-[#FF5500]
+{%- elif name == "rss" -%}text-[#F26522]
+{%- elif name == "discord" -%}text-[#5865F2]
+{%- elif name == "signal" -%}text-[#3A76F0]
+{%- elif name == "telegram" -%}text-[#26A5E4]
+{%- elif name == "matrix" -%}text-[#000000] dark:text-[#e6e6e6]
+{%- elif name == "reddit" -%}text-[#FF4500]
+{%- elif name == "hackernews" -%}text-[#FF6600]
+{%- elif name == "funkwhale" -%}text-[#0D47A1]
+{%- elif name == "lastfm" -%}text-[#D51007]
+{%- elif name == "peertube" -%}text-[#F1680D]
+{%- elif name == "bookwyrm" -%}text-[#002200] dark:text-[#78b578]
+{%- elif name == "indieweb" -%}text-[#FF5C00]
+{%- elif name == "email" -%}text-surface-600 dark:text-surface-400
+{%- elif name == "website" -%}text-surface-600 dark:text-surface-400
+{%- elif name == "keybase" -%}text-[#33A0FF]
+{%- elif name == "orcid" -%}text-[#A6CE39]
+{%- elif name == "flickr" -%}text-[#0063DC]
+{%- elif name == "xmpp" -%}text-[#002B5C] dark:text-[#5badff]
+{%- elif name == "sourcehut" -%}text-[#000000] dark:text-[#e0e0e0]
+{%- elif name == "facebook" -%}text-[#0866FF]
+{%- elif name == "instagram" -%}text-[#E4405F]
+{%- else -%}text-surface-600 dark:text-surface-400
+{%- endif -%}
+{% endmacro %}
+
+{% macro socialIcon(name, cssClass) %}
+{%- if name == "github" -%}
+
+{%- elif name == "gitlab" -%}
+
+{%- elif name == "forgejo" -%}
+
+{%- elif name == "codeberg" -%}
+
+{%- elif name == "sourcehut" -%}
+
+{%- elif name == "linkedin" -%}
+
+{%- elif name == "bluesky" -%}
+
+{%- elif name == "mastodon" -%}
+
+{%- elif name == "activitypub" -%}
+
+{%- elif name == "pixelfed" -%}
+
+{%- elif name == "twitter" -%}
+
+{%- elif name == "facebook" -%}
+
+{%- elif name == "instagram" -%}
+
+{%- elif name == "threads" -%}
+
+{%- elif name == "youtube" -%}
+
+{%- elif name == "twitch" -%}
+
+{%- elif name == "flickr" -%}
+
+{%- elif name == "spotify" -%}
+
+{%- elif name == "bandcamp" -%}
+
+{%- elif name == "soundcloud" -%}
+
+{%- elif name == "rss" -%}
+
+{%- elif name == "matrix" -%}
+
+{%- elif name == "discord" -%}
+
+{%- elif name == "signal" -%}
+
+{%- elif name == "telegram" -%}
+
+{%- elif name == "xmpp" -%}
+
+{%- elif name == "reddit" -%}
+
+{%- elif name == "hackernews" -%}
+
+{%- elif name == "keybase" -%}
+
+{%- elif name == "orcid" -%}
+
+{%- elif name == "indieweb" -%}
+
+{%- elif name == "website" -%}
+
+{%- elif name == "email" -%}
+
+{%- elif name == "funkwhale" -%}
+
+{%- elif name == "lastfm" -%}
+
+{%- elif name == "peertube" -%}
+
+{%- elif name == "bookwyrm" -%}
+
+{%- endif -%}
+{% endmacro %}
diff --git a/theme/_includes/components/webmentions.njk b/theme/_includes/components/webmentions.njk
new file mode 100644
index 0000000..971984b
--- /dev/null
+++ b/theme/_includes/components/webmentions.njk
@@ -0,0 +1,206 @@
+{# Webmentions Component #}
+{# Displays likes, reposts, and replies for a post #}
+{# Also checks legacy URLs from micro.blog and old blog for historical webmentions #}
+{# Client-side JS supplements build-time data with real-time fetches #}
+
+{% set mentions = webmentions | webmentionsForUrl(page.url, urlAliases, conversationMentions) %}
+{% set absoluteUrl = site.url + page.url %}
+{% set buildTimestamp = "" | timestamp %}
+
+{# Data container for client-side JS to fetch new webmentions #}
+
+
+{% if mentions.length %}
+
+
+ Webmentions ({{ mentions.length }})
+
+
+ {# Likes #}
+ {% set likes = mentions | webmentionsByType('likes') %}
+ {% if likes.length %}
+
+
+ {{ likes.length }} Like{% if likes.length != 1 %}s{% endif %}
+
+
+
+ {% for like in likes %}
+
+
+
+ {% endfor %}
+
+
+
+ {% endif %}
+
+ {# Reposts #}
+ {% set reposts = mentions | webmentionsByType('reposts') %}
+ {% if reposts.length %}
+
+
+ {{ reposts.length }} Repost{% if reposts.length != 1 %}s{% endif %}
+
+
+
+ {% for repost in reposts %}
+
+
+
+ {% endfor %}
+
+
+
+ {% endif %}
+
+ {# Bookmarks #}
+ {% set bookmarks = mentions | webmentionsByType('bookmarks') %}
+ {% if bookmarks.length %}
+
+
+ {{ bookmarks.length }} Bookmark{% if bookmarks.length != 1 %}s{% endif %}
+
+
+
+ {% for bookmark in bookmarks %}
+
+
+
+ {% endfor %}
+
+
+
+ {% endif %}
+
+ {# Replies #}
+ {% set replies = mentions | webmentionsByType('replies') %}
+ {% if replies.length %}
+
+
+ {{ replies.length }} Repl{% if replies.length != 1 %}ies{% else %}y{% endif %}
+
+
+ {% for reply in replies %}
+
+
+
+
+
+
+
+
+ {{ reply.content.html | safe if reply.content.html else reply.content.text }}
+
+
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {# Other mentions #}
+ {% set otherMentions = mentions | webmentionsByType('mentions') %}
+ {% if otherMentions.length %}
+
+
+ {{ otherMentions.length }} Mention{% if otherMentions.length != 1 %}s{% endif %}
+
+
+
+ {% endif %}
+
+{% endif %}
+
+{# Webmention send form — collapsed by default #}
+
+
+
+
+
+ Send a Webmention
+
+
+
+ Have you written a response to this post? Send a webmention by entering your post URL below.
+
+
+
+
diff --git a/theme/_includes/components/widgets/author-card-compact.njk b/theme/_includes/components/widgets/author-card-compact.njk
new file mode 100644
index 0000000..b139b66
--- /dev/null
+++ b/theme/_includes/components/widgets/author-card-compact.njk
@@ -0,0 +1,30 @@
+{# Author Compact Card - h-card microformat (compact version for blog sidebars) #}
+
+
+
diff --git a/theme/_includes/components/widgets/author-card.njk b/theme/_includes/components/widgets/author-card.njk
new file mode 100644
index 0000000..f205d58
--- /dev/null
+++ b/theme/_includes/components/widgets/author-card.njk
@@ -0,0 +1,6 @@
+{# Author Card Widget - includes the canonical h-card component #}
+
+
+ {% include "components/h-card.njk" %}
+
+
diff --git a/theme/_includes/components/widgets/blogroll.njk b/theme/_includes/components/widgets/blogroll.njk
new file mode 100644
index 0000000..572cff0
--- /dev/null
+++ b/theme/_includes/components/widgets/blogroll.njk
@@ -0,0 +1,110 @@
+{# Blogroll Widget - Dynamic loading from API with source tabs #}
+
+
+
+
+
diff --git a/theme/_includes/components/widgets/categories.njk b/theme/_includes/components/widgets/categories.njk
new file mode 100644
index 0000000..b071191
--- /dev/null
+++ b/theme/_includes/components/widgets/categories.njk
@@ -0,0 +1,15 @@
+{# Categories/Tags Widget #}
+{% if categories and categories.length %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/widgets/fediverse-follow.njk b/theme/_includes/components/widgets/fediverse-follow.njk
new file mode 100644
index 0000000..10d081f
--- /dev/null
+++ b/theme/_includes/components/widgets/fediverse-follow.njk
@@ -0,0 +1,38 @@
+{# Fediverse Follow Me Widget — uses the fediverseInteract Alpine.js component #}
+{# Requires fediverse-interact.js loaded in base.njk (already present) #}
+{# Determines actor URI from site social links: prefers self-hosted AP, falls back to Mastodon #}
+
+{% set actorUrl = "" %}
+{% for link in site.social %}
+ {% if link.icon == "activitypub" and not actorUrl %}
+ {% set actorUrl = link.url %}
+ {% endif %}
+{% endfor %}
+{% if not actorUrl %}
+ {% for link in site.social %}
+ {% if link.icon == "mastodon" and not actorUrl %}
+ {% set actorUrl = link.url %}
+ {% endif %}
+ {% endfor %}
+{% endif %}
+
+{% if actorUrl %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/widgets/feedland.njk b/theme/_includes/components/widgets/feedland.njk
new file mode 100644
index 0000000..5a4b1c3
--- /dev/null
+++ b/theme/_includes/components/widgets/feedland.njk
@@ -0,0 +1,383 @@
+{# FeedLand Widget - Matches Dave Winer's blogroll.js visual rendering #}
+{# Uses Alpine.js + blogroll API instead of jQuery + external blogroll.js #}
+
+
+
+
+
+
+
+
+
+
diff --git a/theme/_includes/components/widgets/funkwhale.njk b/theme/_includes/components/widgets/funkwhale.njk
new file mode 100644
index 0000000..0f9bbac
--- /dev/null
+++ b/theme/_includes/components/widgets/funkwhale.njk
@@ -0,0 +1,115 @@
+{# Listening Widget — combined Funkwhale + Last.fm recent tracks #}
+{% set hasListening = (funkwhaleActivity and (funkwhaleActivity.nowPlaying or funkwhaleActivity.listenings.length)) or (lastfmActivity and (lastfmActivity.nowPlaying or lastfmActivity.scrobbles.length)) %}
+{% if hasListening %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/widgets/github-repos.njk b/theme/_includes/components/widgets/github-repos.njk
new file mode 100644
index 0000000..ee27453
--- /dev/null
+++ b/theme/_includes/components/widgets/github-repos.njk
@@ -0,0 +1,213 @@
+{# GitHub Activity Widget - Tabbed Commits/Repos/Featured/PRs with live API data #}
+
+
+
+
+
diff --git a/theme/_includes/components/widgets/post-categories.njk b/theme/_includes/components/widgets/post-categories.njk
new file mode 100644
index 0000000..0d015d5
--- /dev/null
+++ b/theme/_includes/components/widgets/post-categories.njk
@@ -0,0 +1,21 @@
+{# Categories for This Post #}
+{% if category %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/widgets/post-navigation.njk b/theme/_includes/components/widgets/post-navigation.njk
new file mode 100644
index 0000000..f0b0155
--- /dev/null
+++ b/theme/_includes/components/widgets/post-navigation.njk
@@ -0,0 +1,66 @@
+{# Post Navigation Widget - Previous/Next #}
+{# Uses previousInCollection/nextInCollection filters to find adjacent posts #}
+{% set _prevPost = collections.posts | previousInCollection(page) %}
+{% set _nextPost = collections.posts | nextInCollection(page) %}
+
+{% if _prevPost or _nextPost %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/widgets/recent-comments.njk b/theme/_includes/components/widgets/recent-comments.njk
new file mode 100644
index 0000000..9e0b78a
--- /dev/null
+++ b/theme/_includes/components/widgets/recent-comments.njk
@@ -0,0 +1,27 @@
+{# Recent Comments Widget — sidebar #}
+{% if recentComments and recentComments.length %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/widgets/recent-posts-blog.njk b/theme/_includes/components/widgets/recent-posts-blog.njk
new file mode 100644
index 0000000..af6e224
--- /dev/null
+++ b/theme/_includes/components/widgets/recent-posts-blog.njk
@@ -0,0 +1,85 @@
+{# Recent Posts Widget — type-aware, for blog/post sidebars #}
+{# Uses collections.posts directly (all post types, not just recentPosts collection) #}
+{% if collections.posts %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/widgets/recent-posts.njk b/theme/_includes/components/widgets/recent-posts.njk
new file mode 100644
index 0000000..7fef78d
--- /dev/null
+++ b/theme/_includes/components/widgets/recent-posts.njk
@@ -0,0 +1,93 @@
+{# Recent Posts Widget (sidebar) - compact type-aware list #}
+{% set recentPosts = recentPosts or collections.recentPosts %}
+{% if recentPosts and recentPosts.length %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/widgets/search.njk b/theme/_includes/components/widgets/search.njk
new file mode 100644
index 0000000..c547241
--- /dev/null
+++ b/theme/_includes/components/widgets/search.njk
@@ -0,0 +1,10 @@
+{# Search Widget — redirects to /search/?q=query #}
+
diff --git a/theme/_includes/components/widgets/share.njk b/theme/_includes/components/widgets/share.njk
new file mode 100644
index 0000000..0c8d9b1
--- /dev/null
+++ b/theme/_includes/components/widgets/share.njk
@@ -0,0 +1,31 @@
+{# Share Widget #}
+{% set shareText = title + " " + site.url + page.url %}
+
+
+
diff --git a/theme/_includes/components/widgets/social-activity.njk b/theme/_includes/components/widgets/social-activity.njk
new file mode 100644
index 0000000..21910a5
--- /dev/null
+++ b/theme/_includes/components/widgets/social-activity.njk
@@ -0,0 +1,96 @@
+{# Social Feed Widget - Tabbed Bluesky/Mastodon #}
+{% if (blueskyFeed and blueskyFeed.length) or (mastodonFeed and mastodonFeed.length) %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/widgets/subscribe.njk b/theme/_includes/components/widgets/subscribe.njk
new file mode 100644
index 0000000..61f4005
--- /dev/null
+++ b/theme/_includes/components/widgets/subscribe.njk
@@ -0,0 +1,20 @@
+{# Subscribe Widget #}
+
+
+
diff --git a/theme/_includes/components/widgets/toc.njk b/theme/_includes/components/widgets/toc.njk
new file mode 100644
index 0000000..19a86d3
--- /dev/null
+++ b/theme/_includes/components/widgets/toc.njk
@@ -0,0 +1,19 @@
+{# Table of Contents Widget (for articles with headings) #}
+{% if toc and toc.length %}
+
+
+
+{% endif %}
diff --git a/theme/_includes/components/widgets/webmentions.njk b/theme/_includes/components/widgets/webmentions.njk
new file mode 100644
index 0000000..ae51b4d
--- /dev/null
+++ b/theme/_includes/components/widgets/webmentions.njk
@@ -0,0 +1,168 @@
+{# Recent Webmentions Widget - site-wide inbound/outbound activity #}
+{# Uses client-side fetch from /webmentions/api/mentions (same as /interactions page) #}
+{# Outbound tab uses Eleventy collections (likes, replies, bookmarks, reposts) #}
+
+
+
+
+
diff --git a/theme/_includes/layouts/base.njk b/theme/_includes/layouts/base.njk
new file mode 100644
index 0000000..cf5c196
--- /dev/null
+++ b/theme/_includes/layouts/base.njk
@@ -0,0 +1,549 @@
+
+
+
+ {# OG image resolution handled by og-fix transform in eleventy.config.js
+ to bypass Eleventy 3.x parallel rendering race condition (#3183).
+ Template outputs __OG_IMAGE_PLACEHOLDER__ and __TWITTER_CARD_PLACEHOLDER__
+ which the transform replaces using the correct slug derived from outputPath. #}
+
+
+
+ {% if title %}{{ title }} - {% endif %}{{ site.name }}
+
+ {# OpenGraph meta tags #}
+ {% set ogTitle = title | default(site.name) %}
+ {% set ogDesc = description | default(content | ogDescription(200)) | default(site.description) %}
+ {# Normalize photo - could be array for multi-photo posts #}
+ {% set ogPhoto = photo %}
+ {% if ogPhoto %}
+ {% if ogPhoto[0] and (ogPhoto[0] | length) > 10 %}
+ {% set ogPhoto = ogPhoto[0] %}
+ {% endif %}
+ {% endif %}
+
+
+
+
+
+
+ {% if ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10 %}
+
+ {% elif image and image != "" and (image | length) > 10 %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
+ {# Twitter Card meta tags #}
+ {% set hasExplicitImage = (ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10) or (image and image != "" and (image | length) > 10) %}
+
+
+
+ {% if ogPhoto and ogPhoto != "" and (ogPhoto | length) > 10 %}
+
+ {% elif image and image != "" and (image | length) > 10 %}
+
+ {% else %}
+
+ {% endif %}
+
+ {# Favicon #}
+
+
+
+ {# Critical CSS — inlined for fast first paint #}
+
+ {# Defer full stylesheet — loads after first paint #}
+
+
+
+
+
+
+
+
+
+ {# Alpine.js components — MUST load before Alpine core (Alpine.data() registration via alpine:init) #}
+
+
+
+
+
+
+ {# Graceful no-JS fallback: show content that Alpine would normally control #}
+
+
+
+
+
+
+
+ {% if site.markdownAgents.enabled and page.url and page.url.startsWith('/articles/') and page.url != '/articles/' %}
+
+ {% endif %}
+ {% if category and page.url and page.url.startsWith('/categories/') and page.url != '/categories/' %}
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+ {# Fediverse creator meta tag for Mastodon verification #}
+ {% if site.fediverseCreator %}
+
+ {% endif %}
+
+ {# IndieAuth rel="me" links for identity verification #}
+ {# Note: Bluesky links use "me atproto" for verification #}
+ {% for social in site.social %}
+
+ {% endfor %}
+
+
+
+
+
+
+ {% if withSidebar and page.url == "/" and homepageConfig and homepageConfig.sections %}
+ {# Homepage: builder controls its own layout and sidebar #}
+ {{ content | safe }}
+ {% elif withSidebar %}
+
+ {% elif withBlogSidebar %}
+
+ {% else %}
+ {{ content | safe }}
+ {% endif %}
+
+
+
+
+ {# Island architecture - lazy hydration for widgets #}
+
+ {# Relative date display - progressively enhances elements #}
+
+ {# Responsive tables - auto-enhances on narrow screens #}
+
+ {# Client-side filtering for archive pages #}
+
+ {# Client-side webmention fetcher - supplements build-time cache with real-time data #}
+
+ {# Admin auth detection - shows dashboard link + FAB when logged in #}
+
+ {# Save for Later buttons — active when logged in #}
+
+ {# Share Post buttons — opens share form popup when logged in #}
+
+
+ {# Floating Action Button - visible only when logged in #}
+
+ {# Backdrop #}
+
+ {# Menu items #}
+
+ {# FAB button #}
+
+
+
+
+
+
+ {# Pagefind — load at end of body so all DOM elements exist, then process queue #}
+
+
+
+
diff --git a/theme/_includes/layouts/fullwidth.njk b/theme/_includes/layouts/fullwidth.njk
new file mode 100644
index 0000000..7a84d59
--- /dev/null
+++ b/theme/_includes/layouts/fullwidth.njk
@@ -0,0 +1,25 @@
+---
+layout: layouts/base.njk
+---
+{# Full-width layout for rich HTML pages (interactive guides, architecture diagrams, etc.)
+ Inherits site header + footer from base.njk but renders content at full container width
+ with no sidebar, no post metadata, and no prose constraints. #}
+
+
+ {% if title %}
+
+
+ {{ title }}
+
+ {% if description %}
+
+ {{ description }}
+
+ {% endif %}
+
+ {% endif %}
+
+
+ {{ content | safe }}
+
+
diff --git a/theme/_includes/layouts/home.njk b/theme/_includes/layouts/home.njk
new file mode 100644
index 0000000..0f26556
--- /dev/null
+++ b/theme/_includes/layouts/home.njk
@@ -0,0 +1,157 @@
+---
+layout: layouts/base.njk
+withSidebar: true
+---
+
+{# Homepage content — two-tier fallback: #}
+{# 1. Plugin config (homepageConfig) — homepage builder controls everything #}
+{# 2. Default — show recent posts with default hero #}
+
+{# Default hero — only shown for Tier 2 (plugin controls its own hero) #}
+{% if not (homepageConfig and homepageConfig.sections) %}
+
+
+ {# Avatar #}
+
+
+ {# Introduction #}
+
+
+ {{ site.author.name }}
+
+
+ {{ site.author.title }}
+
+ {% if site.author.bio %}
+
+ {{ site.author.bio }}
+
+ {% endif %}
+ {% if site.description %}
+
+ {{ site.description }}
+ Read more →
+
+ {% endif %}
+
+ {# Social Links #}
+
+
+
+
+{% endif %}
+
+{# --- Tier 1: Plugin-driven layout --- #}
+{% if homepageConfig and homepageConfig.sections %}
+ {% include "components/homepage-builder.njk" %}
+
+{# --- Tier 2: Default — recent posts and explore links --- #}
+{% else %}
+
+{# Recent Posts #}
+{% if collections.posts and collections.posts.length %}
+
+ Recent Posts
+
+ {% for post in collections.posts | head(10) %}
+
+
+ {% if post.data.summary %}
+ {{ post.data.summary }}
+ {% endif %}
+
+
+ {{ (post.data.published or post.date) | date("MMM d, yyyy") }}
+
+ {% if post.data.postType %}
+ {{ post.data.postType }}
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ View all posts
+
+
+
+{% endif %}
+
+{# Explore — quick links to key sections #}
+
+
+{# Posting Activity — contribution graph (Tier 2 default only) #}
+{% if collections.posts and collections.posts.length %}
+
+{% endif %}
+
+{% endif %} {# end two-tier fallback #}
diff --git a/theme/_includes/layouts/page.njk b/theme/_includes/layouts/page.njk
new file mode 100644
index 0000000..5dc14a6
--- /dev/null
+++ b/theme/_includes/layouts/page.njk
@@ -0,0 +1,135 @@
+---
+layout: layouts/base.njk
+withSidebar: true
+---
+{# Layout for slash pages (/about, /now, /uses, etc.) #}
+{# These are root-level pages created via Indiekit's page post type #}
+
+
+
+
+
+ {{ content | safe }}
+
+
+ {# AI post-graph — shown only on the /ai/ page #}
+ {% if page.url == "/ai/" and collections.posts %}
+ {% set stats = collections.posts | aiStats %}
+ {% set aiPostsList = collections.posts | aiPosts %}
+
+ AI Usage Across Posts
+
+
+
{{ stats.total }}
+
Total posts
+
+
+
{{ stats.aiCount }}
+
AI-involved
+
+
+
{{ stats.total - stats.aiCount }}
+
Human-only
+
+
+
{{ stats.percentage }}%
+
AI ratio
+
+
+
+ {# Breakdown by level #}
+
+
+ Level 0 (None): {{ stats.byLevel[0] }}
+
+
+ Level 1 (Editorial): {{ stats.byLevel[1] }}
+
+
+ Level 2 (Co-drafted): {{ stats.byLevel[2] }}
+
+
+ Level 3 (AI-generated): {{ stats.byLevel[3] }}
+
+
+
+ {# Post graph showing AI posts (highlighted) on the full year grid #}
+ AI-Involved Posts Over Time
+ Highlighted days had posts with AI involvement (level 1+). Empty boxes represent days with no AI-involved posts.
+ {% postGraph aiPostsList, { prefix: "ai", highlightColorLight: "#d97706", highlightColorDark: "#fbbf24" } %}
+
+ {% endif %}
+
+ {# AI usage disclosure #}
+ {% set aiTextLevel = aiTextLevel or ai_text_level %}
+ {% set aiCodeLevel = aiCodeLevel or ai_code_level %}
+ {% set aiTools = aiTools or ai_tools %}
+ {% set aiDescription = aiDescription or ai_description %}
+ {% if aiTextLevel or aiCodeLevel or aiTools %}
+
+
+
+ {% if aiTextLevel %}
+
+ Text: {% if aiTextLevel === "0" %}None{% elif aiTextLevel === "1" %}Editorial{% elif aiTextLevel === "2" %}Co-drafted{% elif aiTextLevel === "3" %}AI-generated{% endif %}
+
+ {% endif %}
+ {% if aiCodeLevel %}
+
+ Code: {% if aiCodeLevel === "0" %}Human{% elif aiCodeLevel === "1" %}AI-assisted{% elif aiCodeLevel === "2" %}AI-generated{% endif %}
+
+ {% endif %}
+ {% if aiTools %}
+
+ Tools: {{ aiTools }}
+
+ {% endif %}
+
+ {% if aiDescription %}
+ {{ aiDescription }}
+ {% endif %}
+
+ {% endif %}
+
+ {# Categories/tags if present #}
+ {% if category %}
+
+ {% endif %}
+
+ {# Hidden metadata for microformats #}
+
+
+
diff --git a/theme/_includes/layouts/post.njk b/theme/_includes/layouts/post.njk
new file mode 100644
index 0000000..babf064
--- /dev/null
+++ b/theme/_includes/layouts/post.njk
@@ -0,0 +1,263 @@
+---
+layout: layouts/base.njk
+withBlogSidebar: true
+---
+
+ {# Support both camelCase (Indiekit Eleventy preset) and underscore (legacy) property names #}
+ {% set bookmarkedUrl = bookmarkOf or bookmark_of %}
+ {% set likedUrl = likeOf or like_of %}
+ {% set replyTo = inReplyTo or in_reply_to %}
+ {% set repostedUrl = repostOf or repost_of %}
+
+ {% if title %}
+ {{ title }}
+ {% else %}
+
+
+ {% if replyTo %}↩ Reply{% elif likedUrl %}♥ Like{% elif repostedUrl %}♻ Repost{% elif bookmarkedUrl %}🔖 Bookmark{% else %}✎ Note{% endif %}
+
+
+ {% endif %}
+
+
+
+ {{ date | dateDisplay }}
+
+ {% if category %}
+
+ {# Handle both string and array categories #}
+ {% if category is string %}
+ {{ category }}
+ {% else %}
+ {% for cat in category %}
+ {{ cat }}
+ {% endfor %}
+ {% endif %}
+
+ {% endif %}
+
+
+ {# Bridgy syndication content - controls what gets posted to social networks #}
+ {# For interaction types (bookmarks, likes, replies, reposts), include the target URL #}
+ {% set bridgySummary = description or summary or (content | ogDescription(280)) %}
+ {% set interactionUrl = bookmarkedUrl or likedUrl or replyTo or repostedUrl %}
+
+ {% if bridgySummary or interactionUrl %}
+ {% if bookmarkedUrl %}🔖 {{ bookmarkedUrl }}{% if bridgySummary %} - {{ bridgySummary }}{% endif %}{% elif likedUrl %}❤️ {{ likedUrl }}{% if bridgySummary %} - {{ bridgySummary }}{% endif %}{% elif replyTo %}↩️ {{ replyTo }}{% if bridgySummary %} - {{ bridgySummary }}{% endif %}{% elif repostedUrl %}🔁 {{ repostedUrl }}{% if bridgySummary %} - {{ bridgySummary }}{% endif %}{% else %}{{ bridgySummary }}{% endif %}
+ {% endif %}
+
+ {# Render photo(s) from frontmatter for photo posts - use eleventy:ignore to skip image transform #}
+ {% if photo %}
+
+ {% for img in photo %}
+ {% set photoUrl = img.url %}
+ {% if photoUrl and photoUrl[0] != '/' and 'http' not in photoUrl %}
+ {% set photoUrl = '/' + photoUrl %}
+ {% endif %}
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% set isInteraction = replyTo or likedUrl or repostedUrl or bookmarkedUrl %}
+ {% set hasContent = content and content | striptags | trim %}
+
+ {{ content | safe }}
+
+
+ {# Rich reply context with h-cite microformat #}
+ {% include "components/reply-context.njk" %}
+
+ {# AI usage disclosure — always shown, collapsed by default, placed after reply context #}
+ {% set aiTextLevel = aiTextLevel or ai_text_level or "0" %}
+ {% set aiCodeLevel = aiCodeLevel or ai_code_level %}
+ {% set aiTools = aiTools or ai_tools %}
+ {% set aiDescription = aiDescription or ai_description %}
+
+
+
+
+
+ AI:
+ Text {% if aiTextLevel === "0" %}None{% elif aiTextLevel === "1" %}Editorial{% elif aiTextLevel === "2" %}Co-drafted{% elif aiTextLevel === "3" %}AI-generated{% endif %}{% if aiCodeLevel %} · Code {% if aiCodeLevel === "0" %}Human{% elif aiCodeLevel === "1" %}AI-assisted{% elif aiCodeLevel === "2" %}AI-generated{% endif %}{% endif %}{% if aiTools %} · {{ aiTools }}{% endif %}
+
+
+ {% if aiDescription %}
+ {{ aiDescription }}
+ {% endif %}
+
+
+ {# Pending syndication targets (for services like IndieNews that require u-syndication before webmention) #}
+ {% if mpSyndicateTo %}
+
+ {% for url in mpSyndicateTo %}
+ {% if "news.indieweb.org" in url %}
+
IndieNews
+ {% endif %}
+ {% endfor %}
+
+ {% endif %}
+
+ {# Syndication Footer - shows where this post was also published #}
+ {# Separate self-hosted AP URLs from external syndication targets #}
+ {% set externalSyndication = [] %}
+ {% set selfHostedApUrl = "" %}
+ {% if syndication %}
+ {% for url in syndication %}
+ {% if url.indexOf(site.url) == 0 %}
+ {% set selfHostedApUrl = url %}
+ {% else %}
+ {% set externalSyndication = (externalSyndication.push(url), externalSyndication) %}
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ {% if externalSyndication.length or selfHostedApUrl %}
+
+ {% endif %}
+
+ Permalink
+
+ {# Author h-card for IndieWeb authorship #}
+
+ {{ site.author.name }}
+
+
+
+ {# JSON-LD Structured Data for SEO #}
+ {# Handle photo as potentially an array #}
+ {% set postImage = photo %}
+ {% if postImage %}
+ {# If photo is an array, use first element (check if first element looks like a URL) #}
+ {% if postImage[0] and (postImage[0] | length) > 10 %}
+ {% set postImage = postImage[0] %}
+ {% endif %}
+ {% endif %}
+ {% if not postImage or postImage == "" %}
+ {% set postImage = image or (content | extractFirstImage) %}
+ {% endif %}
+ {% set postDesc = description | default(content | ogDescription(160)) %}
+
+
+ {# Lightbox overlay for article images #}
+
+
+
×
+
+ ‹
+
+
+ ›
+
+
+
+
+
+
+
+{# Comments section #}
+{% include "components/comments.njk" %}
+
+{# Webmentions display - likes, reposts, replies #}
+{% include "components/webmentions.njk" %}
+
+{# Post Navigation - Previous/Next #}
+{% include "components/post-navigation.njk" %}
diff --git a/theme/about.njk b/theme/about.njk
new file mode 100644
index 0000000..5cd5a40
--- /dev/null
+++ b/theme/about.njk
@@ -0,0 +1,69 @@
+---
+layout: layouts/base.njk
+title: About
+permalink: false
+eleventyExcludeFromCollections: true
+---
+
+
+
+
+
{{ site.author.bio }}
+
+
About This Site
+
+ This site is powered by Indiekit , an IndieWeb
+ server that supports Micropub, Webmentions, and other IndieWeb standards. It runs on
+ Cloudron for easy self-hosting.
+
+
+
IndieWeb
+
+ I'm part of the IndieWeb movement - owning my content
+ and identity online. You can interact with my posts through Webmentions - reply, like,
+ or repost from your own website and it will show up here.
+
+
+ {% if site.social.length > 0 %}
+
Connect
+
Find me on:
+
+ {% endif %}
+
+ {% if site.author.email %}
+
+ Or send me an email at
+ {{ site.author.email }}
+
+ {% endif %}
+
+
diff --git a/theme/articles.njk b/theme/articles.njk
new file mode 100644
index 0000000..5b6f2bb
--- /dev/null
+++ b/theme/articles.njk
@@ -0,0 +1,100 @@
+---
+layout: layouts/base.njk
+title: Articles
+withSidebar: true
+pagination:
+ data: collections.articles
+ size: 20
+ alias: paginatedArticles
+ generatePageOnEmptyData: true
+permalink: "articles/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
+---
+
+
+
Articles
+ {% set sparklineSvg = collections.articles | postingFrequency %}
+ {% if sparklineSvg %}
+ {{ sparklineSvg | safe }}
+ {% endif %}
+
+
+ Long-form posts and essays.
+ ({{ collections.articles.length }} total)
+
+
+ {% if paginatedArticles.length > 0 %}
+
+ {% for post in paginatedArticles %}
+
+
+
+
+ {{ post.date | dateDisplay }}
+
+ {% if post.data.category %}
+
+ {% if post.data.category is string %}
+ {{ post.data.category }}
+ {% else %}
+ {% for cat in post.data.category %}
+ {{ cat }}
+ {% endfor %}
+ {% endif %}
+
+ {% endif %}
+
+
+ {{ post.templateContent | striptags | truncate(250) }}
+
+
+ Read more →
+
+
+ {% endfor %}
+
+
+ {# Pagination controls #}
+ {% if pagination.pages.length > 1 %}
+
+ {% endif %}
+
+ {% else %}
+ {% set postType = "article" %}
+ {% include "components/empty-collection.njk" %}
+ {% endif %}
+
diff --git a/theme/blog.njk b/theme/blog.njk
new file mode 100644
index 0000000..c68087d
--- /dev/null
+++ b/theme/blog.njk
@@ -0,0 +1,389 @@
+---
+layout: layouts/base.njk
+title: Blog
+withSidebar: true
+pagination:
+ data: collections.posts
+ size: 20
+ alias: paginatedPosts
+permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
+---
+
+
+
Blog
+ {% set sparklineSvg = collections.posts | postingFrequency %}
+ {% if sparklineSvg %}
+ {{ sparklineSvg | safe }}
+ {% endif %}
+
+
+ All posts including articles and notes.
+ ({{ collections.posts.length }} total)
+
+
+ {% if paginatedPosts.length > 0 %}
+
+
+
+ All Types
+ Articles
+ Notes
+ Photos
+ Bookmarks
+ Likes
+ Replies
+ Reposts
+
+
+
+
+ {% for post in paginatedPosts %}
+ {# Detect post type from frontmatter properties #}
+ {% set likedUrl = post.data.likeOf or post.data.like_of %}
+ {% set bookmarkedUrl = post.data.bookmarkOf or post.data.bookmark_of %}
+ {% set repostedUrl = post.data.repostOf or post.data.repost_of %}
+ {% set replyToUrl = post.data.inReplyTo or post.data.in_reply_to %}
+ {% set hasPhotos = post.data.photo and post.data.photo.length %}
+ {% set _postType %}{% if likedUrl %}like{% elif bookmarkedUrl %}bookmark{% elif repostedUrl %}repost{% elif replyToUrl %}reply{% elif hasPhotos %}photo{% elif post.data.title %}article{% else %}note{% endif %}{% endset %}
+ {% set borderClass = "" %}
+ {% if likedUrl %}
+ {% set borderClass = "border-l-[3px] border-l-red-400 dark:border-l-red-500" %}
+ {% elif bookmarkedUrl %}
+ {% set borderClass = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif repostedUrl %}
+ {% set borderClass = "border-l-[3px] border-l-green-400 dark:border-l-green-500" %}
+ {% elif replyToUrl %}
+ {% set borderClass = "border-l-[3px] border-l-sky-400 dark:border-l-sky-500" %}
+ {% elif hasPhotos %}
+ {% set borderClass = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
+ {% else %}
+ {% set borderClass = "border-l-[3px] border-l-surface-300 dark:border-l-surface-600" %}
+ {% endif %}
+
+
+ {% if likedUrl %}
+ {# ── Like card ── #}
+
+
+ {% elif bookmarkedUrl %}
+ {# ── Bookmark card ── #}
+
+
+ {% elif repostedUrl %}
+ {# ── Repost card ── #}
+
+
+ {% elif replyToUrl %}
+ {# ── Reply card ── #}
+
+
+ {% elif hasPhotos %}
+ {# ── Photo card ── #}
+
+
+ {% elif post.data.title %}
+ {# ── Article card (unchanged) ── #}
+
+
+ {{ post.templateContent | striptags | truncate(250) }}
+
+
+ Read more →
+
+
+ {% else %}
+ {# ── Note card (unchanged) ── #}
+
+
+ {{ post.templateContent | safe }}
+
+
+ {% endif %}
+
+ {# AI usage badge — only show when AI was actually used (level > 0) #}
+ {% set postAiText = post.data.aiTextLevel or post.data.ai_text_level %}
+ {% set postAiCode = post.data.aiCodeLevel or post.data.ai_code_level %}
+ {% if (postAiText and postAiText !== "0") or (postAiCode and postAiCode !== "0") %}
+
+
+ AI{% if postAiText %}: T{{ postAiText }}{% endif %}{% if postAiCode %}/C{{ postAiCode }}{% endif %}
+
+ {% endif %}
+
+ {% endfor %}
+
+
+
+ {# Pagination controls #}
+ {% if pagination.pages.length > 1 %}
+
+ {% endif %}
+
+ {% else %}
+
No posts yet. Create your first post using a Micropub client!
+
Some popular Micropub clients:
+
+ Quill - Web-based
+ IndiePass - Mobile app
+ Micropublish - Web-based
+
+ {% endif %}
+
diff --git a/theme/bookmarks.njk b/theme/bookmarks.njk
new file mode 100644
index 0000000..0feb73e
--- /dev/null
+++ b/theme/bookmarks.njk
@@ -0,0 +1,109 @@
+---
+layout: layouts/base.njk
+title: Bookmarks
+withSidebar: true
+pagination:
+ data: collections.bookmarks
+ size: 20
+ alias: paginatedBookmarks
+ generatePageOnEmptyData: true
+permalink: "bookmarks/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
+---
+
+
+
Bookmarks
+ {% set sparklineSvg = collections.bookmarks | postingFrequency %}
+ {% if sparklineSvg %}
+ {{ sparklineSvg | safe }}
+ {% endif %}
+
+
+ Links I've saved for later.
+ ({{ collections.bookmarks.length }} total)
+
+
+ {% if paginatedBookmarks.length > 0 %}
+
+ {% for post in paginatedBookmarks %}
+
+
+
+
+ {{ post.date | dateDisplay }}
+
+ {% if post.data.category %}
+
+ {% if post.data.category is string %}
+ {{ post.data.category }}
+ {% else %}
+ {% for cat in post.data.category %}
+ {{ cat }}
+ {% endfor %}
+ {% endif %}
+
+ {% endif %}
+
+ {# Support both camelCase (Indiekit Eleventy preset) and underscore (legacy) property names #}
+ {% set bookmarkedUrl = post.data.bookmarkOf or post.data.bookmark_of %}
+ {% if bookmarkedUrl %}
+ {% unfurl bookmarkedUrl %}
+
+ {{ bookmarkedUrl }}
+
+ {% endif %}
+ {% if post.templateContent %}
+
+ {{ post.templateContent | safe }}
+
+ {% endif %}
+
+ {% endfor %}
+
+
+ {# Pagination controls #}
+ {% if pagination.pages.length > 1 %}
+
+ {% endif %}
+
+ {% else %}
+ {% set postType = "bookmark" %}
+ {% include "components/empty-collection.njk" %}
+ {% endif %}
+
diff --git a/theme/categories-index.njk b/theme/categories-index.njk
new file mode 100644
index 0000000..3c4d298
--- /dev/null
+++ b/theme/categories-index.njk
@@ -0,0 +1,30 @@
+---
+layout: layouts/base.njk
+title: Categories
+withSidebar: true
+permalink: categories/
+eleventyImport:
+ collections:
+ - categories
+---
+
+
Categories
+
+ Browse posts by category.
+ ({{ collections.categories.length }} categories)
+
+
+ {% if collections.categories.length > 0 %}
+
+ {% for cat in collections.categories %}
+
+
+ {{ cat }}
+
+
+ {% endfor %}
+
+ {% else %}
+
No categories yet.
+ {% endif %}
+
diff --git a/theme/categories.njk b/theme/categories.njk
new file mode 100644
index 0000000..087aa52
--- /dev/null
+++ b/theme/categories.njk
@@ -0,0 +1,83 @@
+---
+layout: layouts/base.njk
+withSidebar: true
+pagination:
+ data: collections.categories
+ size: 1
+ alias: category
+permalink: "categories/{{ category | slugify }}/"
+eleventyComputed:
+ title: "{{ category }}"
+---
+
+
{{ category }}
+
+ Posts tagged with "{{ category }}".
+
+
+ {% set categoryPosts = [] %}
+ {% for post in collections.posts %}
+ {% if post.data.category %}
+ {% if post.data.category is string %}
+ {% if post.data.category == category %}
+ {% set categoryPosts = (categoryPosts.push(post), categoryPosts) %}
+ {% endif %}
+ {% else %}
+ {% if category in post.data.category %}
+ {% set categoryPosts = (categoryPosts.push(post), categoryPosts) %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
+ {% endfor %}
+
+ {% if categoryPosts.length > 0 %}
+
{{ categoryPosts.length }} post{% if categoryPosts.length != 1 %}s{% endif %}
+
+ {% for post in categoryPosts %}
+ {% set postType = post.inputPath | replace("./content/", "") %}
+ {% set postType = postType.split("/")[0] %}
+ {% set borderClass = "" %}
+ {% if postType == "likes" %}
+ {% set borderClass = "border-l-[3px] border-l-red-400 dark:border-l-red-500" %}
+ {% elif postType == "bookmarks" %}
+ {% set borderClass = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif postType == "reposts" %}
+ {% set borderClass = "border-l-[3px] border-l-green-400 dark:border-l-green-500" %}
+ {% elif postType == "replies" %}
+ {% set borderClass = "border-l-[3px] border-l-accent-400 dark:border-l-accent-500" %}
+ {% elif postType == "photos" %}
+ {% set borderClass = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
+ {% else %}
+ {% set borderClass = "border-l-[3px] border-l-surface-300 dark:border-l-surface-600" %}
+ {% endif %}
+
+
+
+
+ {{ post.date | dateDisplay }}
+
+ {{ postType }}
+
+
+ {{ post.templateContent | striptags | truncate(250) }}
+
+
+ View →
+
+
+ {% endfor %}
+
+ {% else %}
+
No posts found with this category.
+ {% endif %}
+
+
+
diff --git a/theme/category-feed-json.njk b/theme/category-feed-json.njk
new file mode 100644
index 0000000..d7612f0
--- /dev/null
+++ b/theme/category-feed-json.njk
@@ -0,0 +1,69 @@
+---
+eleventyExcludeFromCollections: true
+eleventyImport:
+ collections:
+ - categoryFeeds
+pagination:
+ data: collections.categoryFeeds
+ size: 1
+ alias: categoryFeed
+permalink: "categories/{{ categoryFeed.slug }}/feed.json"
+---
+{
+ "version": "https://jsonfeed.org/version/1.1",
+ "title": "{{ site.name }} — {{ categoryFeed.name }}",
+ "home_page_url": "{{ site.url }}/categories/{{ categoryFeed.slug }}/",
+ "feed_url": "{{ site.url }}/categories/{{ categoryFeed.slug }}/feed.json",
+ "hubs": [
+ {
+ "type": "WebSub",
+ "url": "https://websubhub.com/hub"
+ }
+ ],
+ "description": "Posts tagged with \"{{ categoryFeed.name }}\" on {{ site.name }}",
+ "language": "{{ site.locale | default('en') }}",
+ "authors": [
+ {
+ "name": "{{ site.author.name | default(site.name) }}",
+ "url": "{{ site.url }}/"
+ }
+ ],
+ "_textcasting": {
+ "version": "1.0",
+ "about": "https://textcasting.org/"
+ {%- set hasSupport = site.support and (site.support.url or site.support.stripe or site.support.lightning or site.support.paymentPointer) %}
+ {%- if hasSupport %},
+ "support": {{ site.support | textcastingSupport | jsonEncode | safe }}
+ {%- endif %}
+ },
+ "items": [
+ {%- for post in categoryFeed.posts %}
+ {%- set absolutePostUrl = site.url + post.url %}
+ {%- set postImage = post.data.photo %}
+ {%- if postImage %}
+ {%- if postImage[0] and (postImage[0] | length) > 10 %}
+ {%- set postImage = postImage[0] %}
+ {%- endif %}
+ {%- endif %}
+ {%- if not postImage or postImage == "" %}
+ {%- set postImage = post.data.image or (post.content | extractFirstImage) %}
+ {%- endif %}
+ {
+ "id": "{{ absolutePostUrl }}",
+ "url": "{{ absolutePostUrl }}",
+ "title": {% if post.data.title %}{{ post.data.title | jsonEncode | safe }}{% else %}null{% endif %},
+ "content_html": {{ post.content | htmlToAbsoluteUrls(absolutePostUrl) | jsonEncode | safe }},
+ "content_text": {{ post.content | striptags | jsonEncode | safe }},
+ "date_published": "{{ post.date | dateToRfc3339 }}",
+ "date_modified": "{{ (post.data.updated or post.date) | dateToRfc3339 }}"
+ {%- if postImage and postImage != "" and (postImage | length) > 10 %},
+ "image": "{{ postImage | url | absoluteUrl(site.url) }}"
+ {%- endif %}
+ {%- set attachments = post.data | feedAttachments %}
+ {%- if attachments.length > 0 %},
+ "attachments": {{ attachments | jsonEncode | safe }}
+ {%- endif %}
+ }{% if not loop.last %},{% endif %}
+ {%- endfor %}
+ ]
+}
diff --git a/theme/category-feed.njk b/theme/category-feed.njk
new file mode 100644
index 0000000..0ada9b6
--- /dev/null
+++ b/theme/category-feed.njk
@@ -0,0 +1,47 @@
+---
+eleventyExcludeFromCollections: true
+eleventyImport:
+ collections:
+ - categoryFeeds
+pagination:
+ data: collections.categoryFeeds
+ size: 1
+ alias: categoryFeed
+permalink: "categories/{{ categoryFeed.slug }}/feed.xml"
+---
+
+
+
+ {{ site.name }} — {{ categoryFeed.name }}
+ {{ site.url }}/categories/{{ categoryFeed.slug }}/
+ Posts tagged with "{{ categoryFeed.name }}" on {{ site.name }}
+ {{ site.locale | default('en') }}
+
+
+ {{ categoryFeed.posts | getNewestCollectionItemDate | dateToRfc822 }}
+ {%- for post in categoryFeed.posts %}
+ {%- set absolutePostUrl = site.url + post.url %}
+ {%- set postImage = post.data.photo %}
+ {%- if postImage %}
+ {%- if postImage[0] and (postImage[0] | length) > 10 %}
+ {%- set postImage = postImage[0] %}
+ {%- endif %}
+ {%- endif %}
+ {%- if not postImage or postImage == "" %}
+ {%- set postImage = post.data.image or (post.content | extractFirstImage) %}
+ {%- endif %}
+ -
+
{{ post.data.title | default(post.content | striptags | truncate(80)) | escape }}
+ {{ absolutePostUrl }}
+ {{ absolutePostUrl }}
+ {{ post.date | dateToRfc822 }}
+ {{ post.content | htmlToAbsoluteUrls(absolutePostUrl) | escape }}
+ {%- if postImage and postImage != "" and (postImage | length) > 10 %}
+ {%- set imageUrl = postImage | url | absoluteUrl(site.url) %}
+
+
+ {%- endif %}
+
+ {%- endfor %}
+
+
diff --git a/theme/changelog.njk b/theme/changelog.njk
new file mode 100644
index 0000000..68c0a72
--- /dev/null
+++ b/theme/changelog.njk
@@ -0,0 +1,208 @@
+---
+layout: layouts/base.njk
+title: Changelog
+permalink: /changelog/
+eleventyExcludeFromCollections: true
+pagefindIgnore: true
+withSidebar: false
+---
+
+
+
+
+ {# Tab navigation #}
+
+
+
+
+
+
+
+
+
+ {# Loading state #}
+
+
+
+
+
+
Loading changelog...
+
+
+ {# Commit list #}
+
+
+ No recent activity in this category.
+
+
+
+
+
+
+
+
+
+
+
+
+ Show details
+
+
+
+
+
+
+
+
+
+ {# Load more button #}
+
+
+ {# Summary #}
+
+
+ from the last days
+ (all time)
+
+
+
+
+
diff --git a/theme/chardonsbleus.njk b/theme/chardonsbleus.njk
new file mode 100644
index 0000000..3368bc0
--- /dev/null
+++ b/theme/chardonsbleus.njk
@@ -0,0 +1,28 @@
+---
+layout: layouts/base.njk
+title: ChardonsBleus
+permalink: /chardonsbleus/
+eleventyExcludeFromCollections: true
+---
+
+ ChardonsBleus
+
+
diff --git a/theme/css/critical.css b/theme/css/critical.css
new file mode 100644
index 0000000..d662e18
--- /dev/null
+++ b/theme/css/critical.css
@@ -0,0 +1,57 @@
+/* Critical CSS — inlined in for first paint */
+/* Covers: layout shell, header, dark mode toggle, font display, basic typography */
+
+*,*::before,*::after{box-sizing:border-box}
+body{margin:0;font-family:"Inter",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;line-height:1.5;-webkit-font-smoothing:antialiased}
+
+/* Dark mode base — warm stone palette */
+body{background-color:#faf8f5;color:#1c1b19}
+.dark body{background-color:#0f0e0d;color:#faf8f5}
+
+/* Container */
+.container{max-width:64rem;margin-left:auto;margin-right:auto;padding-left:1rem;padding-right:1rem}
+
+/* Header — sticky, visible immediately */
+.site-header{background-color:#faf8f5;border-bottom:1px solid #e8e5df;padding-top:1rem;padding-bottom:1rem;position:sticky;top:0;z-index:50}
+.dark .site-header{background-color:#1c1b19;border-bottom-color:#3f3b35}
+.header-container{display:flex;align-items:center;justify-content:space-between}
+.site-title{font-size:1.25rem;font-weight:700;color:#1c1b19;text-decoration:none}
+.dark .site-title{color:#faf8f5}
+
+/* Header actions — hidden on mobile */
+.header-actions{display:none}
+@media(min-width:768px){.header-actions{display:flex;align-items:center;gap:1rem}}
+
+/* Mobile menu toggle */
+.menu-toggle{display:block;padding:0.5rem;border-radius:0.5rem;background:none;border:none;color:#5c5750;cursor:pointer}
+@media(min-width:768px){.menu-toggle{display:none}}
+.dark .menu-toggle{color:#a09a90}
+
+/* Hidden utility */
+.hidden{display:none!important}
+[x-cloak]{display:none!important}
+
+/* Dark mode theme toggle icons */
+.theme-toggle .sun-icon{display:none}
+.theme-toggle .moon-icon{display:block}
+.dark .theme-toggle .sun-icon{display:block}
+.dark .theme-toggle .moon-icon{display:none}
+
+/* Main content padding */
+main.container{padding-top:1.5rem;padding-bottom:1.5rem}
+@media(min-width:768px){main.container{padding-top:2rem;padding-bottom:2rem}}
+
+/* Layout with sidebar */
+.layout-with-sidebar{display:grid;grid-template-columns:1fr;gap:1.5rem}
+@media(min-width:1024px){.layout-with-sidebar{grid-template-columns:2fr 1fr;gap:2rem}}
+.main-content{min-width:0;overflow-x:hidden}
+
+/* Basic typography — prevent FOUT */
+h1,h2,h3,h4{margin:0;line-height:1.25}
+a{color:#b45309}
+.dark a{color:#fbbf24}
+
+/* Prevent flash of unstyled content for nav */
+.site-nav{display:flex;align-items:center;gap:1rem}
+.site-nav>a,.site-nav .nav-dropdown-trigger{color:#5c5750;text-decoration:none;padding-top:0.5rem;padding-bottom:0.5rem}
+.dark .site-nav>a,.dark .site-nav .nav-dropdown-trigger{color:#a09a90}
diff --git a/theme/css/lite-yt-embed.css b/theme/css/lite-yt-embed.css
new file mode 100644
index 0000000..c96a472
--- /dev/null
+++ b/theme/css/lite-yt-embed.css
@@ -0,0 +1,95 @@
+lite-youtube {
+ background-color: #000;
+ position: relative;
+ display: block;
+ contain: content;
+ background-position: center center;
+ background-size: cover;
+ cursor: pointer;
+ max-width: 720px;
+}
+
+/* gradient */
+lite-youtube::before {
+ content: attr(data-title);
+ display: block;
+ position: absolute;
+ top: 0;
+ /* Pixel-perfect port of YT's gradient PNG, using https://github.com/bluesmoon/pngtocss plus optimizations */
+ background-image: linear-gradient(180deg, rgb(0 0 0 / 67%) 0%, rgb(0 0 0 / 54%) 14%, rgb(0 0 0 / 15%) 54%, rgb(0 0 0 / 5%) 72%, rgb(0 0 0 / 0%) 94%);
+ height: 99px;
+ width: 100%;
+ font-family: "YouTube Noto",Roboto,Arial,Helvetica,sans-serif;
+ color: hsl(0deg 0% 93.33%);
+ text-shadow: 0 0 2px rgba(0,0,0,.5);
+ font-size: 18px;
+ padding: 25px 20px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ box-sizing: border-box;
+}
+
+lite-youtube:hover::before {
+ color: white;
+}
+
+/* responsive iframe with a 16:9 aspect ratio
+ thanks https://css-tricks.com/responsive-iframes/
+*/
+lite-youtube::after {
+ content: "";
+ display: block;
+ padding-bottom: calc(100% / (16 / 9));
+}
+lite-youtube > iframe {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ border: 0;
+}
+
+/* play button */
+lite-youtube > .lty-playbtn {
+ display: block;
+ /* Make the button element cover the whole area for a large hover/click target… */
+ width: 100%;
+ height: 100%;
+ /* …but visually it's still the same size */
+ background: no-repeat center/68px 48px;
+ /* YT's actual play button svg */
+ background-image: url('data:image/svg+xml;utf8, ');
+ position: absolute;
+ cursor: pointer;
+ z-index: 1;
+ filter: grayscale(100%);
+ transition: filter .1s cubic-bezier(0, 0, 0.2, 1);
+ border: 0;
+}
+
+lite-youtube:hover > .lty-playbtn,
+lite-youtube .lty-playbtn:focus {
+ filter: none;
+}
+
+/* Post-click styles */
+lite-youtube.lyt-activated {
+ cursor: unset;
+}
+lite-youtube.lyt-activated::before,
+lite-youtube.lyt-activated > .lty-playbtn {
+ opacity: 0;
+ pointer-events: none;
+}
+
+.lyt-visually-hidden {
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+ }
diff --git a/theme/css/prism-theme.css b/theme/css/prism-theme.css
new file mode 100644
index 0000000..90bf299
--- /dev/null
+++ b/theme/css/prism-theme.css
@@ -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: #f4f2ee;
+ border: 1px solid #e1e4e8;
+}
+
+:not(pre) > code[class*="language-"] {
+ background: #f4f2ee;
+}
+
+.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);
+}
diff --git a/theme/css/tailwind.css b/theme/css/tailwind.css
new file mode 100644
index 0000000..fe7d4b4
--- /dev/null
+++ b/theme/css/tailwind.css
@@ -0,0 +1,986 @@
+/* Inter font — latin + latin-ext subsets, weights 400/500/600/700 */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 400;
+ src: url(/fonts/inter-latin-ext-400-normal.woff2) format('woff2');
+ unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
+}
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 400;
+ src: url(/fonts/inter-latin-400-normal.woff2) format('woff2');
+ unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
+}
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 500;
+ src: url(/fonts/inter-latin-ext-500-normal.woff2) format('woff2');
+ unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
+}
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 500;
+ src: url(/fonts/inter-latin-500-normal.woff2) format('woff2');
+ unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
+}
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 600;
+ src: url(/fonts/inter-latin-ext-600-normal.woff2) format('woff2');
+ unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
+}
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 600;
+ src: url(/fonts/inter-latin-600-normal.woff2) format('woff2');
+ unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
+}
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 700;
+ src: url(/fonts/inter-latin-ext-700-normal.woff2) format('woff2');
+ unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
+}
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 700;
+ src: url(/fonts/inter-latin-700-normal.woff2) format('woff2');
+ unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
+}
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* Accessibility utilities */
+@layer utilities {
+ .visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+ }
+
+ .skip-link {
+ @apply absolute -top-full left-0 z-50 bg-accent-600 text-white px-4 py-2;
+ }
+
+ .skip-link:focus {
+ @apply top-0;
+ }
+}
+
+/* Reduce motion for accessibility */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
+}
+
+/* Body background — warm stone canvas */
+@layer base {
+ body {
+ @apply bg-surface-50 dark:bg-surface-950 text-surface-900 dark:text-surface-100;
+ }
+
+ /* P1: Date typography — all elements get monospace for technical texture */
+ time {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ }
+
+ /* P2: Focus-visible states — keyboard-only focus rings for accessibility (WCAG 2.4.7) */
+ button:focus-visible,
+ [role="button"]:focus-visible,
+ summary:focus-visible {
+ @apply outline-none ring-2 ring-amber-500/70 ring-offset-2 ring-offset-surface-50 rounded-sm;
+ }
+
+ .dark button:focus-visible,
+ .dark [role="button"]:focus-visible,
+ .dark summary:focus-visible {
+ @apply ring-offset-surface-900;
+ }
+
+ input:focus-visible,
+ select:focus-visible,
+ textarea:focus-visible {
+ @apply outline-none ring-2 ring-amber-500/70 border-transparent;
+ }
+
+ a:focus-visible {
+ @apply outline-none ring-2 ring-amber-500/70 ring-offset-1 ring-offset-surface-50 rounded-sm;
+ }
+
+ .dark a:focus-visible {
+ @apply ring-offset-surface-900;
+ }
+}
+
+/* Layout styles */
+@layer components {
+ /* Site header */
+ .site-header {
+ @apply bg-surface-50 dark:bg-surface-900 border-b border-surface-200 dark:border-surface-700 py-4 sticky top-0 z-50;
+ }
+
+ .header-container {
+ @apply flex items-center justify-between;
+ }
+
+ .site-title {
+ @apply text-xl font-bold text-surface-900 dark:text-white no-underline hover:text-surface-600 dark:hover:text-surface-300 transition-colors;
+ }
+
+ /* Header actions (nav + theme toggle) */
+ .header-actions {
+ @apply hidden md:flex items-center gap-4;
+ }
+
+ .site-nav {
+ @apply flex items-center gap-4;
+ }
+
+ .site-nav > a,
+ .site-nav .nav-dropdown-trigger {
+ @apply text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 no-underline transition-colors py-2;
+ }
+
+ /* Navigation dropdown */
+ .nav-dropdown {
+ @apply relative;
+ }
+
+ .nav-dropdown-trigger {
+ @apply flex items-center gap-1 cursor-pointer bg-transparent border-none text-base;
+ }
+
+ .nav-dropdown-menu {
+ @apply absolute top-full left-0 mt-1 py-2 bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 rounded-lg shadow-lg min-w-[160px] z-50 overflow-y-auto;
+ max-height: calc(100vh - 5rem);
+ max-height: calc(100dvh - 5rem);
+ }
+
+ .nav-dropdown-menu a {
+ @apply block px-4 py-2 text-sm text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-700 hover:text-surface-900 dark:hover:text-surface-100 no-underline;
+ }
+
+ .nav-dropdown-divider {
+ @apply my-2 border-t border-surface-200 dark:border-surface-700;
+ }
+
+ /* Mobile menu toggle button */
+ .menu-toggle {
+ @apply md:hidden p-2 rounded-lg text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors;
+ }
+
+ /* Mobile navigation dropdown */
+ .mobile-nav {
+ @apply md:hidden border-t border-surface-200 dark:border-surface-700 bg-surface-50 dark:bg-surface-900 overflow-y-auto;
+ max-height: calc(100vh - 4rem);
+ max-height: calc(100dvh - 4rem);
+ }
+
+ .mobile-nav a {
+ @apply block px-4 py-3 text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 hover:text-surface-900 dark:hover:text-surface-100 no-underline transition-colors border-b border-surface-100 dark:border-surface-800 last:border-b-0;
+ }
+
+ /* Mobile nav collapsible sections */
+ .mobile-nav-section {
+ @apply border-b border-surface-100 dark:border-surface-800;
+ }
+
+ .mobile-nav-toggle {
+ @apply flex items-center justify-between w-full px-4 py-3 text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 hover:text-surface-900 dark:hover:text-surface-100 transition-colors bg-transparent border-none text-base text-left cursor-pointer;
+ }
+
+ .mobile-nav-submenu {
+ @apply bg-surface-50 dark:bg-surface-800;
+ }
+
+ .mobile-nav-submenu a {
+ @apply pl-8 py-2 text-sm border-b-0;
+ }
+
+ .mobile-nav-divider {
+ @apply my-2 mx-4 border-t border-surface-200 dark:border-surface-700;
+ }
+
+ /* Theme toggle button */
+ .theme-toggle {
+ @apply p-2 rounded-lg text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors;
+ }
+
+ .theme-toggle .sun-icon {
+ @apply hidden;
+ }
+
+ .theme-toggle .moon-icon {
+ @apply block;
+ }
+
+ .dark .theme-toggle .sun-icon {
+ @apply block;
+ }
+
+ .dark .theme-toggle .moon-icon {
+ @apply hidden;
+ }
+
+ /* Mobile theme toggle */
+ .mobile-theme-toggle {
+ @apply flex items-center justify-between w-full px-4 py-3 text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 hover:text-surface-900 dark:hover:text-surface-100 transition-colors bg-transparent border-none text-base text-left cursor-pointer border-t border-surface-200 dark:border-surface-700;
+ }
+
+ .mobile-theme-toggle .theme-label {
+ @apply font-normal;
+ }
+
+ .mobile-theme-toggle .theme-icons {
+ @apply flex items-center;
+ }
+
+ .mobile-theme-toggle .sun-icon {
+ @apply hidden;
+ }
+
+ .mobile-theme-toggle .moon-icon {
+ @apply block;
+ }
+
+ .dark .mobile-theme-toggle .sun-icon {
+ @apply block;
+ }
+
+ .dark .mobile-theme-toggle .moon-icon {
+ @apply hidden;
+ }
+
+ /* Container */
+ .container {
+ @apply max-w-5xl mx-auto px-4;
+ }
+
+ /* Site footer */
+ .site-footer {
+ @apply mt-12 py-8 border-t border-surface-200 dark:border-surface-700 text-center text-sm text-surface-500;
+ }
+
+ .site-footer a {
+ @apply text-accent-600 dark:text-accent-400 hover:underline;
+ }
+
+ /* Layout with sidebar - mobile-first with responsive grid */
+ .layout-with-sidebar {
+ @apply grid grid-cols-1 gap-6 md:gap-8 lg:grid-cols-3;
+ }
+
+ .main-content {
+ @apply lg:col-span-2 min-w-0 overflow-x-hidden; /* min-w-0 + overflow-x-hidden prevents layout breaking */
+ }
+
+ .sidebar {
+ @apply space-y-6 lg:sticky lg:top-24 lg:self-start overflow-hidden;
+ }
+
+ /* Main content area - adjust padding for mobile */
+ main.container {
+ @apply py-6 md:py-8;
+ }
+}
+
+/* Custom component styles */
+@layer components {
+ /* Post list */
+ .post-list {
+ @apply list-none p-0 m-0 space-y-6;
+ }
+
+ .post-list li {
+ @apply pb-6 border-b border-b-surface-200 dark:border-b-surface-700 last:border-0;
+ }
+
+ /* Post meta */
+ .post-meta {
+ @apply text-sm text-surface-600 dark:text-surface-400 flex flex-wrap gap-2 items-center;
+ }
+
+ /* Category tags (post metadata pills) */
+ .p-category {
+ @apply inline-block px-2 py-0.5 text-xs bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-300 rounded border border-surface-200 dark:border-surface-700 hover:border-surface-400 dark:hover:border-surface-500 transition-colors;
+ }
+
+ /* Inline hashtags in post content — styled as subtle links, not pills */
+ .e-content a.hashtag,
+ .prose a.hashtag {
+ @apply text-accent-600 dark:text-accent-400 no-underline hover:underline font-medium;
+ /* Override prose default link styling (no border-bottom, no color shift) */
+ text-decoration: none;
+ }
+
+ /* Webmention facepile - overlapping avatar display */
+ .facepile {
+ @apply flex flex-wrap items-center;
+ }
+
+ .facepile-avatar {
+ @apply inline-block -ml-2 first:ml-0 transition-transform hover:z-10 hover:scale-110;
+ }
+
+ .facepile-avatar img {
+ @apply w-8 h-8 rounded-full;
+ }
+
+ /* GitHub components */
+ .repo-card {
+ @apply p-4 border border-surface-200 dark:border-surface-700 rounded-lg;
+ }
+
+ .repo-meta {
+ @apply flex gap-4 text-sm text-surface-600 dark:text-surface-400 mt-2;
+ }
+
+ /* Timeline for CV */
+ .timeline {
+ @apply relative pl-6 border-l-2 border-accent-500;
+ }
+
+ .timeline-item {
+ @apply relative pb-6 last:pb-0;
+ }
+
+ .timeline-item::before {
+ content: '';
+ @apply absolute -left-[calc(1.5rem+5px)] top-1.5 w-3 h-3 bg-accent-500 rounded-full;
+ }
+
+ /* Skills badges */
+ .skill-badge {
+ @apply inline-block px-3 py-1 text-sm bg-surface-100 dark:bg-surface-800 rounded-full;
+ }
+
+ /* Ensure is-land custom elements don't break block layout flow */
+ is-land {
+ @apply block;
+ }
+
+ /* Widget cards */
+ .widget {
+ @apply p-4 mb-4 bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden;
+ }
+
+ .widget-title {
+ @apply font-bold text-lg mb-4;
+ }
+
+ /* Collapsible widget wrapper */
+ .widget-header {
+ @apply flex items-center justify-between cursor-pointer;
+ }
+
+ .widget-header .widget-title {
+ @apply mb-0;
+ }
+
+ .widget-chevron {
+ @apply w-4 h-4 text-surface-400 transition-transform duration-200 shrink-0;
+ }
+
+ /* Hide inner widget titles when the collapsible wrapper provides one */
+ .widget-collapsible .widget .widget-title {
+ @apply hidden;
+ }
+
+ /* Hide FeedLand's custom title in collapsible wrapper */
+ .widget-collapsible .widget .fl-title {
+ @apply hidden;
+ }
+
+ /* Neutralize inner widget card styling when inside collapsible wrapper */
+ .widget-collapsible .widget {
+ @apply border-0 shadow-none rounded-none mb-0 bg-transparent;
+ }
+
+ /* Post cards */
+ .post-card {
+ @apply p-5 bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden;
+ }
+
+ .post-header {
+ @apply flex flex-wrap items-center gap-2;
+ }
+
+ .post-footer {
+ @apply pt-3 border-t border-surface-100 dark:border-surface-700;
+ }
+
+ /* Photo gallery on listing pages */
+ .photo-list li {
+ @apply pb-8;
+ }
+
+ .photo-gallery {
+ @apply my-4 grid gap-2;
+ }
+
+ .photo-gallery img {
+ @apply w-full max-h-[500px] object-cover rounded-lg;
+ }
+
+ .photo-link {
+ @apply block;
+ }
+
+ .photo-caption {
+ @apply mt-3 text-surface-600 dark:text-surface-400;
+ }
+
+ /* Multi-photo grid */
+ .photo-gallery:has(img:nth-child(2)) {
+ @apply grid-cols-2;
+ }
+
+ .photo-gallery:has(img:nth-child(3)) {
+ @apply grid-cols-2;
+ }
+
+ .photo-gallery:has(img:nth-child(4)) {
+ @apply grid-cols-2;
+ }
+
+ /* Pagination */
+ .pagination {
+ @apply mt-12 pt-8 border-t border-surface-200 dark:border-surface-700 flex flex-col sm:flex-row items-center justify-between gap-4;
+ }
+
+ .pagination-info {
+ @apply text-sm text-surface-600 dark:text-surface-400;
+ }
+
+ .pagination-links {
+ @apply flex items-center gap-2;
+ }
+
+ .pagination-link {
+ @apply inline-flex items-center gap-1 px-4 py-2 text-sm font-medium bg-surface-100 dark:bg-surface-800 rounded-lg hover:bg-surface-200 dark:hover:bg-surface-700 transition-colors;
+ }
+
+ .pagination-link.disabled {
+ @apply opacity-50 cursor-not-allowed hover:bg-surface-100 dark:hover:bg-surface-800;
+ }
+}
+
+/* Focus states */
+@layer base {
+ a:focus-visible,
+ button:focus-visible,
+ input:focus-visible,
+ textarea:focus-visible,
+ select:focus-visible {
+ @apply outline-2 outline-offset-2 outline-accent-500;
+ }
+}
+
+/* Active states — subtle press feedback on buttons */
+@layer base {
+ button:active:not(:disabled),
+ .pagination-link:active:not(.disabled) {
+ transform: scale(0.97);
+ }
+}
+
+/* Video embeds */
+@layer components {
+ .video-embed {
+ @apply relative w-full aspect-video my-4;
+ }
+
+ .video-embed iframe {
+ @apply absolute inset-0 w-full h-full rounded-lg;
+ }
+}
+
+/* Admin UI - FAB and dashboard link */
+@layer components {
+ .fab-container {
+ @apply fixed bottom-6 right-6 z-50 flex flex-col items-end;
+ }
+
+ .fab-backdrop {
+ @apply fixed inset-0 bg-black/20 dark:bg-black/40 z-40;
+ }
+
+ .fab-button {
+ @apply relative z-50 w-14 h-14 rounded-full bg-accent-600 hover:bg-accent-700 dark:bg-accent-500 dark:hover:bg-accent-600 text-white shadow-lg hover:shadow-xl transition-all duration-200 flex items-center justify-center;
+ }
+
+ .fab-button:focus-visible {
+ @apply outline-2 outline-offset-2 outline-accent-500;
+ }
+
+ .fab-menu {
+ @apply relative z-50 mb-3 flex flex-col gap-2 items-end;
+ }
+
+ .fab-menu-item {
+ @apply flex items-center gap-3 px-4 py-3 rounded-xl bg-surface-50 dark:bg-surface-800 shadow-md hover:shadow-lg border border-surface-200 dark:border-surface-700 text-surface-700 dark:text-surface-200 hover:text-accent-600 dark:hover:text-accent-400 no-underline transition-all duration-150 text-sm font-medium;
+ }
+
+ .fab-menu-divider {
+ @apply border-t border-surface-200 dark:border-surface-700 my-1 w-full;
+ }
+
+ .admin-nav-link {
+ @apply text-accent-600 dark:text-accent-400 hover:text-accent-700 dark:hover:text-accent-300 no-underline transition-colors py-2 inline-flex items-center gap-1;
+ }
+}
+
+/* Performance: content-visibility for off-screen rendering optimization */
+@layer utilities {
+ .content-auto {
+ content-visibility: auto;
+ contain-intrinsic-size: auto 500px;
+ }
+}
+
+/* Dates — monospace for technical texture (system.md: every gets font-mono) */
+@layer base {
+ time {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ }
+}
+
+/* Apply content-visibility to images and post items for performance */
+@layer base {
+ /* Responsive typography */
+ html {
+ @apply text-base md:text-lg;
+ }
+
+ /* Prevent horizontal overflow */
+ body {
+ @apply overflow-x-hidden;
+ }
+
+ /* Images - prevent overflow and add content-visibility */
+ img {
+ @apply max-w-full h-auto;
+ content-visibility: auto;
+ }
+
+ /* Pre/code blocks - prevent overflow on mobile */
+ pre {
+ @apply overflow-x-auto max-w-full;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ code {
+ @apply break-words;
+ }
+
+ pre code {
+ word-break: normal;
+ overflow-wrap: normal;
+ }
+
+ /* Links in content - break long URLs */
+ .e-content a,
+ .prose a {
+ @apply break-words;
+ word-break: break-word;
+ }
+
+ /* Content containers - clip horizontal overflow but allow pre blocks to scroll */
+ .e-content,
+ .prose {
+ overflow-x: clip;
+ max-width: 100%;
+ }
+
+ article {
+ 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;
+ }
+
+ /* Tables - responsive handling */
+ table {
+ @apply w-full;
+ display: block;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ /* Ensure truncate works properly in flex containers */
+ .truncate {
+ @apply overflow-hidden text-ellipsis whitespace-nowrap;
+ }
+
+ /* Video embeds - maintain aspect ratio */
+ lite-youtube,
+ iframe[src*="youtube"],
+ iframe[src*="vimeo"] {
+ @apply max-w-full;
+ }
+}
+
+/* Pagefind UI theme overrides — outside @layer for higher specificity over Pagefind's :root defaults */
+#search .pagefind-ui {
+ --pagefind-ui-scale: 1;
+ --pagefind-ui-primary: #b45309;
+ --pagefind-ui-text: #1c1b19;
+ --pagefind-ui-background: #faf8f5;
+ --pagefind-ui-border: #e8e5df;
+ --pagefind-ui-tag: #f4f2ee;
+ --pagefind-ui-border-width: 1px;
+ --pagefind-ui-border-radius: 8px;
+ --pagefind-ui-image-border-radius: 8px;
+ --pagefind-ui-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+}
+
+.dark #search .pagefind-ui {
+ --pagefind-ui-primary: #fbbf24;
+ --pagefind-ui-text: #faf8f5;
+ --pagefind-ui-background: #0f0e0d;
+ --pagefind-ui-border: #3f3b35;
+ --pagefind-ui-tag: #2a2722;
+}
+
+/* Search input */
+#search .pagefind-ui__search-input {
+ background-color: #faf8f5;
+ color: #1c1b19;
+ border-color: #e8e5df;
+ font-weight: 400;
+}
+
+.dark #search .pagefind-ui__search-input {
+ background-color: #1c1b19;
+ color: #faf8f5;
+ border-color: #3f3b35;
+}
+
+#search .pagefind-ui__search-input:focus {
+ outline: 2px solid #d97706;
+ outline-offset: 2px;
+ border-color: #d97706;
+}
+
+.dark #search .pagefind-ui__search-input:focus {
+ outline-color: #fbbf24;
+ border-color: #fbbf24;
+}
+
+/* Search clear button */
+#search .pagefind-ui__search-clear {
+ color: #5c5750;
+ background-color: #faf8f5;
+}
+
+.dark #search .pagefind-ui__search-clear {
+ color: #a09a90;
+ background-color: #1c1b19;
+}
+
+#search .pagefind-ui__search-clear:hover {
+ color: #1c1b19;
+}
+
+.dark #search .pagefind-ui__search-clear:hover {
+ color: #faf8f5;
+}
+
+/* Result links */
+#search .pagefind-ui__result-link {
+ color: #b45309;
+}
+
+#search .pagefind-ui__result-link:hover {
+ text-decoration: underline;
+}
+
+.dark #search .pagefind-ui__result-link {
+ color: #fbbf24;
+}
+
+/* Result excerpts */
+#search .pagefind-ui__result-excerpt {
+ color: #5c5750;
+}
+
+.dark #search .pagefind-ui__result-excerpt {
+ color: #a09a90;
+}
+
+/* Highlighted search terms in results */
+#search .pagefind-ui__result-excerpt mark,
+#search mark {
+ background-color: #fef3c7;
+ color: #92400e;
+ padding: 0.1em 0.2em;
+ border-radius: 2px;
+}
+
+.dark #search .pagefind-ui__result-excerpt mark,
+.dark #search mark {
+ background-color: #78350f;
+ color: #fde68a;
+}
+
+/* Message (result count) */
+#search .pagefind-ui__message {
+ color: #5c5750;
+}
+
+.dark #search .pagefind-ui__message {
+ color: #a09a90;
+}
+
+/* "Load more" button */
+#search .pagefind-ui__button {
+ color: #b45309;
+ background-color: #faf8f5;
+ border-color: #e8e5df;
+ cursor: pointer;
+}
+
+#search .pagefind-ui__button:hover {
+ background-color: #fffbeb;
+ border-color: #b45309;
+}
+
+.dark #search .pagefind-ui__button {
+ color: #fbbf24;
+ background-color: #0f0e0d;
+ border-color: #3f3b35;
+}
+
+.dark #search .pagefind-ui__button:hover {
+ background-color: #1c1b19;
+ border-color: #fbbf24;
+}
+
+/* Filter panel labels */
+#search .pagefind-ui__filter-name,
+#search .pagefind-ui__filter-label {
+ color: #18181b;
+}
+
+.dark #search .pagefind-ui__filter-name,
+.dark #search .pagefind-ui__filter-label {
+ color: #f4f4f5;
+}
+
+/* Result tags */
+#search .pagefind-ui__result-tag {
+ background-color: #f4f4f5;
+ color: #52525b;
+}
+
+.dark #search .pagefind-ui__result-tag {
+ background-color: #27272a;
+ color: #a1a1aa;
+}
+
+/* Sub-result nested links */
+#search .pagefind-ui__result-nested .pagefind-ui__result-link {
+ color: #2563eb;
+ font-weight: 400;
+}
+
+.dark #search .pagefind-ui__result-nested .pagefind-ui__result-link {
+ color: #60a5fa;
+}
+
+/* Mobile-specific improvements */
+@layer utilities {
+ /* Ensure proper touch scrolling on overflow containers */
+ .overflow-x-auto {
+ -webkit-overflow-scrolling: touch;
+ }
+
+ /* Hide scrollbar but allow scrolling */
+ .scrollbar-hide {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ }
+
+ .scrollbar-hide::-webkit-scrollbar {
+ display: none;
+ }
+}
+
+/* Sparkline — inline SVG posting frequency chart */
+.sparkline {
+ width: 120px;
+ height: 28px;
+ display: block;
+}
+
+@media (min-width: 640px) {
+ .sparkline {
+ width: 180px;
+ height: 32px;
+ }
+}
+
+/* Save for Later buttons — hidden until auth confirmed */
+.save-later-btn {
+ display: none;
+}
+
+body[data-indiekit-auth="true"] .save-later-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ cursor: pointer;
+ background: none;
+ border: 1px solid transparent;
+ border-radius: 6px;
+ padding: 2px 8px;
+ font-size: 0.75rem;
+ color: #6b7280;
+ transition: all 0.2s ease;
+}
+
+body[data-indiekit-auth="true"] .save-later-btn:hover {
+ border-color: #d1d5db;
+ color: #4a9eff;
+}
+
+.save-later--saved {
+ color: #4a9eff !important;
+ opacity: 0.6;
+ pointer-events: none;
+}
+
+/* Share Post buttons — hidden until auth confirmed */
+.share-post-btn {
+ display: none;
+}
+
+body[data-indiekit-auth="true"] .share-post-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ cursor: pointer;
+ background: none;
+ border: 1px solid transparent;
+ border-radius: 6px;
+ padding: 2px 8px;
+ font-size: 0.75rem;
+ color: #6b7280;
+ transition: all 0.2s ease;
+}
+
+body[data-indiekit-auth="true"] .share-post-btn:hover {
+ border-color: #d1d5db;
+ color: #10b981;
+}
+
+/* Post type dropdown */
+.post-type-dropdown {
+ display: none;
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 4px;
+ background: white;
+ border: 1px solid #e5e7eb;
+ border-radius: 6px;
+ box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); /* shadow-sm */
+ z-index: 50;
+ min-width: 120px;
+ overflow: hidden;
+}
+
+.post-type-dropdown.open {
+ display: block;
+}
+
+.post-type-dropdown-item {
+ display: block;
+ width: 100%;
+ padding: 6px 12px;
+ font-size: 13px;
+ color: #374151;
+ text-align: left;
+ background: none;
+ border: none;
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+.post-type-dropdown-item:hover {
+ background: #f3f4f6;
+ color: #10b981;
+}
+
+/* Dark mode */
+@media (prefers-color-scheme: dark) {
+ .post-type-dropdown {
+ background: #1f2937;
+ border-color: #374151;
+ }
+ .post-type-dropdown-item {
+ color: #d1d5db;
+ }
+ .post-type-dropdown-item:hover {
+ background: #374151;
+ color: #34d399;
+ }
+}
diff --git a/theme/cv.njk b/theme/cv.njk
new file mode 100644
index 0000000..afb1116
--- /dev/null
+++ b/theme/cv.njk
@@ -0,0 +1,146 @@
+---
+layout: layouts/base.njk
+withSidebar: false
+title: CV
+permalink: /cv/
+pagefindIgnore: true
+---
+
+{# CV page — uses configurable layout when cvPageConfig exists, falls back to hardcoded layout #}
+
+{% set hasCvData = (cv.experience and cv.experience.length) or
+ (cv.projects and cv.projects.length) or
+ (cv.skills and ((cv.skills or {}) | dictsort | length)) or
+ (cv.interests and ((cv.interests or {}) | dictsort | length)) %}
+
+{% if hasCvData %}
+
+ {# Configurable layout — use cvPageConfig if available #}
+ {% if cvPageConfig and cvPageConfig.sections %}
+ {% include "components/cv-builder.njk" %}
+
+ {# Fallback — hardcoded layout for backward compatibility #}
+ {% else %}
+
+ {# CV identity — check cvPageConfig.identity first, fall back to site.author #}
+ {% set cvId = cvPageConfig.identity if (cvPageConfig and cvPageConfig.identity) else {} %}
+ {% set authorName = cvId.name or site.author.name %}
+ {% set authorAvatar = cvId.avatar or site.author.avatar %}
+ {% set authorTitle = cvId.title or site.author.title %}
+ {% set authorBio = cvId.bio or site.author.bio %}
+ {% set socialLinks = cvId.social if (cvId.social and cvId.social.length) else site.social %}
+ {% set cvLocality = cvId.locality or site.author.locality %}
+ {% set cvCountry = cvId.country or site.author.country %}
+ {% set cvOrg = cvId.org or site.author.org %}
+ {% set cvUrl = cvId.url or '' %}
+ {% set cvEmail = cvId.email or site.author.email %}
+ {% set cvKeyUrl = cvId.keyUrl or site.author.keyUrl %}
+
+ {# Hero / intro #}
+
+
+
+
+
+ {{ authorName }}
+
+ {% if authorTitle %}
+
+ {{ authorTitle }}
+
+ {% endif %}
+ {% if authorBio %}
+
+ {{ authorBio }}
+
+ {% endif %}
+ {% from "components/social-icon.njk" import socialIcon %}
+ {% if socialLinks %}
+
+ {% endif %}
+ {# Contact details #}
+ {% if cvLocality or cvCountry or cvOrg or cvUrl or cvEmail or cvKeyUrl %}
+
+ {% if cvLocality or cvCountry %}
+
{% if cvLocality %}{{ cvLocality }}{% endif %}{% if cvLocality and cvCountry %}, {% endif %}{% if cvCountry %}{{ cvCountry }}{% endif %}
+ {% endif %}
+ {% if cvOrg %}
+
{{ cvOrg }}
+ {% endif %}
+ {% if cvUrl %}
+
{{ cvUrl | replace("https://", "") | replace("http://", "") }}
+ {% endif %}
+ {% if cvEmail %}
+
{{ cvEmail }}
+ {% endif %}
+ {% if cvKeyUrl %}
+
PGP Key
+ {% endif %}
+
+ {% endif %}
+
+
+
+
+ {# Experience — work-only variant #}
+ {% set section = { type: "cv-experience-work", config: {} } %}
+ {% include "components/sections/cv-experience-work.njk" ignore missing %}
+
+ {# Skills — work-only variant #}
+ {% set section = { type: "cv-skills-work", config: {} } %}
+ {% include "components/sections/cv-skills-work.njk" ignore missing %}
+
+ {# Work Projects (only work-related projects on the CV page) #}
+ {% set section = { type: "cv-projects-work", config: {} } %}
+ {% include "components/sections/cv-projects-work.njk" ignore missing %}
+
+ {# Education — work-only variant #}
+ {% set section = { type: "cv-education-work", config: {} } %}
+ {% include "components/sections/cv-education-work.njk" ignore missing %}
+
+ {# Languages — standalone section #}
+ {% set section = { type: "cv-languages", config: {} } %}
+ {% include "components/sections/cv-languages.njk" ignore missing %}
+
+ {# Interests — work-only variant #}
+ {% set section = { type: "cv-interests-work", config: {} } %}
+ {% include "components/sections/cv-interests-work.njk" ignore missing %}
+
+ {# Last Updated #}
+ {% if cv.lastUpdated %}
+
+ Last updated: {{ cv.lastUpdated | date("PPP") }}
+
+ {% endif %}
+
+ {% endif %}
+
+{% else %}
+
+
+
CV
+
+ No CV data available yet. Add your experience, projects, and skills via the
+ admin dashboard .
+
+
+
+{% endif %}
diff --git a/theme/digest-feed.njk b/theme/digest-feed.njk
new file mode 100644
index 0000000..6505876
--- /dev/null
+++ b/theme/digest-feed.njk
@@ -0,0 +1,31 @@
+---
+eleventyExcludeFromCollections: true
+eleventyImport:
+ collections:
+ - weeklyDigests
+permalink: /digest/feed.xml
+---
+
+
+
+ {{ site.name }} — Weekly Digest
+ {{ site.url }}/digest/
+ Weekly summary of all posts on {{ site.name }}. One update per week.
+ {{ site.locale | default('en') }}
+
+
+ {%- set latestDigests = collections.weeklyDigests | head(20) %}
+ {%- if latestDigests.length %}
+ {{ latestDigests[0].endDate | dateToRfc822 }}
+ {%- endif %}
+ {%- for digest in latestDigests %}
+ -
+
{{ digest.label }} ({{ digest.startDate | dateDisplay }} – {{ digest.endDate | dateDisplay }})
+ {{ site.url }}/digest/{{ digest.slug }}/
+ {{ site.url }}/digest/{{ digest.slug }}/
+ {{ digest.endDate | dateToRfc822 }}
+ {{ digest | digestToHtml(site.url) | escape }}
+
+ {%- endfor %}
+
+
diff --git a/theme/digest-index.njk b/theme/digest-index.njk
new file mode 100644
index 0000000..959788e
--- /dev/null
+++ b/theme/digest-index.njk
@@ -0,0 +1,83 @@
+---
+layout: layouts/base.njk
+title: Weekly Digest
+withSidebar: true
+eleventyExcludeFromCollections: true
+eleventyImport:
+ collections:
+ - weeklyDigests
+pagination:
+ data: collections.weeklyDigests
+ size: 20
+ alias: paginatedDigests
+permalink: "digest/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
+---
+
+
Weekly Digest
+
+ A weekly summary of all posts. Subscribe via RSS for one update per week.
+
+
+ {% if paginatedDigests.length > 0 %}
+
+
+ {% if pagination.pages.length > 1 %}
+
+ {% endif %}
+
+ {% else %}
+
No digests yet. Posts will be grouped into weekly digests automatically.
+ {% endif %}
+
diff --git a/theme/digest.njk b/theme/digest.njk
new file mode 100644
index 0000000..aa40bf8
--- /dev/null
+++ b/theme/digest.njk
@@ -0,0 +1,168 @@
+---
+layout: layouts/base.njk
+withSidebar: true
+eleventyExcludeFromCollections: true
+eleventyImport:
+ collections:
+ - weeklyDigests
+pagination:
+ data: collections.weeklyDigests
+ size: 1
+ alias: digest
+eleventyComputed:
+ title: "{{ digest.label }}"
+permalink: "digest/{{ digest.slug }}/"
+---
+
+
+ {{ digest.label }}
+
+
+ {{ digest.startDate | dateDisplay }} – {{ digest.endDate | dateDisplay }}
+ ({{ digest.posts.length }} post{% if digest.posts.length != 1 %}s{% endif %})
+
+
+ {# Type display order #}
+ {% set typeOrder = [
+ { key: "articles", label: "Articles" },
+ { key: "notes", label: "Notes" },
+ { key: "photos", label: "Photos" },
+ { key: "bookmarks", label: "Bookmarks" },
+ { key: "likes", label: "Likes" },
+ { key: "reposts", label: "Reposts" }
+ ] %}
+
+ {% for typeInfo in typeOrder %}
+ {% set typePosts = digest.byType[typeInfo.key] %}
+ {% if typePosts and typePosts.length %}
+
+
+ {{ typeInfo.label }}
+ ({{ typePosts.length }})
+
+
+ {% for post in typePosts %}
+
+ {% if typeInfo.key == "likes" %}
+ {% set targetUrl = post.data.likeOf or post.data.like_of %}
+
+
+ {% elif typeInfo.key == "bookmarks" %}
+ {% set targetUrl = post.data.bookmarkOf or post.data.bookmark_of %}
+
+
+ {% elif typeInfo.key == "reposts" %}
+ {% set targetUrl = post.data.repostOf or post.data.repost_of %}
+
+
+ {% elif typeInfo.key == "photos" %}
+
+ {% if post.data.photo and post.data.photo[0] %}
+ {% set photoUrl = post.data.photo[0].url or post.data.photo[0] %}
+ {% if photoUrl and photoUrl[0] != '/' and 'http' not in photoUrl %}
+ {% set photoUrl = '/' + photoUrl %}
+ {% endif %}
+
+
+
+ {% endif %}
+ {% if post.data.title %}
+
{{ post.data.title }}
+ {% elif post.templateContent %}
+
{{ post.templateContent | striptags | truncate(120) }}
+ {% endif %}
+
+
{{ post.date | dateDisplay }}
+ ·
Permalink
+
+
+
+ {% elif typeInfo.key == "articles" %}
+
+
+ {% else %}
+
+
{{ post.templateContent | striptags | truncate(200) }}
+
+
{{ post.date | dateDisplay }}
+ ·
Permalink
+
+
+ {% endif %}
+
+ {% endfor %}
+
+
+ {% endif %}
+ {% endfor %}
+
+ {# Previous/Next digest navigation #}
+ {% set allDigests = collections.weeklyDigests %}
+ {% set currentIndex = -1 %}
+ {% for d in allDigests %}
+ {% if d.slug == digest.slug %}
+ {% set currentIndex = loop.index0 %}
+ {% endif %}
+ {% endfor %}
+
+
+ {% if currentIndex > 0 %}
+ {% set newer = allDigests[currentIndex - 1] %}
+
+ ← {{ newer.label }}
+
+ {% else %}
+
+ {% endif %}
+ {% if currentIndex < allDigests.length - 1 %}
+ {% set older = allDigests[currentIndex + 1] %}
+
+ {{ older.label }} →
+
+ {% else %}
+
+ {% endif %}
+
+
diff --git a/theme/eleventy.config.js b/theme/eleventy.config.js
new file mode 100644
index 0000000..64954c6
--- /dev/null
+++ b/theme/eleventy.config.js
@@ -0,0 +1,1299 @@
+import pluginWebmentions from "@chrisburnell/eleventy-cache-webmentions";
+import pluginRss from "@11ty/eleventy-plugin-rss";
+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 registerUnfurlShortcode, { getCachedCard, prefetchUrl } from "./lib/unfurl-shortcode.js";
+import matter from "gray-matter";
+import { createHash, createHmac } from "crypto";
+import { createRequire } from "module";
+import { execFileSync } from "child_process";
+import { readFileSync, readdirSync, existsSync, mkdirSync, writeFileSync, copyFileSync } from "fs";
+import { resolve, dirname } from "path";
+import { fileURLToPath } from "url";
+
+const esmRequire = createRequire(import.meta.url);
+const postGraph = esmRequire("@rknightuk/eleventy-plugin-post-graph");
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const siteUrl = process.env.SITE_URL || "https://example.com";
+
+export default function (eleventyConfig) {
+ // Don't use .gitignore for determining what to process
+ // (content/ is in .gitignore because it's a symlink, but we need to process it)
+ eleventyConfig.setUseGitIgnore(false);
+
+ // Ignore output directory (prevents re-processing generated files via symlink)
+ eleventyConfig.ignores.add("_site");
+ eleventyConfig.ignores.add("_site/**");
+ eleventyConfig.ignores.add("/app/data/site");
+ eleventyConfig.ignores.add("/app/data/site/**");
+ eleventyConfig.ignores.add("node_modules");
+ eleventyConfig.ignores.add("node_modules/**");
+ eleventyConfig.ignores.add("CLAUDE.md");
+ eleventyConfig.ignores.add("README.md");
+
+ // Ignore Pagefind output directory
+ eleventyConfig.ignores.add("pagefind");
+ eleventyConfig.ignores.add("pagefind/**");
+ // Ignore interactive assets (served via passthrough copy, not processed as templates)
+ eleventyConfig.ignores.add("interactive");
+ eleventyConfig.ignores.add("interactive/**");
+
+ // Configure watch targets to exclude output directory
+ eleventyConfig.watchIgnores.add("_site");
+ eleventyConfig.watchIgnores.add("_site/**");
+ eleventyConfig.watchIgnores.add("/app/data/site");
+ eleventyConfig.watchIgnores.add("/app/data/site/**");
+ eleventyConfig.watchIgnores.add("pagefind");
+ eleventyConfig.watchIgnores.add("pagefind/**");
+ eleventyConfig.watchIgnores.add(".cache/og");
+ eleventyConfig.watchIgnores.add(".cache/og/**");
+ eleventyConfig.watchIgnores.add(".cache/unfurl");
+ eleventyConfig.watchIgnores.add(".cache/unfurl/**");
+
+ // Watcher tuning: handle rapid successive file changes
+ // When a post is created via Micropub, the file is written twice in quick
+ // succession: first the initial content, then ~2s later a Micropub update
+ // adds syndication URLs. awaitWriteFinish delays the watcher event until
+ // the file is stable (no writes for 2s), so both changes are captured in
+ // one build. The throttle adds a 3s build-level debounce on top.
+ eleventyConfig.setChokidarConfig({
+ awaitWriteFinish: {
+ stabilityThreshold: 2000,
+ pollInterval: 100,
+ },
+ });
+ eleventyConfig.setWatchThrottleWaitTime(3000);
+
+ // Configure markdown-it with linkify enabled (auto-convert URLs to links)
+ const md = markdownIt({
+ html: true,
+ 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],
+ });
+
+ // Hashtag plugin: converts #tag to category links on-site
+ // Syndication targets (Bluesky, Mastodon) handle raw #tag natively via facet detection
+ md.inline.ruler.push("hashtag", (state, silent) => {
+ const pos = state.pos;
+ if (state.src.charCodeAt(pos) !== 0x23 /* # */) return false;
+
+ // Must be at start of string or preceded by whitespace/punctuation (not part of a URL fragment or hex color)
+ if (pos > 0) {
+ const prevChar = state.src.charAt(pos - 1);
+ if (!/[\s()\[\]{},;:!?"'«»""'']/.test(prevChar)) return false;
+ }
+
+ // Match hashtag: # followed by letter/underscore, then word chars (letters, digits, underscores)
+ const tail = state.src.slice(pos + 1);
+ const match = tail.match(/^([a-zA-Z_]\w*)/);
+ if (!match) return false;
+
+ const tag = match[1];
+
+ // Skip pure hex color codes (3, 4, 6, or 8 hex digits with nothing else)
+ if (/^[0-9a-fA-F]{3,8}$/.test(tag)) return false;
+
+ if (!silent) {
+ const slug = tag.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
+ const tokenOpen = state.push("link_open", "a", 1);
+ tokenOpen.attrSet("href", `/categories/${slug}/`);
+ tokenOpen.attrSet("class", "p-category hashtag");
+
+ const tokenText = state.push("text", "", 0);
+ tokenText.content = `#${tag}`;
+
+ state.push("link_close", "a", -1);
+ }
+
+ state.pos = pos + 1 + tag.length;
+ return true;
+ });
+
+ 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);
+
+ // Post graph — GitHub-style contribution grid for posting frequency
+ eleventyConfig.addPlugin(postGraph, {
+ sort: "desc",
+ limit: 2,
+ dayBoxTitle: true,
+ selectorLight: ":root",
+ selectorDark: ".dark",
+ boxColorLight: "#e7e5e4", // surface-200 (warm stone)
+ highlightColorLight: "#d97706", // amber-600 (accent)
+ textColorLight: "#1c1917", // surface-900
+ boxColorDark: "#292524", // surface-800
+ highlightColorDark: "#fbbf24", // amber-400
+ textColorDark: "#fafaf9", // surface-50
+ });
+
+ // JSON encode filter for JSON feed
+ eleventyConfig.addFilter("jsonEncode", (value) => {
+ return JSON.stringify(value);
+ });
+
+ // Guess MIME type from URL extension
+ function guessMimeType(url, category) {
+ const lower = (typeof url === "string" ? url : "").toLowerCase();
+ if (category === "photo") {
+ if (lower.includes(".png")) return "image/png";
+ if (lower.includes(".gif")) return "image/gif";
+ if (lower.includes(".webp")) return "image/webp";
+ if (lower.includes(".svg")) return "image/svg+xml";
+ return "image/jpeg";
+ }
+ if (category === "audio") {
+ if (lower.includes(".ogg") || lower.includes(".opus")) return "audio/ogg";
+ if (lower.includes(".flac")) return "audio/flac";
+ if (lower.includes(".wav")) return "audio/wav";
+ return "audio/mpeg";
+ }
+ if (category === "video") {
+ if (lower.includes(".webm")) return "video/webm";
+ if (lower.includes(".mov")) return "video/quicktime";
+ return "video/mp4";
+ }
+ return "application/octet-stream";
+ }
+
+ // Extract URL string from value that may be a string or {url, alt} object
+ function resolveMediaUrl(value) {
+ if (typeof value === "string") return value;
+ if (value && typeof value === "object" && value.url) return value.url;
+ return null;
+ }
+
+ // Feed attachments filter — builds JSON Feed attachments array from post data
+ eleventyConfig.addFilter("feedAttachments", (postData) => {
+ const attachments = [];
+ const processMedia = (items, category) => {
+ const list = Array.isArray(items) ? items : [items];
+ for (const item of list) {
+ const rawUrl = resolveMediaUrl(item);
+ if (!rawUrl) continue;
+ const url = rawUrl.startsWith("http") ? rawUrl : `${siteUrl}${rawUrl}`;
+ attachments.push({ url, mime_type: guessMimeType(rawUrl, category) });
+ }
+ };
+ if (postData.photo) processMedia(postData.photo, "photo");
+ if (postData.audio) processMedia(postData.audio, "audio");
+ if (postData.video) processMedia(postData.video, "video");
+ return attachments;
+ });
+
+ // Textcasting support filter — builds clean support object excluding null values
+ eleventyConfig.addFilter("textcastingSupport", (support) => {
+ if (!support) return {};
+ const obj = {};
+ if (support.url) obj.url = support.url;
+ if (support.stripe) obj.stripe = support.stripe;
+ if (support.lightning) obj.lightning = support.lightning;
+ if (support.paymentPointer) obj.payment_pointer = support.paymentPointer;
+ return obj;
+ });
+
+ // Protocol type filter — classifies a URL by its origin protocol/network
+ eleventyConfig.addFilter("protocolType", (url) => {
+ if (!url || typeof url !== "string") return "web";
+ const lower = url.toLowerCase();
+ if (lower.includes("bsky.app") || lower.includes("bluesky")) return "atmosphere";
+ // Match Fediverse instances by known domain patterns (avoid overly broad "social")
+ if (lower.includes("mastodon.") || lower.includes("mstdn.") || lower.includes("fosstodon.") ||
+ lower.includes("pleroma.") || lower.includes("misskey.") || lower.includes("pixelfed.") ||
+ lower.includes("fediverse")) return "fediverse";
+ return "web";
+ });
+
+ // Email obfuscation filter - converts email to HTML entities
+ // Blocks ~95% of spam harvesters while remaining valid for microformat parsers
+ // Usage: {{ email | obfuscateEmail }} or {{ email | obfuscateEmail("href") }}
+ eleventyConfig.addFilter("obfuscateEmail", (email, mode = "display") => {
+ if (!email) return "";
+ // Convert each character to HTML decimal entity
+ const encoded = [...email].map(char => `${char.charCodeAt(0)};`).join("");
+ if (mode === "href") {
+ // For mailto: links, also encode the "mailto:" prefix
+ const mailto = [...("mailto:")].map(char => `${char.charCodeAt(0)};`).join("");
+ return mailto + encoded;
+ }
+ return encoded;
+ });
+
+ // Alias dateToRfc822 (plugin provides dateToRfc2822)
+ eleventyConfig.addFilter("dateToRfc822", (date) => {
+ return pluginRss.dateToRfc2822(date);
+ });
+
+ // Embed Everything - auto-embed YouTube, Vimeo, Bluesky, Mastodon, etc.
+ eleventyConfig.addPlugin(embedEverything, {
+ use: ["youtube", "vimeo", "twitter", "mastodon", "bluesky", "spotify", "soundcloud"],
+ youtube: {
+ options: {
+ lite: false,
+ recommendSelfOnly: true,
+ },
+ },
+ mastodon: {
+ options: {
+ server: "indieweb.social",
+ },
+ },
+ });
+
+ // Unfurl shortcode — renders any URL as a rich card (OpenGraph/Twitter Card metadata)
+ // Usage in templates: {% unfurl "https://example.com/article" %}
+ registerUnfurlShortcode(eleventyConfig);
+
+ // Synchronous unfurl filter — reads from pre-populated disk cache.
+ // Safe for deeply nested includes where async shortcodes fail silently.
+ // Usage: {{ url | unfurlCard | safe }}
+ eleventyConfig.addFilter("unfurlCard", getCachedCard);
+
+ // Custom transform to convert YouTube links to embeds
+ eleventyConfig.addTransform("youtube-link-to-embed", function (content, outputPath) {
+ if (!outputPath || !outputPath.endsWith(".html")) {
+ return content;
+ }
+ // Match tags where href contains youtube.com/watch or youtu.be
+ // Link text can be: URL, www.youtube..., youtube..., or youtube-related text
+ const youtubePattern = / ]+href="https?:\/\/(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]+)[^"]*"[^>]*>(?:https?:\/\/)?(?:www\.)?[^<]*(?:youtube|youtu\.be)[^<]*<\/a>/gi;
+
+ content = content.replace(youtubePattern, (match, videoId) => {
+ // Use standard YouTube iframe with exact oEmbed parameters
+ return `VIDEO
`;
+ });
+
+ // Clean up empty
tags created by the replacement
+ content = content.replace(/\s*<\/p>/g, '');
+
+ return content;
+ });
+
+ // Image optimization - transforms tags automatically
+ // PROCESS_REMOTE_IMAGES: set to "true" to let Sharp download and re-encode remote images.
+ // Default "false" — skips remote URLs (adds eleventy:ignore) to avoid OOM from Sharp's
+ // native memory usage when processing hundreds of external images (bookmarks, webmentions).
+ const processRemoteImages = process.env.PROCESS_REMOTE_IMAGES === "true";
+ if (!processRemoteImages) {
+ eleventyConfig.htmlTransformer.addPosthtmlPlugin("html", () => {
+ return (tree) => {
+ tree.match({ tag: "img" }, (node) => {
+ if (node.attrs?.src && /^https?:\/\//.test(node.attrs.src)) {
+ node.attrs["eleventy:ignore"] = "";
+ }
+ return node;
+ });
+ return tree;
+ };
+ }, { priority: 1 }); // priority > 0 runs before image plugin (priority -1)
+ }
+
+ eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
+ extensions: "html",
+ formats: ["webp", "jpeg"],
+ widths: ["auto"],
+ failOnError: false,
+ cacheOptions: {
+ duration: process.env.ELEVENTY_RUN_MODE === "build" ? "1d" : "30d",
+ },
+ concurrency: 4,
+ defaultAttributes: {
+ loading: "lazy",
+ decoding: "async",
+ sizes: "auto",
+ alt: "",
+ },
+ });
+
+ // Sitemap generation
+ eleventyConfig.addPlugin(sitemap, {
+ sitemap: {
+ hostname: siteUrl,
+ },
+ });
+
+ // Wrap
elements in for responsive tables
+ eleventyConfig.addTransform("table-saw-wrap", function (content, outputPath) {
+ if (outputPath && outputPath.endsWith(".html")) {
+ return content.replace(/)/g, " ");
+ }
+ return content;
+ });
+
+ // Fix OG image meta tags post-rendering — bypasses Eleventy 3.x race condition (#3183).
+ // page.url is unreliable during parallel rendering, but outputPath IS correct
+ // since files are written to the correct location. Derives the OG slug from
+ // outputPath and replaces placeholders emitted by base.njk.
+ eleventyConfig.addTransform("og-fix", function (content, outputPath) {
+ if (!outputPath || !outputPath.endsWith(".html")) return content;
+
+ // Derive correct page URL and OG slug from outputPath (immune to race condition)
+ // Content pages match: .../type/yyyy/MM/dd/slug/index.html
+ const dateMatch = outputPath.match(
+ /\/([\w-]+)\/(\d{4})\/(\d{2})\/(\d{2})\/([\w-]+)\/index\.html$/
+ );
+
+ if (dateMatch) {
+ const [, type, year, month, day, slug] = dateMatch;
+ const pageUrlPath = `/${type}/${year}/${month}/${day}/${slug}/`;
+ const correctFullUrl = `${siteUrl}${pageUrlPath}`;
+ const ogSlug = `${year}-${month}-${day}-${slug}`;
+ const hasOg = existsSync(resolve(__dirname, ".cache", "og", `${ogSlug}.png`));
+ const ogImageUrl = hasOg
+ ? `${siteUrl}/og/${ogSlug}.png`
+ : `${siteUrl}/images/og-default.png`;
+ const twitterCard = hasOg ? "summary_large_image" : "summary";
+
+ // Fix og:url and canonical (also affected by race condition)
+ content = content.replace(
+ /( {
+ if (!dateObj) return "";
+ const date = new Date(dateObj);
+ return date.toLocaleDateString("en-GB", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ });
+ });
+
+ // ISO date filter
+ eleventyConfig.addFilter("isoDate", (dateObj) => {
+ if (!dateObj) return "";
+ return new Date(dateObj).toISOString();
+ });
+
+ // Digest-to-HTML filter for RSS feed descriptions
+ eleventyConfig.addFilter("digestToHtml", (digest, siteUrl) => {
+ const typeLabels = {
+ articles: "Articles",
+ notes: "Notes",
+ photos: "Photos",
+ bookmarks: "Bookmarks",
+ likes: "Likes",
+ reposts: "Reposts",
+ };
+ const typeOrder = ["articles", "notes", "photos", "bookmarks", "likes", "reposts"];
+ let html = "";
+
+ for (const type of typeOrder) {
+ const posts = digest.byType[type];
+ if (!posts || !posts.length) continue;
+
+ html += `${typeLabels[type]} `;
+ for (const post of posts) {
+ const postUrl = siteUrl + post.url;
+ let label;
+ if (type === "likes") {
+ const target = post.data.likeOf || post.data.like_of;
+ label = `Liked: ${target}`;
+ } else if (type === "bookmarks") {
+ const target = post.data.bookmarkOf || post.data.bookmark_of;
+ label = post.data.title || `Bookmarked: ${target}`;
+ } else if (type === "reposts") {
+ const target = post.data.repostOf || post.data.repost_of;
+ label = `Reposted: ${target}`;
+ } else if (post.data.title) {
+ label = post.data.title;
+ } else {
+ const content = post.templateContent || "";
+ label = content.replace(/<[^>]*>/g, "").slice(0, 120).trim() || "Untitled";
+ }
+ html += `${label} `;
+ }
+ html += ` `;
+ }
+
+ return html;
+ });
+
+ // Truncate filter
+ eleventyConfig.addFilter("truncate", (str, len = 200) => {
+ if (!str) return "";
+ if (str.length <= len) return str;
+ return str.slice(0, len).trim() + "...";
+ });
+
+ // Clean excerpt for OpenGraph - strips HTML, decodes entities, removes extra whitespace
+ eleventyConfig.addFilter("ogDescription", (content, len = 200) => {
+ if (!content) return "";
+ // Strip HTML tags
+ let text = content.replace(/<[^>]+>/g, ' ');
+ // Decode common HTML entities
+ text = text.replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, "'")
+ .replace(/ /g, ' ');
+ // Remove extra whitespace
+ text = text.replace(/\s+/g, ' ').trim();
+ // Truncate
+ if (text.length > len) {
+ text = text.slice(0, len).trim() + "...";
+ }
+ return text;
+ });
+
+ // Extract first image from content for OpenGraph fallback
+ eleventyConfig.addFilter("extractFirstImage", (content) => {
+ if (!content) return null;
+ // Match all tags, skip hidden ones and data URIs
+ const imgRegex = / ]*?\ssrc=["']([^"']+)["'][^>]*>/gi;
+ let match;
+ while ((match = imgRegex.exec(content)) !== null) {
+ const fullTag = match[0];
+ const src = match[1];
+ if (src.startsWith("data:")) continue;
+ if (/\bhidden\b/.test(fullTag)) continue;
+ return src;
+ }
+ return null;
+ });
+
+ // Head filter for arrays
+ eleventyConfig.addFilter("head", (array, n) => {
+ if (!Array.isArray(array) || n < 1) return array;
+ return array.slice(0, n);
+ });
+
+ // Slugify filter
+ eleventyConfig.addFilter("slugify", (str) => {
+ if (!str) return "";
+ return str
+ .toLowerCase()
+ .replace(/[^\w\s-]/g, "")
+ .replace(/[\s_-]+/g, "-")
+ .replace(/^-+|-+$/g, "");
+ });
+
+ eleventyConfig.addFilter("stripTrailingSlash", (url) => {
+ if (!url || typeof url !== "string") return url || "";
+ return url.endsWith("/") ? url.slice(0, -1) : url;
+ });
+
+ // Hash filter for cache busting - generates MD5 hash of file content
+ eleventyConfig.addFilter("hash", (filePath) => {
+ try {
+ const fullPath = resolve(__dirname, filePath.startsWith("/") ? `.${filePath}` : filePath);
+ const content = readFileSync(fullPath);
+ return createHash("md5").update(content).digest("hex").slice(0, 8);
+ } catch {
+ // Return timestamp as fallback if file not found
+ return Date.now().toString(36);
+ }
+ });
+
+ // Derive OG slug from page.url (reliable) instead of page.fileSlug
+ // (which suffers from Nunjucks race conditions in Eleventy 3.x parallel rendering).
+ // OG images are named with the full date prefix to match URL segments exactly.
+ eleventyConfig.addFilter("ogSlug", (url) => {
+ if (!url) return "";
+ const segments = url.split("/").filter(Boolean);
+ // Date-based URL: /type/yyyy/MM/dd/slug/ → 5 segments → "yyyy-MM-dd-slug"
+ if (segments.length === 5) {
+ const [, year, month, day, slug] = segments;
+ return `${year}-${month}-${day}-${slug}`;
+ }
+ // Fallback: last segment (for pages, legacy URLs)
+ return segments[segments.length - 1] || "";
+ });
+
+ // Check if a generated OG image exists for this slug
+ eleventyConfig.addFilter("hasOgImage", (slug) => {
+ if (!slug) return false;
+ const ogPath = resolve(__dirname, ".cache", "og", `${slug}.png`);
+ 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 "";
+ }
+ });
+
+ // Extract raw Markdown body from a source file (strips front matter)
+ eleventyConfig.addFilter("rawMarkdownBody", (inputPath) => {
+ try {
+ const src = readFileSync(inputPath, "utf-8");
+ const { content } = matter(src);
+ return content.trim();
+ } catch {
+ return "";
+ }
+ });
+
+ // Current timestamp filter (for client-side JS buildtime)
+ eleventyConfig.addFilter("timestamp", () => Date.now());
+
+ // Date filter (for sidebar dates)
+ eleventyConfig.addFilter("date", (dateObj, format) => {
+ if (!dateObj) return "";
+ const date = new Date(dateObj);
+ const options = {};
+
+ if (format.includes("MMM")) options.month = "short";
+ if (format.includes("d")) options.day = "numeric";
+ if (format.includes("yyyy")) options.year = "numeric";
+
+ return date.toLocaleDateString("en-US", options);
+ });
+
+ // Webmention filters - with legacy URL support
+ // This filter checks both current URL and any legacy URLs from redirects
+ // Merges webmentions + conversations with deduplication (conversations first)
+ eleventyConfig.addFilter("webmentionsForUrl", function (webmentions, url, urlAliases, conversationMentions = []) {
+ if (!url) return [];
+
+ // Merge conversations + webmentions with deduplication
+ const seen = new Set();
+ const merged = [];
+
+ // Add conversations first (richer metadata)
+ for (const item of conversationMentions) {
+ const key = item['wm-id'] || item.url;
+ if (key && !seen.has(key)) {
+ seen.add(key);
+ merged.push(item);
+ }
+ }
+
+ // Add webmentions (skip duplicates)
+ if (webmentions) {
+ for (const item of webmentions) {
+ const key = item['wm-id'];
+ if (!key || seen.has(key)) continue;
+ if (item.url && seen.has(item.url)) continue;
+ seen.add(key);
+ merged.push(item);
+ }
+ }
+
+ // Build list of all URLs to check (current + legacy)
+ const urlsToCheck = new Set();
+
+ // Add current URL variations
+ const absoluteUrl = url.startsWith("http") ? url : `${siteUrl}${url}`;
+ urlsToCheck.add(absoluteUrl);
+ urlsToCheck.add(absoluteUrl.replace(/\/$/, ""));
+ urlsToCheck.add(absoluteUrl.endsWith("/") ? absoluteUrl : `${absoluteUrl}/`);
+
+ // Add legacy URLs from aliases (if provided)
+ if (urlAliases?.aliases) {
+ const normalizedUrl = url.replace(/\/$/, "");
+ const oldUrls = urlAliases.aliases[normalizedUrl] || [];
+ for (const oldUrl of oldUrls) {
+ urlsToCheck.add(`${siteUrl}${oldUrl}`);
+ urlsToCheck.add(`${siteUrl}${oldUrl}/`);
+ urlsToCheck.add(`${siteUrl}${oldUrl}`.replace(/\/$/, ""));
+ }
+ }
+
+ // Compute legacy /content/ URL from current URL for old webmention.io targets
+ // Pattern: /type/yyyy/MM/dd/slug/ → /content/type/yyyy-MM-dd-slug/
+ const pathSegments = url.replace(/\/$/, "").split("/").filter(Boolean);
+ if (pathSegments.length === 5) {
+ const [type, year, month, day, slug] = pathSegments;
+ const contentUrl = `/content/${type}/${year}-${month}-${day}-${slug}/`;
+ urlsToCheck.add(`${siteUrl}${contentUrl}`);
+ urlsToCheck.add(`${siteUrl}${contentUrl}`.replace(/\/$/, ""));
+ }
+
+ // Filter merged data matching any of our URLs
+ const matched = merged.filter((wm) => urlsToCheck.has(wm["wm-target"]));
+
+ // Deduplicate cross-source: same author + same interaction type = same mention
+ // (webmention.io and conversations API may both report the same like/reply)
+ const deduped = [];
+ const authorActions = new Set();
+ for (const wm of matched) {
+ const authorUrl = wm.author?.url || wm.url || "";
+ const action = wm["wm-property"] || "mention";
+ const key = `${authorUrl}::${action}`;
+ if (authorActions.has(key)) continue;
+ authorActions.add(key);
+ deduped.push(wm);
+ }
+ return deduped;
+ });
+
+ eleventyConfig.addFilter("webmentionsByType", function (mentions, type) {
+ if (!mentions) return [];
+ const typeMap = {
+ likes: "like-of",
+ reposts: "repost-of",
+ bookmarks: "bookmark-of",
+ replies: "in-reply-to",
+ mentions: "mention-of",
+ };
+ const wmProperty = typeMap[type] || type;
+ return mentions.filter((m) => m["wm-property"] === wmProperty);
+ });
+
+ // Post navigation — find previous/next post in a collection
+ // (Nunjucks {% set %} inside {% for %} doesn't propagate, so we need filters)
+ eleventyConfig.addFilter("previousInCollection", function (collection, page) {
+ if (!collection || !page) return null;
+ const index = collection.findIndex((p) => p.url === page.url);
+ return index > 0 ? collection[index - 1] : null;
+ });
+
+ eleventyConfig.addFilter("nextInCollection", function (collection, page) {
+ if (!collection || !page) return null;
+ const index = collection.findIndex((p) => p.url === page.url);
+ return index >= 0 && index < collection.length - 1
+ ? collection[index + 1]
+ : null;
+ });
+
+ // Posting frequency — compute posts-per-month for last 12 months (for sparkline).
+ // Returns an inline SVG that uses currentColor for stroke and a semi-transparent
+ // gradient fill. Wrap in a colored span to set the domain color via Tailwind.
+ 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]++;
+ }
+ }
+
+ // Extrapolate the current (partial) month to avoid false downward trend.
+ // e.g. 51 posts in 5 days of a 31-day month projects to ~316.
+ const dayOfMonth = now.getDate();
+ const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
+ if (dayOfMonth < daysInMonth && counts[11] > 0) {
+ counts[11] = Math.round(counts[11] / dayOfMonth * daysInMonth);
+ }
+
+ const max = Math.max(...counts, 1);
+ const w = 200;
+ const h = 32;
+ const pad = 2;
+ const step = w / (counts.length - 1);
+ const points = counts.map((v, i) => {
+ const x = i * step;
+ const y = h - pad - ((v / max) * (h - pad * 2));
+ return `${x.toFixed(1)},${y.toFixed(1)}`;
+ }).join(" ");
+ // Closed polygon for gradient fill (line path + bottom corners)
+ const fillPoints = `${points} ${w},${h} 0,${h}`;
+ return [
+ ``,
+ ``,
+ ` `,
+ ` `,
+ ` `,
+ ` `,
+ ` `,
+ ` `,
+ ].join("");
+ });
+
+ // Filter AI-involved posts (ai-text-level > "0" or aiTextLevel > "0")
+ eleventyConfig.addFilter("aiPosts", (posts) => {
+ if (!Array.isArray(posts)) return [];
+ return posts.filter((post) => {
+ const level = post.data?.aiTextLevel || post.data?.["ai-text-level"] || "0";
+ return level !== "0" && level !== 0;
+ });
+ });
+
+ // AI stats — returns { total, aiCount, percentage, byLevel }
+ eleventyConfig.addFilter("aiStats", (posts) => {
+ if (!Array.isArray(posts)) return { total: 0, aiCount: 0, percentage: 0, byLevel: {} };
+ const total = posts.length;
+ const byLevel = { 0: 0, 1: 0, 2: 0, 3: 0 };
+ for (const post of posts) {
+ const level = parseInt(post.data?.aiTextLevel || post.data?.["ai-text-level"] || "0", 10);
+ byLevel[level] = (byLevel[level] || 0) + 1;
+ }
+ const aiCount = total - byLevel[0];
+ return {
+ total,
+ aiCount,
+ percentage: total > 0 ? ((aiCount / total) * 100).toFixed(1) : "0",
+ byLevel,
+ };
+ });
+
+ // Helper: exclude drafts from collections
+ const isPublished = (item) => !item.data.draft;
+
+ // Collections for different post types
+ // Note: content path is content/ due to symlink structure
+ // "posts" shows ALL content types combined
+ eleventyConfig.addCollection("posts", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/**/*.md")
+ .filter(isPublished)
+ .sort((a, b) => b.date - a.date);
+ });
+
+ eleventyConfig.addCollection("notes", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/notes/**/*.md")
+ .filter(isPublished)
+ .sort((a, b) => b.date - a.date);
+ });
+
+ eleventyConfig.addCollection("articles", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/articles/**/*.md")
+ .filter(isPublished)
+ .sort((a, b) => b.date - a.date);
+ });
+
+ eleventyConfig.addCollection("bookmarks", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/bookmarks/**/*.md")
+ .filter(isPublished)
+ .sort((a, b) => b.date - a.date);
+ });
+
+ eleventyConfig.addCollection("photos", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/photos/**/*.md")
+ .filter(isPublished)
+ .sort((a, b) => b.date - a.date);
+ });
+
+ eleventyConfig.addCollection("likes", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/likes/**/*.md")
+ .filter(isPublished)
+ .sort((a, b) => b.date - a.date);
+ });
+
+ // Replies collection - posts with inReplyTo/in_reply_to property
+ // Supports both camelCase (Indiekit Eleventy preset) and underscore (legacy) names
+ eleventyConfig.addCollection("replies", function (collectionApi) {
+ return collectionApi
+ .getAll()
+ .filter((item) => isPublished(item) && (item.data.inReplyTo || item.data.in_reply_to))
+ .sort((a, b) => b.date - a.date);
+ });
+
+ // Reposts collection - posts with repostOf/repost_of property
+ // Supports both camelCase (Indiekit Eleventy preset) and underscore (legacy) names
+ eleventyConfig.addCollection("reposts", function (collectionApi) {
+ return collectionApi
+ .getAll()
+ .filter((item) => isPublished(item) && (item.data.repostOf || item.data.repost_of))
+ .sort((a, b) => b.date - a.date);
+ });
+
+ // Pages collection - root-level slash pages (about, now, uses, etc.)
+ // Includes both content/*.md (legacy) and content/pages/*.md (new post-type-page)
+ // Created via Indiekit's page post type
+ eleventyConfig.addCollection("pages", function (collectionApi) {
+ const rootPages = collectionApi.getFilteredByGlob("content/*.md");
+ const pagesDir = collectionApi.getFilteredByGlob("content/pages/*.md");
+ return [...rootPages, ...pagesDir]
+ .filter(page => isPublished(page) && !page.inputPath.includes('content.json') && !page.inputPath.includes('pages.json'))
+ .sort((a, b) => (a.data.title || a.data.name || "").localeCompare(b.data.title || b.data.name || ""));
+ });
+
+ // All content combined for homepage feed
+ eleventyConfig.addCollection("feed", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/**/*.md")
+ .filter(isPublished)
+ .sort((a, b) => b.date - a.date)
+ .slice(0, 20);
+ });
+
+ // Categories collection - deduplicated by slug to avoid duplicate permalinks
+ eleventyConfig.addCollection("categories", function (collectionApi) {
+ const categoryMap = new Map(); // slug -> original name (first seen)
+ const slugify = (str) => str.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
+
+ collectionApi.getAll().filter(isPublished).forEach((item) => {
+ if (item.data.category) {
+ const cats = Array.isArray(item.data.category) ? item.data.category : [item.data.category];
+ cats.forEach((cat) => {
+ if (cat && typeof cat === 'string' && cat.trim()) {
+ const slug = slugify(cat.trim());
+ if (slug && !categoryMap.has(slug)) {
+ categoryMap.set(slug, cat.trim());
+ }
+ }
+ });
+ }
+ });
+ return [...categoryMap.values()].sort();
+ });
+
+ // Category feeds — pre-grouped posts for per-category RSS/JSON feeds
+ eleventyConfig.addCollection("categoryFeeds", function (collectionApi) {
+ const slugify = (str) => str.toLowerCase().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
+ const grouped = new Map(); // slug -> { name, slug, posts[] }
+
+ collectionApi
+ .getFilteredByGlob("content/**/*.md")
+ .filter(isPublished)
+ .sort((a, b) => b.date - a.date)
+ .forEach((item) => {
+ if (!item.data.category) return;
+ const cats = Array.isArray(item.data.category) ? item.data.category : [item.data.category];
+ for (const cat of cats) {
+ if (!cat || typeof cat !== "string" || !cat.trim()) continue;
+ const slug = slugify(cat.trim());
+ if (!slug) continue;
+ if (!grouped.has(slug)) {
+ grouped.set(slug, { name: cat.trim(), slug, posts: [] });
+ }
+ const entry = grouped.get(slug);
+ if (entry.posts.length < 50) {
+ entry.posts.push(item);
+ }
+ }
+ });
+
+ return [...grouped.values()].sort((a, b) => a.name.localeCompare(b.name));
+ });
+
+ // Recent posts for sidebar
+ eleventyConfig.addCollection("recentPosts", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/**/*.md")
+ .filter(isPublished)
+ .sort((a, b) => b.date - a.date)
+ .slice(0, 5);
+ });
+
+ // Featured posts — curated selection via `pinned: true` frontmatter
+ // Property named "pinned" to avoid conflict with "featured" (hero image) in MF2/Micropub
+ eleventyConfig.addCollection("featuredPosts", function (collectionApi) {
+ return collectionApi
+ .getFilteredByGlob("content/**/*.md")
+ .filter(isPublished)
+ .filter((item) => item.data.pinned === true || item.data.pinned === "true")
+ .sort((a, b) => b.date - a.date);
+ });
+
+ // Weekly digests — posts grouped by ISO week for digest pages and RSS feed
+ eleventyConfig.addCollection("weeklyDigests", function (collectionApi) {
+ const allPosts = collectionApi
+ .getFilteredByGlob("content/**/*.md")
+ .filter(isPublished)
+ .filter((item) => {
+ // Exclude replies
+ return !(item.data.inReplyTo || item.data.in_reply_to);
+ })
+ .sort((a, b) => b.date - a.date);
+
+ // ISO week helpers
+ const getISOWeek = (date) => {
+ const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
+ d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
+ return Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
+ };
+ const getISOYear = (date) => {
+ const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
+ d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
+ return d.getUTCFullYear();
+ };
+
+ // Group by ISO week
+ const weekMap = new Map();
+
+ for (const post of allPosts) {
+ const d = new Date(post.date);
+ const week = getISOWeek(d);
+ const year = getISOYear(d);
+ const key = `${year}-W${String(week).padStart(2, "0")}`;
+
+ if (!weekMap.has(key)) {
+ // Calculate Monday (start) and Sunday (end) of ISO week
+ const jan4 = new Date(Date.UTC(year, 0, 4));
+ const dayOfWeek = jan4.getUTCDay() || 7;
+ const monday = new Date(jan4);
+ monday.setUTCDate(jan4.getUTCDate() - dayOfWeek + 1 + (week - 1) * 7);
+ const sunday = new Date(monday);
+ sunday.setUTCDate(monday.getUTCDate() + 6);
+
+ weekMap.set(key, {
+ year,
+ week,
+ slug: `${year}/W${String(week).padStart(2, "0")}`,
+ label: `Week ${week}, ${year}`,
+ startDate: monday.toISOString().slice(0, 10),
+ endDate: sunday.toISOString().slice(0, 10),
+ posts: [],
+ });
+ }
+
+ weekMap.get(key).posts.push(post);
+ }
+
+ // Post type detection (matches blog.njk logic)
+ const typeDetect = (post) => {
+ if (post.data.likeOf || post.data.like_of) return "likes";
+ if (post.data.bookmarkOf || post.data.bookmark_of) return "bookmarks";
+ if (post.data.repostOf || post.data.repost_of) return "reposts";
+ if (post.data.photo && post.data.photo.length) return "photos";
+ if (post.data.title) return "articles";
+ return "notes";
+ };
+
+ // Build byType for each week and convert to array
+ const digests = [...weekMap.values()].map((entry) => {
+ const byType = {};
+ for (const post of entry.posts) {
+ const type = typeDetect(post);
+ if (!byType[type]) byType[type] = [];
+ byType[type].push(post);
+ }
+ return { ...entry, byType };
+ });
+
+ // Sort newest-week-first
+ digests.sort((a, b) => {
+ if (a.year !== b.year) return b.year - a.year;
+ return b.week - a.week;
+ });
+
+ return digests;
+ });
+
+ // Generate OpenGraph images for posts without photos
+ // Runs on every build (including watcher rebuilds) — manifest caching makes it fast
+ // for incremental: only new posts without an OG image get generated (~200ms each)
+ eleventyConfig.on("eleventy.before", () => {
+ const contentDir = resolve(__dirname, "content");
+ const cacheDir = resolve(__dirname, ".cache");
+ const siteName = process.env.SITE_NAME || "My IndieWeb Blog";
+ try {
+ execFileSync(process.execPath, [
+ "--max-old-space-size=768",
+ resolve(__dirname, "lib", "og-cli.js"),
+ contentDir,
+ cacheDir,
+ siteName,
+ ], {
+ stdio: "inherit",
+ env: { ...process.env, NODE_OPTIONS: "" },
+ });
+
+ // Sync new OG images to output directory.
+ // During incremental builds, .cache/og is in watchIgnores so Eleventy's
+ // passthrough copy won't pick up newly generated images. Copy them manually.
+ const ogCacheDir = resolve(cacheDir, "og");
+ const ogOutputDir = resolve(__dirname, "_site", "og");
+ if (existsSync(ogCacheDir) && existsSync(resolve(__dirname, "_site"))) {
+ mkdirSync(ogOutputDir, { recursive: true });
+ let synced = 0;
+ for (const file of readdirSync(ogCacheDir)) {
+ if (file.endsWith(".png") && !existsSync(resolve(ogOutputDir, file))) {
+ copyFileSync(resolve(ogCacheDir, file), resolve(ogOutputDir, file));
+ synced++;
+ }
+ }
+ if (synced > 0) {
+ console.log(`[og] Synced ${synced} new image(s) to output`);
+ }
+ }
+ } catch (err) {
+ console.error("[og] Image generation failed:", err.message);
+ }
+ });
+
+ // Pre-fetch unfurl metadata for all interaction URLs in content files.
+ // Populates the disk cache BEFORE templates render, so the synchronous
+ // unfurlCard filter (used in nested includes like recent-posts) has data.
+ eleventyConfig.on("eleventy.before", async () => {
+ const contentDir = resolve(__dirname, "content");
+ if (!existsSync(contentDir)) return;
+
+ const urls = new Set();
+ const interactionProps = [
+ "likeOf", "like_of", "bookmarkOf", "bookmark_of",
+ "repostOf", "repost_of", "inReplyTo", "in_reply_to",
+ ];
+
+ const walk = (dir) => {
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
+ const full = resolve(dir, entry.name);
+ if (entry.isDirectory()) { walk(full); continue; }
+ if (!entry.name.endsWith(".md")) continue;
+ try {
+ const { data } = matter(readFileSync(full, "utf-8"));
+ for (const prop of interactionProps) {
+ if (data[prop]) urls.add(data[prop]);
+ }
+ } catch { /* skip unparseable files */ }
+ }
+ };
+ walk(contentDir);
+
+ if (urls.size === 0) return;
+ console.log(`[unfurl] Pre-fetching ${urls.size} interaction URLs...`);
+ await Promise.all([...urls].map((url) => prefetchUrl(url)));
+ console.log(`[unfurl] Pre-fetch complete.`);
+ });
+
+ // Post-build hook: pagefind indexing + WebSub notification
+ // Pagefind runs once on the first build (initial or watcher's first full build), then never again.
+ // WebSub runs on every non-incremental build.
+ // Note: --incremental CLI flag sets incremental=true even for the watcher's first full build,
+ // so we cannot use the incremental flag to guard pagefind. Use a one-shot flag instead.
+ let pagefindDone = false;
+ eleventyConfig.on("eleventy.after", async ({ dir, directories, runMode, incremental }) => {
+ // Markdown for Agents — generate index.md alongside index.html for articles
+ const mdEnabled = (process.env.MARKDOWN_AGENTS_ENABLED || "true").toLowerCase() === "true";
+ if (mdEnabled && !incremental) {
+ const outputDir = directories?.output || dir.output;
+ const contentDir = resolve(__dirname, "content/articles");
+ const aiTrain = process.env.MARKDOWN_AGENTS_AI_TRAIN || "yes";
+ const search = process.env.MARKDOWN_AGENTS_SEARCH || "yes";
+ const aiInput = process.env.MARKDOWN_AGENTS_AI_INPUT || "yes";
+ const authorName = process.env.AUTHOR_NAME || "Blog Author";
+ let mdCount = 0;
+ try {
+ const files = readdirSync(contentDir).filter(f => f.endsWith(".md"));
+ for (const file of files) {
+ const src = readFileSync(resolve(contentDir, file), "utf-8");
+ const { data: fm, content: body } = matter(src);
+ if (!fm || fm.draft) continue;
+ // Derive the output path from the article's permalink or url
+ const articleUrl = fm.permalink || fm.url;
+ if (!articleUrl || !articleUrl.startsWith("/articles/")) continue;
+ const mdDir = resolve(outputDir, articleUrl.replace(/^\//, "").replace(/\/$/, ""));
+ const mdPath = resolve(mdDir, "index.md");
+ const trimmedBody = body.trim();
+ const tokens = Math.ceil(trimmedBody.length / 4);
+ const title = (fm.title || "").replace(/"/g, '\\"');
+ const date = fm.date ? new Date(fm.date).toISOString() : fm.published || "";
+ let frontLines = [
+ "---",
+ `title: "${title}"`,
+ `date: ${date}`,
+ `author: ${authorName}`,
+ `url: ${siteUrl}${articleUrl}`,
+ ];
+ if (fm.category && Array.isArray(fm.category) && fm.category.length > 0) {
+ frontLines.push("categories:");
+ for (const cat of fm.category) {
+ frontLines.push(` - ${cat}`);
+ }
+ }
+ if (fm.description) {
+ frontLines.push(`description: "${fm.description.replace(/"/g, '\\"')}"`);
+ }
+ frontLines.push(`tokens: ${tokens}`);
+ frontLines.push(`content_signal: ai-train=${aiTrain}, search=${search}, ai-input=${aiInput}`);
+ frontLines.push("---");
+ mkdirSync(mdDir, { recursive: true });
+ writeFileSync(mdPath, frontLines.join("\n") + "\n\n# " + (fm.title || "") + "\n\n" + trimmedBody + "\n");
+ mdCount++;
+ }
+ console.log(`[markdown-agents] Generated ${mdCount} article .md files`);
+ } catch (err) {
+ console.error("[markdown-agents] Error generating .md files:", err.message);
+ }
+ }
+
+ // Pagefind indexing — run exactly once per process lifetime
+ if (!pagefindDone) {
+ pagefindDone = true;
+ const outputDir = directories?.output || dir.output;
+ try {
+ console.log(`[pagefind] Indexing ${outputDir} (${runMode})...`);
+ execFileSync("npx", ["pagefind", "--site", outputDir, "--output-subdir", "pagefind", "--glob", "**/*.html"], {
+ stdio: "inherit",
+ timeout: 120000,
+ });
+ console.log("[pagefind] Indexing complete");
+ } catch (err) {
+ console.error("[pagefind] Indexing failed:", err.message);
+ }
+
+ }
+
+ // Syndication webhook — trigger after incremental rebuilds (new posts are now live)
+ // Cuts syndication latency from ~2 min (poller) to ~5 sec (immediate trigger)
+ if (incremental) {
+ const syndicateUrl = process.env.SYNDICATE_WEBHOOK_URL;
+ if (syndicateUrl) {
+ try {
+ const secretFile = process.env.SYNDICATE_SECRET_FILE || "/app/data/config/.secret";
+ const secret = readFileSync(secretFile, "utf-8").trim();
+
+ // Build a minimal HS256 JWT using built-in crypto (no jsonwebtoken dependency)
+ const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
+ const now = Math.floor(Date.now() / 1000);
+ const payload = Buffer.from(JSON.stringify({
+ me: siteUrl,
+ scope: "update",
+ iat: now,
+ exp: now + 300, // 5 minutes
+ })).toString("base64url");
+ const signature = createHmac("sha256", secret)
+ .update(`${header}.${payload}`)
+ .digest("base64url");
+ const token = `${header}.${payload}.${signature}`;
+
+ const res = await fetch(`${syndicateUrl}?token=${token}`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ signal: AbortSignal.timeout(30000),
+ });
+ console.log(`[syndicate-hook] Triggered syndication: ${res.status}`);
+ } catch (err) {
+ console.error(`[syndicate-hook] Failed:`, err.message);
+ }
+ }
+ }
+
+ // WebSub hub notification — skip on incremental rebuilds
+ if (incremental) return;
+ const hubUrl = "https://websubhub.com/hub";
+ const feedUrls = [
+ `${siteUrl}/`,
+ `${siteUrl}/feed.xml`,
+ `${siteUrl}/feed.json`,
+ ];
+
+ // Discover category feed URLs from build output
+ const outputDir = directories?.output || dir.output;
+ const categoriesDir = resolve(outputDir, "categories");
+ try {
+ for (const entry of readdirSync(categoriesDir, { withFileTypes: true })) {
+ if (entry.isDirectory() && existsSync(resolve(categoriesDir, entry.name, "feed.xml"))) {
+ feedUrls.push(`${siteUrl}/categories/${entry.name}/feed.xml`);
+ feedUrls.push(`${siteUrl}/categories/${entry.name}/feed.json`);
+ }
+ }
+ } catch {
+ // categoriesDir may not exist on first build — ignore
+ }
+
+ console.log(`[websub] Notifying hub for ${feedUrls.length} URLs...`);
+ for (const feedUrl of feedUrls) {
+ try {
+ const res = await fetch(hubUrl, {
+ method: "POST",
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ body: `hub.mode=publish&hub.url=${encodeURIComponent(feedUrl)}`,
+ });
+ console.log(`[websub] Notified hub for ${feedUrl}: ${res.status}`);
+ } catch (err) {
+ console.error(`[websub] Hub notification failed for ${feedUrl}:`, err.message);
+ }
+ }
+ });
+
+ return {
+ dir: {
+ input: ".",
+ output: "_site",
+ includes: "_includes",
+ data: "_data",
+ },
+ markdownTemplateEngine: false, // Disable to avoid Nunjucks interpreting {{ in content
+ htmlTemplateEngine: "njk",
+ };
+}
diff --git a/theme/featured.njk b/theme/featured.njk
new file mode 100644
index 0000000..5e66927
--- /dev/null
+++ b/theme/featured.njk
@@ -0,0 +1,186 @@
+---
+layout: layouts/base.njk
+title: Featured
+withSidebar: true
+pagination:
+ data: collections.featuredPosts
+ size: 20
+ alias: paginatedFeatured
+ generatePageOnEmptyData: true
+permalink: "featured/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
+---
+
+
+
+ Curated posts pinned as featured.
+ ({{ collections.featuredPosts.length }} total)
+
+
+ {% if paginatedFeatured.length > 0 %}
+
+ {% for post in paginatedFeatured %}
+ {# Detect post type from frontmatter properties #}
+ {% set likedUrl = post.data.likeOf or post.data.like_of %}
+ {% set bookmarkedUrl = post.data.bookmarkOf or post.data.bookmark_of %}
+ {% set repostedUrl = post.data.repostOf or post.data.repost_of %}
+ {% set replyToUrl = post.data.inReplyTo or post.data.in_reply_to %}
+ {% set hasPhotos = post.data.photo and post.data.photo.length %}
+
+ {# Determine border color by post type #}
+ {% if likedUrl %}
+ {% set borderClass = "border-l-red-400 dark:border-l-red-500" %}
+ {% elif bookmarkedUrl %}
+ {% set borderClass = "border-l-amber-400 dark:border-l-amber-500" %}
+ {% elif repostedUrl %}
+ {% set borderClass = "border-l-green-400 dark:border-l-green-500" %}
+ {% elif replyToUrl %}
+ {% set borderClass = "border-l-sky-400 dark:border-l-sky-500" %}
+ {% elif hasPhotos %}
+ {% set borderClass = "border-l-purple-400 dark:border-l-purple-500" %}
+ {% else %}
+ {% set borderClass = "border-l-surface-300 dark:border-l-surface-600" %}
+ {% endif %}
+
+
+
+ {% if likedUrl %}
+ {# ── Like ── #}
+
+ {{ likedUrl }}
+ {% if post.templateContent %}
+ {{ post.templateContent | safe }}
+ {% endif %}
+
+ {% elif bookmarkedUrl %}
+ {# ── Bookmark ── #}
+
+ {% if post.data.title %}
+
+ {% endif %}
+ {{ bookmarkedUrl }}
+ {% if post.templateContent %}
+ {{ post.templateContent | safe }}
+ {% endif %}
+
+ {% elif repostedUrl %}
+ {# ── Repost ── #}
+
+ {{ repostedUrl }}
+ {% if post.templateContent %}
+ {{ post.templateContent | safe }}
+ {% endif %}
+
+ {% elif replyToUrl %}
+ {# ── Reply ── #}
+
+ {{ replyToUrl }}
+ {% if post.templateContent %}
+ {{ post.templateContent | safe }}
+ {% endif %}
+
+ {% elif post.data.title %}
+ {# ── Article/Page ── #}
+
+
+ {{ post.date | dateDisplay }}
+ {% if post.data.postType %}
+ {{ post.data.postType }}
+ {% endif %}
+
+ {% if post.templateContent %}
+ {{ post.templateContent | striptags | truncate(250) }}
+ {% endif %}
+ Read more →
+
+ {% else %}
+ {# ── Note ── #}
+
+ {% if post.templateContent %}
+ {{ post.templateContent | safe }}
+ {% endif %}
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ {# Pagination controls #}
+ {% if pagination.pages.length > 1 %}
+
+ {% endif %}
+
+ {% else %}
+ {% set postType = "featured" %}
+ {% include "components/empty-collection.njk" %}
+ {% endif %}
+
diff --git a/theme/feed-json.njk b/theme/feed-json.njk
new file mode 100644
index 0000000..aa9a6e3
--- /dev/null
+++ b/theme/feed-json.njk
@@ -0,0 +1,66 @@
+---
+permalink: /feed.json
+eleventyExcludeFromCollections: true
+eleventyImport:
+ collections:
+ - feed
+---
+{
+ "version": "https://jsonfeed.org/version/1.1",
+ "title": "{{ site.name }}",
+ "home_page_url": "{{ site.url }}/",
+ "feed_url": "{{ site.url }}/feed.json",
+ "hubs": [
+ {
+ "type": "WebSub",
+ "url": "https://websubhub.com/hub"
+ }
+ ],
+ "description": "{{ site.description }}",
+ "language": "{{ site.locale | default('en') }}",
+ "authors": [
+ {
+ "name": "{{ site.author.name | default(site.name) }}",
+ "url": "{{ site.url }}/"
+ }
+ ],
+ "_textcasting": {
+ "version": "1.0",
+ "about": "https://textcasting.org/"
+ {%- set hasSupport = site.support and (site.support.url or site.support.stripe or site.support.lightning or site.support.paymentPointer) %}
+ {%- if hasSupport %},
+ "support": {{ site.support | textcastingSupport | jsonEncode | safe }}
+ {%- endif %}
+ },
+ "items": [
+ {%- for post in collections.feed %}
+ {%- set absolutePostUrl = site.url + post.url %}
+ {%- set postImage = post.data.photo %}
+ {%- if postImage %}
+ {# If photo is an array, use first element (check if first element looks like a URL) #}
+ {%- if postImage[0] and (postImage[0] | length) > 10 %}
+ {%- set postImage = postImage[0] %}
+ {%- endif %}
+ {%- endif %}
+ {%- if not postImage or postImage == "" %}
+ {%- set postImage = post.data.image or (post.content | extractFirstImage) %}
+ {%- endif %}
+ {
+ "id": "{{ absolutePostUrl }}",
+ "url": "{{ absolutePostUrl }}",
+ "title": {% if post.data.title %}{{ post.data.title | jsonEncode | safe }}{% else %}null{% endif %},
+ "content_html": {{ post.content | htmlToAbsoluteUrls(absolutePostUrl) | jsonEncode | safe }},
+ "content_text": {{ post.content | striptags | jsonEncode | safe }},
+ "date_published": "{{ post.date | dateToRfc3339 }}",
+ "date_modified": "{{ (post.data.updated or post.date) | dateToRfc3339 }}"
+ {%- if postImage and postImage != "" and (postImage | length) > 10 %},
+ "image": "{{ postImage | url | absoluteUrl(site.url) }}"
+ {%- endif %}
+ {%- set attachments = post.data | feedAttachments %}
+ {%- if attachments.length > 0 %},
+ "attachments": {{ attachments | jsonEncode | safe }}
+ {%- endif %}
+ }{% if not loop.last %},{% endif %}
+ {%- endfor %}
+ ]
+}
diff --git a/theme/feed.njk b/theme/feed.njk
new file mode 100644
index 0000000..0fdfab4
--- /dev/null
+++ b/theme/feed.njk
@@ -0,0 +1,44 @@
+---
+permalink: /feed.xml
+eleventyExcludeFromCollections: true
+eleventyImport:
+ collections:
+ - feed
+---
+
+
+
+ {{ site.name }}
+ {{ site.url }}/
+ {{ site.description }}
+ {{ site.locale | default('en') }}
+
+
+ {{ collections.feed | getNewestCollectionItemDate | dateToRfc822 }}
+ {%- for post in collections.feed %}
+ {%- set absolutePostUrl = site.url + post.url %}
+ {%- set postImage = post.data.photo %}
+ {%- if postImage %}
+ {# If photo is an array, use first element (check if first element looks like a URL) #}
+ {%- if postImage[0] and (postImage[0] | length) > 10 %}
+ {%- set postImage = postImage[0] %}
+ {%- endif %}
+ {%- endif %}
+ {%- if not postImage or postImage == "" %}
+ {%- set postImage = post.data.image or (post.content | extractFirstImage) %}
+ {%- endif %}
+ -
+
{{ post.data.title | default(post.content | striptags | truncate(80)) | escape }}
+ {{ absolutePostUrl }}
+ {{ absolutePostUrl }}
+ {{ post.date | dateToRfc822 }}
+ {{ post.content | htmlToAbsoluteUrls(absolutePostUrl) | escape }}
+ {%- if postImage and postImage != "" and (postImage | length) > 10 %}
+ {%- set imageUrl = postImage | url | absoluteUrl(site.url) %}
+
+
+ {%- endif %}
+
+ {%- endfor %}
+
+
diff --git a/theme/funkwhale.njk b/theme/funkwhale.njk
new file mode 100644
index 0000000..8694d8f
--- /dev/null
+++ b/theme/funkwhale.njk
@@ -0,0 +1,269 @@
+---
+layout: layouts/base.njk
+title: Funkwhale Listening Activity
+permalink: /funkwhale/
+withSidebar: true
+---
+
+
+
+ {# Now Playing / Recently Played Hero #}
+ {% if funkwhaleActivity.nowPlaying and funkwhaleActivity.nowPlaying.status %}
+
+
+
+ {% if funkwhaleActivity.nowPlaying.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if funkwhaleActivity.nowPlaying.status == 'now-playing' %}
+
+
+
+
+
+
+ Now Playing
+
+ {% else %}
+
+
+ Recently Played
+
+ {% endif %}
+
+
+
+ {% if funkwhaleActivity.nowPlaying.trackUrl %}
+
+ {{ funkwhaleActivity.nowPlaying.track }}
+
+ {% else %}
+ {{ funkwhaleActivity.nowPlaying.track }}
+ {% endif %}
+
+
{{ funkwhaleActivity.nowPlaying.artist }}
+ {% if funkwhaleActivity.nowPlaying.album %}
+
{{ funkwhaleActivity.nowPlaying.album }}
+ {% endif %}
+
{{ funkwhaleActivity.nowPlaying.relativeTime }}
+
+
+
+
+ {% endif %}
+
+ {# Stats Section with Tabs #}
+ {% if funkwhaleActivity.stats %}
+
+
+
+
+
+ Listening Statistics
+
+
+ {# Tab buttons #}
+
+
+ All Time
+
+
+ This Month
+
+
+ This Week
+
+
+ Trends
+
+
+
+ {# All Time Tab #}
+
+ {% set summary = funkwhaleActivity.stats.summary.all %}
+ {% set topArtists = funkwhaleActivity.stats.topArtists.all %}
+ {% set topAlbums = funkwhaleActivity.stats.topAlbums.all %}
+ {% include "components/funkwhale-stats-content.njk" %}
+
+
+ {# This Month Tab #}
+
+ {% set summary = funkwhaleActivity.stats.summary.month %}
+ {% set topArtists = funkwhaleActivity.stats.topArtists.month %}
+ {% set topAlbums = funkwhaleActivity.stats.topAlbums.month %}
+ {% include "components/funkwhale-stats-content.njk" %}
+
+
+ {# This Week Tab #}
+
+ {% set summary = funkwhaleActivity.stats.summary.week %}
+ {% set topArtists = funkwhaleActivity.stats.topArtists.week %}
+ {% set topAlbums = funkwhaleActivity.stats.topAlbums.week %}
+ {% include "components/funkwhale-stats-content.njk" %}
+
+
+ {# Trends Tab #}
+
+ {% if funkwhaleActivity.stats.trends and funkwhaleActivity.stats.trends.length %}
+
+
Daily Listening (Last 30 Days)
+
+ {% set maxCount = 1 %}
+ {% for day in funkwhaleActivity.stats.trends %}
+ {% if day.count > maxCount %}
+ {% set maxCount = day.count %}
+ {% endif %}
+ {% endfor %}
+ {% for day in funkwhaleActivity.stats.trends %}
+
+ {% endfor %}
+
+
+ {{ funkwhaleActivity.stats.trends[0].date }}
+ {{ funkwhaleActivity.stats.trends[funkwhaleActivity.stats.trends.length - 1].date }}
+
+
+ {% else %}
+
No trend data available yet.
+ {% endif %}
+
+
+ {% endif %}
+
+ {# Recent Listenings #}
+
+
+
+
+
+ Recent Listens
+
+
+ {% if funkwhaleActivity.listenings.length %}
+
+ {% for listening in funkwhaleActivity.listenings | head(15) %}
+
+ {% if listening.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if listening.trackUrl %}
+
+ {{ listening.track }}
+
+ {% else %}
+ {{ listening.track }}
+ {% endif %}
+
+
{{ listening.artist }}
+
+
+
+ {{ listening.relativeTime }}
+ {% if listening.duration %}
+ {{ listening.duration }}
+ {% endif %}
+
+
+ {% endfor %}
+
+ {% else %}
+ No recent listening history available.
+ {% endif %}
+
+
+ {# Favorites #}
+ {% if funkwhaleActivity.favorites.length %}
+
+
+
+
+
+ Favorite Tracks
+
+
+
+ {% for favorite in funkwhaleActivity.favorites | head(10) %}
+
+ {% if favorite.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if favorite.trackUrl %}
+
+ {{ favorite.track }}
+
+ {% else %}
+ {{ favorite.track }}
+ {% endif %}
+
+
{{ favorite.artist }}
+ {% if favorite.album %}
+
{{ favorite.album }}
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
diff --git a/theme/github.njk b/theme/github.njk
new file mode 100644
index 0000000..48cdd64
--- /dev/null
+++ b/theme/github.njk
@@ -0,0 +1,270 @@
+---
+layout: layouts/base.njk
+title: GitHub Activity
+permalink: /github/
+withSidebar: true
+---
+
+
+
+ {# Featured Projects Section #}
+ {% if githubActivity.featured.length %}
+
+
+
+
+
+ Featured Projects
+
+
+
+ {% for repo in githubActivity.featured %}
+
+
+
+ {% if repo.isPrivate %}
+
Private
+ {% endif %}
+
+
+ {% if repo.description %}
+ {{ repo.description }}
+ {% endif %}
+
+
+ {% if repo.language %}
+
+
+ {{ repo.language }}
+
+ {% endif %}
+
+
+
+
+ {{ repo.stars }}
+
+ {% if repo.forks > 0 %}
+
+
+
+
+ {{ repo.forks }}
+
+ {% endif %}
+
+
+ {% if repo.commits and repo.commits.length %}
+
+
+ Recent commits ({{ repo.commits.length }})
+
+
+ {% for commit in repo.commits %}
+
+
+ {{ commit.sha }}
+
+ {{ commit.message }}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {# Recent Commits Section #}
+
+
+
+
+
+ Recent Commits
+
+
+ {% if githubActivity.commits.length %}
+
+ {% for commit in githubActivity.commits %}
+
+
+ {{ commit.sha }}
+
+
+
+ {% endfor %}
+
+ {% else %}
+ No recent commits found.
+ {% endif %}
+
+
+ {# Contributions Section (PRs & Issues) #}
+ {% if githubActivity.contributions.length %}
+
+
+
+
+
+ Pull Requests & Issues
+
+
+
+ {% for item in githubActivity.contributions %}
+
+ {% if item.type == "pr" %}
+
PR
+ {% else %}
+
Issue
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {# My Repositories Section #}
+
+
+
+
+
+ My Repositories
+
+
+ {% if githubRepos.length %}
+
+ {% for repo in githubRepos | head(6) %}
+
+
+
+ {% if repo.description %}
+ {{ repo.description | truncate(100) }}
+ {% endif %}
+
+
+ {% if repo.language %}
+
+
+ {{ repo.language }}
+
+ {% endif %}
+
+
+
+
+ {{ repo.stargazers_count }}
+
+
+
+
+
+ {{ repo.forks_count }}
+
+
+
+ {% endfor %}
+
+
+
+ View all repositories →
+
+ {% else %}
+ No repositories found.
+ {% endif %}
+
+
+ {# Starred Repos Section #}
+
+
+
+
+
+ Starred Repositories
+
+
+ {% if githubActivity.stars.length %}
+
+ {% for repo in githubActivity.stars | head(10) %}
+
+
+
+ {% if repo.description %}
+ {{ repo.description }}
+ {% endif %}
+
+
+ {% for topic in repo.topics %}
+
+ {{ topic }}
+
+ {% endfor %}
+
+
+
+ {% if repo.language %}
+
+
+ {{ repo.language }}
+
+ {% endif %}
+
+
+
+
+ {{ repo.stars }}
+
+
+
+ {% endfor %}
+
+
+
+ View all {{ githubStarred.totalCount | default(githubActivity.stars | length) }} starred repos →
+
+ {% else %}
+ No starred repositories found.
+ {% endif %}
+
+
diff --git a/theme/graph.njk b/theme/graph.njk
new file mode 100644
index 0000000..e6ef5e6
--- /dev/null
+++ b/theme/graph.njk
@@ -0,0 +1,18 @@
+---
+layout: layouts/base.njk
+title: Posting Activity
+permalink: /graph/
+withSidebar: true
+---
+
+Posting Activity
+
+
+ A contribution-style graph showing posting frequency across all years.
+
+
+{% if collections.posts and collections.posts.length %}
+ {% postGraph collections.posts, { limit: 0 } %}
+{% else %}
+ No posts found.
+{% endif %}
diff --git a/theme/images/default-avatar.svg b/theme/images/default-avatar.svg
new file mode 100644
index 0000000..3b15f50
--- /dev/null
+++ b/theme/images/default-avatar.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/theme/images/favicon.svg b/theme/images/favicon.svg
new file mode 100644
index 0000000..c15703d
--- /dev/null
+++ b/theme/images/favicon.svg
@@ -0,0 +1,4 @@
+
+
+ i
+
diff --git a/theme/images/og-default.png b/theme/images/og-default.png
new file mode 100644
index 0000000..bfaee94
Binary files /dev/null and b/theme/images/og-default.png differ
diff --git a/theme/images/rick.jpg b/theme/images/rick.jpg
new file mode 100644
index 0000000..f976d6d
Binary files /dev/null and b/theme/images/rick.jpg differ
diff --git a/theme/index.njk b/theme/index.njk
new file mode 100644
index 0000000..9d2a090
--- /dev/null
+++ b/theme/index.njk
@@ -0,0 +1,8 @@
+---
+layout: layouts/home.njk
+title: Home
+eleventyImport:
+ collections:
+ - posts
+ - feed
+---
diff --git a/theme/interactions.njk b/theme/interactions.njk
new file mode 100644
index 0000000..206a600
--- /dev/null
+++ b/theme/interactions.njk
@@ -0,0 +1,533 @@
+---
+layout: layouts/base.njk
+title: Interactions
+permalink: /interactions/
+---
+
+
+
+
+
The inbound webmentions tab requires JavaScript to load data from the API. Enable JavaScript for the full interactive experience. Outbound interactions are shown below.
+
+
+
+{# Tab navigation for Outbound/Inbound #}
+
+ {# Tab buttons #}
+
+
+ My Activity
+ (outbound)
+
+
+ Received
+ (inbound)
+
+
+
+
+ {# ===== OUTBOUND TAB - My Activity ===== #}
+
+
Content I've interacted with across the web.
+
+
+
+
+
About IndieWeb Interactions
+
+ These pages show different types of IndieWeb interactions I've made. Each type uses specific microformat properties
+ to indicate the relationship to the original content.
+
+
+ Likes use u-like-of
+ Replies use u-in-reply-to
+ Bookmarks use u-bookmark-of
+ Reposts use u-repost-of
+
+
+
+
+ {# ===== INBOUND TAB - Received Webmentions ===== #}
+
+
+
Webmentions and interactions others have made with my content.
+
+
+
+
+
+
+
+
+ {# Loading state #}
+
+
+
Loading webmentions...
+
+
+ {# Setup required state — shown when webmentions proxy is not configured #}
+
+
Webmentions Not Configured
+
+ To receive inbound webmentions, you need to set up the webmentions proxy plugin
+ (@rmdes/indiekit-endpoint-webmentions-proxy).
+
+
+ Register your domain at webmention.io and get your API token
+ Set the WEBMENTION_IO_TOKEN environment variable
+ Ensure the webmentions proxy plugin is installed and configured in your Indiekit config
+ Restart Indiekit to apply the changes
+
+
+
+ {# Error state — only shown for real errors, not missing config #}
+
+
+ {# Filter by type #}
+
+
+ All
+
+
+ ❤️ Likes
+
+
+ 🔄 Reposts
+
+
+ 💬 Replies
+
+
+ 📣 Mentions
+
+
+
+ {# Webmentions list #}
+
+
+
+
+ {# Author avatar #}
+
+
+
+
+
+ {# Header with author, type badge, and date #}
+
+
+
+ {# Type badge #}
+
+ ❤️ liked
+
+
+ 🔄 reposted
+
+
+ 💬 replied
+
+
+ 📣 mentioned
+
+
+ 🔖 bookmarked
+
+
+ {# Platform badge (from conversations API) #}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {# Content (for replies) #}
+
+
+ {# Target URL - which of my posts this is about #}
+
+
+
+
+
+
+ {# Empty state #}
+
+
No webmentions found for this filter.
+
+
+ {# Pagination / Load more #}
+
+
+
+
+
+
+
+ {# Info box #}
+
+
About Webmentions
+
+ Webmentions are a W3C standard for cross-site communication. When someone likes, reposts, or replies to my content
+ from their own site (or via Bluesky/Mastodon bridges), I receive a webmention notification.
+
+
+ Likes - Someone appreciated this post
+ Reposts - Someone shared this post
+ Replies - Someone responded to this post
+ Mentions - Someone linked to this post
+
+
+
+
+
+
diff --git a/theme/interactive/architecture.html b/theme/interactive/architecture.html
new file mode 100644
index 0000000..7062ece
--- /dev/null
+++ b/theme/interactive/architecture.html
@@ -0,0 +1,1162 @@
+
+
+
+
+
+Indiekit Architecture Guide
+
+
+
+
+
+ Indiekit
+
+
+
+
+
+
+
diff --git a/theme/js/admin.js b/theme/js/admin.js
new file mode 100644
index 0000000..66b11ec
--- /dev/null
+++ b/theme/js/admin.js
@@ -0,0 +1,67 @@
+/**
+ * Admin UI auth detection
+ * Checks if the user is logged in to Indiekit by probing /session/login.
+ * Dispatches a custom event for Alpine.js components to react to.
+ */
+
+(function () {
+ const cacheKey = 'indiekit-auth-status';
+ const cacheTTL = 5 * 60 * 1000; // 5 minutes
+
+ function getCachedStatus() {
+ try {
+ const cached = sessionStorage.getItem(cacheKey);
+ if (!cached) return null;
+ const parsed = JSON.parse(cached);
+ if (Date.now() - parsed.ts > cacheTTL) {
+ sessionStorage.removeItem(cacheKey);
+ return null;
+ }
+ return parsed.loggedIn;
+ } catch {
+ return null;
+ }
+ }
+
+ function setCachedStatus(loggedIn) {
+ try {
+ sessionStorage.setItem(cacheKey, JSON.stringify({ ts: Date.now(), loggedIn: loggedIn }));
+ } catch {
+ // sessionStorage full or unavailable
+ }
+ }
+
+ function dispatch(loggedIn) {
+ window.dispatchEvent(new CustomEvent('indiekit:auth', { detail: { loggedIn: loggedIn } }));
+ if (loggedIn) {
+ document.body.setAttribute('data-indiekit-auth', 'true');
+ } else {
+ document.body.removeAttribute('data-indiekit-auth');
+ }
+ }
+
+ function checkAuth() {
+ return fetch('/session/login', { credentials: 'same-origin', redirect: 'manual', cache: 'no-store' })
+ .then(function (response) {
+ // opaqueredirect means 302 → user is logged in
+ return response.type === 'opaqueredirect';
+ })
+ .catch(function () {
+ return false;
+ });
+ }
+
+ // Cache-then-verify: show from cache instantly, correct in background
+ var cached = getCachedStatus();
+ if (cached !== null) {
+ dispatch(cached);
+ }
+
+ checkAuth().then(function (loggedIn) {
+ setCachedStatus(loggedIn);
+ // Only re-dispatch if different from cache or no cache existed
+ if (cached === null || cached !== loggedIn) {
+ dispatch(loggedIn);
+ }
+ });
+})();
diff --git a/theme/js/comments.js b/theme/js/comments.js
new file mode 100644
index 0000000..11219e4
--- /dev/null
+++ b/theme/js/comments.js
@@ -0,0 +1,151 @@
+/**
+ * Client-side comments component (Alpine.js)
+ * Handles IndieAuth flow, comment submission, and display
+ *
+ * Registered via Alpine.data() so the component is available
+ * regardless of script loading order.
+ */
+
+document.addEventListener("alpine:init", () => {
+ Alpine.data("commentsSection", (targetUrl) => ({
+ targetUrl,
+ user: null,
+ meUrl: "",
+ commentText: "",
+ comments: [],
+ loading: true,
+ authLoading: false,
+ submitting: false,
+ statusMessage: "",
+ statusType: "info",
+ maxLength: 2000,
+ showForm: false,
+
+ async init() {
+ await this.checkSession();
+ await this.loadComments();
+ this.handleAuthReturn();
+ },
+
+ async checkSession() {
+ try {
+ const res = await fetch("/comments/api/session", {
+ credentials: "include",
+ });
+ if (res.ok) {
+ const data = await res.json();
+ if (data.user) this.user = data.user;
+ }
+ } catch {
+ // No session
+ }
+ },
+
+ handleAuthReturn() {
+ const params = new URLSearchParams(window.location.search);
+ const authError = params.get("auth_error");
+ if (authError) {
+ this.showStatus(`Authentication failed: ${authError}`, "error");
+ window.history.replaceState(
+ {},
+ "",
+ window.location.pathname + "#comments",
+ );
+ }
+ },
+
+ async loadComments() {
+ this.loading = true;
+ try {
+ const url = `/comments/api/comments?target=${encodeURIComponent(this.targetUrl)}`;
+ const res = await fetch(url);
+ if (res.ok) {
+ const data = await res.json();
+ this.comments = data.children || [];
+ }
+ } catch (e) {
+ console.error("[Comments] Load error:", e);
+ } finally {
+ this.loading = false;
+ }
+ },
+
+ async startAuth() {
+ this.authLoading = true;
+ try {
+ const res = await fetch("/comments/api/auth", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ me: this.meUrl,
+ returnUrl: window.location.pathname + "#comments",
+ }),
+ credentials: "include",
+ });
+
+ if (!res.ok) {
+ const data = await res.json();
+ this.showStatus(data.error || "Auth failed", "error");
+ return;
+ }
+
+ const data = await res.json();
+ if (data.authUrl) {
+ window.location.href = data.authUrl;
+ }
+ } catch {
+ this.showStatus("Failed to start authentication", "error");
+ } finally {
+ this.authLoading = false;
+ }
+ },
+
+ async submitComment() {
+ if (!this.commentText.trim()) return;
+ this.submitting = true;
+
+ try {
+ const res = await fetch("/comments/api/submit", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ content: this.commentText,
+ target: this.targetUrl,
+ }),
+ credentials: "include",
+ });
+
+ if (res.ok) {
+ const data = await res.json();
+ if (data.comment) {
+ this.comments.unshift(data.comment);
+ }
+ this.commentText = "";
+ this.showStatus("Comment posted!", "success");
+ } else {
+ const data = await res.json();
+ this.showStatus(data.error || "Failed to post", "error");
+ }
+ } catch {
+ this.showStatus("Error posting comment", "error");
+ } finally {
+ this.submitting = false;
+ }
+ },
+
+ signOut() {
+ document.cookie =
+ "comment_session=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
+ this.user = null;
+ this.showStatus("Signed out", "info");
+ },
+
+ showStatus(message, type = "info") {
+ this.statusMessage = message;
+ this.statusType = type;
+ setTimeout(() => {
+ this.statusMessage = "";
+ }, 5000);
+ },
+ }));
+});
diff --git a/theme/js/fediverse-interact.js b/theme/js/fediverse-interact.js
new file mode 100644
index 0000000..82e3ef7
--- /dev/null
+++ b/theme/js/fediverse-interact.js
@@ -0,0 +1,144 @@
+/**
+ * Fediverse interaction & sharing component (Alpine.js)
+ *
+ * Two modes:
+ * - "interact" (default): redirect to authorize_interaction for like/boost/reply/follow
+ * - "share": redirect to /share?text=... for composing a new post
+ *
+ * Stores multiple domains in localStorage with usage tracking.
+ * Registered via Alpine.data() so the component is available
+ * regardless of script loading order.
+ */
+
+const STORAGE_KEY = "fediverse_domains_v1";
+const OLD_STORAGE_KEY = "fediverse_instance";
+
+function loadDomains() {
+ try {
+ const json = localStorage.getItem(STORAGE_KEY);
+ if (json) return JSON.parse(json);
+ } catch { /* corrupted data */ }
+
+ // Migrate from old single-domain key
+ const old = localStorage.getItem(OLD_STORAGE_KEY);
+ if (old) {
+ const domains = [{ domain: old, used: 1, lastUsed: new Date().toISOString() }];
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(domains));
+ localStorage.removeItem(OLD_STORAGE_KEY);
+ return domains;
+ }
+
+ return [];
+}
+
+function saveDomains(domains) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(domains));
+}
+
+function addDomain(domain) {
+ const domains = loadDomains();
+ const existing = domains.find((d) => d.domain === domain);
+ if (existing) {
+ existing.used += 1;
+ existing.lastUsed = new Date().toISOString();
+ } else {
+ domains.push({ domain, used: 1, lastUsed: new Date().toISOString() });
+ }
+ saveDomains(domains);
+ return domains;
+}
+
+function removeDomain(domain) {
+ const domains = loadDomains().filter((d) => d.domain !== domain);
+ saveDomains(domains);
+ return domains;
+}
+
+function isValidDomain(str) {
+ try {
+ return new URL(`https://${str}`).hostname === str;
+ } catch {
+ return false;
+ }
+}
+
+document.addEventListener("alpine:init", () => {
+ Alpine.data("fediverseInteract", (targetUrl, mode) => ({
+ targetUrl,
+ mode: mode || "interact",
+ showModal: false,
+ instance: "",
+ savedDomains: [],
+ showInput: false,
+ error: "",
+
+ handleClick(event) {
+ event.preventDefault();
+ this.savedDomains = loadDomains().sort((a, b) => b.used - a.used);
+
+ if (this.savedDomains.length === 1 && !event.shiftKey) {
+ addDomain(this.savedDomains[0].domain);
+ this.redirectToInstance(this.savedDomains[0].domain);
+ return;
+ }
+
+ if (this.savedDomains.length === 0) {
+ this.showInput = true;
+ } else {
+ this.showInput = false;
+ }
+
+ this.instance = "";
+ this.error = "";
+ this.showModal = true;
+ },
+
+ showAddNew() {
+ this.showInput = true;
+ this.instance = "";
+ this.error = "";
+ this.$nextTick(() => {
+ const input = this.$refs.instanceInput;
+ if (input) input.focus();
+ });
+ },
+
+ confirm() {
+ let domain = this.instance.trim();
+ if (!domain) return;
+ // Strip protocol and trailing slashes
+ domain = domain.replace(/^https?:\/\//, "").replace(/\/+$/, "");
+
+ if (!isValidDomain(domain)) {
+ this.error = "Please enter a valid domain (e.g. mastodon.social)";
+ return;
+ }
+
+ this.error = "";
+ this.savedDomains = addDomain(domain);
+ this.showModal = false;
+ this.redirectToInstance(domain);
+ },
+
+ useSaved(domain) {
+ this.savedDomains = addDomain(domain);
+ this.showModal = false;
+ this.redirectToInstance(domain);
+ },
+
+ deleteSaved(domain) {
+ this.savedDomains = removeDomain(domain);
+ if (this.savedDomains.length === 0) {
+ this.showInput = true;
+ }
+ },
+
+ redirectToInstance(domain) {
+ if (this.mode === "share") {
+ window.location.href = `https://${domain}/share?text=${encodeURIComponent(this.targetUrl)}`;
+ } else {
+ window.location.href = `https://${domain}/authorize_interaction?uri=${encodeURIComponent(this.targetUrl)}`;
+ }
+ },
+ }));
+});
diff --git a/theme/js/lightbox.js b/theme/js/lightbox.js
new file mode 100644
index 0000000..07e440b
--- /dev/null
+++ b/theme/js/lightbox.js
@@ -0,0 +1,80 @@
+/**
+ * Alpine.js lightbox component for article images.
+ * Registers via alpine:init so it's available before Alpine starts.
+ * Click any image inside .e-content to view fullscreen.
+ * Navigate with arrow keys, close with Escape or click outside.
+ */
+document.addEventListener("alpine:init", () => {
+ Alpine.data("lightbox", () => ({
+ open: false,
+ src: "",
+ alt: "",
+ images: [],
+ currentIndex: 0,
+
+ init() {
+ const container = this.$root;
+ const imgs = container.querySelectorAll(
+ ".e-content img:not(.u-photo), .photo-gallery img.u-photo"
+ );
+ this.images = Array.from(imgs);
+
+ this.images.forEach((img, i) => {
+ img.style.cursor = "zoom-in";
+ img.addEventListener("click", (e) => {
+ e.preventDefault();
+ this.show(i);
+ });
+ });
+ },
+
+ show(index) {
+ this.currentIndex = index;
+ const img = this.images[index];
+ // Use the largest source available
+ const picture = img.closest("picture");
+ if (picture) {
+ const source = picture.querySelector("source");
+ if (source) {
+ // Extract the URL from srcset (strip width descriptor)
+ const srcset = source.getAttribute("srcset") || "";
+ this.src = srcset.split(/\s+/)[0] || img.src;
+ } else {
+ this.src = img.src;
+ }
+ } else {
+ this.src = img.src;
+ }
+ this.alt = img.alt || "";
+ this.open = true;
+ document.body.style.overflow = "hidden";
+ },
+
+ close() {
+ this.open = false;
+ this.src = "";
+ document.body.style.overflow = "";
+ },
+
+ next() {
+ if (this.images.length > 1) {
+ this.show((this.currentIndex + 1) % this.images.length);
+ }
+ },
+
+ prev() {
+ if (this.images.length > 1) {
+ this.show(
+ (this.currentIndex - 1 + this.images.length) % this.images.length
+ );
+ }
+ },
+
+ onKeydown(e) {
+ if (!this.open) return;
+ if (e.key === "Escape") this.close();
+ if (e.key === "ArrowRight") this.next();
+ if (e.key === "ArrowLeft") this.prev();
+ },
+ }));
+});
diff --git a/theme/js/save-later.js b/theme/js/save-later.js
new file mode 100644
index 0000000..eff4f33
--- /dev/null
+++ b/theme/js/save-later.js
@@ -0,0 +1,51 @@
+/**
+ * Save for Later — shared frontend module
+ * Handles save button clicks on blogroll, podroll, listening, and news pages.
+ * Only active when user is logged in (body[data-indiekit-auth="true"]).
+ */
+(function () {
+ function isLoggedIn() {
+ return document.body.getAttribute('data-indiekit-auth') === 'true';
+ }
+
+ async function saveForLater(button) {
+ var url = button.dataset.saveUrl;
+ var title = button.dataset.saveTitle || url;
+ var source = button.dataset.saveSource || 'manual';
+ if (!url) return;
+
+ button.disabled = true;
+
+ try {
+ var response = await fetch('/readlater/save', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ url: url, title: title, source: source }),
+ credentials: 'same-origin'
+ });
+
+ if (response.ok) {
+ button.classList.add('save-later--saved');
+ button.title = 'Saved';
+ button.setAttribute('aria-label', 'Saved');
+ var label = button.querySelector('.save-later-label');
+ if (label) label.textContent = 'Saved';
+ var icon = button.querySelector('.save-later-icon');
+ if (icon) icon.textContent = '🔖';
+ } else {
+ button.disabled = false;
+ }
+ } catch (e) {
+ button.disabled = false;
+ }
+ }
+
+ document.addEventListener('click', function (e) {
+ if (!isLoggedIn()) return;
+ var button = e.target.closest('.save-later-btn');
+ if (button) {
+ e.preventDefault();
+ saveForLater(button);
+ }
+ });
+})();
diff --git a/theme/js/share-post.js b/theme/js/share-post.js
new file mode 100644
index 0000000..9165613
--- /dev/null
+++ b/theme/js/share-post.js
@@ -0,0 +1,86 @@
+/**
+ * Share Post — frontend module
+ * Opens the Indiekit post creation form in a popup window.
+ * Provides a dropdown menu with post type choices.
+ * Only active when user is logged in (body[data-indiekit-auth="true"]).
+ */
+(function () {
+ var POST_TYPES = [
+ { value: 'note', label: 'Note' },
+ { value: 'bookmark', label: 'Bookmark' },
+ { value: 'reply', label: 'Reply' },
+ { value: 'like', label: 'Like' },
+ { value: 'repost', label: 'Repost' },
+ { value: 'article', label: 'Article' }
+ ];
+
+ function isLoggedIn() {
+ return document.body.getAttribute('data-indiekit-auth') === 'true';
+ }
+
+ function openPostPopup(type, url, title) {
+ var createUrl = '/posts/create'
+ + '?type=' + encodeURIComponent(type)
+ + '&url=' + encodeURIComponent(url)
+ + '&name=' + encodeURIComponent(title);
+
+ window.open(
+ createUrl,
+ 'PostCreator',
+ 'resizable,scrollbars,status=0,toolbar=0,menubar=0,titlebar=0,width=620,height=780,location=0'
+ );
+ }
+
+ function createDropdown(button) {
+ if (button.querySelector('.post-type-dropdown')) return;
+
+ var url = button.dataset.shareUrl;
+ var title = button.dataset.shareTitle || '';
+
+ var dropdown = document.createElement('div');
+ dropdown.className = 'post-type-dropdown';
+
+ POST_TYPES.forEach(function (pt) {
+ var item = document.createElement('button');
+ item.type = 'button';
+ item.className = 'post-type-dropdown-item';
+ item.textContent = pt.label;
+ item.addEventListener('click', function (e) {
+ e.stopPropagation();
+ openPostPopup(pt.value, url, title);
+ closeAllDropdowns();
+ });
+ dropdown.appendChild(item);
+ });
+
+ button.style.position = 'relative';
+ button.appendChild(dropdown);
+ }
+
+ function closeAllDropdowns() {
+ document.querySelectorAll('.post-type-dropdown.open').forEach(function (d) {
+ d.classList.remove('open');
+ });
+ }
+
+ document.addEventListener('click', function (e) {
+ if (!isLoggedIn()) return;
+
+ var button = e.target.closest('.share-post-btn');
+ if (button) {
+ e.preventDefault();
+ e.stopPropagation();
+ createDropdown(button);
+ var dropdown = button.querySelector('.post-type-dropdown');
+ var wasOpen = dropdown.classList.contains('open');
+ closeAllDropdowns();
+ if (!wasOpen) {
+ dropdown.classList.add('open');
+ }
+ return;
+ }
+
+ // Click outside closes dropdowns
+ closeAllDropdowns();
+ });
+})();
diff --git a/theme/js/time-difference.js b/theme/js/time-difference.js
new file mode 100644
index 0000000..792de54
--- /dev/null
+++ b/theme/js/time-difference.js
@@ -0,0 +1,91 @@
+/**
+ * Web Component
+ * Progressively enhances elements with relative date display.
+ * Falls back to static date text when JS is unavailable.
+ *
+ * Usage: February 15, 2026
+ *
+ * Inspired by zachleat.com's time-difference component.
+ */
+class TimeDifference extends HTMLElement {
+ static register(tagName = "time-difference") {
+ if ("customElements" in window) {
+ customElements.define(tagName, TimeDifference);
+ }
+ }
+
+ connectedCallback() {
+ this.update();
+ // Auto-update every 60 seconds
+ this._interval = setInterval(() => this.update(), 60000);
+ }
+
+ disconnectedCallback() {
+ clearInterval(this._interval);
+ }
+
+ update() {
+ const time = this.querySelector("time[datetime]");
+ if (!time) return;
+
+ const datetime = time.getAttribute("datetime");
+ if (!datetime) return;
+
+ const date = new Date(datetime);
+ if (isNaN(date.getTime())) return;
+
+ const now = new Date();
+ const diffMs = now - date;
+
+ // Don't show relative time for future dates
+ if (diffMs < 0) return;
+
+ const diffSec = Math.floor(diffMs / 1000);
+ const diffMin = Math.floor(diffSec / 60);
+ const diffHour = Math.floor(diffMin / 60);
+ const diffDay = Math.floor(diffHour / 24);
+ const diffWeek = Math.floor(diffDay / 7);
+ const diffMonth = Math.floor(diffDay / 30.44);
+ const diffYear = Math.floor(diffDay / 365.25);
+
+ let value, unit;
+
+ if (diffSec < 60) {
+ value = -diffSec;
+ unit = "second";
+ } else if (diffMin < 60) {
+ value = -diffMin;
+ unit = "minute";
+ } else if (diffHour < 24) {
+ value = -diffHour;
+ unit = "hour";
+ } else if (diffDay < 7) {
+ value = -diffDay;
+ unit = "day";
+ } else if (diffWeek < 4) {
+ value = -diffWeek;
+ unit = "week";
+ } else if (diffMonth < 12) {
+ value = -diffMonth;
+ unit = "month";
+ } else {
+ value = -diffYear;
+ unit = "year";
+ }
+
+ try {
+ const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
+ const relative = rtf.format(value, unit);
+
+ // Store original text as title for hover tooltip
+ if (!time.hasAttribute("title")) {
+ time.setAttribute("title", time.textContent.trim());
+ }
+ time.textContent = relative;
+ } catch {
+ // Intl.RelativeTimeFormat not supported, keep static text
+ }
+ }
+}
+
+TimeDifference.register();
diff --git a/theme/js/vendor/alpine-collapse.min.js b/theme/js/vendor/alpine-collapse.min.js
new file mode 100644
index 0000000..a865343
--- /dev/null
+++ b/theme/js/vendor/alpine-collapse.min.js
@@ -0,0 +1 @@
+(()=>{function g(n){n.directive("collapse",e),e.inline=(t,{modifiers:i})=>{i.includes("min")&&(t._x_doShow=()=>{},t._x_doHide=()=>{})};function e(t,{modifiers:i}){let r=l(i,"duration",250)/1e3,h=l(i,"min",0),u=!i.includes("min");t._x_isShown||(t.style.height=`${h}px`),!t._x_isShown&&u&&(t.hidden=!0),t._x_isShown||(t.style.overflow="hidden");let c=(d,s)=>{let o=n.setStyles(d,s);return s.height?()=>{}:o},f={transitionProperty:"height",transitionDuration:`${r}s`,transitionTimingFunction:"cubic-bezier(0.4, 0.0, 0.2, 1)"};t._x_transition={in(d=()=>{},s=()=>{}){u&&(t.hidden=!1),u&&(t.style.display=null);let o=t.getBoundingClientRect().height;t.style.height="auto";let a=t.getBoundingClientRect().height;o===a&&(o=h),n.transition(t,n.setStyles,{during:f,start:{height:o+"px"},end:{height:a+"px"}},()=>t._x_isShown=!0,()=>{Math.abs(t.getBoundingClientRect().height-a)<1&&(t.style.overflow=null)})},out(d=()=>{},s=()=>{}){let o=t.getBoundingClientRect().height;n.transition(t,c,{during:f,start:{height:o+"px"},end:{height:h+"px"}},()=>t.style.overflow="hidden",()=>{t._x_isShown=!1,t.style.height==`${h}px`&&u&&(t.style.display="none",t.hidden=!0)})}}}}function l(n,e,t){if(n.indexOf(e)===-1)return t;let i=n[n.indexOf(e)+1];if(!i)return t;if(e==="duration"){let r=i.match(/([0-9]+)ms/);if(r)return r[1]}if(e==="min"){let r=i.match(/([0-9]+)px/);if(r)return r[1]}return i}document.addEventListener("alpine:init",()=>{window.Alpine.plugin(g)});})();
diff --git a/theme/js/vendor/alpine.min.js b/theme/js/vendor/alpine.min.js
new file mode 100644
index 0000000..8e339a2
--- /dev/null
+++ b/theme/js/vendor/alpine.min.js
@@ -0,0 +1,5 @@
+(()=>{var ie=!1,oe=!1,Y=[],se=-1,ae=!1;function We(t){Bn(t)}function Ge(){ae=!0}function Je(){ae=!1,Xe()}function Bn(t){Y.includes(t)||Y.push(t),Xe()}function Ye(t){let e=Y.indexOf(t);e!==-1&&e>se&&Y.splice(e,1)}function Xe(){if(!oe&&!ie){if(ae)return;ie=!0,queueMicrotask(zn)}}function zn(){ie=!1,oe=!0;for(let t=0;tt.effect(e,{scheduler:r=>{ce?We(r):r()}}),le=t.raw}function ue(t){D=t}function tr(t){let e=()=>{};return[n=>{let i=D(n);return t._x_effects||(t._x_effects=new Set,t._x_runEffects=()=>{t._x_effects.forEach(o=>o())}),t._x_effects.add(i),e=()=>{i!==void 0&&(t._x_effects.delete(i),z(i))},i},()=>{e()}]}function Ot(t,e){let r=!0,n,i=D(()=>{let o=t();if(JSON.stringify(o),!r&&(typeof o=="object"||o!==n)){let s=n;queueMicrotask(()=>{e(o,s)})}n=o,r=!1});return()=>z(i)}async function er(t){Ge();try{await t(),await Promise.resolve()}finally{Je()}}var rr=[],nr=[],ir=[];function or(t){ir.push(t)}function it(t,e){typeof e=="function"?(t._x_cleanups||(t._x_cleanups=[]),t._x_cleanups.push(e)):(e=t,nr.push(e))}function Tt(t){rr.push(t)}function Rt(t,e,r){t._x_attributeCleanups||(t._x_attributeCleanups={}),t._x_attributeCleanups[e]||(t._x_attributeCleanups[e]=[]),t._x_attributeCleanups[e].push(r)}function fe(t,e){t._x_attributeCleanups&&Object.entries(t._x_attributeCleanups).forEach(([r,n])=>{(e===void 0||e.includes(r))&&(n.forEach(i=>i()),delete t._x_attributeCleanups[r])})}function sr(t){for(t._x_effects?.forEach(Ye);t._x_cleanups?.length;)t._x_cleanups.pop()()}var de=new MutationObserver(_e),pe=!1;function mt(){de.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),pe=!0}function me(){Hn(),de.disconnect(),pe=!1}var pt=[];function Hn(){let t=de.takeRecords();pt.push(()=>t.length>0&&_e(t));let e=pt.length;queueMicrotask(()=>{if(pt.length===e)for(;pt.length>0;)pt.shift()()})}function h(t){if(!pe)return t();me();let e=t();return mt(),e}var he=!1,Ct=[];function ar(){he=!0}function cr(){he=!1,_e(Ct),Ct=[]}function _e(t){if(he){Ct=Ct.concat(t);return}let e=[],r=new Set,n=new Map,i=new Map;for(let o=0;o{s.nodeType===1&&s._x_marker&&r.add(s)}),t[o].addedNodes.forEach(s=>{if(s.nodeType===1){if(r.has(s)){r.delete(s);return}s._x_marker||e.push(s)}})),t[o].type==="attributes")){let s=t[o].target,a=t[o].attributeName,c=t[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{fe(s,o)}),n.forEach((o,s)=>{rr.forEach(a=>a(s,o))});for(let o of r)e.some(s=>s.contains(o))||nr.forEach(s=>s(o));for(let o of e)o.isConnected&&ir.forEach(s=>s(o));e=null,r=null,n=null,i=null}function Mt(t){return I(H(t))}function P(t,e,r){return t._x_dataStack=[e,...H(r||t)],()=>{t._x_dataStack=t._x_dataStack.filter(n=>n!==e)}}function H(t){return t._x_dataStack?t._x_dataStack:typeof ShadowRoot=="function"&&t instanceof ShadowRoot?H(t.host):t.parentNode?H(t.parentNode):[]}function I(t){return new Proxy({objects:t},Kn)}var Kn={ownKeys({objects:t}){return Array.from(new Set(t.flatMap(e=>Object.keys(e))))},has({objects:t},e){return e==Symbol.unscopables?!1:t.some(r=>Object.prototype.hasOwnProperty.call(r,e)||Reflect.has(r,e))},get({objects:t},e,r){return e=="toJSON"?Vn:Reflect.get(t.find(n=>Reflect.has(n,e))||{},e,r)},set({objects:t},e,r,n){let i=t.find(s=>Object.prototype.hasOwnProperty.call(s,e))||t[t.length-1],o=Object.getOwnPropertyDescriptor(i,e);return o?.set&&o?.get?o.set.call(n,r)||!0:Reflect.set(i,e,r)}};function Vn(){return Reflect.ownKeys(this).reduce((e,r)=>(e[r]=Reflect.get(this,r),e),{})}function ot(t){let e=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0||typeof s=="object"&&s!==null&&s.__v_skip)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(t,c,o):e(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(t)}function Nt(t,e=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return t(this.initialValue,()=>Un(n,i),s=>ge(n,i,s),i,o)}};return e(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function Un(t,e){return e.split(".").reduce((r,n)=>r[n],t)}function ge(t,e,r){if(typeof e=="string"&&(e=e.split(".")),e.length===1)t[e[0]]=r;else{if(e.length===0)throw error;return t[e[0]]||(t[e[0]]={}),ge(t[e[0]],e.slice(1),r)}}var lr={};function y(t,e){lr[t]=e}function U(t,e){let r=qn(e);return Object.entries(lr).forEach(([n,i])=>{Object.defineProperty(t,`$${n}`,{get(){return i(e,r)},enumerable:!1})}),t}function qn(t){let[e,r]=xe(t),n={interceptor:Nt,...e};return it(t,r),n}function ur(t,e,r,...n){try{return r(...n)}catch(i){st(i,t,e)}}function st(...t){return fr(...t)}var fr=Wn;function dr(t){fr=t}function Wn(t,e,r=void 0){t=Object.assign(t??{message:"No error message given."},{el:e,expression:r}),console.warn(`Alpine Expression Error: ${t.message}
+
+${r?'Expression: "'+r+`"
+
+`:""}`,e),setTimeout(()=>{throw t},0)}var at=!0;function kt(t){let e=at;at=!1;let r=t();return at=e,r}function N(t,e,r={}){let n;return x(t,e)(i=>n=i,r),n}function x(...t){return pr(...t)}var pr=be;function mr(t){pr=t}var hr;function _r(t){hr=t}function be(t,e){let r={};U(r,t);let n=[r,...H(t)],i=typeof e=="function"?Gn(n,e):Yn(n,e,t);return ur.bind(null,t,e,i)}function Gn(t,e){return(r=()=>{},{scope:n={},params:i=[],context:o}={})=>{if(!at){ht(r,e,I([n,...t]),i);return}let s=e.apply(I([n,...t]),i);ht(r,s)}}var ye={};function Jn(t,e){if(ye[t])return ye[t];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(t.trim())||/^(let|const)\s/.test(t.trim())?`(async()=>{ ${t} })()`:t,o=(()=>{try{let s=new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`);return Object.defineProperty(s,"name",{value:`[Alpine] ${t}`}),s}catch(s){return st(s,e,t),Promise.resolve()}})();return ye[t]=o,o}function Yn(t,e,r){let n=Jn(e,r);return(i=()=>{},{scope:o={},params:s=[],context:a}={})=>{n.result=void 0,n.finished=!1;let c=I([o,...t]);if(typeof n=="function"){let l=n.call(a,n,c).catch(u=>st(u,r,e));n.finished?(ht(i,n.result,c,s,r),n.result=void 0):l.then(u=>{ht(i,u,c,s,r)}).catch(u=>st(u,r,e)).finally(()=>n.result=void 0)}}}function ht(t,e,r,n,i){if(at&&typeof e=="function"){let o=e.apply(r,n);o instanceof Promise?o.then(s=>ht(t,s,r,n)).catch(s=>st(s,i,e)):t(o)}else typeof e=="object"&&e instanceof Promise?e.then(o=>t(o)):t(e)}function gr(...t){return hr(...t)}function xr(t,e,r={}){let n={};U(n,t);let i=[n,...H(t)],o=I([r.scope??{},...i]),s=r.params??[];if(e.includes("await")){let a=Object.getPrototypeOf(async function(){}).constructor,c=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e;return new a(["scope"],`with (scope) { let __result = ${c}; return __result }`).call(r.context,o)}else{let a=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(()=>{ ${e} })()`:e,l=new Function(["scope"],`with (scope) { let __result = ${a}; return __result }`).call(r.context,o);return typeof l=="function"&&at?l.apply(o,s):l}}var ve="x-";function T(t=""){return ve+t}function yr(t){ve=t}var Dt={};function d(t,e){return Dt[t]=e,{before(r){if(!Dt[r]){console.warn(String.raw`Cannot find directive \`${r}\`. \`${t}\` will use the default order of execution`);return}let n=X.indexOf(r);X.splice(n>=0?n:X.indexOf("DEFAULT"),0,t)}}}function br(t){return Object.keys(Dt).includes(t)}function gt(t,e,r){if(e=Array.from(e),t._x_virtualDirectives){let o=Object.entries(t._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=Se(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),e=e.concat(o)}let n={};return e.map(vr((o,s)=>n[o]=s)).filter(Ar).map(Zn(n,r)).sort(Qn).map(o=>Xn(t,o))}function Se(t){return Array.from(t).map(vr()).filter(e=>!Ar(e))}var we=!1,_t=new Map,wr=Symbol();function Er(t){we=!0;let e=Symbol();wr=e,_t.set(e,[]);let r=()=>{for(;_t.get(e).length;)_t.get(e).shift()();_t.delete(e)},n=()=>{we=!1,r()};t(r),n()}function xe(t){let e=[],r=a=>e.push(a),[n,i]=tr(t);return e.push(i),[{Alpine:K,effect:n,cleanup:r,evaluateLater:x.bind(x,t),evaluate:N.bind(N,t)},()=>e.forEach(a=>a())]}function Xn(t,e){let r=()=>{},n=Dt[e.type]||r,[i,o]=xe(t);Rt(t,e.original,o);let s=()=>{t._x_ignore||t._x_ignoreSelf||(n.inline&&n.inline(t,e,i),n=n.bind(n,t,e,i),we?_t.get(wr).push(n):n())};return s.runCleanups=o,s}var Pt=(t,e)=>({name:r,value:n})=>(r.startsWith(t)&&(r=r.replace(t,e)),{name:r,value:n}),It=t=>t;function vr(t=()=>{}){return({name:e,value:r})=>{let{name:n,value:i}=Sr.reduce((o,s)=>s(o),{name:e,value:r});return n!==e&&t(n,e),{name:n,value:i}}}var Sr=[];function ct(t){Sr.push(t)}function Ar({name:t}){return Or().test(t)}var Or=()=>new RegExp(`^${ve}([^:^.]+)\\b`);function Zn(t,e){return({name:r,value:n})=>{r===n&&(n="");let i=r.match(Or()),o=r.match(/:([a-zA-Z0-9\-_:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=e||t[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var Ee="DEFAULT",X=["ignore","ref","data","id","anchor","bind","init","for","model","modelable","transition","show","if",Ee,"teleport"];function Qn(t,e){let r=X.indexOf(t.type)===-1?Ee:t.type,n=X.indexOf(e.type)===-1?Ee:e.type;return X.indexOf(r)-X.indexOf(n)}function Z(t,e,r={}){t.dispatchEvent(new CustomEvent(e,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function $(t,e){if(typeof ShadowRoot=="function"&&t instanceof ShadowRoot){Array.from(t.children).forEach(i=>$(i,e));return}let r=!1;if(e(t,()=>r=!0),r)return;let n=t.firstElementChild;for(;n;)$(n,e,!1),n=n.nextElementSibling}function w(t,...e){console.warn(`Alpine Warning: ${t}`,...e)}var Cr=!1;function Tr(){Cr&&w("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),Cr=!0,document.body||w("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `
diff --git a/theme/notes.njk b/theme/notes.njk
new file mode 100644
index 0000000..646d757
--- /dev/null
+++ b/theme/notes.njk
@@ -0,0 +1,97 @@
+---
+layout: layouts/base.njk
+title: Notes
+withSidebar: true
+pagination:
+ data: collections.notes
+ size: 20
+ alias: paginatedNotes
+ generatePageOnEmptyData: true
+permalink: "notes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
+---
+
+
+
Notes
+ {% set sparklineSvg = collections.notes | postingFrequency %}
+ {% if sparklineSvg %}
+ {{ sparklineSvg | safe }}
+ {% endif %}
+
+
+ Short thoughts, updates, and quick posts.
+ ({{ collections.notes.length }} total)
+
+
+ {% if paginatedNotes.length > 0 %}
+
+ {% for post in paginatedNotes %}
+
+
+
+ {{ post.templateContent | safe }}
+
+
+
+ {% endfor %}
+
+
+ {# Pagination controls #}
+ {% if pagination.pages.length > 1 %}
+
+ {% endif %}
+
+ {% else %}
+ {% set postType = "note" %}
+ {% include "components/empty-collection.njk" %}
+ {% endif %}
+
diff --git a/theme/package-lock.json b/theme/package-lock.json
new file mode 100644
index 0000000..829a01a
--- /dev/null
+++ b/theme/package-lock.json
@@ -0,0 +1,6455 @@
+{
+ "name": "indiekit-eleventy-theme",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "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",
+ "@11ty/is-land": "^5.0.1",
+ "@alpinejs/collapse": "^3.15.8",
+ "@atproto/api": "^0.12.0",
+ "@chrisburnell/eleventy-cache-webmentions": "^2.2.7",
+ "@fontsource/inter": "^5.2.8",
+ "@quasibit/eleventy-plugin-sitemap": "^2.2.0",
+ "@resvg/resvg-js": "^2.6.2",
+ "@rknightuk/eleventy-plugin-post-graph": "^1.0.8",
+ "@zachleat/filter-container": "^4.0.0",
+ "@zachleat/table-saw": "^1.0.7",
+ "alpinejs": "^3.15.8",
+ "eleventy-plugin-embed-everything": "^1.21.0",
+ "gray-matter": "^4.0.3",
+ "html-minifier-terser": "^7.0.0",
+ "lite-youtube-embed": "^0.3.2",
+ "markdown-it": "^14.0.0",
+ "markdown-it-anchor": "^9.2.0",
+ "pagefind": "^1.3.0",
+ "rss-parser": "^3.13.0",
+ "satori": "^0.19.2",
+ "unfurl.js": "^6.4.0"
+ },
+ "devDependencies": {
+ "@tailwindcss/typography": "^0.5.0",
+ "autoprefixer": "^10.4.0",
+ "postcss": "^8.4.0",
+ "postcss-cli": "^11.0.0",
+ "tailwindcss": "^3.4.0"
+ }
+ },
+ "node_modules/@11ty/dependency-tree": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-4.0.2.tgz",
+ "integrity": "sha512-RTF6VTZHatYf7fSZBUN3RKwiUeJh5dhWV61gDPrHhQF2/gzruAkYz8yXuvGLx3w3ZBKreGrR+MfYpSVkdbdbLA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.1"
+ }
+ },
+ "node_modules/@11ty/dependency-tree-esm": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@11ty/dependency-tree-esm/-/dependency-tree-esm-2.0.4.tgz",
+ "integrity": "sha512-MYKC0Ac77ILr1HnRJalzKDlb9Z8To3kXQCltx299pUXXUFtJ1RIONtULlknknqW8cLe19DLVgmxVCtjEFm7h0A==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.7",
+ "acorn": "^8.15.0",
+ "dependency-graph": "^1.0.0",
+ "normalize-path": "^3.0.0"
+ }
+ },
+ "node_modules/@11ty/eleventy": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-3.1.2.tgz",
+ "integrity": "sha512-IcsDlbXnBf8cHzbM1YBv3JcTyLB35EK88QexmVyFdVJVgUU6bh9g687rpxryJirHzo06PuwnYaEEdVZQfIgRGg==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/dependency-tree": "^4.0.0",
+ "@11ty/dependency-tree-esm": "^2.0.0",
+ "@11ty/eleventy-dev-server": "^2.0.8",
+ "@11ty/eleventy-plugin-bundle": "^3.0.6",
+ "@11ty/eleventy-utils": "^2.0.7",
+ "@11ty/lodash-custom": "^4.17.21",
+ "@11ty/posthtml-urls": "^1.0.1",
+ "@11ty/recursive-copy": "^4.0.2",
+ "@sindresorhus/slugify": "^2.2.1",
+ "bcp-47-normalize": "^2.3.0",
+ "chokidar": "^3.6.0",
+ "debug": "^4.4.1",
+ "dependency-graph": "^1.0.0",
+ "entities": "^6.0.1",
+ "filesize": "^10.1.6",
+ "gray-matter": "^4.0.3",
+ "iso-639-1": "^3.1.5",
+ "js-yaml": "^4.1.0",
+ "kleur": "^4.1.5",
+ "liquidjs": "^10.21.1",
+ "luxon": "^3.6.1",
+ "markdown-it": "^14.1.0",
+ "minimist": "^1.2.8",
+ "moo": "^0.5.2",
+ "node-retrieve-globals": "^6.0.1",
+ "nunjucks": "^3.2.4",
+ "picomatch": "^4.0.2",
+ "please-upgrade-node": "^3.2.0",
+ "posthtml": "^0.16.6",
+ "posthtml-match-helper": "^2.0.3",
+ "semver": "^7.7.2",
+ "slugify": "^1.6.6",
+ "tinyglobby": "^0.2.14"
+ },
+ "bin": {
+ "eleventy": "cmd.cjs"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@11ty/eleventy-dev-server": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-2.0.8.tgz",
+ "integrity": "sha512-15oC5M1DQlCaOMUq4limKRYmWiGecDaGwryr7fTE/oM9Ix8siqMvWi+I8VjsfrGr+iViDvWcH/TVI6D12d93mA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.1",
+ "chokidar": "^3.6.0",
+ "debug": "^4.4.0",
+ "finalhandler": "^1.3.1",
+ "mime": "^3.0.0",
+ "minimist": "^1.2.8",
+ "morphdom": "^2.7.4",
+ "please-upgrade-node": "^3.2.0",
+ "send": "^1.1.0",
+ "ssri": "^11.0.0",
+ "urlpattern-polyfill": "^10.0.0",
+ "ws": "^8.18.1"
+ },
+ "bin": {
+ "eleventy-dev-server": "cmd.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@11ty/eleventy-fetch": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-fetch/-/eleventy-fetch-4.0.1.tgz",
+ "integrity": "sha512-yIiLM5ziBmg86i4TlXpBdcIygJHvh/GgPJyAiFOckO9H4y9cQDM8eIcJCUQ4Mum0NEVui/OjhEut2R08xw0vlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "flat-cache": "^3.0.4",
+ "node-fetch": "^2.6.7",
+ "p-queue": "^6.6.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@11ty/eleventy-img": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-img/-/eleventy-img-6.0.4.tgz",
+ "integrity": "sha512-jSy9BmubVs0mN76dcXWfSYDgRU+1+/rq/SxUR3MgIvTUAJRDop5pFW+Z1f56CDcOlEHaiPqHgnfOlqRmJvXl7g==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-fetch": "^5.1.0",
+ "@11ty/eleventy-utils": "^2.0.7",
+ "brotli-size": "^4.0.0",
+ "debug": "^4.4.0",
+ "entities": "^6.0.0",
+ "image-size": "^1.2.1",
+ "p-queue": "^6.6.2",
+ "sharp": "^0.33.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@11ty/eleventy-img/node_modules/@11ty/eleventy-fetch": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-fetch/-/eleventy-fetch-5.1.1.tgz",
+ "integrity": "sha512-/xFJLCrqKlcnRKIfO9Qjd1QOs4IpvypljXET955+EgdRPFA+h8Or6bDnZBbcwr6KS7yeUuzp5k1DhXgbentSTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.7",
+ "@rgrove/parse-xml": "^4.2.0",
+ "debug": "^4.4.3",
+ "flatted": "^3.3.3",
+ "p-queue": "6.6.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@11ty/eleventy-plugin-bundle": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-bundle/-/eleventy-plugin-bundle-3.0.7.tgz",
+ "integrity": "sha512-QK1tRFBhQdZASnYU8GMzpTdsMMFLVAkuU0gVVILqNyp09xJJZb81kAS3AFrNrwBCsgLxTdWHJ8N64+OTTsoKkA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.2",
+ "debug": "^4.4.0",
+ "posthtml-match-helper": "^2.0.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@11ty/eleventy-plugin-rss": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-rss/-/eleventy-plugin-rss-2.0.4.tgz",
+ "integrity": "sha512-LF60sGVlxGTryQe3hTifuzrwF8R7XbrNsM2xfcDcNMSliLN4kmB+7zvoLRySRx0AQDjqhPTAeeeT0ra6/9zHUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.0",
+ "@11ty/posthtml-urls": "^1.0.1",
+ "debug": "^4.4.0",
+ "posthtml": "^0.16.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "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",
+ "integrity": "sha512-6QE+duqSQ0GY9rENXYb4iPR4AYGdrFpqnmi59tFp9VrleOl0QSh8VlBr2yd6dlhkdtj7904poZW5PvGr9cMiJQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@11ty/is-land": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@11ty/is-land/-/is-land-5.0.1.tgz",
+ "integrity": "sha512-Rh/sLhE4vrc2JaSjeY385v2UxnDY9BhnQtitETb3SKyr0A48Q5Vn06q2AvDBHObtk9+dcFWsoZX4jhT+O9g+xQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@11ty/lodash-custom": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz",
+ "integrity": "sha512-Mqt6im1xpb1Ykn3nbcCovWXK3ggywRJa+IXIdoz4wIIK+cvozADH63lexcuPpGS/gJ6/m2JxyyXDyupkMr5DHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@11ty/posthtml-urls": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@11ty/posthtml-urls/-/posthtml-urls-1.0.2.tgz",
+ "integrity": "sha512-0vaV3Wt0surZ+oS1VdKKe0axeeupuM+l7W/Z866WFQwF+dGg2Tc/nmhk/5l74/Y55P8KyImnLN9CdygNw2huHg==",
+ "license": "MIT",
+ "dependencies": {
+ "evaluate-value": "^2.0.0",
+ "http-equiv-refresh": "^2.0.1",
+ "list-to-array": "^1.1.0",
+ "parse-srcset": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/@11ty/recursive-copy": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.3.tgz",
+ "integrity": "sha512-SX48BTLEGX8T/OsKWORsHAAeiDsbFl79Oa/0Wg/mv/d27b7trCVZs7fMHvpSgDvZz/fZqx5rDk8+nx5oyT7xBw==",
+ "license": "ISC",
+ "dependencies": {
+ "errno": "^1.0.0",
+ "junk": "^3.1.0",
+ "maximatch": "^0.1.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@alpinejs/collapse": {
+ "version": "3.15.8",
+ "resolved": "https://registry.npmjs.org/@alpinejs/collapse/-/collapse-3.15.8.tgz",
+ "integrity": "sha512-zZhD8DHdHuzGFe8+cHNH99K//oFutzKwcy6vagydb3KFlTzmqxTnHZo5sSV81lAazhV7qKsYCKtNV14tR9QkJw==",
+ "license": "MIT"
+ },
+ "node_modules/@atproto/api": {
+ "version": "0.12.29",
+ "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.12.29.tgz",
+ "integrity": "sha512-PyzPLjGWR0qNOMrmj3Nt3N5NuuANSgOk/33Bu3j+rFjjPrHvk9CI6iQPU6zuDaDCoyOTRJRafw8X/aMQw+ilgw==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/common-web": "^0.3.0",
+ "@atproto/lexicon": "^0.4.0",
+ "@atproto/syntax": "^0.3.0",
+ "@atproto/xrpc": "^0.5.0",
+ "await-lock": "^2.2.2",
+ "multiformats": "^9.9.0",
+ "tlds": "^1.234.0"
+ }
+ },
+ "node_modules/@atproto/common-web": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.3.2.tgz",
+ "integrity": "sha512-Vx0JtL1/CssJbFAb0UOdvTrkbUautsDfHNOXNTcX2vyPIxH9xOameSqLLunM1hZnOQbJwyjmQCt6TV+bhnanDg==",
+ "license": "MIT",
+ "dependencies": {
+ "graphemer": "^1.4.0",
+ "multiformats": "^9.9.0",
+ "uint8arrays": "3.0.0",
+ "zod": "^3.23.8"
+ }
+ },
+ "node_modules/@atproto/lex-data": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.8.tgz",
+ "integrity": "sha512-1Y5tz7BkS7380QuLNXaE8GW8Xba+mRWugt8BKM4BUFYjjUZdmirU8lr72iM4XlEBrzRu8Cfvj+MbsbYaZv+IgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/syntax": "0.4.2",
+ "multiformats": "^9.9.0",
+ "tslib": "^2.8.1",
+ "uint8arrays": "3.0.0",
+ "unicode-segmenter": "^0.14.0"
+ }
+ },
+ "node_modules/@atproto/lex-data/node_modules/@atproto/syntax": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz",
+ "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==",
+ "license": "MIT"
+ },
+ "node_modules/@atproto/lex-json": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.0.8.tgz",
+ "integrity": "sha512-w1Qmkae1QhmNz+i1Zm3xr3jp0UPPRENmdlpU0qIrdxWDo9W4Mzkeyc3eSoa+Zs+zN8xkRSQw7RLZte/B7Ipdwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/lex-data": "0.0.8",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@atproto/lexicon": {
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.14.tgz",
+ "integrity": "sha512-jiKpmH1QER3Gvc7JVY5brwrfo+etFoe57tKPQX/SmPwjvUsFnJAow5xLIryuBaJgFAhnTZViXKs41t//pahGHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/common-web": "^0.4.2",
+ "@atproto/syntax": "^0.4.0",
+ "iso-datestring-validator": "^2.2.2",
+ "multiformats": "^9.9.0",
+ "zod": "^3.23.8"
+ }
+ },
+ "node_modules/@atproto/lexicon/node_modules/@atproto/common-web": {
+ "version": "0.4.12",
+ "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.12.tgz",
+ "integrity": "sha512-3aCJemqM/fkHQrVPbTCHCdiVstKFI+2LkFLvUhO6XZP0EqUZa/rg/CIZBKTFUWu9I5iYiaEiXL9VwcDRpEevSw==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/lex-data": "0.0.8",
+ "@atproto/lex-json": "0.0.8",
+ "zod": "^3.23.8"
+ }
+ },
+ "node_modules/@atproto/lexicon/node_modules/@atproto/syntax": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.2.tgz",
+ "integrity": "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA==",
+ "license": "MIT"
+ },
+ "node_modules/@atproto/syntax": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.4.tgz",
+ "integrity": "sha512-8CNmi5DipOLaVeSMPggMe7FCksVag0aO6XZy9WflbduTKM4dFZVCs4686UeMLfGRXX+X966XgwECHoLYrovMMg==",
+ "license": "MIT"
+ },
+ "node_modules/@atproto/xrpc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.5.0.tgz",
+ "integrity": "sha512-swu+wyOLvYW4l3n+VAuJbHcPcES+tin2Lsrp8Bw5aIXIICiuFn1YMFlwK9JwVUzTH21Py1s1nHEjr4CJeElJog==",
+ "license": "MIT",
+ "dependencies": {
+ "@atproto/lexicon": "^0.4.0",
+ "zod": "^3.21.4"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@chrisburnell/eleventy-cache-webmentions": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/@chrisburnell/eleventy-cache-webmentions/-/eleventy-cache-webmentions-2.2.7.tgz",
+ "integrity": "sha512-9Z4fD5MdTBvOprzcNcRGOyyaVo0ETyoQEiotNgV/eFFJiSidV3VRcab69yybJCkrq7L+aqWRrCdrx7ilL8YKTg==",
+ "funding": [
+ {
+ "type": "buymeacoffee",
+ "url": "https://buymeacoffee.com/chrisburnell"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/chrisburnell"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-fetch": "^5.1.1",
+ "sanitize-html": "^2.17.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@chrisburnell/eleventy-cache-webmentions/node_modules/@11ty/eleventy-fetch": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-fetch/-/eleventy-fetch-5.1.1.tgz",
+ "integrity": "sha512-/xFJLCrqKlcnRKIfO9Qjd1QOs4IpvypljXET955+EgdRPFA+h8Or6bDnZBbcwr6KS7yeUuzp5k1DhXgbentSTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.7",
+ "@rgrove/parse-xml": "^4.2.0",
+ "debug": "^4.4.3",
+ "flatted": "^3.3.3",
+ "p-queue": "6.6.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@fontsource/inter": {
+ "version": "5.2.8",
+ "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz",
+ "integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@iarna/toml": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
+ "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==",
+ "license": "ISC"
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
+ "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+ "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
+ "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+ "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+ "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+ "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+ "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+ "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+ "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+ "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+ "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.0.5"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+ "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+ "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+ "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+ "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+ "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+ "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+ "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+ "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
+ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "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",
+ "integrity": "sha512-7YoU4jjipLjifBhZwttLWbAAkImmBfeMQ0+1ST6mJO45z2mFLHZcgnfHwGF2joNk74wiYNsNOB1ennouzQFZIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "array-flat-polyfill": "^1.0.1",
+ "sitemap": "^6.3.2"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@resvg/resvg-js": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz",
+ "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==",
+ "license": "MPL-2.0",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@resvg/resvg-js-android-arm-eabi": "2.6.2",
+ "@resvg/resvg-js-android-arm64": "2.6.2",
+ "@resvg/resvg-js-darwin-arm64": "2.6.2",
+ "@resvg/resvg-js-darwin-x64": "2.6.2",
+ "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2",
+ "@resvg/resvg-js-linux-arm64-gnu": "2.6.2",
+ "@resvg/resvg-js-linux-arm64-musl": "2.6.2",
+ "@resvg/resvg-js-linux-x64-gnu": "2.6.2",
+ "@resvg/resvg-js-linux-x64-musl": "2.6.2",
+ "@resvg/resvg-js-win32-arm64-msvc": "2.6.2",
+ "@resvg/resvg-js-win32-ia32-msvc": "2.6.2",
+ "@resvg/resvg-js-win32-x64-msvc": "2.6.2"
+ }
+ },
+ "node_modules/@resvg/resvg-js-android-arm-eabi": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz",
+ "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-android-arm64": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz",
+ "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-darwin-arm64": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz",
+ "integrity": "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-darwin-x64": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz",
+ "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz",
+ "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-linux-arm64-gnu": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz",
+ "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-linux-arm64-musl": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz",
+ "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-linux-x64-gnu": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz",
+ "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-linux-x64-musl": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz",
+ "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-win32-arm64-msvc": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz",
+ "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-win32-ia32-msvc": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz",
+ "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-win32-x64-msvc": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz",
+ "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@rgrove/parse-xml": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@rgrove/parse-xml/-/parse-xml-4.2.0.tgz",
+ "integrity": "sha512-UuBOt7BOsKVOkFXRe4Ypd/lADuNIfqJXv8GvHqtXaTYXPPKkj2nS2zPllVsrtRjcomDhIJVBnZwfmlI222WH8g==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@rknightuk/eleventy-plugin-post-graph/-/eleventy-plugin-post-graph-1.0.8.tgz",
+ "integrity": "sha512-GRllThUf7HKigjjnxsqj4G23UGq3sIShNN+wfbqQMSOuNtIQbS2l2eHI6hn2UErvK/W9n3RKc0eYmMpDMyO98w==",
+ "license": "ISC",
+ "dependencies": {
+ "@11ty/eleventy": "^2.0.1",
+ "moment": "^2.29.4"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/@11ty/dependency-tree": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-2.0.1.tgz",
+ "integrity": "sha512-5R+DsT9LJ9tXiSQ4y+KLFppCkQyXhzAm1AIuBWE/sbU0hSXY5pkhoqQYEcPJQFg/nglL+wD55iv2j+7O96UAvg==",
+ "license": "MIT"
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/@11ty/eleventy": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-2.0.1.tgz",
+ "integrity": "sha512-t8XVUbCJByhVEa1RzO0zS2QzbL3wPY8ot1yUw9noqiSHxJWUwv6jiwm1/MZDPTYtkZH2ZHvdQIRQ5/SjG9XmLw==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/dependency-tree": "^2.0.1",
+ "@11ty/eleventy-dev-server": "^1.0.4",
+ "@11ty/eleventy-utils": "^1.0.1",
+ "@11ty/lodash-custom": "^4.17.21",
+ "@iarna/toml": "^2.2.5",
+ "@sindresorhus/slugify": "^1.1.2",
+ "bcp-47-normalize": "^1.1.1",
+ "chokidar": "^3.5.3",
+ "cross-spawn": "^7.0.3",
+ "debug": "^4.3.4",
+ "dependency-graph": "^0.11.0",
+ "ejs": "^3.1.9",
+ "fast-glob": "^3.2.12",
+ "graceful-fs": "^4.2.11",
+ "gray-matter": "^4.0.3",
+ "hamljs": "^0.6.2",
+ "handlebars": "^4.7.7",
+ "is-glob": "^4.0.3",
+ "iso-639-1": "^2.1.15",
+ "kleur": "^4.1.5",
+ "liquidjs": "^10.7.0",
+ "luxon": "^3.3.0",
+ "markdown-it": "^13.0.1",
+ "micromatch": "^4.0.5",
+ "minimist": "^1.2.8",
+ "moo": "^0.5.2",
+ "multimatch": "^5.0.0",
+ "mustache": "^4.2.0",
+ "normalize-path": "^3.0.0",
+ "nunjucks": "^3.2.3",
+ "path-to-regexp": "^6.2.1",
+ "please-upgrade-node": "^3.2.0",
+ "posthtml": "^0.16.6",
+ "posthtml-urls": "^1.0.0",
+ "pug": "^3.0.2",
+ "recursive-copy": "^2.0.14",
+ "semver": "^7.3.8",
+ "slugify": "^1.6.6"
+ },
+ "bin": {
+ "eleventy": "cmd.js"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/@11ty/eleventy-dev-server": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-1.0.4.tgz",
+ "integrity": "sha512-qVBmV2G1KF/0o5B/3fITlrrDHy4bONUI2YuN3/WJ3BNw4NU1d/we8XhKrlgq13nNvHoBx5czYp3LZt8qRG53Fg==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^1.0.1",
+ "chokidar": "^3.5.3",
+ "debug": "^4.3.4",
+ "dev-ip": "^1.0.1",
+ "finalhandler": "^1.2.0",
+ "mime": "^3.0.0",
+ "minimist": "^1.2.8",
+ "morphdom": "^2.7.0",
+ "please-upgrade-node": "^3.2.0",
+ "ssri": "^8.0.1",
+ "ws": "^8.13.0"
+ },
+ "bin": {
+ "eleventy-dev-server": "cmd.js"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/@11ty/eleventy-utils": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-1.0.3.tgz",
+ "integrity": "sha512-nULO91om7vQw4Y/UBjM8i7nJ1xl+/nyK4rImZ41lFxiY2d+XUz7ChAj1CDYFjrLZeu0utAYJTZ45LlcHTkUG4g==",
+ "license": "MIT",
+ "dependencies": {
+ "normalize-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/@sindresorhus/slugify": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-1.1.2.tgz",
+ "integrity": "sha512-V9nR/W0Xd9TSGXpZ4iFUcFGhuOJtZX82Fzxj1YISlbSgKvIiNa7eLEZrT0vAraPOt++KHauIVNYgGRgjc13dXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/transliterate": "^0.1.1",
+ "escape-string-regexp": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/@sindresorhus/transliterate": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-0.1.2.tgz",
+ "integrity": "sha512-5/kmIOY9FF32nicXH+5yLNTX4NJ4atl7jRgqAJuIn/iyDFXBktOKDxCvyGE/EzmF4ngSUvjXxQUQlQiZ5lfw+w==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0",
+ "lodash.deburr": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/@sindresorhus/transliterate/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/bcp-47": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz",
+ "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^1.0.0",
+ "is-alphanumerical": "^1.0.0",
+ "is-decimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/bcp-47-match": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz",
+ "integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/bcp-47-normalize": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-1.1.1.tgz",
+ "integrity": "sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==",
+ "license": "MIT",
+ "dependencies": {
+ "bcp-47": "^1.0.0",
+ "bcp-47-match": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/dependency-graph": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz",
+ "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/entities": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
+ "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/is-alphabetical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
+ "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/is-alphanumerical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
+ "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^1.0.0",
+ "is-decimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/is-decimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
+ "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/iso-639-1": {
+ "version": "2.1.15",
+ "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-2.1.15.tgz",
+ "integrity": "sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/linkify-it": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz",
+ "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
+ "license": "MIT",
+ "dependencies": {
+ "uc.micro": "^1.0.1"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/markdown-it": {
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz",
+ "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "entities": "~3.0.1",
+ "linkify-it": "^4.0.1",
+ "mdurl": "^1.0.1",
+ "uc.micro": "^1.0.5"
+ },
+ "bin": {
+ "markdown-it": "bin/markdown-it.js"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/mdurl": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
+ "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==",
+ "license": "MIT"
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/ssri": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
+ "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rknightuk/eleventy-plugin-post-graph/node_modules/uc.micro": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
+ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
+ "license": "MIT"
+ },
+ "node_modules/@shuding/opentype.js": {
+ "version": "1.4.0-beta.0",
+ "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
+ "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
+ "license": "MIT",
+ "dependencies": {
+ "fflate": "^0.7.3",
+ "string.prototype.codepointat": "^0.2.1"
+ },
+ "bin": {
+ "ot": "bin/ot"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/@sindresorhus/slugify": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz",
+ "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==",
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/transliterate": "^1.0.0",
+ "escape-string-regexp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@sindresorhus/transliterate": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz",
+ "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@tailwindcss/typography": {
+ "version": "0.5.19",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
+ "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "6.0.10"
+ },
+ "peerDependencies": {
+ "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"
+ },
+ "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"
+ },
+ "node_modules/@types/minimatch": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
+ "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "14.18.63",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
+ "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/sax": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
+ "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
+ "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.1.5"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
+ "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
+ "license": "MIT"
+ },
+ "node_modules/@zachleat/filter-container": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@zachleat/filter-container/-/filter-container-4.0.0.tgz",
+ "integrity": "sha512-4etUifhHciQTMtlDymb4cus03qYZrKiuK0gY0cOU4Jrt0LA3ZvPVTwO6esqUdvbpte5ypodmpx84wwbT3Fy5Jw==",
+ "license": "MIT"
+ },
+ "node_modules/@zachleat/table-saw": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@zachleat/table-saw/-/table-saw-1.0.7.tgz",
+ "integrity": "sha512-AxYShldkUoofdzqyH/pgbiXBgoRbFtoQYKbCjTK7diViqepM2SqZoMZm7ofc9UeGaDWgktAOmoJg3T+yN0SAxA==",
+ "license": "MIT"
+ },
+ "node_modules/a-sync-waterfall": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
+ "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==",
+ "license": "MIT"
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/alpinejs": {
+ "version": "3.15.8",
+ "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.8.tgz",
+ "integrity": "sha512-zxIfCRTBGvF1CCLIOMQOxAyBuqibxSEwS6Jm1a3HGA9rgrJVcjEWlwLcQTVGAWGS8YhAsTRLVrtQ5a5QT9bSSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "~3.1.1"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
+ "node_modules/array-differ": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz",
+ "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/array-flat-polyfill": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz",
+ "integrity": "sha512-hfJmKupmQN0lwi0xG6FQ5U8Rd97RnIERplymOv/qpq8AoNKPPAnxJadjFA23FNWm88wykh9HmpLJUUwUtNU/iw==",
+ "license": "CC0-1.0",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==",
+ "license": "MIT",
+ "dependencies": {
+ "array-uniq": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/array-uniq": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+ "license": "MIT"
+ },
+ "node_modules/assert-never": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz",
+ "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==",
+ "license": "MIT"
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.23",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
+ "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.1",
+ "caniuse-lite": "^1.0.30001760",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/await-lock": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz",
+ "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==",
+ "license": "MIT"
+ },
+ "node_modules/babel-walk": {
+ "version": "3.0.0-canary-5",
+ "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz",
+ "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.9.6"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
+ "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.17",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.17.tgz",
+ "integrity": "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/bcp-47": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz",
+ "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/bcp-47-match": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
+ "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/bcp-47-normalize": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-2.3.0.tgz",
+ "integrity": "sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "bcp-47": "^2.0.0",
+ "bcp-47-match": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/brotli-size": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/brotli-size/-/brotli-size-4.0.0.tgz",
+ "integrity": "sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==",
+ "license": "MIT",
+ "dependencies": {
+ "duplexer": "0.1.1"
+ },
+ "engines": {
+ "node": ">= 10.16.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/camel-case": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
+ "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
+ "license": "MIT",
+ "dependencies": {
+ "pascal-case": "^3.1.2",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001766",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
+ "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/character-parser": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
+ "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-regex": "^1.0.3"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/clean-css": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
+ "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==",
+ "license": "MIT",
+ "dependencies": {
+ "source-map": "~0.6.0"
+ },
+ "engines": {
+ "node": ">= 10.0"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/commander": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "license": "MIT"
+ },
+ "node_modules/constantinople": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz",
+ "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.6.0",
+ "@babel/types": "^7.6.1"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-background-parser": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
+ "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==",
+ "license": "MIT"
+ },
+ "node_modules/css-box-shadow": {
+ "version": "1.0.0-3",
+ "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
+ "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==",
+ "license": "MIT"
+ },
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-gradient-parser": {
+ "version": "0.0.17",
+ "resolved": "https://registry.npmjs.org/css-gradient-parser/-/css-gradient-parser-0.0.17.tgz",
+ "integrity": "sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dependency-graph": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz",
+ "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dev-ip": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz",
+ "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==",
+ "bin": {
+ "dev-ip": "lib/dev-ip.js"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/doctypes": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
+ "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==",
+ "license": "MIT"
+ },
+ "node_modules/dom-serializer": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
+ "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.2.0",
+ "entities": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/dom-serializer/node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "license": "BSD-2-Clause",
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/domhandler": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
+ "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.2.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
+ "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^1.0.1",
+ "domelementtype": "^2.2.0",
+ "domhandler": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/dot-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
+ "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
+ "license": "MIT",
+ "dependencies": {
+ "no-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/duplexer": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+ "integrity": "sha512-sxNZ+ljy+RA1maXoUReeqBBpBC6RLKmg5ewzV+x+mSETmWNoKdZN6vcQjpFROemza23hGFskJtFNoUWUaQ+R4Q=="
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/ejs": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.277",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.277.tgz",
+ "integrity": "sha512-wKXFZw4erWmmOz5N/grBoJ2XrNJGDFMu2+W5ACHza5rHtvsqrK4gb6rnLC7XxKB9WlJ+RmyQatuEXmtm86xbnw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/eleventy-plugin-embed-bluesky": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-bluesky/-/eleventy-plugin-embed-bluesky-1.0.1.tgz",
+ "integrity": "sha512-6+0A92TIiJ5MJaJhcMshX5a2R4pdAJjAYz7EvDOk7T2zyr59Tyq9bRm9lEx2IRdZ5fx6CiRNMgLb+KXkzlLd2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-fetch": "^5.1.0",
+ "string-replace-async": "^3.0.2"
+ }
+ },
+ "node_modules/eleventy-plugin-embed-bluesky/node_modules/@11ty/eleventy-fetch": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-fetch/-/eleventy-fetch-5.1.1.tgz",
+ "integrity": "sha512-/xFJLCrqKlcnRKIfO9Qjd1QOs4IpvypljXET955+EgdRPFA+h8Or6bDnZBbcwr6KS7yeUuzp5k1DhXgbentSTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.7",
+ "@rgrove/parse-xml": "^4.2.0",
+ "debug": "^4.4.3",
+ "flatted": "^3.3.3",
+ "p-queue": "6.6.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/eleventy-plugin-embed-everything": {
+ "version": "1.21.1",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-everything/-/eleventy-plugin-embed-everything-1.21.1.tgz",
+ "integrity": "sha512-Ucr27//kAkmpAKGvzSYbtlSCTVu+F7iJF5Smw+81PxAaofouiHhusJUQA6GJkJ1A7WgTDuwHWJjZ5mAXJWNtuA==",
+ "license": "MIT",
+ "dependencies": {
+ "deepmerge": "^4.3.1",
+ "eleventy-plugin-embed-bluesky": "^1.0.1",
+ "eleventy-plugin-embed-instagram": "^1.3.0",
+ "eleventy-plugin-embed-mastodon": "^1.0.2",
+ "eleventy-plugin-embed-openstreetmap": "^1.1.0",
+ "eleventy-plugin-embed-soundcloud": "^1.2.10",
+ "eleventy-plugin-embed-spotify": "^1.3.0",
+ "eleventy-plugin-embed-ted": "^1.0.1",
+ "eleventy-plugin-embed-tiktok": "^1.1.8",
+ "eleventy-plugin-embed-twitch": "^1.2.8",
+ "eleventy-plugin-embed-twitter": "^1.4.3",
+ "eleventy-plugin-vimeo-embed": "^1.4.0",
+ "eleventy-plugin-youtube-embed": "^1.13.1"
+ }
+ },
+ "node_modules/eleventy-plugin-embed-instagram": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-instagram/-/eleventy-plugin-embed-instagram-1.3.0.tgz",
+ "integrity": "sha512-uF63snWEViwLQzr85ZAbKRXlWcLdPwepxuy7gERQVPzTNYWiPU1ruyf8H40qmznWPo7kWHa9Nnb5p4tM2Ilcjg==",
+ "license": "MIT"
+ },
+ "node_modules/eleventy-plugin-embed-mastodon": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-mastodon/-/eleventy-plugin-embed-mastodon-1.0.2.tgz",
+ "integrity": "sha512-JPDUcec2UouQ+tP1087ENMzfo0j8t0JXIYGfTjt+7HxF3cVUmPk+ZZhqX367OJmK2XSbeM5ZfPDORJ4Lhep8uQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-fetch": "^5.1.0",
+ "deepmerge": "^4.3.1",
+ "regex": "^6.0.1",
+ "string-replace-async": "^3.0.2"
+ }
+ },
+ "node_modules/eleventy-plugin-embed-mastodon/node_modules/@11ty/eleventy-fetch": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-fetch/-/eleventy-fetch-5.1.1.tgz",
+ "integrity": "sha512-/xFJLCrqKlcnRKIfO9Qjd1QOs4IpvypljXET955+EgdRPFA+h8Or6bDnZBbcwr6KS7yeUuzp5k1DhXgbentSTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.7",
+ "@rgrove/parse-xml": "^4.2.0",
+ "debug": "^4.4.3",
+ "flatted": "^3.3.3",
+ "p-queue": "6.6.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/eleventy-plugin-embed-openstreetmap": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-openstreetmap/-/eleventy-plugin-embed-openstreetmap-1.1.0.tgz",
+ "integrity": "sha512-XKwEziI7feVZCkJXGSzgAlXR3ee9HeQ/WuTLS6KJKBQ+qGjOQHoi1seoTzsxeC/qt5V0pzgSD1xCgypQUlJ6Bw==",
+ "license": "MIT",
+ "dependencies": {
+ "deepmerge": "^4.3.1"
+ }
+ },
+ "node_modules/eleventy-plugin-embed-soundcloud": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-soundcloud/-/eleventy-plugin-embed-soundcloud-1.2.10.tgz",
+ "integrity": "sha512-4G8z4rmbPJDGi43kJG6K9pRWyVxWCo+CdFpWDUlkvUX5XyKfEzVNbO/8ML0ci4WU79U9FyjLtF1yKlGPEAO2lg==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-fetch": "^5.1.0"
+ }
+ },
+ "node_modules/eleventy-plugin-embed-soundcloud/node_modules/@11ty/eleventy-fetch": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-fetch/-/eleventy-fetch-5.1.1.tgz",
+ "integrity": "sha512-/xFJLCrqKlcnRKIfO9Qjd1QOs4IpvypljXET955+EgdRPFA+h8Or6bDnZBbcwr6KS7yeUuzp5k1DhXgbentSTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.7",
+ "@rgrove/parse-xml": "^4.2.0",
+ "debug": "^4.4.3",
+ "flatted": "^3.3.3",
+ "p-queue": "6.6.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/eleventy-plugin-embed-spotify": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-spotify/-/eleventy-plugin-embed-spotify-1.3.0.tgz",
+ "integrity": "sha512-waVUtW5PO9aKYQEjnvHG4P5bPyKj+SSUBuDtqfF2K+j7dGItmEDD4DP7CmBz6lkn/LlJGmJ7uT4Mr6L4jAP4bg==",
+ "license": "MIT"
+ },
+ "node_modules/eleventy-plugin-embed-ted": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-ted/-/eleventy-plugin-embed-ted-1.0.1.tgz",
+ "integrity": "sha512-F1CayKC05sGDAcdEgaZUP3Cs/71mwLT717sIUUiZWu+Gjd1Lp7m2gqL1R/uKnHN/CFxKYTpAA6ZNN/LCjg0ufw==",
+ "license": "MIT"
+ },
+ "node_modules/eleventy-plugin-embed-tiktok": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-tiktok/-/eleventy-plugin-embed-tiktok-1.1.8.tgz",
+ "integrity": "sha512-U0J7LV0lXmRbEt05E0NzbXiPw8V0Rr5eEQSyFwzAMrdrsxhoCSYFuE2L6+b7SXbV8ZzFjbPX07n1c4hgLfvfrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "deepmerge": "^4.3.1",
+ "regex": "^6.0.1"
+ }
+ },
+ "node_modules/eleventy-plugin-embed-twitch": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-twitch/-/eleventy-plugin-embed-twitch-1.2.8.tgz",
+ "integrity": "sha512-ScjROm5sOwcClyE0DvZQ+6qF67lV4Yycsvb8p189QyJz3/wFw+9SQhyToY1FINRmUURIFRfLgp7AUXb3xAHmfg==",
+ "license": "MIT"
+ },
+ "node_modules/eleventy-plugin-embed-twitter": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-embed-twitter/-/eleventy-plugin-embed-twitter-1.4.3.tgz",
+ "integrity": "sha512-gHpoz7OFfpw/8QuK+m1ENQuzOdLAdGiP4R0cfAdMBAJMjMpB0moW6L3CDfG6Ai7R/Y8KwnTwnmLEznLv5v68BA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-fetch": "^5.1.0",
+ "deepmerge": "^4.3.1"
+ }
+ },
+ "node_modules/eleventy-plugin-embed-twitter/node_modules/@11ty/eleventy-fetch": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-fetch/-/eleventy-fetch-5.1.1.tgz",
+ "integrity": "sha512-/xFJLCrqKlcnRKIfO9Qjd1QOs4IpvypljXET955+EgdRPFA+h8Or6bDnZBbcwr6KS7yeUuzp5k1DhXgbentSTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.7",
+ "@rgrove/parse-xml": "^4.2.0",
+ "debug": "^4.4.3",
+ "flatted": "^3.3.3",
+ "p-queue": "6.6.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/eleventy-plugin-vimeo-embed": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-vimeo-embed/-/eleventy-plugin-vimeo-embed-1.4.0.tgz",
+ "integrity": "sha512-GaDCi3b4gFx+TSzTEPjHlcGPqDBc8tQwJIQqkFMfkFBEnwMpC8fYLh8x31DLJ9C7R7Qh1FGVrb+A6qgKt//IqA==",
+ "license": "MIT"
+ },
+ "node_modules/eleventy-plugin-youtube-embed": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/eleventy-plugin-youtube-embed/-/eleventy-plugin-youtube-embed-1.13.1.tgz",
+ "integrity": "sha512-JfmPyNan5x9FC08RjLQIeBexpo1u+g06FCnEuQMkRbORh4dy/6LILJGcsPkrAHP6fw7ZnD/bogfTTuW5lCyukA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-fetch": "^5.1.0",
+ "deepmerge": "^4.3.1",
+ "lite-youtube-embed": "^0.3.4",
+ "string-replace-async": "^3.0.2"
+ }
+ },
+ "node_modules/eleventy-plugin-youtube-embed/node_modules/@11ty/eleventy-fetch": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@11ty/eleventy-fetch/-/eleventy-fetch-5.1.1.tgz",
+ "integrity": "sha512-/xFJLCrqKlcnRKIfO9Qjd1QOs4IpvypljXET955+EgdRPFA+h8Or6bDnZBbcwr6KS7yeUuzp5k1DhXgbentSTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@11ty/eleventy-utils": "^2.0.7",
+ "@rgrove/parse-xml": "^4.2.0",
+ "debug": "^4.4.3",
+ "flatted": "^3.3.3",
+ "p-queue": "6.6.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/11ty"
+ }
+ },
+ "node_modules/eleventy-plugin-youtube-embed/node_modules/lite-youtube-embed": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/lite-youtube-embed/-/lite-youtube-embed-0.3.4.tgz",
+ "integrity": "sha512-aXgxpwK7AIW58GEbRzA8EYaY4LWvF3FKak6B9OtSJmuNyLhX2ouD4cMTxz/yR5HFInhknaYd2jLWOTRTvT8oAw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/emoji-regex-xs": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-2.0.1.tgz",
+ "integrity": "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/errno": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz",
+ "integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prr": "~1.0.1"
+ },
+ "bin": {
+ "errno": "cli.js"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/esm-import-transformer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/esm-import-transformer/-/esm-import-transformer-3.0.5.tgz",
+ "integrity": "sha512-1GKLvfuMnnpI75l8c6sHoz0L3Z872xL5akGuBudgqTDPv4Vy6f2Ec7jEMKTxlqWl/3kSvNbHELeimJtnqgYniw==",
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.15.0"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/evaluate-value": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/evaluate-value/-/evaluate-value-2.0.0.tgz",
+ "integrity": "sha512-VonfiuDJc0z4sOO7W0Pd130VLsXN6vmBWZlrog1mCb/o7o/Nl5Lr25+Kj/nkCCAhG+zqeeGjxhkK9oHpkgTHhQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
+ "node_modules/extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+ "license": "MIT",
+ "dependencies": {
+ "is-extendable": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
+ "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
+ "license": "MIT"
+ },
+ "node_modules/filelist": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
+ "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
+ "node_modules/filelist/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/filelist/node_modules/minimatch": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/filesize": {
+ "version": "10.1.6",
+ "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz",
+ "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 10.4.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "license": "ISC"
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.3.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
+ "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "license": "MIT"
+ },
+ "node_modules/gray-matter": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
+ "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-yaml": "^3.13.1",
+ "kind-of": "^6.0.2",
+ "section-matter": "^1.0.0",
+ "strip-bom-string": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/gray-matter/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/gray-matter/node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/hamljs": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/hamljs/-/hamljs-0.6.2.tgz",
+ "integrity": "sha512-/chXRp4WpL47I+HX1vCCdSbEXAljEG2FBMmgO7Am0bYsqgnEjreeWzUdX1onXqwZtcfgxbCg5WtEYYvuZ5muBg=="
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.8",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
+ "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "license": "MIT",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/hex-rgb": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz",
+ "integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/html-minifier-terser": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
+ "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==",
+ "license": "MIT",
+ "dependencies": {
+ "camel-case": "^4.1.2",
+ "clean-css": "~5.3.2",
+ "commander": "^10.0.0",
+ "entities": "^4.4.0",
+ "param-case": "^3.0.4",
+ "relateurl": "^0.2.7",
+ "terser": "^5.15.1"
+ },
+ "bin": {
+ "html-minifier-terser": "cli.js"
+ },
+ "engines": {
+ "node": "^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/html-minifier-terser/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/htmlparser2": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz",
+ "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.2.2",
+ "domutils": "^2.8.0",
+ "entities": "^3.0.1"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/entities": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
+ "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/http-equiv-refresh": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-2.0.1.tgz",
+ "integrity": "sha512-XJpDL/MLkV3dKwLzHwr2dY05dYNfBNlyPu4STQ8WvKCFdc6vC5tPXuq28of663+gHVg03C+16pHHs/+FmmDjcw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/image-size": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
+ "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
+ "license": "MIT",
+ "dependencies": {
+ "queue": "6.0.2"
+ },
+ "bin": {
+ "image-size": "bin/image-size.js"
+ },
+ "engines": {
+ "node": ">=16.x"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
+ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
+ "license": "MIT"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-expression": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz",
+ "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==",
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^7.1.1",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "node_modules/is-expression/node_modules/acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-json": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz",
+ "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==",
+ "license": "ISC"
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/iso-639-1": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.5.tgz",
+ "integrity": "sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/iso-datestring-validator": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz",
+ "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==",
+ "license": "MIT"
+ },
+ "node_modules/jake": {
+ "version": "10.9.4",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
+ "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "async": "^3.2.6",
+ "filelist": "^1.0.4",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-stringify": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
+ "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jstransformer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
+ "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==",
+ "license": "MIT",
+ "dependencies": {
+ "is-promise": "^2.0.0",
+ "promise": "^7.0.1"
+ }
+ },
+ "node_modules/junk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz",
+ "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/linebreak": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
+ "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "0.0.8",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+ "license": "MIT",
+ "dependencies": {
+ "uc.micro": "^2.0.0"
+ }
+ },
+ "node_modules/liquidjs": {
+ "version": "10.24.0",
+ "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.24.0.tgz",
+ "integrity": "sha512-TAUNAdgwaAXjjcUFuYVJm9kOVH7zc0mTKxsG9t9Lu4qdWjB2BEblyVIYpjWcmJLMGgiYqnGNJjpNMHx0gp/46A==",
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^10.0.0"
+ },
+ "bin": {
+ "liquid": "bin/liquid.js",
+ "liquidjs": "bin/liquid.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/liquidjs"
+ }
+ },
+ "node_modules/list-to-array": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/list-to-array/-/list-to-array-1.1.0.tgz",
+ "integrity": "sha512-+dAZZ2mM+/m+vY9ezfoueVvrgnHIGi5FvgSymbIgJOFwiznWyA59mav95L+Mc6xPtL3s9gm5eNTlNtxJLbNM1g==",
+ "license": "MIT"
+ },
+ "node_modules/lite-youtube-embed": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/lite-youtube-embed/-/lite-youtube-embed-0.3.2.tgz",
+ "integrity": "sha512-b1dgKyF4PHhinonmr3PB172Nj0qQgA/7DE9EmeIXHR1ksnFEC2olWjNJyJGdsN2cleKHRjjsmrziKlwXtPlmLQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/lodash.deburr": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz",
+ "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==",
+ "license": "MIT"
+ },
+ "node_modules/lower-case": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
+ "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/luxon": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
+ "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/markdown-it": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
+ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "entities": "^4.4.0",
+ "linkify-it": "^5.0.0",
+ "mdurl": "^2.0.0",
+ "punycode.js": "^2.3.1",
+ "uc.micro": "^2.1.0"
+ },
+ "bin": {
+ "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",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/maximatch": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz",
+ "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==",
+ "license": "MIT",
+ "dependencies": {
+ "array-differ": "^1.0.0",
+ "array-union": "^1.0.1",
+ "arrify": "^1.0.0",
+ "minimatch": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/mime": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/moment": {
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/moo": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
+ "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/morphdom": {
+ "version": "2.7.8",
+ "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.8.tgz",
+ "integrity": "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/multiformats": {
+ "version": "9.9.0",
+ "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
+ "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
+ "license": "(Apache-2.0 AND MIT)"
+ },
+ "node_modules/multimatch": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz",
+ "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/minimatch": "^3.0.3",
+ "array-differ": "^3.0.0",
+ "array-union": "^2.1.0",
+ "arrify": "^2.0.1",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/multimatch/node_modules/array-differ": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz",
+ "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/multimatch/node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/multimatch/node_modules/arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mustache": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+ "license": "MIT",
+ "bin": {
+ "mustache": "bin/mustache"
+ }
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "license": "MIT"
+ },
+ "node_modules/no-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
+ "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
+ "license": "MIT",
+ "dependencies": {
+ "lower-case": "^2.0.2",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-retrieve-globals": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/node-retrieve-globals/-/node-retrieve-globals-6.0.1.tgz",
+ "integrity": "sha512-j0DeFuZ/Wg3VlklfbxUgZF/mdHMTEiEipBb3q0SpMMbHaV3AVfoUQF8UGxh1s/yjqO0TgRZd4Pi/x2yRqoQ4Eg==",
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.1",
+ "acorn-walk": "^8.3.4",
+ "esm-import-transformer": "^3.0.3"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/nunjucks": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz",
+ "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "a-sync-waterfall": "^1.0.0",
+ "asap": "^2.0.3",
+ "commander": "^5.1.0"
+ },
+ "bin": {
+ "nunjucks-precompile": "bin/precompile"
+ },
+ "engines": {
+ "node": ">= 6.9.0"
+ },
+ "peerDependencies": {
+ "chokidar": "^3.3.0"
+ },
+ "peerDependenciesMeta": {
+ "chokidar": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/nunjucks/node_modules/commander": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
+ "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-queue": {
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
+ "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "eventemitter3": "^4.0.4",
+ "p-timeout": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-timeout": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
+ "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
+ "license": "MIT",
+ "dependencies": {
+ "p-finally": "^1.0.0"
+ },
+ "engines": {
+ "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/pako": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
+ "license": "MIT"
+ },
+ "node_modules/param-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
+ "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==",
+ "license": "MIT",
+ "dependencies": {
+ "dot-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/parse-css-color": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz",
+ "integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^1.1.4",
+ "hex-rgb": "^4.1.0"
+ }
+ },
+ "node_modules/parse-srcset": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
+ "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
+ "license": "MIT"
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/pascal-case": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
+ "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
+ "license": "MIT",
+ "dependencies": {
+ "no-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/please-upgrade-node": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
+ "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
+ "license": "MIT",
+ "dependencies": {
+ "semver-compare": "^1.0.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-cli": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.1.tgz",
+ "integrity": "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.3.0",
+ "dependency-graph": "^1.0.0",
+ "fs-extra": "^11.0.0",
+ "picocolors": "^1.0.0",
+ "postcss-load-config": "^5.0.0",
+ "postcss-reporter": "^7.0.0",
+ "pretty-hrtime": "^1.0.3",
+ "read-cache": "^1.0.0",
+ "slash": "^5.0.0",
+ "tinyglobby": "^0.2.12",
+ "yargs": "^17.0.0"
+ },
+ "bin": {
+ "postcss": "index.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-cli/node_modules/slash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+ "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz",
+ "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1",
+ "yaml": "^2.4.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-nested/node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-reporter": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz",
+ "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "picocolors": "^1.0.0",
+ "thenby": "^1.3.4"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
+ "node_modules/posthtml": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.7.tgz",
+ "integrity": "sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "posthtml-parser": "^0.11.0",
+ "posthtml-render": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/posthtml-match-helper": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-2.0.3.tgz",
+ "integrity": "sha512-p9oJgTdMF2dyd7WE54QI1LvpBIkNkbSiiECKezNnDVYhGhD1AaOnAkw0Uh0y5TW+OHO8iBdSqnd8Wkpb6iUqmw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "posthtml": "^0.16.6"
+ }
+ },
+ "node_modules/posthtml-parser": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz",
+ "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==",
+ "license": "MIT",
+ "dependencies": {
+ "htmlparser2": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/posthtml-render": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz",
+ "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==",
+ "license": "MIT",
+ "dependencies": {
+ "is-json": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/posthtml-urls": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/posthtml-urls/-/posthtml-urls-1.0.0.tgz",
+ "integrity": "sha512-CMJ0L009sGQVUuYM/g6WJdscsq6ooAwhUuF6CDlYPMLxKp2rmCYVebEU+wZGxnQstGJhZPMvXsRhtqekILd5/w==",
+ "license": "MIT",
+ "dependencies": {
+ "http-equiv-refresh": "^1.0.0",
+ "list-to-array": "^1.1.0",
+ "parse-srcset": "^1.0.2",
+ "promise-each": "^2.2.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/posthtml-urls/node_modules/http-equiv-refresh": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-1.0.0.tgz",
+ "integrity": "sha512-TScO04soylRN9i/QdOdgZyhydXg9z6XdaGzEyOgDKycePeDeTT4KvigjBcI+tgfTlieLWauGORMq5F1eIDa+1w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pretty-hrtime": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
+ "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "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/promise": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+ "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+ "license": "MIT",
+ "dependencies": {
+ "asap": "~2.0.3"
+ }
+ },
+ "node_modules/promise-each": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/promise-each/-/promise-each-2.2.0.tgz",
+ "integrity": "sha512-67roqt1k3QDA41DZ8xi0V+rF3GoaMiX7QilbXu0vXimut+9RcKBNZ/t60xCRgcsihmNUsEjh48xLfNqOrKblUg==",
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^0.1.0"
+ }
+ },
+ "node_modules/promise-each/node_modules/any-promise": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-0.1.0.tgz",
+ "integrity": "sha512-lqzY9o+BbeGHRCOyxQkt/Tgvz0IZhTmQiA+LxQW8wSNpcTbj8K+0cZiSEvbpNZZP9/11Gy7dnLO3GNWUXO4d1g==",
+ "license": "MIT"
+ },
+ "node_modules/prr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+ "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
+ "license": "MIT"
+ },
+ "node_modules/pug": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz",
+ "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==",
+ "license": "MIT",
+ "dependencies": {
+ "pug-code-gen": "^3.0.3",
+ "pug-filters": "^4.0.0",
+ "pug-lexer": "^5.0.1",
+ "pug-linker": "^4.0.0",
+ "pug-load": "^3.0.0",
+ "pug-parser": "^6.0.0",
+ "pug-runtime": "^3.0.1",
+ "pug-strip-comments": "^2.0.0"
+ }
+ },
+ "node_modules/pug-attrs": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz",
+ "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==",
+ "license": "MIT",
+ "dependencies": {
+ "constantinople": "^4.0.1",
+ "js-stringify": "^1.0.2",
+ "pug-runtime": "^3.0.0"
+ }
+ },
+ "node_modules/pug-code-gen": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz",
+ "integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==",
+ "license": "MIT",
+ "dependencies": {
+ "constantinople": "^4.0.1",
+ "doctypes": "^1.1.0",
+ "js-stringify": "^1.0.2",
+ "pug-attrs": "^3.0.0",
+ "pug-error": "^2.1.0",
+ "pug-runtime": "^3.0.1",
+ "void-elements": "^3.1.0",
+ "with": "^7.0.0"
+ }
+ },
+ "node_modules/pug-error": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz",
+ "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==",
+ "license": "MIT"
+ },
+ "node_modules/pug-filters": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz",
+ "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==",
+ "license": "MIT",
+ "dependencies": {
+ "constantinople": "^4.0.1",
+ "jstransformer": "1.0.0",
+ "pug-error": "^2.0.0",
+ "pug-walk": "^2.0.0",
+ "resolve": "^1.15.1"
+ }
+ },
+ "node_modules/pug-lexer": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz",
+ "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==",
+ "license": "MIT",
+ "dependencies": {
+ "character-parser": "^2.2.0",
+ "is-expression": "^4.0.0",
+ "pug-error": "^2.0.0"
+ }
+ },
+ "node_modules/pug-linker": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz",
+ "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==",
+ "license": "MIT",
+ "dependencies": {
+ "pug-error": "^2.0.0",
+ "pug-walk": "^2.0.0"
+ }
+ },
+ "node_modules/pug-load": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz",
+ "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4.1.1",
+ "pug-walk": "^2.0.0"
+ }
+ },
+ "node_modules/pug-parser": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz",
+ "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==",
+ "license": "MIT",
+ "dependencies": {
+ "pug-error": "^2.0.0",
+ "token-stream": "1.0.0"
+ }
+ },
+ "node_modules/pug-runtime": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz",
+ "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==",
+ "license": "MIT"
+ },
+ "node_modules/pug-strip-comments": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz",
+ "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pug-error": "^2.0.0"
+ }
+ },
+ "node_modules/pug-walk": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz",
+ "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==",
+ "license": "MIT"
+ },
+ "node_modules/punycode.js": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
+ "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "~2.0.3"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/readdirp/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/recursive-copy": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/recursive-copy/-/recursive-copy-2.0.14.tgz",
+ "integrity": "sha512-K8WNY8f8naTpfbA+RaXmkaQuD1IeW9EgNEfyGxSqqTQukpVtoOKros9jUqbpEsSw59YOmpd8nCBgtqJZy5nvog==",
+ "license": "ISC",
+ "dependencies": {
+ "errno": "^0.1.2",
+ "graceful-fs": "^4.1.4",
+ "junk": "^1.0.1",
+ "maximatch": "^0.1.0",
+ "mkdirp": "^0.5.1",
+ "pify": "^2.3.0",
+ "promise": "^7.0.1",
+ "rimraf": "^2.7.1",
+ "slash": "^1.0.0"
+ }
+ },
+ "node_modules/recursive-copy/node_modules/errno": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
+ "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
+ "license": "MIT",
+ "dependencies": {
+ "prr": "~1.0.1"
+ },
+ "bin": {
+ "errno": "cli.js"
+ }
+ },
+ "node_modules/recursive-copy/node_modules/junk": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/junk/-/junk-1.0.3.tgz",
+ "integrity": "sha512-3KF80UaaSSxo8jVnRYtMKNGFOoVPBdkkVPsw+Ad0y4oxKXPduS6G6iHkrf69yJVff/VAaYXkV42rtZ7daJxU3w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/recursive-copy/node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/recursive-copy/node_modules/slash": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+ "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz",
+ "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==",
+ "license": "MIT",
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
+ }
+ },
+ "node_modules/regex-utilities": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
+ "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
+ "license": "MIT"
+ },
+ "node_modules/relateurl": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+ "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rss-parser": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.13.0.tgz",
+ "integrity": "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^2.0.3",
+ "xml2js": "^0.5.0"
+ }
+ },
+ "node_modules/rss-parser/node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "license": "BSD-2-Clause",
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/sanitize-html": {
+ "version": "2.17.0",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz",
+ "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==",
+ "license": "MIT",
+ "dependencies": {
+ "deepmerge": "^4.2.2",
+ "escape-string-regexp": "^4.0.0",
+ "htmlparser2": "^8.0.0",
+ "is-plain-object": "^5.0.0",
+ "parse-srcset": "^1.0.2",
+ "postcss": "^8.3.11"
+ }
+ },
+ "node_modules/sanitize-html/node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/sanitize-html/node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/sanitize-html/node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/sanitize-html/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/sanitize-html/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/sanitize-html/node_modules/htmlparser2": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
+ "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1",
+ "entities": "^4.4.0"
+ }
+ },
+ "node_modules/satori": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/satori/-/satori-0.19.2.tgz",
+ "integrity": "sha512-71plFHWcq6WJBM5sf/n0eHOmTBiKLUB/G8du7SmLTTLHKEKrV3TPHGKcEVIoyjnbhnjvu9HhLyF9MATB/zzL7g==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "@shuding/opentype.js": "1.4.0-beta.0",
+ "css-background-parser": "^0.1.0",
+ "css-box-shadow": "1.0.0-3",
+ "css-gradient-parser": "^0.0.17",
+ "css-to-react-native": "^3.0.0",
+ "emoji-regex-xs": "^2.0.1",
+ "escape-html": "^1.0.3",
+ "linebreak": "^1.1.0",
+ "parse-css-color": "^0.2.1",
+ "postcss-value-parser": "^4.2.0",
+ "yoga-layout": "^3.2.1"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/sax": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
+ "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
+ "node_modules/section-matter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
+ "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
+ "license": "MIT",
+ "dependencies": {
+ "extend-shallow": "^2.0.1",
+ "kind-of": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/sharp": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
+ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.33.5",
+ "@img/sharp-darwin-x64": "0.33.5",
+ "@img/sharp-libvips-darwin-arm64": "1.0.4",
+ "@img/sharp-libvips-darwin-x64": "1.0.4",
+ "@img/sharp-libvips-linux-arm": "1.0.5",
+ "@img/sharp-libvips-linux-arm64": "1.0.4",
+ "@img/sharp-libvips-linux-s390x": "1.0.4",
+ "@img/sharp-libvips-linux-x64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+ "@img/sharp-linux-arm": "0.33.5",
+ "@img/sharp-linux-arm64": "0.33.5",
+ "@img/sharp-linux-s390x": "0.33.5",
+ "@img/sharp-linux-x64": "0.33.5",
+ "@img/sharp-linuxmusl-arm64": "0.33.5",
+ "@img/sharp-linuxmusl-x64": "0.33.5",
+ "@img/sharp-wasm32": "0.33.5",
+ "@img/sharp-win32-ia32": "0.33.5",
+ "@img/sharp-win32-x64": "0.33.5"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
+ "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/sitemap": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-6.4.0.tgz",
+ "integrity": "sha512-DoPKNc2/apQZTUnfiOONWctwq7s6dZVspxAZe2VPMNtoqNq7HgXRvlRnbIpKjf+8+piQdWncwcy+YhhTGY5USQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^14.14.28",
+ "@types/sax": "^1.2.1",
+ "arg": "^5.0.0",
+ "sax": "^1.2.4"
+ },
+ "bin": {
+ "sitemap": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=10.3.0",
+ "npm": ">=5.6.0"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/slugify": {
+ "version": "1.6.6",
+ "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
+ "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/ssri": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-11.0.0.tgz",
+ "integrity": "sha512-aZpUoMN/Jj2MqA4vMCeiKGnc/8SuSyHbGSBdgFbZxP8OJGF/lFkIuElzPxsN0q8TQQ+prw3P4EDfB3TBHHgfXw==",
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.3"
+ },
+ "engines": {
+ "node": "^16.14.0 || >=18.0.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/string-replace-async": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/string-replace-async/-/string-replace-async-3.0.2.tgz",
+ "integrity": "sha512-s6hDtXJ7FKyRap/amefqrOMpkEQvxUDueyvJygQeHxCK5Za90dOMgdibCCrPdfdAYAkr8imrZ1PPXW7DOf0RzQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string.prototype.codepointat": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
+ "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==",
+ "license": "MIT"
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom-string": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
+ "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.46.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz",
+ "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.15.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/terser/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT"
+ },
+ "node_modules/thenby": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz",
+ "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tiny-inflate": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
+ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tlds": {
+ "version": "1.261.0",
+ "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz",
+ "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==",
+ "license": "MIT",
+ "bin": {
+ "tlds": "bin.js"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/token-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz",
+ "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==",
+ "license": "MIT"
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+ "license": "MIT"
+ },
+ "node_modules/uglify-js": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/uint8arrays": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz",
+ "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
+ "license": "MIT",
+ "dependencies": {
+ "multiformats": "^9.4.2"
+ }
+ },
+ "node_modules/unfurl.js": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/unfurl.js/-/unfurl.js-6.4.0.tgz",
+ "integrity": "sha512-DogJFWPkOWMcu2xPdpmbcsL+diOOJInD3/jXOv6saX1upnWmMK8ndAtDWUfJkuInqNI9yzADud4ID9T+9UeWCw==",
+ "license": "ISC",
+ "dependencies": {
+ "debug": "^3.2.7",
+ "he": "^1.2.0",
+ "htmlparser2": "^8.0.1",
+ "iconv-lite": "^0.4.24",
+ "node-fetch": "^2.6.7"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/htmlparser2": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
+ "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1",
+ "entities": "^4.4.0"
+ }
+ },
+ "node_modules/unicode-segmenter": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz",
+ "integrity": "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==",
+ "license": "MIT"
+ },
+ "node_modules/unicode-trie": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
+ "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^0.2.5",
+ "tiny-inflate": "^1.0.0"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/urlpattern-polyfill": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz",
+ "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==",
+ "license": "MIT"
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/with": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz",
+ "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.9.6",
+ "@babel/types": "^7.9.6",
+ "assert-never": "^1.2.1",
+ "babel-walk": "3.0.0-canary-5"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/ws": {
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml2js": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
+ "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
+ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yoga-layout": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
+ "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
+ "license": "MIT"
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/theme/package.json b/theme/package.json
new file mode 100644
index 0000000..67f95e5
--- /dev/null
+++ b/theme/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "indiekit-eleventy-theme",
+ "version": "1.0.0",
+ "description": "Eleventy theme for Indiekit — IndieWeb-ready personal website",
+ "type": "module",
+ "scripts": {
+ "build": "eleventy",
+ "dev": "eleventy --serve --watch",
+ "build:css": "postcss css/tailwind.css -o css/style.css"
+ },
+ "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",
+ "@11ty/is-land": "^5.0.1",
+ "@alpinejs/collapse": "^3.15.8",
+ "@atproto/api": "^0.12.0",
+ "@chrisburnell/eleventy-cache-webmentions": "^2.2.7",
+ "@fontsource/inter": "^5.2.8",
+ "@quasibit/eleventy-plugin-sitemap": "^2.2.0",
+ "@resvg/resvg-js": "^2.6.2",
+ "@rknightuk/eleventy-plugin-post-graph": "^1.0.8",
+ "@zachleat/filter-container": "^4.0.0",
+ "@zachleat/table-saw": "^1.0.7",
+ "alpinejs": "^3.15.8",
+ "eleventy-plugin-embed-everything": "^1.21.0",
+ "gray-matter": "^4.0.3",
+ "html-minifier-terser": "^7.0.0",
+ "lite-youtube-embed": "^0.3.2",
+ "markdown-it": "^14.0.0",
+ "markdown-it-anchor": "^9.2.0",
+ "pagefind": "^1.3.0",
+ "rss-parser": "^3.13.0",
+ "satori": "^0.19.2",
+ "unfurl.js": "^6.4.0"
+ },
+ "devDependencies": {
+ "@tailwindcss/typography": "^0.5.0",
+ "autoprefixer": "^10.4.0",
+ "postcss": "^8.4.0",
+ "postcss-cli": "^11.0.0",
+ "tailwindcss": "^3.4.0"
+ }
+}
diff --git a/theme/podroll.njk b/theme/podroll.njk
new file mode 100644
index 0000000..d1e8f54
--- /dev/null
+++ b/theme/podroll.njk
@@ -0,0 +1,386 @@
+---
+layout: layouts/base.njk
+title: Podroll
+permalink: /podroll/
+---
+
+
+
diff --git a/theme/postcss.config.js b/theme/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/theme/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/theme/readlater.njk b/theme/readlater.njk
new file mode 100644
index 0000000..01de949
--- /dev/null
+++ b/theme/readlater.njk
@@ -0,0 +1,250 @@
+---
+layout: layouts/base.njk
+title: Reading List
+permalink: /readlater/
+---
+
+
+
diff --git a/theme/replies.njk b/theme/replies.njk
new file mode 100644
index 0000000..48666de
--- /dev/null
+++ b/theme/replies.njk
@@ -0,0 +1,139 @@
+---
+layout: layouts/base.njk
+title: Replies
+withSidebar: true
+pagination:
+ data: collections.replies
+ size: 20
+ alias: paginatedReplies
+ generatePageOnEmptyData: true
+permalink: "replies/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
+---
+
+
+
Replies
+ {% set sparklineSvg = collections.replies | postingFrequency %}
+ {% if sparklineSvg %}
+ {{ sparklineSvg | safe }}
+ {% endif %}
+
+
+ My responses to posts across the web.
+ ({{ collections.replies.length }} total)
+
+
+ {% if paginatedReplies.length > 0 %}
+
+ {% for post in paginatedReplies %}
+
+
+
+ {% endfor %}
+
+
+ {# Pagination controls #}
+ {% if pagination.pages.length > 1 %}
+
+ {% endif %}
+
+ {% else %}
+ {% set postType = "reply" %}
+ {% include "components/empty-collection.njk" %}
+ {% endif %}
+
diff --git a/theme/reposts.njk b/theme/reposts.njk
new file mode 100644
index 0000000..45bdf0b
--- /dev/null
+++ b/theme/reposts.njk
@@ -0,0 +1,115 @@
+---
+layout: layouts/base.njk
+title: Reposts
+withSidebar: true
+pagination:
+ data: collections.reposts
+ size: 20
+ alias: paginatedReposts
+ generatePageOnEmptyData: true
+permalink: "reposts/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
+---
+
+
+
Reposts
+ {% set sparklineSvg = collections.reposts | postingFrequency %}
+ {% if sparklineSvg %}
+ {{ sparklineSvg | safe }}
+ {% endif %}
+
+
+ Content I've shared from others.
+ ({{ collections.reposts.length }} total)
+
+
+ {% if paginatedReposts.length > 0 %}
+
+ {% for post in paginatedReposts %}
+
+
+
+ {% endfor %}
+
+
+ {# Pagination controls #}
+ {% if pagination.pages.length > 1 %}
+
+ {% endif %}
+
+ {% else %}
+ {% set postType = "repost" %}
+ {% include "components/empty-collection.njk" %}
+ {% endif %}
+
diff --git a/theme/search.njk b/theme/search.njk
new file mode 100644
index 0000000..73d065c
--- /dev/null
+++ b/theme/search.njk
@@ -0,0 +1,39 @@
+---
+layout: layouts/base.njk
+title: Search
+permalink: /search/
+eleventyExcludeFromCollections: true
+pagefindIgnore: true
+---
+
+
+
+
+
+
Search requires JavaScript to be enabled. Please enable JavaScript in your browser settings to use the search feature.
+
Alternatively, you can browse content via the blog archive or categories .
+
+
+
+
diff --git a/theme/slashes.njk b/theme/slashes.njk
new file mode 100644
index 0000000..8c2a14c
--- /dev/null
+++ b/theme/slashes.njk
@@ -0,0 +1,153 @@
+---
+layout: layouts/base.njk
+title: Slash Pages
+withSidebar: true
+permalink: /slashes/
+eleventyImport:
+ collections:
+ - pages
+---
+
+
Slash Pages
+
+ Root-level pages on this site. Inspired by slashpages.net .
+
+
+ {# Dynamic pages (created via Indiekit) #}
+
+
Pages
+ {% if collections.pages.length > 0 %}
+
+ {% for page in collections.pages %}
+
+
+ {% if page.data.summary %}
+ {{ page.data.summary }}
+ {% elif page.data.title %}
+ {{ page.data.title }}
+ {% endif %}
+ {% if page.data.updated %}
+
+ Updated: {{ page.data.updated | dateDisplay }}
+
+ {% endif %}
+
+ {% endfor %}
+
+ {% else %}
+
+
+ No root pages yet. To create pages like /now, /uses, or /colophon, you need two plugins:
+
+
+ @rmdes/indiekit-post-type-page — registers the "page" post type with Indiekit, using root-level URL paths (/slug instead of /type/YYYY/MM/DD/slug)
+ @rmdes/indiekit-endpoint-posts — publishing UI that sends the h=page Micropub type so pages are created at root level
+
+
+ Once both plugins are installed, "Page" appears as a post type in the Indiekit admin UI, and pages are published directly at /slug.
+
+
+ {% endif %}
+
+
+ {# Activity pages — only show when their plugin backend is available #}
+ {% set hasActivityPages = (funkwhaleActivity and funkwhaleActivity.source == "indiekit") or
+ (githubActivity and githubActivity.source != "error") or
+ (lastfmActivity and lastfmActivity.source == "indiekit") or
+ (newsActivity and newsActivity.source == "indiekit") or
+ (youtubeChannel and youtubeChannel.source == "indiekit") or
+ (blogrollStatus and blogrollStatus.source == "indiekit") or
+ (podrollStatus and podrollStatus.source == "indiekit") %}
+ {% if hasActivityPages %}
+
+
Activity Feeds
+
+ {% if blogrollStatus and blogrollStatus.source == "indiekit" %}
+
+
+ Sites I follow
+
+ {% endif %}
+ {% if funkwhaleActivity and funkwhaleActivity.source == "indiekit" %}
+
+
+ Funkwhale activity
+
+ {% endif %}
+ {% if githubActivity and githubActivity.source != "error" %}
+
+
+ GitHub activity
+
+ {% endif %}
+ {% if lastfmActivity and lastfmActivity.source == "indiekit" %}
+
+
+ Last.fm scrobbles
+
+ {% endif %}
+ {% if newsActivity and newsActivity.source == "indiekit" %}
+
+
+ RSS feed aggregator
+
+ {% endif %}
+ {% if podrollStatus and podrollStatus.source == "indiekit" %}
+
+
+ Podcasts I listen to
+
+ {% endif %}
+ {% if youtubeChannel and youtubeChannel.source == "indiekit" %}
+
+
+ YouTube channel
+
+ {% endif %}
+
+
+ {% endif %}
+
+ {# Inspiration section #}
+
+
Want more slash pages?
+
+ Check out slashpages.net
+ for inspiration on pages like /now, /uses, /colophon, /blogroll, and more.
+
+
+
diff --git a/theme/starred.njk b/theme/starred.njk
new file mode 100644
index 0000000..08eb83b
--- /dev/null
+++ b/theme/starred.njk
@@ -0,0 +1,435 @@
+---
+layout: layouts/base.njk
+title: Starred Repositories
+permalink: /github/starred/
+eleventyExcludeFromCollections: true
+---
+
+
+
+
+ {# Loading state #}
+
+
+
+
Loading starred repositories…
+
+
+
+ {# Error state #}
+
+
+
+
+ {# Main content — shown after loading #}
+
+
+
+ {# ===== TAB BAR ===== #}
+
+
+ {# ===== CONTROLS BAR ===== #}
+
+ {# Search + Sort row #}
+
+ {# Search #}
+
+
+ {# Sort dropdown #}
+
+ Sort: Stars
+ Sort: Recently Starred
+ Sort: Recently Updated
+ Sort: Name
+
+
+
+ {# Filters row #}
+
+ {# Language filter #}
+
+ All Languages
+
+
+
+
+
+ {# Star count filter #}
+
+
+
+
+
+
+ {# Archived toggle #}
+
+
+ Show archived
+
+
+ {# Active filter summary #}
+
+ Clear filters
+
+
+
+
+ {# ===== RESULTS SUMMARY ===== #}
+
+
+
+
+ {# ===== REPO GRID ===== #}
+
+
+
+
+
+
+
+
+
+
+
+ Archived
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {# ===== EMPTY FILTERED STATE ===== #}
+
+
+
No repos match your current filters.
+
Clear all filters
+
+
+
+ {# ===== LOAD MORE ===== #}
+
+
+
+ Load More
+
+
+
+
+
+
+
+
+
+{# Alpine.js component #}
+
diff --git a/theme/tailwind.config.js b/theme/tailwind.config.js
new file mode 100644
index 0000000..3d3477f
--- /dev/null
+++ b/theme/tailwind.config.js
@@ -0,0 +1,97 @@
+import typography from "@tailwindcss/typography";
+
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./**/*.njk",
+ "./**/*.md",
+ "./_includes/**/*.njk",
+ "./content/**/*.md",
+ "./lib/**/*.js",
+ ],
+ darkMode: "class",
+ theme: {
+ extend: {
+ colors: {
+ // Warm stone — surfaces, text, structure
+ surface: {
+ 50: "#faf8f5",
+ 100: "#f4f2ee",
+ 200: "#e8e5df",
+ 300: "#d5d0c8",
+ 400: "#a09a90",
+ 500: "#7a746a",
+ 600: "#5c5750",
+ 700: "#3f3b35",
+ 800: "#2a2722",
+ 900: "#1c1b19",
+ 950: "#0f0e0d",
+ },
+ // Warm amber — default interactive, CTAs, focus rings
+ accent: {
+ 50: "#fffbeb",
+ 100: "#fef3c7",
+ 200: "#fde68a",
+ 300: "#fcd34d",
+ 400: "#fbbf24",
+ 500: "#f59e0b",
+ 600: "#d97706",
+ 700: "#b45309",
+ 800: "#92400e",
+ 900: "#78350f",
+ 950: "#451a03",
+ },
+ // Legacy — kept for compatibility, not used in templates
+ primary: {
+ 50: "#eff6ff",
+ 100: "#dbeafe",
+ 200: "#bfdbfe",
+ 300: "#93c5fd",
+ 400: "#60a5fa",
+ 500: "#3b82f6",
+ 600: "#2563eb",
+ 700: "#1d4ed8",
+ 800: "#1e40af",
+ 900: "#1e3a8a",
+ 950: "#172554",
+ },
+ },
+ fontFamily: {
+ sans: [
+ '"Inter"',
+ "system-ui",
+ "-apple-system",
+ "BlinkMacSystemFont",
+ "Segoe UI",
+ "Roboto",
+ "sans-serif",
+ ],
+ mono: [
+ "ui-monospace",
+ "SF Mono",
+ "Monaco",
+ "Cascadia Code",
+ "monospace",
+ ],
+ },
+ maxWidth: {
+ content: "720px",
+ wide: "1200px",
+ },
+ typography: (theme) => ({
+ DEFAULT: {
+ css: {
+ "--tw-prose-links": theme("colors.accent.600"),
+ maxWidth: "none",
+ },
+ },
+ invert: {
+ css: {
+ "--tw-prose-links": theme("colors.accent.400"),
+ },
+ },
+ }),
+ },
+ },
+ plugins: [typography],
+};
diff --git a/theme/webmention-debug.njk b/theme/webmention-debug.njk
new file mode 100644
index 0000000..172a933
--- /dev/null
+++ b/theme/webmention-debug.njk
@@ -0,0 +1,124 @@
+---
+layout: layouts/base.njk
+title: Webmention Debug
+permalink: /debug/webmentions/
+eleventyExcludeFromCollections: true
+pagefindIgnore: true
+---
+
+
+
+ {# Summary #}
+
+ Summary
+
+
+
Total URL Mappings
+ {{ urlAliases.aliases | length }}
+
+
+
Total Webmentions
+ {{ webmentions | length }}
+
+
+
+
+ {# Recent Posts with Webmentions #}
+
+ Posts with Webmentions
+
+
+
+
+ Current URL
+ Legacy URLs
+ Webmentions
+
+
+
+ {% for post in collections.posts | head(50) %}
+ {% set allMentions = webmentions | webmentionsForUrl(post.url, urlAliases, conversationMentions) %}
+ {% set legacyUrls = urlAliases.getOldUrls(post.url) %}
+ {% if allMentions.length > 0 or legacyUrls.length > 0 %}
+
+
+
+ {{ post.url }}
+
+
+
+ {% if legacyUrls.length %}
+ {% for legacyUrl in legacyUrls %}
+ {{ legacyUrl }}
+ {% endfor %}
+ {% else %}
+ -
+ {% endif %}
+
+
+ {% if allMentions.length %}
+
+ {{ allMentions.length }}
+
+ {% else %}
+ 0
+ {% endif %}
+
+
+ {% endif %}
+ {% endfor %}
+
+
+
+
+
+ {# URL Alias Sample #}
+
+ URL Alias Sample (first 20)
+
+
+
+
+ New URL
+ Old URL(s)
+
+
+
+ {% set aliasEntries = urlAliases.aliases | dictsort %}
+ {% for newUrl, oldUrls in aliasEntries | head(20) %}
+
+ {{ newUrl }}
+
+ {% for oldUrl in oldUrls %}
+ {{ oldUrl }}
+ {% endfor %}
+
+
+ {% endfor %}
+
+
+
+
+
+ {# Raw Webmention Targets (for debugging) #}
+
+ Recent Webmention Targets
+
+ Shows which URLs webmentions were sent to (useful for verifying legacy URL matches).
+
+
+ {% for wm in webmentions | head(30) %}
+
+ {{ wm["wm-property"] }}:
+ {{ wm["wm-target"] }}
+
+ {% endfor %}
+
+
+
diff --git a/theme/youtube.njk b/theme/youtube.njk
new file mode 100644
index 0000000..28cfb2c
--- /dev/null
+++ b/theme/youtube.njk
@@ -0,0 +1,262 @@
+---
+layout: layouts/base.njk
+title: YouTube Channels
+permalink: /youtube/
+withSidebar: true
+---
+
+
+
+ {# Multi-channel tabs #}
+ {% if youtubeChannel.isMultiChannel and youtubeChannel.channels.length > 1 %}
+
+
+ {% for channel in youtubeChannel.channels %}
+
+ {{ channel.configName or channel.title }}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {# Channel sections #}
+ {% for channel in youtubeChannel.channels %}
+
+ {# Channel Header #}
+
+
+ {% if channel.thumbnail %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if channel.customUrl %}
+
{{ channel.customUrl }}
+ {% endif %}
+
+
+
+
+
+ {{ channel.subscriberCountFormatted }} subscribers
+
+
+
+
+
+ {{ channel.videoCountFormatted }} videos
+
+
+
+
+
+
+ {{ channel.viewCountFormatted }} views
+
+
+
+
+ {# Live Status Badge for this channel #}
+ {% set channelLiveStatus = youtubeChannel.liveStatuses | selectattr("channelConfigName", "equalto", channel.configName) | first %}
+
+ {% if channelLiveStatus and channelLiveStatus.isLive %}
+
+
+ LIVE
+
+ {% elif channelLiveStatus and channelLiveStatus.isUpcoming %}
+
+
+
+
+ Upcoming
+
+ {% else %}
+
+ Offline
+
+ {% endif %}
+
+
+
+
+ {# Live Stream Section for this channel #}
+ {% set channelLiveStatus = youtubeChannel.liveStatuses | selectattr("channelConfigName", "equalto", channel.configName) | first %}
+ {% if channelLiveStatus and channelLiveStatus.stream and (channelLiveStatus.isLive or channelLiveStatus.isUpcoming) %}
+
+ {% endif %}
+
+ {# Videos Grid for this channel #}
+
+
+
+
+
+ Latest Videos
+
+
+ {% set channelName = channel.configName or channel.title %}
+ {% set channelVideos = youtubeChannel.videosByChannel[channelName] %}
+ {% if channelVideos and channelVideos.length %}
+
+
+
+ {% else %}
+ No videos available yet.
+ {% endif %}
+
+
+ {% endfor %}
+
+ {# Fallback for no channels #}
+ {% if not youtubeChannel.channels.length %}
+
No YouTube channels configured.
+ {% endif %}
+
+
+ ++ Comments + +
+ +Sign in with your website to comment:
+ +Loading comments...
+ + + +No comments yet. Be the first to share your thoughts!
+ +