Commit Graph

77 Commits

Author SHA1 Message Date
svemagie
89fbd222f2 feat: add Umami reader-source tracking tags
Classify visitors by referrer into segments (direct, search, fediverse,
bluesky, indieweb, web) via a custom umami.track('reader-source') event.
Add noscript pixels to RSS/JSON feeds so feed-reader activity appears
under virtual /rss/* paths in Umami.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 09:04:34 +01:00
svemagie
5e5c88c825 feat: umami tracking 2026-03-25 08:22:54 +01:00
svemagie
4cc002795f feat(been): cluster duplicate checkins with Leaflet.markercluster 2026-03-20 22:07:37 +01:00
svemagie
0755564ef6 feat: conditionally load Leaflet on pages with leafletMap: true 2026-03-20 21:51:54 +01:00
svemagie
a166af2306 chore: sync upstream — performance, webmentions v2, OG v3
- _data: switch to cachedFetch wrapper (10s timeout + 4h watch cache)
- js/webmentions.js: owner reply threading, platform provenance badges, DOM dedup, Micropub reply support
- js/comments.js: owner detection, reply system, Alpine.store integration
- _includes/components/webmentions.njk: data-wm-* attrs, provenance badge slots, reply buttons
- _includes/components/comments.njk: owner-aware comment form, threaded replies
- widgets/toc.njk: Alpine.js tocScanner upgrade (replaces is-land/inline-JS)
- lib/og.js + og-cli.js: OG card v3 (light theme, avatar, batched spawn, DESIGN_VERSION=3)
- eleventy.config.js: hasOgImage cache, memoized date filters, batched OG/unfurl, post-build GC, YouTube check opt
- base.njk: Inter font preloads + toc-scanner.js script
- critical.css: font-face declarations (font-display:optional)
- tailwind.css: font-display swap→optional
- tailwind.config.js: prose link colors -700→-600
- Color design system: accent-700/300 → accent-600/400 across components

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 23:56:56 +01:00
svemagie
ba1dba4661 feat: integrate eleventy-plugin-mermaid for diagram support
Adds @kevingimbel/eleventy-plugin-mermaid so mermaid fenced code blocks
render as interactive diagrams in the browser.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 13:01:01 +01:00
svemagie
e8ba3b9ae6 feat: nested tags (Obsidian-style) for categories system
Adds hierarchical tag support using "/" separator (e.g. "tech/programming/js").
- New filters: nestedSlugify, categoryMatches, categoryBreadcrumb,
  categoryGroupByRoot, categoryDirectChildren
- categories collection auto-generates ancestor pages for nested tags
- categories.njk: breadcrumb nav, sub-tags section, ancestor-aware post matching
- categories-index.njk: grouped tree view (root + indented children)
- categories widget: shows root tags only with child count badge
- All category links updated from slugify → nestedSlugify (backward-compatible)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 10:56:34 +01:00
svemagie
43101e622a feat: add /garden to nav, evergreen stage, remove /where from nav
- Add /garden as first item in Pages dropdown (desktop + mobile)
- Remove /where from nav dropdown
- Add evergreen (🌳) garden stage to filter, CSS badge, and garden page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 08:51:01 +01:00
svemagie
f8bceb9e20 small visual fix 2026-03-14 20:55:05 +01:00
svemagie
558c125a49 footer: remove Eleventy from Powered by 2026-03-14 20:54:00 +01:00
svemagie
aca2e97be2 footer: add coffee icon after Eleventy in Powered by 2026-03-14 20:51:51 +01:00
svemagie
31d2ec0e4e chore: opt out of fed.brid.gy (meta tag + nginx header instructions) 2026-03-14 19:45:26 +01:00
svemagie
7f4e8c93e2 fix(a11y): fix WCAG AA contrast and WCAG 2.5.3 failures from third report
- css/tailwind.css: upgrade nav/mobile-nav/theme-toggle text from
  surface-600 (fails 4.5:1) to surface-700 across .site-nav, .nav-dropdown-menu,
  .menu-toggle, .mobile-nav, .mobile-nav-toggle, .theme-toggle, .mobile-theme-toggle
- post.njk: fix AI disclosure details and 'Also on:' span to surface-700
- webmentions.njk: fix summary to surface-700; fix Send button from
  bg-accent-600 (3.18:1) to bg-accent-700 (4.95:1)
- h-card.njk: fix p-adr address and PGP key link to surface-700
- base.njk: fix mobile theme toggle aria-label per WCAG 2.5.3
- post-categories.njk: fix category pills text-accent-700 to text-accent-800
- post-navigation.njk: fix no-OG fallback Previous/Next labels to surface-700

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 16:13:06 +01:00
svemagie
d2fa2fb809 fix(a11y): improve colour contrast and fix semantic heading elements
- Bump all post-type label colours from -600 to -700 (light) and
  -400 to -300 (dark) in blog.njk to meet WCAG AA/AAA contrast
  ratios (green/teal/sky labels were failing AA at ~3-4:1)
- Replace four <p role="heading" aria-level="2"> in footer with
  real <h2> elements for robust screen-reader support
- Bump footer link text from surface-600 to surface-700 (light)
  and surface-400 to surface-300 (dark)
- Update hero.njk accent job-title and "Read more" link from
  accent-600 to accent-700 / accent-400 to accent-300
- Update Tailwind typography prose link token from accent-600/400
  to accent-700/300
- Tighten .p-category and .post-meta base styles in tailwind.css
  (surface-600→700 light, surface-300→200 / surface-400→300)
- Bump hashtag link colours in tailwind.css (accent-600→700)
- Fix sparkline colour in notes.njk (teal-600→700 / teal-400→300)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 15:28:43 +01:00
svemagie
d9494a798b fix: Dashboard link 2026-03-14 15:02:54 +01:00
svemagie
c087742237 fix: replace broken /dashboard links with / (Indiekit admin home)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 07:03:03 +01:00
svemagie
0e66579f36 feat(where): add OwnYourSwarm checkins page and metadata 2026-03-08 15:27:56 +01:00
Ricardo
6ff40c8317 perf: address PageSpeed Insights issues (CLS, contrast, touch targets, JS minification)
- Reserve sidebar min-height on desktop to prevent CLS from Alpine.js hydration
- Defer lite-yt-embed.css with media="print" onload pattern
- Add terser JS minification in eleventy.after build hook
- Increase touch target sizing for category pills, facepile avatars, nav items
- Fix text-surface-400 contrast ratio (3.05:1 → 6.23:1) across 20 instances

Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
2026-03-07 20:13:45 +01:00
Ricardo
1026d728af a11y: fix all remaining WCAG 2.1 AA issues from audit round 2
- Focus traps for fediverse modal and lightbox dialogs (C3, C4)
- Search widget input label (C5)
- Blogroll widget tab ARIA semantics (C6)
- Footer social links "opens in new tab" warning (S5)
- Reply context aria-label on aside (S8)
- Photo alt text fallback includes post title (S10)
- Post categories use list markup (M3)
- Funkwhale now-playing bars aria-hidden (M7)
- TOC uses static Tailwind classes instead of dynamic (M9)
- Footer headings use proper aria heading roles (M15)
- Header anchor opacity increased to 1 for contrast (M18)
- Custom HTML widgets labeled as regions (M19)
- Empty collection placeholder role=status (M22)
- GitHub widget loading state announced (N5)
- Subscribe icon contrast improved (m1)
- All Permalink links have aria-label with post context (m3)
- Podroll audio element aria-label (m4)
- Obfuscated email link aria-label (m6)
- Fediverse follow button uses aria-label (M10)

Score: 53.6% → 92.9% (26/28 WCAG criteria passing)

Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
2026-03-07 19:34:25 +01:00
Ricardo
e236b4bf65 a11y: comprehensive WCAG 2.1 Level AA accessibility audit
- Add skip-to-main-content link and main content ID target
- Add prefers-reduced-motion media queries for all animations
- Enhance visible focus indicators (2px offset, high-contrast ring)
- Replace ~160 text-surface-500 instances with text-surface-600/dark:text-surface-400
  for 4.5:1+ contrast ratio compliance
- Add aria-hidden="true" to ~30+ decorative SVG icons across sidebars/widgets
- Convert facepile containers from div to semantic ul/li with role="list"
- Add aria-label to icon-only buttons (share, sort controls)
- Add sr-only labels to form inputs (webmention, search)
- Add aria-live="polite" to dynamically loaded webmentions
- Add aria-label with relative+absolute date to time-difference component
- Add keyboard handlers (Enter/Space) to custom interactive elements
- Add aria-label to nav landmarks (table of contents)
- Fix modal focus trap and dialog accessibility
- Fix lightbox keyboard navigation and screen reader announcements

Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
2026-03-07 18:58:08 +01:00
Ricardo
15b88b7087 feat: add skeleton loader to prevent FOUC during CSS load
Replace unstyled content flash with pulsing gray placeholder shapes
while the deferred Tailwind stylesheet loads. Uses a 'loading' class
on <html> that critical CSS uses to show skeleton / hide content,
removed by the stylesheet's onload handler.

Includes noscript fallback to bypass skeleton when JS is disabled.

Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
2026-03-07 16:56:59 +01:00
Ricardo
596334ab38 feat: redesign navigation - curated header, updated footer, comprehensive /slashes/
Replace the 22-item "/" dropdown with a curated "Pages" dropdown (Blogroll,
Podroll, News, All Pages). Add /now to header nav. Move CV and Digest to
footer only. Add auth-gated Dashboard to footer Navigate column. Remove
Interactions from footer Content column.

Refactor /slashes/ into a comprehensive site map with three sections: Pages
(dynamic CMS pages), Activity Feeds (plugin pages), and Site Pages (theme
.njk pages like /graph, /changelog, /featured, /starred, etc.).

Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
2026-03-06 14:05:22 +01:00
Ricardo
2ca3e047a4 feat: UI overhaul — accent color, Inter font, widget icons, diversified colors
- Add teal accent color scale and activate Inter font via @font-face
- Neutralize nav/footer hovers from primary blue to surface neutrals
- Apply accent color to hero subtitle, FAB, CTA buttons, card hovers
- Fix reply post-type color from generic primary to distinctive sky blue
- Create centralized icon macro (icon.njk) with 24 reusable SVG icons
- Add per-widget-type icons and colored left-accent borders to all sidebars
- Update .p-category tags from blue to neutral surface with border
- Diversify color vocabulary: red (likes), amber (bookmarks/blogroll),
  green (reposts), purple (funkwhale), sky (replies), orange (subscribe)

Confab-Link: http://localhost:8080/sessions/bd3f7012-c703-47e9-bfe2-2ad04ce1842d
2026-03-04 11:21:39 +01:00
Ricardo
6cfb92818e feat: include share-post.js in base layout 2026-02-28 08:21:05 +01:00
Ricardo
4c8c44a49e feat: add save-for-later buttons to frontend pages
Add shared save-later.js module and per-item save buttons to
blogroll, podroll, listening, and news pages. Buttons are hidden
by default and only visible when logged in. Posts to the readlater
plugin API at /readlater/save.
2026-02-27 16:17:16 +01:00
Ricardo
e2c40468b6 feat: add fullscreen lightbox for article images
Alpine.js component that lets visitors click any image inside
article content to view it fullscreen with keyboard navigation
(arrow keys, Escape to close) and prev/next buttons.
2026-02-27 10:14:35 +01:00
Ricardo
581c585677 feat: add digest feed discovery link and navigation items 2026-02-26 08:27:57 +01:00
Ricardo
de043020ac feat: add RSS per-category feed template, discovery links, and WebSub notifications
- Create category-feed.njk (RSS 2.0 pagination template)
- Add conditional <link rel="alternate"> tags for category pages in base.njk
- Extend WebSub hub notifications to include per-category feed URLs
2026-02-24 22:45:14 +01:00
Ricardo
4c2828f6fe fix: remove contentImage from OG meta to prevent sidebar image leaking
The extractFirstImage filter picks up <img> tags from the full rendered
page content, including sidebar widgets (like recent post thumbnails).
This caused og:image to reference sidebar OG images from OTHER posts
instead of falling through to the __OG_IMAGE_PLACEHOLDER__ that the
og-fix transform resolves from outputPath.

Only ogPhoto and image (from frontmatter) are now used as explicit
image sources. All other cases use the placeholder resolved by the
og-fix transform.
2026-02-24 20:52:07 +01:00
Ricardo
e56e2c67a5 fix: use Eleventy transform to resolve OG images from outputPath
The Eleventy 3.x parallel rendering race condition (#3183) makes
page.url unreliable in templates — it changes between lines during
concurrent processing. All previous approaches (eleventyComputed,
capturing page.url early with {% set %}) failed because the page
object is shared and mutated by parallel renders.

The transform approach works because outputPath is passed as a
function parameter (not read from a shared object) and IS correct
since files are written to the right location. The transform:

- Derives the OG slug from outputPath pattern matching
- Replaces __OG_IMAGE_PLACEHOLDER__ with the correct OG image URL
- Replaces __TWITTER_CARD_PLACEHOLDER__ with the correct card type
- Fixes og:url and canonical URL from outputPath
2026-02-24 20:31:24 +01:00
Ricardo
ee068c7e5c fix: use _pageUrl for homepage check (consistency with race condition fix) 2026-02-24 20:25:46 +01:00
Ricardo
42a5c67896 feat: add Markdown for Agents — serve clean Markdown to AI agents
Generate index.md alongside index.html for /articles/ at build time.
Agents can access clean Markdown via .md URL extension or Accept:
text/markdown content negotiation. Includes configurable content-signal
policy (ai-train, search, ai-input) and a master on/off toggle via
MARKDOWN_AGENTS_ENABLED env var.
2026-02-24 20:24:06 +01:00
Ricardo
0d439966f7 fix: guard page.url for falsy values in ogSlug computation
Pages with permalink:false (like about.njk) have page.url as false,
which crashes inline string operations. Use the ogSlug filter with
(page.url or "") guard to handle falsy values safely. Also removes
debug comment from previous debugging session.
2026-02-24 19:57:10 +01:00
Ricardo
520bc5f582 debug: inline ogSlug computation + debug comment to diagnose race condition
Computes ogSlug using inline Nunjucks string ops instead of filter call.
Adds HTML debug comment showing page.url, permalink, and computed ogSlug
to diagnose Eleventy 3.x parallel rendering race condition.
2026-02-24 19:39:59 +01:00
Ricardo
ad8af6f027 fix: use page.url instead of permalink for ogSlug in templates
permalink is set by eleventyComputed which cross-contaminates return
values across pages during Eleventy 3.x parallel rendering. page.url
is set by Eleventy's internal pipeline and is correct in templates
(verified via og:url meta tag which always shows the right URL).
2026-02-24 18:54:39 +01:00
Ricardo
7110ba3879 fix: compute ogSlug from permalink in template to avoid Eleventy 3.x race condition
Both page.url AND page.inputPath are unreliable in eleventyComputed due to
Eleventy 3.x parallel rendering (issue #3183). They return values from OTHER
pages being processed concurrently, causing og:image meta tags to reference
wrong OG images.

Fix: compute ogSlug directly in base.njk from the permalink data value using
existing Nunjucks filters (ogSlug, hasOgImage). permalink comes from frontmatter
(per-file data) and is immune to cross-page contamination.
2026-02-24 18:33:46 +01:00
Ricardo
910889cde8 fix: remove trailing slash from site.url to prevent double-slash URLs
site.url had a trailing slash (added for Mastodon rel=me verification),
which caused double slashes in all URL constructions like
{{ site.url }}/auth → https://rmendes.net//auth

This broke IndieAuth login — indielogin.com read the authorization_endpoint
link tag with //auth and redirected users there, which 404'd in nginx.

Split into site.url (no slash, for URL construction) and site.me /
site.author.url (with slash, for Mastodon rel=me strict matching).

Also fixed twitter:image meta tags to use smart slash logic matching
the og:image pattern (check if path starts with / before prepending one).
2026-02-24 16:18:52 +01:00
Ricardo
5d8222e5ae fix: vendor Alpine.js and lite-youtube-embed locally instead of CDN
jsdelivr CDN outages (503) break Alpine.js loading, making all
interactive components non-functional. Bundle vendor JS/CSS locally
to eliminate external CDN dependency.
2026-02-22 16:24:43 +01:00
Ricardo
8597856589 feat: add fediverse remote interaction button and syndication platform buttons
Add a Fediverse button to the "Also on" footer for posts syndicated via
self-hosted ActivityPub. Clicking it redirects users to their own instance
via authorize_interaction so they can like/boost/reply natively. Instance
is stored in localStorage for repeat visits, with a modal for first-time
entry and Shift+click to change.

Also adds branded syndication buttons for LinkedIn and IndieNews, and
replaces the heuristic Mastodon URL detection with exact matching against
the configured MASTODON_INSTANCE.
2026-02-22 15:56:01 +01:00
Ricardo
f3268ed3f9 fix: load comments.js before Alpine in head (correct defer order)
Alpine CDN uses queueMicrotask to auto-start, which fires between
defer scripts. comments.js must execute before Alpine so its
alpine:init listener is registered before Alpine.start() runs.

This is the Alpine-documented pattern: "Include [component scripts]
before Alpine's core JS file."
2026-02-21 22:46:43 +01:00
rmdes
fa7bfb26ea feat: add comment system components and recent comments widget
- Comment area on post pages (IndieAuth sign-in, submit, display)
- Alpine.js client-side component for auth flow and comment CRUD
- Recent comments sidebar widget with build-time data fetching
- Include comments.js in base layout, comments.njk before webmentions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 21:56:15 +01:00
Ricardo
656a70eb0e feat: graceful no-JS fallback and noscript handling
- Add <noscript><style> in base.njk that unhides x-cloak/x-show content,
  hides FAB and tab buttons when JS is disabled (content stacks instead)
- Add noscript message on search page with links to blog/categories
- Add noscript banner on interactions page explaining inbound tab needs JS
2026-02-19 17:35:21 +01:00
Ricardo
cb68b7be00 fix: add rel=nofollow to FAB admin links
AI crawlers (GPTBot) were following edit/create links in the static
HTML, flooding the server with /posts/edit and /session/login requests.
Adding nofollow tells well-behaved crawlers to skip these admin-only links.
2026-02-18 22:17:38 +01:00
Ricardo
2ebe63ffff fix: use eleventyComputed for OG slug to avoid Nunjucks race condition
Eleventy 3.x renders Nunjucks templates in parallel, causing page.url
to return wrong values in {% set %} tags. This caused OG images to be
mismatched between pages (e.g., bookmark showed note's OG image).

Move ogSlug and hasOgImage computation to eleventyComputed, which runs
during the sequential data cascade phase before parallel rendering.
The computed values are then available as plain template variables.

Refs: https://github.com/11ty/eleventy/issues/3183
2026-02-18 17:17:30 +01:00
Ricardo
8d800e2c28 fix: derive OG slug from page.url to avoid Nunjucks race condition
page.fileSlug suffers from a race condition in Eleventy 3.x parallel
rendering where Nunjucks shares state across template compilations,
causing slugs to get mixed up between pages. page.url is always
correct, so derive the OG slug from it instead.
2026-02-18 16:48:11 +01:00
Ricardo
c3eb04570c feat: add zachleat.com-inspired theme enhancements
- Add time-difference web component for relative dates
- Add @zachleat/table-saw for responsive tables
- Add webmention facepile styling with bookmarks support
- Add OG image thumbnails to post navigation
- Add @11ty/is-land for lazy widget hydration
- Wrap sidebar widgets in is-land for deferred loading
- Lazy-load webmention avatars with is-land
- Add @zachleat/filter-container for blog archive filtering
- Add posting frequency sparkline to blog header
- Inline critical CSS and defer full stylesheet loading
2026-02-18 11:16:33 +01:00
Ricardo
fe06fe3f4f feat: auto-generate OpenGraph images for posts without photos
Uses Satori + @resvg/resvg-js to create branded 1200x630 social
preview cards at build time. Cards show post title, type badge,
date, and site name on a dark background with blue accent.

Generated images are cached in .cache/og/ (persistent on Cloudron)
and passthrough-copied to the output. Posts with photos continue
using their own images. Untitled posts (notes) use body text.
2026-02-18 08:37:50 +01:00
Ricardo
29a014506d feat: add "Edit this post" button to FAB menu
When viewing a post that has mpUrl in its frontmatter, the FAB menu
shows an "Edit this post" link at the top that redirects to the
Indiekit admin edit form via /posts/edit?url=<mpUrl>.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 04:11:20 +01:00
Ricardo
28bc7a6c1b fix: load pagefind at end of body instead of deferred in head
The defer + DOMContentLoaded queue approach failed silently when
pagefind-ui.js couldn't load. Moving the script to end of body
ensures all DOM elements exist and processes the queue immediately
after the script loads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 11:03:02 +01:00
Ricardo
7cb0203adc fix: load pagefind once in base layout, eliminate duplicate scripts
Pagefind CSS/JS is now loaded once in base.njk <head> with defer.
A tiny initPagefind() helper queues widget inits until DOMContentLoaded
when PagefindUI is available. Removes duplicate <link>/<script> tags
from all sidebar widgets, search page, and 404 page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 10:50:32 +01:00