feat: Garden dev 0.1

This commit is contained in:
svemagie
2026-03-14 16:53:31 +01:00
parent 3a8c24eb7f
commit 48da3404ea
5 changed files with 204 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
{#
garden-badge.njk — Digital Garden stage indicator
Usage: {% include "components/garden-badge.njk" %}
Requires: gardenStage variable in scope (from post frontmatter)
Renders a coloured pill badge linking to /garden/#<stage>
#}
{% if gardenStage %}
{% set _stageInfo = gardenStage | gardenStageInfo %}
{% if _stageInfo %}
<a href="/garden/#{{ gardenStage }}"
class="garden-badge garden-badge--{{ gardenStage }}"
title="{{ _stageInfo.description }}"
aria-label="Garden stage: {{ _stageInfo.label }}">
<span aria-hidden="true">{{ _stageInfo.emoji }}</span>
<span>{{ _stageInfo.label }}</span>
</a>
{% endif %}
{% endif %}

View File

@@ -43,6 +43,8 @@ withBlogSidebar: true
{% endif %}
</ul>
{% endif %}
{# Digital Garden stage badge #}
{% include "components/garden-badge.njk" %}
</div>
{# Bridgy syndication content - controls what gets posted to social networks #}
@@ -228,6 +230,9 @@ withBlogSidebar: true
<span data-pagefind-filter="ai-text-level">{{ aiTextLevel }}</span>
<span data-pagefind-filter="ai-code-level">{{ aiCodeLevel or "0" }}</span>
<span data-pagefind-filter="ai-used">{% if aiUsed %}yes{% else %}no{% endif %}</span>
{% if gardenStage %}
<span data-pagefind-filter="garden-stage">{{ gardenStage }}</span>
{% endif %}
{% if category %}
{% if category is string %}
<span data-pagefind-filter="category">{{ category }}</span>

View File

@@ -986,3 +986,48 @@ body[data-indiekit-auth="true"] .share-post-btn:hover {
background: #374151;
color: #34d399;
}
/* ============================================================
Digital Garden — stage badges
Used by _includes/components/garden-badge.njk
Stages: plant · cultivate · question · repot · revitalize · revisit
============================================================ */
@layer components {
.garden-badge {
@apply inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium border no-underline transition-colors;
}
.garden-badge--plant {
@apply bg-green-100 text-green-800 border-green-200 hover:bg-green-200
dark:bg-green-900/30 dark:text-green-300 dark:border-green-800 dark:hover:bg-green-900/50;
}
.garden-badge--cultivate {
@apply bg-emerald-100 text-emerald-800 border-emerald-200 hover:bg-emerald-200
dark:bg-emerald-900/30 dark:text-emerald-300 dark:border-emerald-800 dark:hover:bg-emerald-900/50;
}
.garden-badge--question {
@apply bg-yellow-100 text-yellow-800 border-yellow-200 hover:bg-yellow-200
dark:bg-yellow-900/30 dark:text-yellow-300 dark:border-yellow-800 dark:hover:bg-yellow-900/50;
}
.garden-badge--repot {
@apply bg-orange-100 text-orange-800 border-orange-200 hover:bg-orange-200
dark:bg-orange-900/30 dark:text-orange-300 dark:border-orange-800 dark:hover:bg-orange-900/50;
}
.garden-badge--revitalize {
@apply bg-purple-100 text-purple-800 border-purple-200 hover:bg-purple-200
dark:bg-purple-900/30 dark:text-purple-300 dark:border-purple-800 dark:hover:bg-purple-900/50;
}
.garden-badge--revisit {
@apply bg-blue-100 text-blue-800 border-blue-200 hover:bg-blue-200
dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-800 dark:hover:bg-blue-900/50;
}
}
/* Digital Garden index page — post card in garden listing */
@layer components {
.garden-post-card {
@apply p-3 rounded-lg border border-surface-200 dark:border-surface-700
hover:border-surface-300 dark:hover:border-surface-600
hover:bg-surface-50 dark:hover:bg-surface-800/50
transition-colors;
}
}

View File

@@ -927,6 +927,28 @@ export default function (eleventyConfig) {
return posts.filter(isListed);
});
// ── Digital Garden ───────────────────────────────────────────────────────
// Returns display metadata for a garden stage slug.
// Used by garden-badge.njk and garden.njk to render labels + emoji.
// Stages map to Obsidian's #garden/* tag convention:
// #garden/plant → gardenStage: plant (newly planted idea)
// #garden/cultivate → gardenStage: cultivate (actively developing)
// #garden/question → gardenStage: question (open exploration)
// #garden/repot → gardenStage: repot (being restructured)
// #garden/revitalize → gardenStage: revitalize (being refreshed)
// #garden/revisit → gardenStage: revisit (flagged for revisiting)
eleventyConfig.addFilter("gardenStageInfo", (stage) => {
const stages = {
plant: { label: "Seedling", emoji: "🌱", description: "Newly planted idea" },
cultivate: { label: "Growing", emoji: "🌿", description: "Being actively developed" },
question: { label: "Open Question", emoji: "❓", description: "Open for exploration" },
repot: { label: "Repotting", emoji: "🪴", description: "Being restructured" },
revitalize: { label: "Revitalizing", emoji: "✨", description: "Being refreshed and updated" },
revisit: { label: "Revisit", emoji: "🔄", description: "Flagged for revisiting" },
};
return stages[stage] || null;
});
// Collections for different post types
// Note: content path is content/ due to symlink structure
// "posts" shows ALL content types combined
@@ -1093,6 +1115,15 @@ export default function (eleventyConfig) {
.sort((a, b) => b.date - a.date);
});
// Digital Garden — posts with a gardenStage frontmatter property
eleventyConfig.addCollection("gardenPosts", function (collectionApi) {
return collectionApi
.getFilteredByGlob("content/**/*.md")
.filter(isPublished)
.filter((item) => item.data.gardenStage)
.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

105
garden.njk Normal file
View File

@@ -0,0 +1,105 @@
---
layout: layouts/base.njk
title: Digital Garden
withSidebar: true
permalink: /garden/
pagefindIgnore: true
---
<div class="h-feed">
{# ── Header ─────────────────────────────────────────────────────────── #}
<div class="mb-8 sm:mb-10 pb-6 border-b border-surface-200 dark:border-surface-700">
<h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100 mb-3 flex items-center gap-2">
🌱 Digital Garden
</h1>
<p class="text-surface-600 dark:text-surface-400 max-w-2xl">
A growing collection of ideas in various stages of development. Unlike polished blog posts,
garden notes are living documents — planted, cultivated, and sometimes transplanted as my
thinking evolves.
</p>
{# Stage legend #}
<div class="mt-4 flex flex-wrap gap-2">
{% set _allStages = ["plant", "cultivate", "question", "repot", "revitalize", "revisit"] %}
{% for _s in _allStages %}
{% set _info = _s | gardenStageInfo %}
{% if _info %}
<a href="/garden/#{{ _s }}" class="garden-badge garden-badge--{{ _s }}">
<span aria-hidden="true">{{ _info.emoji }}</span>
<span>{{ _info.label }}</span>
</a>
{% endif %}
{% endfor %}
</div>
</div>
{# ── Posts grouped by stage ──────────────────────────────────────────── #}
{% set _stages = ["plant", "cultivate", "question", "repot", "revitalize", "revisit"] %}
{% for _stage in _stages %}
{# Collect posts for this stage #}
{% set _stagePosts = [] %}
{% for post in collections.gardenPosts %}
{% if post.data.gardenStage == _stage %}
{% set _stagePosts = (_stagePosts.push(post), _stagePosts) %}
{% endif %}
{% endfor %}
{% if _stagePosts.length > 0 %}
{% set _stageInfo = _stage | gardenStageInfo %}
<section id="{{ _stage }}" class="mb-10 scroll-mt-20">
<div class="flex items-baseline gap-3 mb-1">
<h2 class="text-xl font-bold text-surface-900 dark:text-surface-100 flex items-center gap-2">
<span>{{ _stageInfo.emoji }}</span>
<span>{{ _stageInfo.label }}</span>
</h2>
<span class="text-sm text-surface-500 dark:text-surface-400">
{{ _stagePosts.length }} note{% if _stagePosts.length != 1 %}s{% endif %}
</span>
</div>
<p class="text-sm text-surface-500 dark:text-surface-400 mb-4 italic">{{ _stageInfo.description }}</p>
<ul class="space-y-3 list-none p-0 m-0">
{% for post in _stagePosts %}
<li class="h-entry garden-post-card">
<div class="flex items-start gap-3">
<div class="flex-1 min-w-0">
<h3 class="font-medium text-surface-900 dark:text-surface-100 mb-0.5">
<a class="u-url p-name hover:text-accent-700 dark:hover:text-accent-300 transition-colors"
href="{{ post.url }}">
{{ post.data.title or (post.templateContent | striptags | truncate(80)) or "Untitled" }}
</a>
</h3>
<div class="flex items-center gap-3 text-xs text-surface-500 dark:text-surface-400">
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
{% set _postType = post.inputPath | replace("./content/", "") %}
{% set _postType = _postType.split("/")[0] %}
<span class="capitalize">{{ _postType }}</span>
</div>
{% if post.data.description %}
<p class="p-summary text-sm text-surface-600 dark:text-surface-400 mt-1 line-clamp-2">
{{ post.data.description }}
</p>
{% endif %}
</div>
</div>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
{% endfor %}
{# ── Empty state ─────────────────────────────────────────────────────── #}
{% if not collections.gardenPosts or collections.gardenPosts.length == 0 %}
<p class="text-surface-600 dark:text-surface-400">
The garden is being prepared. Posts with a <code>gardenStage</code> frontmatter property
will appear here once published.
</p>
{% endif %}
</div>