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
This commit is contained in:
Ricardo
2026-03-07 18:58:08 +01:00
parent db75bd05ea
commit e236b4bf65
77 changed files with 638 additions and 505 deletions

View File

@@ -46,23 +46,23 @@
{% 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 = "" %}
{% set widgetIcon = "user" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "recent-posts" %}
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% 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 = "" %}
{% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "recent-comments" %}
{% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "search" %}
{% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "webmentions" %}
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "ai-usage" %}
{% set widgetIcon = "zap" %}{% 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 == "toc" %}
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "share" %}
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% else %}
{% set widgetIcon = "" %}{% set widgetIconClass = "" %}{% set widgetBorder = "" %}
{% endif %}
@@ -89,6 +89,7 @@
class="widget-chevron"
:class="open && 'rotate-180'"
fill="none" stroke="currentColor" viewBox="0 0 24 24"
aria-hidden="true"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
@@ -169,8 +170,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("user", "w-5 h-5 text-surface-500") }} Author</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("user", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Author</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/author-card-compact.njk" %}
@@ -183,8 +184,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("list", "w-5 h-5 text-surface-500") }} Table of Contents</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("list", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Table of Contents</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/toc.njk" %}
@@ -197,8 +198,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("tag", "w-5 h-5 text-surface-500") }} Categories</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("tag", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Categories</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/post-categories.njk" %}
@@ -211,8 +212,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("list", "w-5 h-5 text-surface-500") }} Recent Posts</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("list", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Recent Posts</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/recent-posts-blog.njk" %}
@@ -225,8 +226,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("share", "w-5 h-5 text-surface-500") }} Webmentions</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("share", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Webmentions</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/webmentions.njk" %}
@@ -239,8 +240,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("share", "w-5 h-5 text-surface-500") }} Share</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("share", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Share</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/share.njk" %}
@@ -254,7 +255,7 @@
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden border-l-[3px] border-l-orange-400 dark:border-l-orange-500">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("rss", "w-5 h-5 text-orange-500") }} Subscribe</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/subscribe.njk" %}
@@ -267,8 +268,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("chat", "w-5 h-5 text-surface-500") }} Recent Comments</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("chat", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Recent Comments</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/recent-comments.njk" %}

View File

@@ -21,6 +21,7 @@
<div class="mt-4">
{# Status messages #}
<div x-show="statusMessage" x-cloak
role="alert"
x-bind:class="statusType === 'error' ? 'bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400' :
statusType === 'success' ? 'bg-green-50 text-green-700 dark:bg-green-900/20 dark:text-green-400' :
'bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400'"
@@ -54,12 +55,13 @@
</div>
<form x-on:submit.prevent="submitComment()">
<textarea x-model="commentText" rows="4" required
<label for="comment-text" class="sr-only">Your comment</label>
<textarea id="comment-text" x-model="commentText" rows="4" required
placeholder="Share your thoughts... (supports **bold**, *italic*, and [links](url))"
class="w-full px-3 py-2 border rounded-lg mb-2 dark:bg-surface-800 dark:border-surface-700 dark:text-surface-100"
x-bind:maxlength="maxLength"></textarea>
<div class="flex items-center justify-between">
<span class="text-xs text-surface-500" x-text="commentText.length + '/' + maxLength"></span>
<span class="text-xs text-surface-600 dark:text-surface-400" x-text="commentText.length + '/' + maxLength"></span>
<button type="submit" class="button" x-bind:disabled="submitting">
<span x-show="!submitting">Post Comment</span>
<span x-show="submitting" x-cloak>Posting...</span>
@@ -71,7 +73,7 @@
{# Comment list #}
<div class="mt-6 space-y-4">
<template x-if="loading">
<p class="text-sm text-surface-500">Loading comments...</p>
<p class="text-sm text-surface-600 dark:text-surface-400">Loading comments...</p>
</template>
<template x-for="comment in comments" x-bind:key="comment.published">
@@ -90,7 +92,7 @@
<div class="flex items-center gap-2">
<a x-bind:href="comment.author?.url" class="font-medium text-sm hover:underline" target="_blank" rel="noopener"
x-text="comment.author?.name || comment.author?.url"></a>
<time class="text-xs text-surface-500 font-mono" x-bind:datetime="comment.published"
<time class="text-xs text-surface-600 dark:text-surface-400 font-mono" x-bind:datetime="comment.published"
x-text="new Date(comment.published).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })"></time>
</div>
<div class="mt-1 text-sm prose dark:prose-invert" x-html="comment.content?.html || comment.content?.text"></div>
@@ -100,7 +102,7 @@
</template>
<template x-if="!loading && comments.length === 0">
<p class="text-sm text-surface-500">No comments yet. Be the first to share your thoughts!</p>
<p class="text-sm text-surface-600 dark:text-surface-400">No comments yet. Be the first to share your thoughts!</p>
</template>
</div>
</div>

View File

@@ -74,7 +74,7 @@
{% endif %}
{# Contact details — location, organization, website, email, PGP #}
{% if cvLocality or cvCountry or cvOrg or cvUrl or cvEmail or cvKeyUrl %}
<div class="flex flex-wrap gap-x-4 gap-y-1 mt-4 text-sm text-surface-500 dark:text-surface-400">
<div class="flex flex-wrap gap-x-4 gap-y-1 mt-4 text-sm text-surface-600 dark:text-surface-400">
{% if cvLocality or cvCountry %}
<span>{% if cvLocality %}{{ cvLocality }}{% endif %}{% if cvLocality and cvCountry %}, {% endif %}{% if cvCountry %}{{ cvCountry }}{% endif %}</span>
{% endif %}
@@ -160,7 +160,7 @@
{# Last Updated #}
{% if cv.lastUpdated %}
<p class="text-sm text-surface-500 text-center mt-8">
<p class="text-sm text-surface-600 dark:text-surface-400 text-center mt-8">
Last updated: <time class="font-mono text-sm" datetime="{{ cv.lastUpdated }}">{{ cv.lastUpdated | date("PPP") }}</time>
</p>
{% endif %}

View File

@@ -12,7 +12,7 @@
</svg>
</div>
<h2 class="text-lg font-semibold text-surface-700 dark:text-surface-300 mb-2">No {{ title | lower }} yet</h2>
<p class="text-surface-500 dark:text-surface-400 mb-6 max-w-md mx-auto">
<p class="text-surface-600 dark:text-surface-400 mb-6 max-w-md mx-auto">
This is where your {{ title | lower }} will appear once you start creating content.
</p>
{% if typeInfo %}

View File

@@ -14,6 +14,7 @@
@click="showModal = false"></div>
{# Panel #}
<div class="relative bg-surface-50 dark:bg-surface-800 rounded-xl shadow-xl w-full max-w-sm p-6"
role="dialog" aria-modal="true" aria-labelledby="fediverse-modal-title"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
@@ -21,8 +22,8 @@
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@click.stop>
<h3 class="text-lg font-semibold text-surface-900 dark:text-surface-100 mb-1">{{ modalTitle }}</h3>
<p class="text-sm text-surface-500 dark:text-surface-400 mb-4">{{ modalDescription }}</p>
<h3 id="fediverse-modal-title" class="text-lg font-semibold text-surface-900 dark:text-surface-100 mb-1">{{ modalTitle }}</h3>
<p class="text-sm text-surface-600 dark:text-surface-400 mb-4">{{ modalDescription }}</p>
{# Saved domains list #}
<template x-if="savedDomains.length > 0 && !showInput">
@@ -35,8 +36,8 @@
x-text="item.domain"></button>
<button class="px-2 py-2.5 text-surface-400 hover:text-red-500 transition-colors cursor-pointer"
@click="deleteSaved(item.domain)"
title="Remove">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
:aria-label="'Remove ' + item.domain">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
</template>
@@ -55,14 +56,16 @@
{# New domain input #}
<template x-if="savedDomains.length === 0 || showInput">
<div>
<input x-ref="instanceInput"
<label for="fediverse-instance-input" class="sr-only">Fediverse instance domain</label>
<input id="fediverse-instance-input"
x-ref="instanceInput"
x-model="instance"
@keydown.enter.prevent="confirm()"
type="text"
placeholder="mastodon.social"
class="w-full px-3 py-2 border border-surface-300 dark:border-surface-600 rounded-lg bg-surface-50 dark:bg-surface-800 text-surface-900 dark:text-surface-100 placeholder-surface-400 text-sm">
<template x-if="error">
<p class="text-xs text-red-500 mt-1" x-text="error"></p>
<p class="text-xs text-red-500 mt-1" x-text="error" role="alert"></p>
</template>
<div class="flex gap-3 mt-4">
<button @click="showInput ? (showInput = false) : (showModal = false)"

View File

@@ -3,19 +3,19 @@
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4 mb-6 sm:mb-8">
<div class="p-4 bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm text-center">
<span class="text-2xl font-bold font-mono text-purple-600 dark:text-purple-400 block">{{ summary.totalPlays or 0 }}</span>
<span class="text-xs text-surface-500 uppercase tracking-wide">Plays</span>
<span class="text-xs text-surface-600 dark:text-surface-400 uppercase tracking-wide">Plays</span>
</div>
<div class="p-4 bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm text-center">
<span class="text-2xl font-bold font-mono text-purple-600 dark:text-purple-400 block">{{ summary.uniqueTracks or 0 }}</span>
<span class="text-xs text-surface-500 uppercase tracking-wide">Tracks</span>
<span class="text-xs text-surface-600 dark:text-surface-400 uppercase tracking-wide">Tracks</span>
</div>
<div class="p-4 bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm text-center">
<span class="text-2xl font-bold font-mono text-purple-600 dark:text-purple-400 block">{{ summary.uniqueArtists or 0 }}</span>
<span class="text-xs text-surface-500 uppercase tracking-wide">Artists</span>
<span class="text-xs text-surface-600 dark:text-surface-400 uppercase tracking-wide">Artists</span>
</div>
<div class="p-4 bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm text-center">
<span class="text-2xl font-bold font-mono text-purple-600 dark:text-purple-400 block">{{ summary.totalDurationFormatted or '0m' }}</span>
<span class="text-xs text-surface-500 uppercase tracking-wide">Listened</span>
<span class="text-xs text-surface-600 dark:text-surface-400 uppercase tracking-wide">Listened</span>
</div>
</div>
{% endif %}
@@ -29,7 +29,7 @@
<div class="flex items-center gap-3 p-3 bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm">
<span class="w-6 h-6 flex items-center justify-center text-sm font-bold text-surface-400 bg-surface-100 dark:bg-surface-700 rounded-full">{{ loop.index }}</span>
<span class="flex-1 font-medium text-surface-900 dark:text-surface-100">{{ artist.name }}</span>
<span class="text-sm text-surface-500">{{ artist.playCount }} plays</span>
<span class="text-sm text-surface-600 dark:text-surface-400">{{ artist.playCount }} plays</span>
</div>
{% endfor %}
</div>
@@ -53,7 +53,7 @@
</div>
{% endif %}
<p class="text-sm font-medium text-surface-900 dark:text-surface-100 truncate">{{ album.title }}</p>
<p class="text-xs text-surface-500 truncate">{{ album.artist }}</p>
<p class="text-xs text-surface-600 dark:text-surface-400 truncate">{{ album.artist }}</p>
<p class="text-xs text-surface-400">{{ album.playCount }} plays</p>
</div>
{% endfor %}

View File

@@ -41,11 +41,11 @@
{{ authorName }}
</a>
{% if authorPronoun %}
<span class="p-pronoun text-xs text-surface-500">({{ authorPronoun }})</span>
<span class="p-pronoun text-xs text-surface-600 dark:text-surface-400">({{ authorPronoun }})</span>
{% endif %}
<p class="p-job-title text-sm text-surface-600 dark:text-surface-400" itemprop="jobTitle">{{ authorTitle }}</p>
{# Structured address #}
<p class="p-adr h-adr text-sm text-surface-500 dark:text-surface-500" itemprop="address" itemscope itemtype="http://schema.org/PostalAddress">
<p class="p-adr h-adr text-sm text-surface-600 dark:text-surface-400" itemprop="address" itemscope itemtype="http://schema.org/PostalAddress">
{% if authorLocality %}
<span class="p-locality" itemprop="addressLocality">{{ authorLocality }}</span>{% if authorCountry %}, {% endif %}
{% endif %}
@@ -79,7 +79,7 @@
</a>
{% endif %}
{% if authorKeyUrl %}
<a href="{{ authorKeyUrl }}" class="u-key text-surface-500 dark:text-surface-400 hover:underline" rel="pgpkey">
<a href="{{ authorKeyUrl }}" class="u-key text-surface-600 dark:text-surface-400 hover:underline" rel="pgpkey">
🔐 PGP Key
</a>
{% endif %}
@@ -87,11 +87,11 @@
{# Categories / Skills #}
{% if authorCategories and authorCategories.length %}
<div class="mt-3 flex flex-wrap gap-1">
<ul class="mt-3 flex flex-wrap gap-1 list-none p-0 m-0" role="list" aria-label="Skills and interests">
{% for category in authorCategories %}
<span class="p-category text-xs px-2 py-0.5 bg-surface-100 dark:bg-surface-800 rounded-full">{{ category }}</span>
<li class="p-category text-xs px-2 py-0.5 bg-surface-100 dark:bg-surface-800 rounded-full">{{ category }}</li>
{% endfor %}
</div>
</ul>
{% endif %}
{# Social links with rel="me" - critical for IndieWeb identity verification #}
@@ -102,8 +102,8 @@
<a
href="{{ link.url }}"
rel="{{ link.rel }} noopener"
class="u-url text-surface-500 hover:text-accent-600 dark:hover:text-accent-400 transition-colors"
aria-label="{{ link.name }}"
class="u-url text-surface-600 dark:text-surface-400 hover:text-accent-600 dark:hover:text-accent-400 transition-colors"
aria-label="{{ link.name }} (opens in new tab)"
target="_blank">
{{ socialIcon(link.icon, "w-5 h-5") }}
</a>

View File

@@ -39,17 +39,17 @@
{% 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 = "" %}
{% set widgetIcon = "user" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "recent-posts" %}
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "categories" %}
{% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "recent-comments" %}
{% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "search" %}
{% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "webmentions" %}
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "ai-usage" %}
{% set widgetIcon = "zap" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
{% else %}
@@ -78,6 +78,7 @@
class="widget-chevron"
:class="open && 'rotate-180'"
fill="none" stroke="currentColor" viewBox="0 0 24 24"
aria-hidden="true"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>

View File

@@ -39,17 +39,17 @@
</span>
{% else %}
<div class="p-4 sm:p-5">
<span class="text-[10px] sm:text-xs font-semibold uppercase tracking-wide text-surface-500 block mb-2">&larr; Previous</span>
<span class="text-[10px] sm:text-xs font-semibold uppercase tracking-wide text-surface-600 dark:text-surface-400 block mb-2">&larr; Previous</span>
<span class="text-sm sm:text-base font-medium text-surface-900 dark:text-surface-100 group-hover:text-accent-600 dark:group-hover:text-accent-400 line-clamp-2 transition-colors">
{{ _prevTitle }}
</span>
<time class="text-xs text-surface-500 mt-1 block font-mono" datetime="{{ _prevPost.date | isoDate }}">{{ _prevPost.date | dateDisplay }}</time>
<time class="text-xs text-surface-600 dark:text-surface-400 mt-1 block font-mono" datetime="{{ _prevPost.date | isoDate }}">{{ _prevPost.date | dateDisplay }}</time>
</div>
{% endif %}
</a>
{% else %}
<div class="rounded-lg bg-surface-50 dark:bg-surface-800/50 border border-surface-200/50 dark:border-surface-700/50"></div>
<div class="rounded-lg bg-surface-50 dark:bg-surface-800/50 border border-surface-200/50 dark:border-surface-700/50" aria-hidden="true"></div>
{% endif %}
{# ── Next Post ── #}
@@ -85,17 +85,17 @@
</span>
{% else %}
<div class="p-4 sm:p-5 text-right">
<span class="text-[10px] sm:text-xs font-semibold uppercase tracking-wide text-surface-500 block mb-2">Next &rarr;</span>
<span class="text-[10px] sm:text-xs font-semibold uppercase tracking-wide text-surface-600 dark:text-surface-400 block mb-2">Next &rarr;</span>
<span class="text-sm sm:text-base font-medium text-surface-900 dark:text-surface-100 group-hover:text-accent-600 dark:group-hover:text-accent-400 line-clamp-2 transition-colors">
{{ _nextTitle }}
</span>
<time class="text-xs text-surface-500 mt-1 block font-mono" datetime="{{ _nextPost.date | isoDate }}">{{ _nextPost.date | dateDisplay }}</time>
<time class="text-xs text-surface-600 dark:text-surface-400 mt-1 block font-mono" datetime="{{ _nextPost.date | isoDate }}">{{ _nextPost.date | dateDisplay }}</time>
</div>
{% endif %}
</a>
{% else %}
<div class="rounded-lg bg-surface-50 dark:bg-surface-800/50 border border-surface-200/50 dark:border-surface-700/50"></div>
<div class="rounded-lg bg-surface-50 dark:bg-surface-800/50 border border-surface-200/50 dark:border-surface-700/50" aria-hidden="true"></div>
{% endif %}
</div>

View File

@@ -13,14 +13,14 @@
<aside class="reply-context mb-6">
{% if replyTo %}
<div class="u-in-reply-to h-cite">
<p class="text-sm text-surface-500 dark:text-surface-400 mb-2 flex items-center gap-2">
<p class="text-sm text-surface-600 dark:text-surface-400 mb-2 flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/>
</svg>
<span>In reply to:</span>
</p>
{% unfurl replyTo %}
<a class="u-url text-xs text-surface-400 dark:text-surface-500 hover:underline break-all" href="{{ replyTo }}">
<a class="u-url text-xs text-surface-600 dark:text-surface-400 hover:underline break-all" href="{{ replyTo }}">
{{ replyTo }}
</a>
</div>
@@ -28,14 +28,14 @@
{% if likedUrl %}
<div class="u-like-of h-cite">
<p class="text-sm text-surface-500 dark:text-surface-400 mb-2 flex items-center gap-2">
<p class="text-sm text-surface-600 dark:text-surface-400 mb-2 flex items-center gap-2">
<svg class="w-4 h-4 text-red-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
<span>Liked:</span>
</p>
{% unfurl likedUrl %}
<a class="u-url text-xs text-surface-400 dark:text-surface-500 hover:underline break-all" href="{{ likedUrl }}">
<a class="u-url text-xs text-surface-600 dark:text-surface-400 hover:underline break-all" href="{{ likedUrl }}">
{{ likedUrl }}
</a>
</div>
@@ -43,14 +43,14 @@
{% if repostedUrl %}
<div class="u-repost-of h-cite">
<p class="text-sm text-surface-500 dark:text-surface-400 mb-2 flex items-center gap-2">
<p class="text-sm text-surface-600 dark:text-surface-400 mb-2 flex items-center gap-2">
<svg class="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<span>Reposted:</span>
</p>
{% unfurl repostedUrl %}
<a class="u-url text-xs text-surface-400 dark:text-surface-500 hover:underline break-all" href="{{ repostedUrl }}">
<a class="u-url text-xs text-surface-600 dark:text-surface-400 hover:underline break-all" href="{{ repostedUrl }}">
{{ repostedUrl }}
</a>
</div>
@@ -58,14 +58,14 @@
{% if bookmarkedUrl %}
<div class="u-bookmark-of h-cite">
<p class="text-sm text-surface-500 dark:text-surface-400 mb-2 flex items-center gap-2">
<p class="text-sm text-surface-600 dark:text-surface-400 mb-2 flex items-center gap-2">
<svg class="w-4 h-4 text-yellow-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path d="M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2z"/>
</svg>
<span>Bookmarked:</span>
</p>
{% unfurl bookmarkedUrl %}
<a class="u-url text-xs text-surface-400 dark:text-surface-500 hover:underline break-all" href="{{ bookmarkedUrl }}">
<a class="u-url text-xs text-surface-600 dark:text-surface-400 hover:underline break-all" href="{{ bookmarkedUrl }}">
{{ bookmarkedUrl }}
</a>
</div>

View File

@@ -15,19 +15,19 @@
<div class="grid gap-4 sm:grid-cols-4 mb-6">
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold font-mono text-surface-900 dark:text-surface-100">{{ stats.total }}</div>
<div class="text-xs text-surface-500 dark:text-surface-400">Total posts</div>
<div class="text-xs text-surface-600 dark:text-surface-400">Total posts</div>
</div>
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold font-mono text-amber-600 dark:text-amber-400">{{ stats.aiCount }}</div>
<div class="text-xs text-surface-500 dark:text-surface-400">AI-involved</div>
<div class="text-xs text-surface-600 dark:text-surface-400">AI-involved</div>
</div>
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold font-mono text-emerald-600 dark:text-emerald-400">{{ stats.total - stats.aiCount }}</div>
<div class="text-xs text-surface-500 dark:text-surface-400">Human-only</div>
<div class="text-xs text-surface-600 dark:text-surface-400">Human-only</div>
</div>
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold font-mono text-surface-900 dark:text-surface-100">{{ stats.percentage }}%</div>
<div class="text-xs text-surface-500 dark:text-surface-400">AI ratio</div>
<div class="text-xs text-surface-600 dark:text-surface-400">AI ratio</div>
</div>
</div>
@@ -50,7 +50,7 @@
{# Post graph — AI-involved posts highlighted #}
{% if aiPostsList and aiPostsList.length %}
<h3 class="text-lg font-semibold text-surface-900 dark:text-surface-100 mb-3">AI-Involved Posts Over Time</h3>
<p class="text-sm text-surface-500 dark:text-surface-400 mb-4">Highlighted days had posts with AI involvement (level 1+). Empty boxes represent days with no AI-involved posts.</p>
<p class="text-sm text-surface-600 dark:text-surface-400 mb-4">Highlighted days had posts with AI involvement (level 1+). Empty boxes represent days with no AI-involved posts.</p>
{% set graphLimit = sectionConfig.limit or 1 %}
{% postGraph aiPostsList, { prefix: "ai-section", limit: graphLimit, boxColorDark: "#44403c", highlightColorLight: "#d97706", highlightColorDark: "#fbbf24" } %}
{% endif %}

View File

@@ -40,11 +40,11 @@
</div>
<div class="flex items-center gap-2 shrink-0">
{% if item.startDate %}
<span class="text-xs text-surface-500 hidden sm:inline font-mono">
<span class="text-xs text-surface-600 dark:text-surface-400 hidden sm:inline font-mono">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</span>
{% elif item.year %}
<span class="text-xs text-surface-500 hidden sm:inline font-mono">{{ item.year }}</span>
<span class="text-xs text-surface-600 dark:text-surface-400 hidden sm:inline font-mono">{{ item.year }}</span>
{% endif %}
<svg
class="w-4 h-4 text-surface-400 transition-transform duration-200"
@@ -69,11 +69,11 @@
class="px-4 pb-4"
>
{% if item.startDate %}
<p class="text-xs text-surface-500 mb-1 sm:hidden font-mono">
<p class="text-xs text-surface-600 dark:text-surface-400 mb-1 sm:hidden font-mono">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</p>
{% elif item.year %}
<p class="text-xs text-surface-500 mb-1 sm:hidden font-mono">{{ item.year }}</p>
<p class="text-xs text-surface-600 dark:text-surface-400 mb-1 sm:hidden font-mono">{{ item.year }}</p>
{% endif %}
{% if item.description %}

View File

@@ -24,7 +24,7 @@
{% if item.type %} &middot; <span class="capitalize">{{ item.type }}</span>{% endif %}
</p>
{% if item.startDate %}
<p class="text-xs text-surface-500 mt-0.5 font-mono">
<p class="text-xs text-surface-600 dark:text-surface-400 mt-0.5 font-mono">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</p>
{% endif %}

View File

@@ -13,7 +13,7 @@
{% for lang in cv.languages %}
<div class="flex items-center gap-2 px-3 py-1.5 bg-surface-50 dark:bg-surface-800 rounded-full border border-surface-200 dark:border-surface-700">
<span class="font-medium text-sm text-surface-900 dark:text-surface-100">{{ lang.name }}</span>
<span class="text-xs text-surface-500 capitalize">{{ lang.level }}</span>
<span class="text-xs text-surface-600 dark:text-surface-400 capitalize">{{ lang.level }}</span>
</div>
{% endfor %}
</div>

View File

@@ -62,7 +62,7 @@
</div>
<div class="flex items-center gap-2 shrink-0">
{% if item.startDate %}
<span class="text-xs text-surface-500 hidden sm:inline font-mono">
<span class="text-xs text-surface-600 dark:text-surface-400 hidden sm:inline font-mono">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</span>
{% endif %}
@@ -89,7 +89,7 @@
class="px-4 pb-4"
>
{% if item.startDate %}
<p class="text-xs text-surface-500 mb-1 sm:hidden font-mono">
<p class="text-xs text-surface-600 dark:text-surface-400 mb-1 sm:hidden font-mono">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</p>
{% endif %}

View File

@@ -62,7 +62,7 @@
</div>
<div class="flex items-center gap-2 shrink-0">
{% if item.startDate %}
<span class="text-xs text-surface-500 hidden sm:inline font-mono">
<span class="text-xs text-surface-600 dark:text-surface-400 hidden sm:inline font-mono">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</span>
{% endif %}
@@ -89,7 +89,7 @@
class="px-4 pb-4"
>
{% if item.startDate %}
<p class="text-xs text-surface-500 mb-1 sm:hidden font-mono">
<p class="text-xs text-surface-600 dark:text-surface-400 mb-1 sm:hidden font-mono">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</p>
{% endif %}

View File

@@ -52,7 +52,7 @@
</div>
<div class="flex items-center gap-2 shrink-0">
{% if item.startDate %}
<span class="text-xs text-surface-500 hidden sm:inline font-mono">
<span class="text-xs text-surface-600 dark:text-surface-400 hidden sm:inline font-mono">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</span>
{% endif %}
@@ -79,7 +79,7 @@
class="px-4 pb-4"
>
{% if item.startDate %}
<p class="text-xs text-surface-500 mb-1 sm:hidden font-mono">
<p class="text-xs text-surface-600 dark:text-surface-400 mb-1 sm:hidden font-mono">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</p>
{% endif %}

View File

@@ -53,14 +53,14 @@
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span class="font-medium text-red-600 dark:text-red-400">Liked</span>
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
</div>
{{ likedUrl | unfurlCard | safe }}
<a class="u-like-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
<a class="u-like-of text-xs text-surface-400 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
{{ likedUrl }}
</a>
{% if post.templateContent %}
@@ -81,7 +81,7 @@
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span class="font-medium text-amber-600 dark:text-amber-400">Bookmarked</span>
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
@@ -93,7 +93,7 @@
</h3>
{% endif %}
{{ bookmarkedUrl | unfurlCard | safe }}
<a class="u-bookmark-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
<a class="u-bookmark-of text-xs text-surface-400 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
{{ bookmarkedUrl }}
</a>
{% if post.templateContent %}
@@ -114,14 +114,14 @@
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span class="font-medium text-green-600 dark:text-green-400">Reposted</span>
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
</div>
{{ repostedUrl | unfurlCard | safe }}
<a class="u-repost-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
<a class="u-repost-of text-xs text-surface-400 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
{{ repostedUrl }}
</a>
{% if post.templateContent %}
@@ -142,14 +142,14 @@
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span class="font-medium text-sky-600 dark:text-sky-400">In reply to</span>
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
</div>
{{ replyToUrl | unfurlCard | safe }}
<a class="u-in-reply-to text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ replyToUrl }}">
<a class="u-in-reply-to text-xs text-surface-400 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ replyToUrl }}">
{{ replyToUrl }}
</a>
{% if post.templateContent %}
@@ -171,7 +171,7 @@
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span class="font-medium text-purple-600 dark:text-purple-400">Photo</span>
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
@@ -209,7 +209,7 @@
{{ post.templateContent | striptags | truncate(250) }}
</p>
{% endif %}
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
@@ -222,9 +222,9 @@
{% else %}
{# ── Note card ── #}
<div class="flex items-center gap-3 text-xs text-surface-500 mb-2">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400 mb-2">
<a class="u-url" href="{{ post.url }}">
<time class="dt-published font-medium font-mono text-surface-500 dark:text-surface-400" datetime="{{ post.date | isoDate }}">
<time class="dt-published font-medium font-mono text-surface-600 dark:text-surface-400" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
</a>

View File

@@ -50,14 +50,14 @@
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span class="font-medium text-red-600 dark:text-red-400">Liked</span>
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
</div>
{{ likedUrl | unfurlCard | safe }}
<a class="u-like-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
<a class="u-like-of text-xs text-surface-400 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
{{ likedUrl }}
</a>
{% if post.templateContent %}
@@ -78,7 +78,7 @@
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span class="font-medium text-amber-600 dark:text-amber-400">Bookmarked</span>
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
@@ -90,7 +90,7 @@
</h3>
{% endif %}
{{ bookmarkedUrl | unfurlCard | safe }}
<a class="u-bookmark-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
<a class="u-bookmark-of text-xs text-surface-400 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
{{ bookmarkedUrl }}
</a>
{% if post.templateContent %}
@@ -111,14 +111,14 @@
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span class="font-medium text-green-600 dark:text-green-400">Reposted</span>
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
</div>
{{ repostedUrl | unfurlCard | safe }}
<a class="u-repost-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
<a class="u-repost-of text-xs text-surface-400 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
{{ repostedUrl }}
</a>
{% if post.templateContent %}
@@ -139,14 +139,14 @@
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span class="font-medium text-sky-600 dark:text-sky-400">In reply to</span>
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
</div>
{{ replyToUrl | unfurlCard | safe }}
<a class="u-in-reply-to text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ replyToUrl }}">
<a class="u-in-reply-to text-xs text-surface-400 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ replyToUrl }}">
{{ replyToUrl }}
</a>
{% if post.templateContent %}
@@ -168,7 +168,7 @@
</svg>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span class="font-medium text-purple-600 dark:text-purple-400">Photo</span>
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
@@ -206,7 +206,7 @@
{{ post.templateContent | striptags | truncate(250) }}
</p>
{% endif %}
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<time class="dt-published font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
@@ -219,9 +219,9 @@
{% else %}
{# ── Note card ── #}
<div class="flex items-center gap-3 text-xs text-surface-500 mb-2">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400 mb-2">
<a class="u-url" href="{{ post.url }}">
<time class="dt-published font-medium font-mono text-surface-500 dark:text-surface-400" datetime="{{ post.date | isoDate }}">
<time class="dt-published font-medium font-mono text-surface-600 dark:text-surface-400" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
</a>

View File

@@ -43,17 +43,17 @@
{% 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 = "" %}
{% set widgetIcon = "user" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "recent-posts" %}
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "categories" %}
{% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "recent-comments" %}
{% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "search" %}
{% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "webmentions" %}
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-600 dark:text-surface-400" %}{% set widgetBorder = "" %}
{% elif widget.type == "ai-usage" %}
{% set widgetIcon = "zap" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
{% else %}
@@ -82,6 +82,7 @@
class="widget-chevron"
:class="open && 'rotate-180'"
fill="none" stroke="currentColor" viewBox="0 0 24 24"
aria-hidden="true"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
@@ -158,8 +159,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("user", "w-5 h-5 text-surface-500") }} Author</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("user", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Author</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/author-card.njk" %}
@@ -173,7 +174,7 @@
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden border-l-[3px] border-l-[#0085ff]">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("globe", "w-5 h-5 text-[#0085ff]") }} Social Activity</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/social-activity.njk" %}
@@ -187,7 +188,7 @@
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden border-l-[3px] border-l-surface-400 dark:border-l-surface-500">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("github", "w-5 h-5 text-surface-800 dark:text-surface-200") }} GitHub</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/github-repos.njk" %}
@@ -201,7 +202,7 @@
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden border-l-[3px] border-l-purple-400 dark:border-l-purple-500">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("headphones", "w-5 h-5 text-purple-500") }} Listening</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/funkwhale.njk" %}
@@ -214,8 +215,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("list", "w-5 h-5 text-surface-500") }} Recent Posts</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("list", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Recent Posts</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/recent-posts.njk" %}
@@ -230,7 +231,7 @@
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden border-l-[3px] border-l-amber-400 dark:border-l-amber-500">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("book-open", "w-5 h-5 text-amber-500") }} Blogroll</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/blogroll.njk" %}
@@ -246,7 +247,7 @@
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden border-l-[3px] border-l-amber-400 dark:border-l-amber-500">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("rss", "w-5 h-5 text-amber-500") }} FeedLand</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/feedland.njk" %}
@@ -260,8 +261,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("chat", "w-5 h-5 text-surface-500") }} Recent Comments</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("chat", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Recent Comments</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/recent-comments.njk" %}
@@ -274,8 +275,8 @@
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
<button class="widget-header w-full p-4" @click="open = !open; localStorage.setItem('{{ widgetKey }}', open)" :aria-expanded="open ? 'true' : 'false'">
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("tag", "w-5 h-5 text-surface-500") }} Categories</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
<h3 class="widget-title font-bold text-lg flex items-center gap-2">{{ icon("tag", "w-5 h-5 text-surface-600 dark:text-surface-400") }} Categories</h3>
<svg class="widget-chevron" :class="open && 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak>
{% include "components/widgets/categories.njk" %}

View File

@@ -28,22 +28,24 @@
{{ likes.length }} Like{% if likes.length != 1 %}s{% endif %}
</h3>
<is-land on:visible>
<div class="facepile">
<ul class="facepile" role="list">
{% for like in likes %}
<a href="{{ like.author.url }}"
class="facepile-avatar"
title="{{ like.author.name }}"
target="_blank"
rel="noopener">
<img
src="{{ like.author.photo or '/images/default-avatar.svg' }}"
alt="{{ like.author.name }}"
class="w-8 h-8 rounded-full ring-2 ring-white dark:ring-surface-900"
loading="lazy"
>
</a>
<li class="inline">
<a href="{{ like.author.url }}"
class="facepile-avatar"
aria-label="{{ like.author.name }} (opens in new tab)"
target="_blank"
rel="noopener">
<img
src="{{ like.author.photo or '/images/default-avatar.svg' }}"
alt=""
class="w-8 h-8 rounded-full ring-2 ring-white dark:ring-surface-900"
loading="lazy"
>
</a>
</li>
{% endfor %}
</div>
</ul>
</is-land>
</div>
{% endif %}
@@ -56,22 +58,24 @@
{{ reposts.length }} Repost{% if reposts.length != 1 %}s{% endif %}
</h3>
<is-land on:visible>
<div class="facepile">
<ul class="facepile" role="list">
{% for repost in reposts %}
<a href="{{ repost.author.url }}"
class="facepile-avatar"
title="{{ repost.author.name }}"
target="_blank"
rel="noopener">
<img
src="{{ repost.author.photo or '/images/default-avatar.svg' }}"
alt="{{ repost.author.name }}"
class="w-8 h-8 rounded-full ring-2 ring-white dark:ring-surface-900"
loading="lazy"
>
</a>
<li class="inline">
<a href="{{ repost.author.url }}"
class="facepile-avatar"
aria-label="{{ repost.author.name }} (opens in new tab)"
target="_blank"
rel="noopener">
<img
src="{{ repost.author.photo or '/images/default-avatar.svg' }}"
alt=""
class="w-8 h-8 rounded-full ring-2 ring-white dark:ring-surface-900"
loading="lazy"
>
</a>
</li>
{% endfor %}
</div>
</ul>
</is-land>
</div>
{% endif %}
@@ -84,22 +88,24 @@
{{ bookmarks.length }} Bookmark{% if bookmarks.length != 1 %}s{% endif %}
</h3>
<is-land on:visible>
<div class="facepile">
<ul class="facepile" role="list">
{% for bookmark in bookmarks %}
<a href="{{ bookmark.author.url }}"
class="facepile-avatar"
title="{{ bookmark.author.name }}"
target="_blank"
rel="noopener">
<img
src="{{ bookmark.author.photo or '/images/default-avatar.svg' }}"
alt="{{ bookmark.author.name }}"
class="w-8 h-8 rounded-full ring-2 ring-white dark:ring-surface-900"
loading="lazy"
>
</a>
<li class="inline">
<a href="{{ bookmark.author.url }}"
class="facepile-avatar"
aria-label="{{ bookmark.author.name }} (opens in new tab)"
target="_blank"
rel="noopener">
<img
src="{{ bookmark.author.photo or '/images/default-avatar.svg' }}"
alt=""
class="w-8 h-8 rounded-full ring-2 ring-white dark:ring-surface-900"
loading="lazy"
>
</a>
</li>
{% endfor %}
</div>
</ul>
</is-land>
</div>
{% endif %}
@@ -132,7 +138,7 @@
{{ reply.author.name }}
</a>
<a href="{{ reply.url }}"
class="text-xs text-surface-500 hover:underline"
class="text-xs text-surface-600 dark:text-surface-400 hover:underline"
target="_blank"
rel="noopener">
<time class="font-mono" datetime="{{ reply.published }}">
@@ -189,7 +195,9 @@
</p>
<form action="https://webmention.io/{{ site.webmentions.domain }}/webmention" method="post" class="flex gap-2">
<input type="hidden" name="target" value="{{ site.url }}{{ page.url }}">
<label for="webmention-source" class="sr-only">Your post URL</label>
<input
id="webmention-source"
type="url"
name="source"
placeholder="https://your-site.com/response"

View File

@@ -5,7 +5,7 @@
<is-land on:visible>
<div class="widget">
<h3 class="widget-title flex items-center gap-2">
<svg class="w-5 h-5 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-5 h-5 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
AI Transparency
@@ -15,19 +15,19 @@
<div class="grid grid-cols-2 gap-2 mb-3">
<div class="text-center p-2 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700">
<div class="text-lg font-bold text-surface-900 dark:text-surface-100">{{ stats.total }}</div>
<div class="text-[10px] text-surface-500 dark:text-surface-400">Total</div>
<div class="text-[10px] text-surface-600 dark:text-surface-400">Total</div>
</div>
<div class="text-center p-2 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700">
<div class="text-lg font-bold text-amber-600 dark:text-amber-400">{{ stats.aiCount }}</div>
<div class="text-[10px] text-surface-500 dark:text-surface-400">AI-involved</div>
<div class="text-[10px] text-surface-600 dark:text-surface-400">AI-involved</div>
</div>
<div class="text-center p-2 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700">
<div class="text-lg font-bold text-emerald-600 dark:text-emerald-400">{{ stats.total - stats.aiCount }}</div>
<div class="text-[10px] text-surface-500 dark:text-surface-400">Human-only</div>
<div class="text-[10px] text-surface-600 dark:text-surface-400">Human-only</div>
</div>
<div class="text-center p-2 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700">
<div class="text-lg font-bold text-surface-900 dark:text-surface-100">{{ stats.percentage }}%</div>
<div class="text-[10px] text-surface-500 dark:text-surface-400">AI ratio</div>
<div class="text-[10px] text-surface-600 dark:text-surface-400">AI ratio</div>
</div>
</div>
@@ -49,13 +49,13 @@
{# Compact post-graph — current year only, AI posts highlighted #}
{% if aiPostsList and aiPostsList.length %}
<div class="text-[10px] text-surface-500 dark:text-surface-400 mb-2">AI-involved posts this year</div>
<div class="text-[10px] text-surface-600 dark:text-surface-400 mb-2">AI-involved posts this year</div>
{% postGraph aiPostsList, { prefix: "ai-widget", limit: 1, noLabels: true, boxColorDark: "#44403c", highlightColorLight: "#d97706", highlightColorDark: "#fbbf24" } %}
{% endif %}
<a href="/ai/" class="text-sm text-amber-600 dark:text-amber-400 hover:underline flex items-center gap-1 mt-3">
View full AI report
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
</a>
</div>
</is-land>

View File

@@ -4,10 +4,10 @@
<div class="h-card p-author flex items-center gap-3">
{# Hidden u-photo for reliable microformat parsing #}
<data class="u-photo hidden" value="{{ site.author.avatar }}"></data>
<a href="{{ site.author.url }}" class="u-url u-uid" rel="me" itemprop="url">
<a href="{{ site.author.url }}" class="u-url u-uid" rel="me" itemprop="url" aria-label="{{ site.author.name }}">
<img
src="{{ site.author.avatar }}"
alt="{{ site.author.name }}"
alt=""
class="w-12 h-12 rounded-full object-cover shadow-lg"
loading="lazy"
>
@@ -16,9 +16,9 @@
<a href="{{ site.author.url }}" class="u-url p-name font-medium text-surface-900 dark:text-surface-100 hover:text-accent-600 dark:hover:text-accent-400 transition-colors">
{{ site.author.name }}
</a>
<p class="p-job-title text-xs text-surface-500">{{ site.author.title }}</p>
<p class="p-job-title text-xs text-surface-600 dark:text-surface-400">{{ site.author.title }}</p>
{% if site.author.locality %}
<p class="p-locality text-xs text-surface-500">{{ site.author.locality }}{% if site.author.country %}, <span class="p-country-name">{{ site.author.country }}</span>{% endif %}</p>
<p class="p-locality text-xs text-surface-600 dark:text-surface-400">{{ site.author.locality }}{% if site.author.country %}, <span class="p-country-name">{{ site.author.country }}</span>{% endif %}</p>
{% endif %}
</div>
</div>

View File

@@ -15,7 +15,7 @@
@click="activeTab = tab.key"
:class="activeTab === tab.key
? 'border-b-2 border-accent-600 text-accent-600 dark:text-accent-400 dark:border-accent-400'
: 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
: 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
class="px-2 py-1 text-xs font-medium transition-colors -mb-px"
x-text="tab.label + ' (' + tab.count + ')'"
></button>
@@ -40,7 +40,7 @@
</template>
</ul>
<div x-show="filteredBlogs.length === 0 && !loading" class="text-sm text-surface-500 py-2">
<div x-show="filteredBlogs.length === 0 && !loading" class="text-sm text-surface-600 dark:text-surface-400 py-2">
No blogs loaded yet.
</div>

View File

@@ -20,7 +20,7 @@
<is-land on:visible>
<div class="widget" x-data="fediverseInteract('{{ actorUrl }}', 'interact')">
<h3 class="widget-title">Follow Me</h3>
<p class="text-sm text-surface-500 dark:text-surface-400 mb-3">Follow me from your fediverse instance.</p>
<p class="text-sm text-surface-600 dark:text-surface-400 mb-3">Follow me from your fediverse instance.</p>
<a href="{{ actorUrl }}"
@click="handleClick($event)"
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-[#a730b8]/10 text-[#a730b8] hover:bg-[#a730b8]/20 transition-colors text-sm font-medium cursor-pointer"

View File

@@ -240,8 +240,8 @@
{# Sort links #}
<div class="fl-sort">
<span :class="sortBy === 'title' ? 'selected' : ''" @click="sortBy = 'title'">Title</span>
<span :class="sortBy === 'when' ? 'selected' : ''" @click="sortBy = 'when'">When</span>
<span :class="sortBy === 'title' ? 'selected' : ''" @click="sortBy = 'title'" @keydown.enter="sortBy = 'title'" @keydown.space.prevent="sortBy = 'title'" tabindex="0" role="button" :aria-pressed="sortBy === 'title'">Title</span>
<span :class="sortBy === 'when' ? 'selected' : ''" @click="sortBy = 'when'" @keydown.enter="sortBy = 'when'" @keydown.space.prevent="sortBy = 'when'" tabindex="0" role="button" :aria-pressed="sortBy === 'when'">When</span>
</div>
{# Feed list — pure divs, no table #}
@@ -253,7 +253,13 @@
<span class="fl-caret"
:class="expandedId === blog.id ? 'fl-caret-dark' : (selectedId === blog.id ? 'fl-caret-dark' : 'fl-caret-light')"
x-text="expandedId === blog.id ? '\u25BC' : '\u25B6'"
@click.stop="toggleExpand(blog)"></span>
@click.stop="toggleExpand(blog)"
@keydown.enter.stop="toggleExpand(blog)"
@keydown.space.prevent.stop="toggleExpand(blog)"
tabindex="0"
role="button"
:aria-label="expandedId === blog.id ? 'Collapse ' + blog.title : 'Expand ' + blog.title"
:aria-expanded="expandedId === blog.id"></span>
<span class="fl-name">
<a :href="blog.siteUrl || blog.feedUrl" target="_blank" rel="noopener"
x-text="blog.title" @click.stop></a>

View File

@@ -68,7 +68,7 @@
{{ listening.track }}
{% endif %}
</p>
<p class="text-xs text-surface-500 truncate">{{ listening.artist }}
<p class="text-xs text-surface-600 dark:text-surface-400 truncate">{{ listening.artist }}
<span class="text-purple-500 ml-1">Funkwhale</span>
</p>
</div>
@@ -97,7 +97,7 @@
{% endif %}
{% if scrobble.loved %}<span class="text-red-500 ml-0.5">&#9829;</span>{% endif %}
</p>
<p class="text-xs text-surface-500 truncate">{{ scrobble.artist }}
<p class="text-xs text-surface-600 dark:text-surface-400 truncate">{{ scrobble.artist }}
<span class="text-red-500 ml-1">Last.fm</span>
</p>
</div>

View File

@@ -9,31 +9,39 @@
</h3>
{# Tab buttons — order: Commits, Repos, Featured, PRs #}
<div class="flex gap-1 mb-4 border-b border-surface-200 dark:border-surface-700">
<div class="flex gap-1 mb-4 border-b border-surface-200 dark:border-surface-700" role="tablist" aria-label="GitHub activity">
<button
@click="activeTab = 'commits'"
:class="activeTab === 'commits' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'commits' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'commits').toString()"
role="tab" id="gh-tab-commits" aria-controls="gh-panel-commits"
class="flex items-center gap-1.5 px-2 py-2 text-xs font-medium transition-colors -mb-px"
>
Commits
</button>
<button
@click="activeTab = 'repos'"
:class="activeTab === 'repos' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'repos' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'repos').toString()"
role="tab" id="gh-tab-repos" aria-controls="gh-panel-repos"
class="flex items-center gap-1.5 px-2 py-2 text-xs font-medium transition-colors -mb-px"
>
Repos
</button>
<button
@click="activeTab = 'featured'"
:class="activeTab === 'featured' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'featured' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'featured').toString()"
role="tab" id="gh-tab-featured" aria-controls="gh-panel-featured"
class="flex items-center gap-1.5 px-2 py-2 text-xs font-medium transition-colors -mb-px"
>
Featured
</button>
<button
@click="activeTab = 'prs'"
:class="activeTab === 'prs' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'prs' ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'prs').toString()"
role="tab" id="gh-tab-prs" aria-controls="gh-panel-prs"
class="flex items-center gap-1.5 px-2 py-2 text-xs font-medium transition-colors -mb-px"
>
PRs
@@ -44,18 +52,18 @@
<div class="h-[420px] overflow-y-auto">
{# Loading state #}
<div x-show="loading" class="text-sm text-surface-500 py-4 text-center">
<div x-show="loading" class="text-sm text-surface-600 dark:text-surface-400 py-4 text-center">
Loading...
</div>
{# Commits Tab #}
<div x-show="activeTab === 'commits' && !loading" x-cloak>
<div x-show="activeTab === 'commits' && !loading" x-cloak role="tabpanel" id="gh-panel-commits" aria-labelledby="gh-tab-commits">
<ul x-show="commits.length > 0" class="space-y-3">
<template x-for="commit in commits.slice(0, 5)" :key="commit.sha">
<li class="border-b border-surface-200 dark:border-surface-700 pb-3 last:border-0">
<a :href="commit.url" target="_blank" rel="noopener" class="block group">
<p class="text-sm text-surface-700 dark:text-surface-300 group-hover:text-surface-900 dark:group-hover:text-surface-100 transition-colors line-clamp-2" x-text="commit.message"></p>
<div class="flex items-center gap-2 mt-1.5 text-xs text-surface-500">
<div class="flex items-center gap-2 mt-1.5 text-xs text-surface-600 dark:text-surface-400">
<code class="text-xs font-mono bg-surface-100 dark:bg-surface-800 px-1 py-0.5 rounded" x-text="commit.sha"></code>
<span class="truncate" x-text="commit.repo?.split('/')[1] || commit.repo"></span>
<span class="font-mono" x-text="formatDate(commit.date)"></span>
@@ -64,11 +72,11 @@
</li>
</template>
</ul>
<div x-show="commits.length === 0" class="text-sm text-surface-500 py-2">No recent commits.</div>
<div x-show="commits.length === 0" class="text-sm text-surface-600 dark:text-surface-400 py-2">No recent commits.</div>
</div>
{# Repos Tab #}
<div x-show="activeTab === 'repos' && !loading" x-cloak>
<div x-show="activeTab === 'repos' && !loading" x-cloak role="tabpanel" id="gh-panel-repos" aria-labelledby="gh-tab-repos">
<ul x-show="repos.length > 0" class="space-y-3">
<template x-for="repo in repos.slice(0, 5)" :key="repo.name">
<li class="border-b border-surface-200 dark:border-surface-700 pb-3 last:border-0">
@@ -78,7 +86,7 @@
<span x-show="repo.language" class="text-xs px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-400 flex-shrink-0" x-text="repo.language"></span>
</div>
<p x-show="repo.description" class="text-xs text-surface-600 dark:text-surface-400 mt-1 line-clamp-2" x-text="repo.description"></p>
<div class="flex items-center gap-3 mt-1.5 text-xs text-surface-500">
<div class="flex items-center gap-3 mt-1.5 text-xs text-surface-600 dark:text-surface-400">
<span x-show="repo.stargazers_count > 0" class="flex items-center gap-1">
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
<span x-text="repo.stargazers_count"></span>
@@ -89,11 +97,11 @@
</li>
</template>
</ul>
<div x-show="repos.length === 0" class="text-sm text-surface-500 py-2">No repositories found.</div>
<div x-show="repos.length === 0" class="text-sm text-surface-600 dark:text-surface-400 py-2">No repositories found.</div>
</div>
{# Featured Tab #}
<div x-show="activeTab === 'featured' && !loading" x-cloak>
<div x-show="activeTab === 'featured' && !loading" x-cloak role="tabpanel" id="gh-panel-featured" aria-labelledby="gh-tab-featured">
<ul x-show="featured.length > 0" class="space-y-3">
<template x-for="repo in featured.slice(0, 5)" :key="repo.fullName || repo.name">
<li class="border-b border-surface-200 dark:border-surface-700 pb-3 last:border-0">
@@ -103,7 +111,7 @@
<span x-show="repo.language" class="text-xs px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-400 flex-shrink-0" x-text="repo.language"></span>
</div>
<p x-show="repo.description" class="text-xs text-surface-600 dark:text-surface-400 mt-1 line-clamp-2" x-text="repo.description"></p>
<div class="flex items-center gap-3 mt-1.5 text-xs text-surface-500">
<div class="flex items-center gap-3 mt-1.5 text-xs text-surface-600 dark:text-surface-400">
<span x-show="repo.stars > 0" class="flex items-center gap-1">
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
<span x-text="repo.stars"></span>
@@ -117,11 +125,11 @@
</li>
</template>
</ul>
<div x-show="featured.length === 0" class="text-sm text-surface-500 py-2">No featured projects.</div>
<div x-show="featured.length === 0" class="text-sm text-surface-600 dark:text-surface-400 py-2">No featured projects.</div>
</div>
{# PRs Tab #}
<div x-show="activeTab === 'prs' && !loading" x-cloak>
<div x-show="activeTab === 'prs' && !loading" x-cloak role="tabpanel" id="gh-panel-prs" aria-labelledby="gh-tab-prs">
<ul x-show="contributions.length > 0" class="space-y-3">
<template x-for="item in contributions.slice(0, 5)" :key="item.url">
<li class="border-b border-surface-200 dark:border-surface-700 pb-3 last:border-0">
@@ -136,7 +144,7 @@
</span>
<span class="text-sm text-surface-700 dark:text-surface-300 group-hover:text-surface-900 dark:group-hover:text-surface-100 transition-colors truncate" x-text="item.title"></span>
</div>
<div class="flex items-center gap-2 mt-1.5 text-xs text-surface-500 pl-6">
<div class="flex items-center gap-2 mt-1.5 text-xs text-surface-600 dark:text-surface-400 pl-6">
<span x-text="item.repo?.split('/')[1] || item.repo"></span>
<span x-show="item.number" x-text="'#' + item.number"></span>
<span class="font-mono" x-text="formatDate(item.date)"></span>
@@ -145,7 +153,7 @@
</li>
</template>
</ul>
<div x-show="contributions.length === 0" class="text-sm text-surface-500 py-2">No recent PRs or issues.</div>
<div x-show="contributions.length === 0" class="text-sm text-surface-600 dark:text-surface-400 py-2">No recent PRs or issues.</div>
</div>
</div>

View File

@@ -10,7 +10,7 @@
<div class="space-y-3">
{% if _prevPost %}
<div class="border-b border-surface-200 dark:border-surface-700 pb-3">
<span class="text-xs text-surface-500 uppercase tracking-wide block mb-1">Previous</span>
<span class="text-xs text-surface-600 dark:text-surface-400 uppercase tracking-wide block mb-1">Previous</span>
{% 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 %}
@@ -36,7 +36,7 @@
{% endif %}
{% if _nextPost %}
<div>
<span class="text-xs text-surface-500 uppercase tracking-wide block mb-1">Next</span>
<span class="text-xs text-surface-600 dark:text-surface-400 uppercase tracking-wide block mb-1">Next</span>
{% 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 %}

View File

@@ -20,7 +20,7 @@
<a href="{{ post.url }}" class="text-sm text-accent-600 dark:text-accent-400 hover:underline line-clamp-1">
Liked {{ _likedUrl | replace("https://", "") | truncate(40) }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published or post.date }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published or post.date }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
</div>
@@ -33,7 +33,7 @@
<a href="{{ post.url }}" class="text-sm text-accent-600 dark:text-accent-400 hover:underline line-clamp-1">
{{ post.data.title or ("Bookmarked " + (_bookmarkedUrl | replace("https://", "") | truncate(35))) }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published or post.date }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published or post.date }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
</div>
@@ -46,7 +46,7 @@
<a href="{{ post.url }}" class="text-sm text-accent-600 dark:text-accent-400 hover:underline line-clamp-1">
Reposted {{ _repostedUrl | replace("https://", "") | truncate(40) }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published or post.date }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published or post.date }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
</div>
@@ -59,7 +59,7 @@
<a href="{{ post.url }}" class="text-sm text-sky-600 dark:text-sky-400 hover:underline line-clamp-1">
Reply to {{ _replyToUrl | replace("https://", "") | truncate(40) }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published or post.date }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published or post.date }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
</div>
@@ -69,7 +69,7 @@
<a href="{{ post.url }}" class="text-sm text-accent-600 dark:text-accent-400 hover:underline line-clamp-2">
{{ post.data.title or post.data.name or (post.templateContent | striptags | truncate(50)) or "Note" }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published or post.date }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published or post.date }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
{% endif %}

View File

@@ -22,7 +22,7 @@
<a href="{{ post.url }}" class="text-sm text-red-600 dark:text-red-400 hover:underline break-all line-clamp-1">
Liked {{ likedUrl | replace("https://", "") | truncate(40) }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
</div>
@@ -37,7 +37,7 @@
<a href="{{ post.url }}" class="text-sm text-amber-600 dark:text-amber-400 hover:underline line-clamp-1">
{{ post.data.title or ("Bookmarked " + (bookmarkedUrl | replace("https://", "") | truncate(35))) }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
</div>
@@ -52,7 +52,7 @@
<a href="{{ post.url }}" class="text-sm text-green-600 dark:text-green-400 hover:underline break-all line-clamp-1">
Reposted {{ repostedUrl | replace("https://", "") | truncate(40) }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
</div>
@@ -67,7 +67,7 @@
<a href="{{ post.url }}" class="text-sm text-sky-600 dark:text-sky-400 hover:underline break-all line-clamp-1">
Reply to {{ replyToUrl | replace("https://", "") | truncate(40) }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
</div>
@@ -83,7 +83,7 @@
<a href="{{ post.url }}" class="text-sm text-indigo-600 dark:text-indigo-400 hover:underline line-clamp-1">
{{ post.data.title }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
</div>
@@ -99,7 +99,7 @@
<a href="{{ post.url }}" class="text-sm text-teal-600 dark:text-teal-400 hover:underline line-clamp-1">
{{ post.templateContent | striptags | truncate(50) or "Note" }}
</a>
<time class="text-xs text-surface-500 block font-mono" datetime="{{ post.data.published }}">
<time class="text-xs text-surface-600 dark:text-surface-400 block font-mono" datetime="{{ post.data.published }}">
{{ (post.data.published or post.date) | dateDisplay }}
</time>
</div>

View File

@@ -8,7 +8,8 @@
target="_blank"
rel="noopener"
class="flex-1 inline-flex items-center justify-center gap-2 px-3 py-2 rounded-lg bg-[#0085ff]/10 text-[#0085ff] hover:bg-[#0085ff]/20 transition-colors text-sm font-medium"
title="Share on Bluesky">
title="Share on Bluesky"
aria-label="Share on Bluesky">
<svg class="w-4 h-4" viewBox="0 0 568 501" fill="currentColor" aria-hidden="true">
<path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664Z"/>
</svg>
@@ -17,7 +18,8 @@
<a href="https://share.joinmastodon.org/#text={{ shareText | urlencode }}"
@click="handleClick($event)"
class="w-full inline-flex items-center justify-center gap-2 px-3 py-2 rounded-lg bg-[#a730b8]/10 text-[#a730b8] hover:bg-[#a730b8]/20 transition-colors text-sm font-medium cursor-pointer"
title="Share on the Fediverse (Shift+click to change instance)">
title="Share on the Fediverse (Shift+click to change instance)"
aria-label="Share on the Fediverse">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M13.09 4.43L24 10.73v2.51L13.09 19.58v-2.51L21.83 12 13.09 6.98v-2.55zM13.09 9.49L17.44 12l-4.35 2.51V9.49z"/><path d="M10.91 4.43L0 10.73v2.51l8.74-5.03v10.09l2.18 1.28V4.43zM6.56 12L2.18 14.51l4.35 2.51V12z"/>
</svg>

View File

@@ -5,11 +5,13 @@
<h3 class="widget-title">Social Activity</h3>
{# Tab buttons #}
<div class="flex gap-1 mb-4 border-b border-surface-200 dark:border-surface-700">
<div class="flex gap-1 mb-4 border-b border-surface-200 dark:border-surface-700" role="tablist" aria-label="Social feeds">
{% if blueskyFeed and blueskyFeed.length %}
<button
@click="activeTab = 'bluesky'"
:class="activeTab === 'bluesky' ? 'border-b-2 border-[#0085ff] text-[#0085ff]' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'bluesky' ? 'border-b-2 border-[#0085ff] text-[#0085ff]' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'bluesky').toString()"
role="tab" id="social-tab-bluesky" aria-controls="social-panel-bluesky"
class="flex items-center gap-1.5 px-3 py-2 text-sm font-medium transition-colors -mb-px"
>
<svg class="w-4 h-4 text-[#0085ff]" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
@@ -21,7 +23,9 @@
{% if mastodonFeed and mastodonFeed.length %}
<button
@click="activeTab = 'mastodon'"
:class="activeTab === 'mastodon' ? 'border-b-2 border-[#a730b8] text-[#a730b8]' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'mastodon' ? 'border-b-2 border-[#a730b8] text-[#a730b8]' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'mastodon').toString()"
role="tab" id="social-tab-mastodon" aria-controls="social-panel-mastodon"
class="flex items-center gap-1.5 px-3 py-2 text-sm font-medium transition-colors -mb-px"
>
<svg class="w-4 h-4 text-[#6364ff]" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
@@ -34,7 +38,7 @@
{# Bluesky Tab Content #}
{% if blueskyFeed and blueskyFeed.length %}
<div x-show="activeTab === 'bluesky'" x-cloak>
<div x-show="activeTab === 'bluesky'" x-cloak role="tabpanel" id="social-panel-bluesky" aria-labelledby="social-tab-bluesky">
<ul class="space-y-3">
{% for post in blueskyFeed | head(5) %}
<li class="border-b border-surface-200 dark:border-surface-700 pb-3 last:border-0">
@@ -42,7 +46,7 @@
<p class="text-sm text-surface-700 dark:text-surface-300 group-hover:text-[#0085ff] transition-colors">
{{ post.text | truncate(140) }}
</p>
<div class="flex items-center gap-3 mt-2 text-xs text-surface-500">
<div class="flex items-center gap-3 mt-2 text-xs text-surface-600 dark:text-surface-400">
<time class="font-mono" datetime="{{ post.createdAt }}">{{ post.createdAt | date("MMM d, yyyy") }}</time>
{% if post.likeCount > 0 %}
<span class="flex items-center gap-1">
@@ -64,7 +68,7 @@
{# Mastodon Tab Content #}
{% if mastodonFeed and mastodonFeed.length %}
<div x-show="activeTab === 'mastodon'" x-cloak>
<div x-show="activeTab === 'mastodon'" x-cloak role="tabpanel" id="social-panel-mastodon" aria-labelledby="social-tab-mastodon">
<ul class="space-y-3">
{% for post in mastodonFeed | head(5) %}
<li class="border-b border-surface-200 dark:border-surface-700 pb-3 last:border-0">
@@ -72,7 +76,7 @@
<p class="text-sm text-surface-700 dark:text-surface-300 group-hover:text-[#a730b8] transition-colors">
{{ post.text | truncate(140) }}
</p>
<div class="flex items-center gap-3 mt-2 text-xs text-surface-500">
<div class="flex items-center gap-3 mt-2 text-xs text-surface-600 dark:text-surface-400">
<time class="font-mono" datetime="{{ post.createdAt }}">{{ post.createdAt | date("MMM d, yyyy") }}</time>
{% if post.favouritesCount > 0 %}
<span class="flex items-center gap-1">

View File

@@ -4,13 +4,13 @@
<h3 class="widget-title">Subscribe</h3>
<div class="space-y-2">
<a href="/feed.xml" class="flex items-center gap-2 text-sm text-surface-600 dark:text-surface-400 hover:text-orange-600 dark:hover:text-orange-400 hover:underline transition-colors">
<svg class="w-4 h-4 text-orange-500" fill="currentColor" viewBox="0 0 24 24">
<svg class="w-4 h-4 text-orange-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path d="M6.18 15.64a2.18 2.18 0 0 1 2.18 2.18C8.36 19 7.38 20 6.18 20C5 20 4 19 4 17.82a2.18 2.18 0 0 1 2.18-2.18M4 4.44A15.56 15.56 0 0 1 19.56 20h-2.83A12.73 12.73 0 0 0 4 7.27V4.44m0 5.66a9.9 9.9 0 0 1 9.9 9.9h-2.83A7.07 7.07 0 0 0 4 12.93V10.1Z"/>
</svg>
RSS Feed
</a>
<a href="/feed.json" class="flex items-center gap-2 text-sm text-surface-600 dark:text-surface-400 hover:text-orange-600 dark:hover:text-orange-400 hover:underline transition-colors">
<svg class="w-4 h-4 text-yellow-500" fill="currentColor" viewBox="0 0 24 24">
<svg class="w-4 h-4 text-yellow-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m3 12h2v2H8v-2m4-8h2v10h-2V7m4 4h2v6h-2v-6Z"/>
</svg>
JSON Feed

View File

@@ -3,7 +3,7 @@
<is-land on:visible>
<div class="widget">
<h3 class="widget-title">Contents</h3>
<nav class="toc">
<nav class="toc" aria-label="Table of contents">
<ul class="space-y-1 text-sm">
{% for item in toc %}
<li class="{% if item.level > 2 %}ml-{{ (item.level - 2) * 3 }}{% endif %}">

View File

@@ -14,14 +14,14 @@
<div class="flex border-b border-surface-200 dark:border-surface-700 mb-3">
<button
@click="tab = 'inbound'"
:class="tab === 'inbound' ? 'border-accent-500 text-accent-600 dark:text-accent-400' : 'border-transparent text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="tab === 'inbound' ? 'border-accent-500 text-accent-600 dark:text-accent-400' : 'border-transparent text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
class="px-2 py-1.5 text-xs font-medium border-b-2 -mb-px transition-colors">
Received
<span x-show="mentions.length" x-text="mentions.length" class="ml-0.5 text-xs opacity-75"></span>
</button>
<button
@click="tab = 'outbound'"
:class="tab === 'outbound' ? 'border-accent-500 text-accent-600 dark:text-accent-400' : 'border-transparent text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="tab === 'outbound' ? 'border-accent-500 text-accent-600 dark:text-accent-400' : 'border-transparent text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
class="px-2 py-1.5 text-xs font-medium border-b-2 -mb-px transition-colors">
Sent
</button>
@@ -52,14 +52,14 @@
<span x-show="wm['wm-property'] === 'in-reply-to'" class="text-sky-500"> replied to</span>
<span x-show="wm['wm-property'] === 'mention-of'" class="text-amber-500"> mentioned</span>
<span x-show="wm['wm-property'] === 'bookmark-of'" class="text-purple-500"> bookmarked</span>
<a :href="wm['wm-target']" class="text-surface-500 hover:underline block truncate" x-text="formatPath(wm['wm-target'])"></a>
<a :href="wm['wm-target']" class="text-surface-600 dark:text-surface-400 hover:underline block truncate" x-text="formatPath(wm['wm-target'])"></a>
</div>
</div>
</template>
</div>
{# Empty #}
<p x-show="!loading && !mentions.length && !error" class="text-xs text-surface-500 py-2">No webmentions received yet.</p>
<p x-show="!loading && !mentions.length && !error" class="text-xs text-surface-600 dark:text-surface-400 py-2">No webmentions received yet.</p>
{# Error #}
<p x-show="error" class="text-xs text-red-500 py-2" x-text="error"></p>

View File

@@ -135,18 +135,19 @@
}
})();
</script>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header class="site-header">
<div class="container header-container">
<a href="/" class="site-title">{{ site.name }}</a>
{# Mobile menu button #}
<button id="menu-toggle" type="button" class="menu-toggle" aria-label="Toggle menu" aria-expanded="false">
<svg class="menu-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<svg class="menu-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
<line x1="3" y1="6" x2="21" y2="6"/>
<line x1="3" y1="12" x2="21" y2="12"/>
<line x1="3" y1="18" x2="21" y2="18"/>
</svg>
<svg class="close-icon hidden" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<svg class="close-icon hidden" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
@@ -159,33 +160,33 @@
<a href="/about/">About</a>
<a href="/now/">Now</a>
{# Blog dropdown #}
<div class="nav-dropdown" x-data="{ open: false }" @mouseenter="open = true" @mouseleave="open = false">
<a href="/blog/" class="nav-dropdown-trigger">
<div class="nav-dropdown" x-data="{ open: false }" @mouseenter="open = true" @mouseleave="open = false" @focusin="open = true" @focusout="!$el.contains($event.relatedTarget) && (open = false)" @keydown.escape="open = false">
<a href="/blog/" class="nav-dropdown-trigger" :aria-expanded="open.toString()" aria-haspopup="true">
Blog
<svg class="w-3 h-3 ml-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-3 h-3 ml-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</a>
<div class="nav-dropdown-menu" x-show="open" x-transition x-cloak>
<a href="/blog/">All Posts</a>
<div class="nav-dropdown-menu" x-show="open" x-transition x-cloak role="menu">
<a href="/blog/" role="menuitem">All Posts</a>
{% for pt in enabledPostTypes %}
<a href="{{ pt.path }}">{{ pt.label }}</a>
<a href="{{ pt.path }}" role="menuitem">{{ pt.label }}</a>
{% endfor %}
</div>
</div>
{# Pages dropdown #}
<div class="nav-dropdown" x-data="{ open: false }" @mouseenter="open = true" @mouseleave="open = false">
<a href="/slashes/" class="nav-dropdown-trigger">
<div class="nav-dropdown" x-data="{ open: false }" @mouseenter="open = true" @mouseleave="open = false" @focusin="open = true" @focusout="!$el.contains($event.relatedTarget) && (open = false)" @keydown.escape="open = false">
<a href="/slashes/" class="nav-dropdown-trigger" :aria-expanded="open.toString()" aria-haspopup="true">
Pages
<svg class="w-3 h-3 ml-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-3 h-3 ml-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</a>
<div class="nav-dropdown-menu" x-show="open" x-transition x-cloak>
{% if blogrollStatus and blogrollStatus.source == "indiekit" %}<a href="/blogroll/">Blogroll</a>{% endif %}
{% if podrollStatus and podrollStatus.source == "indiekit" %}<a href="/podroll/">Podroll</a>{% endif %}
{% if newsActivity and newsActivity.source == "indiekit" %}<a href="/news/">News</a>{% endif %}
<a href="/slashes/">All Pages</a>
<div class="nav-dropdown-menu" x-show="open" x-transition x-cloak role="menu">
{% if blogrollStatus and blogrollStatus.source == "indiekit" %}<a href="/blogroll/" role="menuitem">Blogroll</a>{% endif %}
{% if podrollStatus and podrollStatus.source == "indiekit" %}<a href="/podroll/" role="menuitem">Podroll</a>{% endif %}
{% if newsActivity and newsActivity.source == "indiekit" %}<a href="/news/" role="menuitem">News</a>{% endif %}
<a href="/slashes/" role="menuitem">All Pages</a>
</div>
</div>
<a href="/interactions/">Interactions</a>
@@ -196,23 +197,23 @@
x-transition
@indiekit:auth.window="show = $event.detail.loggedIn"
class="admin-nav-link">
<svg class="w-4 h-4 inline -mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg class="w-4 h-4 inline -mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/>
</svg>
Dashboard
</a>
</nav>
<a href="/search/" aria-label="Search" title="Search" class="p-2 rounded-lg text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>
</svg>
</a>
<button id="theme-toggle" type="button" class="theme-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">
<svg class="sun-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg class="sun-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="5"/>
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
<svg class="moon-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg class="moon-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
</button>
@@ -226,13 +227,13 @@
<a href="/now/">Now</a>
{# Blog section #}
<div class="mobile-nav-section">
<button type="button" class="mobile-nav-toggle" @click="blogOpen = !blogOpen">
<button type="button" class="mobile-nav-toggle" @click="blogOpen = !blogOpen" :aria-expanded="blogOpen.toString()" aria-controls="mobile-blog-submenu">
Blog
<svg class="w-4 h-4 transition-transform" :class="{ 'rotate-180': blogOpen }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-4 h-4 transition-transform" :class="{ 'rotate-180': blogOpen }" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div class="mobile-nav-submenu" x-show="blogOpen" x-collapse>
<div id="mobile-blog-submenu" class="mobile-nav-submenu" x-show="blogOpen" x-collapse>
<a href="/blog/">All Posts</a>
{% for pt in enabledPostTypes %}
<a href="{{ pt.path }}">{{ pt.label }}</a>
@@ -241,13 +242,13 @@
</div>
{# Pages section #}
<div class="mobile-nav-section">
<button type="button" class="mobile-nav-toggle" @click="pagesOpen = !pagesOpen">
<button type="button" class="mobile-nav-toggle" @click="pagesOpen = !pagesOpen" :aria-expanded="pagesOpen.toString()" aria-controls="mobile-pages-submenu">
Pages
<svg class="w-4 h-4 transition-transform" :class="{ 'rotate-180': pagesOpen }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-4 h-4 transition-transform" :class="{ 'rotate-180': pagesOpen }" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div class="mobile-nav-submenu" x-show="pagesOpen" x-collapse>
<div id="mobile-pages-submenu" class="mobile-nav-submenu" x-show="pagesOpen" x-collapse>
{% if blogrollStatus and blogrollStatus.source == "indiekit" %}<a href="/blogroll/">Blogroll</a>{% endif %}
{% if podrollStatus and podrollStatus.source == "indiekit" %}<a href="/podroll/">Podroll</a>{% endif %}
{% if newsActivity and newsActivity.source == "indiekit" %}<a href="/news/">News</a>{% endif %}
@@ -267,11 +268,11 @@
<button type="button" class="mobile-theme-toggle" aria-label="Toggle dark mode">
<span class="theme-label">Theme</span>
<span class="theme-icons">
<svg class="sun-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg class="sun-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="5"/>
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
<svg class="moon-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg class="moon-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
</span>
@@ -279,7 +280,7 @@
</nav>
</header>
<main class="container py-8" data-pagefind-body>
<main class="container py-8" id="main-content" data-pagefind-body>
{# Skeleton loader — shown until Tailwind stylesheet loads #}
<div class="page-skeleton" aria-hidden="true">
<div style="display:flex;gap:1.5rem;align-items:flex-start;margin-bottom:2rem">
@@ -329,7 +330,7 @@
<div class="grid grid-cols-2 md:grid-cols-4 gap-8 mb-8">
{# Navigate #}
<div>
<h4 class="text-sm font-semibold uppercase tracking-wider text-surface-500 dark:text-surface-400 mb-3">Navigate</h4>
<h4 class="text-sm font-semibold uppercase tracking-wider text-surface-600 dark:text-surface-400 mb-3">Navigate</h4>
<ul class="space-y-2">
<li><a href="/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Home</a></li>
<li><a href="/about/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">About</a></li>
@@ -339,7 +340,7 @@
</div>
{# Content #}
<div>
<h4 class="text-sm font-semibold uppercase tracking-wider text-surface-500 dark:text-surface-400 mb-3">Content</h4>
<h4 class="text-sm font-semibold uppercase tracking-wider text-surface-600 dark:text-surface-400 mb-3">Content</h4>
<ul class="space-y-2">
<li><a href="/blog/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Blog</a></li>
{% for pt in enabledPostTypes %}
@@ -350,7 +351,7 @@
</div>
{# Connect #}
<div>
<h4 class="text-sm font-semibold uppercase tracking-wider text-surface-500 dark:text-surface-400 mb-3">Connect</h4>
<h4 class="text-sm font-semibold uppercase tracking-wider text-surface-600 dark:text-surface-400 mb-3">Connect</h4>
<ul class="space-y-2">
{% for social in site.social %}
<li><a href="{{ social.url }}" rel="{{ social.rel }}" target="_blank" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">{{ social.name }}</a></li>
@@ -359,7 +360,7 @@
</div>
{# Meta #}
<div>
<h4 class="text-sm font-semibold uppercase tracking-wider text-surface-500 dark:text-surface-400 mb-3">Meta</h4>
<h4 class="text-sm font-semibold uppercase tracking-wider text-surface-600 dark:text-surface-400 mb-3">Meta</h4>
<ul class="space-y-2">
<li><a href="/feed.xml" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">RSS Feed</a></li>
<li><a href="/feed.json" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">JSON Feed</a></li>
@@ -367,7 +368,7 @@
</ul>
</div>
</div>
<p class="text-center text-sm text-surface-500 dark:text-surface-400">Powered by <a href="https://getindiekit.com" class="hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Indiekit</a> + <a href="https://11ty.dev" class="hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Eleventy</a></p>
<p class="text-center text-sm text-surface-600 dark:text-surface-400">Powered by <a href="https://getindiekit.com" class="hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Indiekit</a> + <a href="https://11ty.dev" class="hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Eleventy</a></p>
</div>
</footer>
<script>
@@ -469,43 +470,43 @@
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 translate-y-4"
class="fab-menu">
class="fab-menu" role="menu" aria-label="Create new post">
{% if mpUrl %}
<a href="/posts/edit?url={{ mpUrl | urlencode }}" @click="open = false" class="fab-menu-item" rel="nofollow">
<svg class="w-5 h-5 text-accent-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<a href="/posts/edit?url={{ mpUrl | urlencode }}" @click="open = false" class="fab-menu-item" rel="nofollow" role="menuitem">
<svg class="w-5 h-5 text-accent-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
<span>Edit this post</span>
</a>
<div class="fab-menu-divider"></div>
<div class="fab-menu-divider" role="separator"></div>
{% endif %}
<a href="/posts/create?type=page" @click="open = false" class="fab-menu-item" rel="nofollow">
<svg class="w-5 h-5 text-surface-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<a href="/posts/create?type=page" @click="open = false" class="fab-menu-item" rel="nofollow" role="menuitem">
<svg class="w-5 h-5 text-surface-600 dark:text-surface-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>
</svg>
<span>Page</span>
</a>
<a href="/posts/create?type=bookmark" @click="open = false" class="fab-menu-item" rel="nofollow">
<svg class="w-5 h-5 text-surface-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<a href="/posts/create?type=bookmark" @click="open = false" class="fab-menu-item" rel="nofollow" role="menuitem">
<svg class="w-5 h-5 text-surface-600 dark:text-surface-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/>
</svg>
<span>Bookmark</span>
</a>
<a href="/posts/create?type=photo" @click="open = false" class="fab-menu-item" rel="nofollow">
<svg class="w-5 h-5 text-surface-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<a href="/posts/create?type=photo" @click="open = false" class="fab-menu-item" rel="nofollow" role="menuitem">
<svg class="w-5 h-5 text-surface-600 dark:text-surface-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>
</svg>
<span>Photo</span>
</a>
<a href="/posts/create?type=article" @click="open = false" class="fab-menu-item" rel="nofollow">
<svg class="w-5 h-5 text-surface-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<a href="/posts/create?type=article" @click="open = false" class="fab-menu-item" rel="nofollow" role="menuitem">
<svg class="w-5 h-5 text-surface-600 dark:text-surface-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>
</svg>
<span>Article</span>
</a>
<a href="/posts/create?type=note" @click="open = false" class="fab-menu-item" rel="nofollow">
<svg class="w-5 h-5 text-surface-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<a href="/posts/create?type=note" @click="open = false" class="fab-menu-item" rel="nofollow" role="menuitem">
<svg class="w-5 h-5 text-surface-600 dark:text-surface-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
</svg>
<span>Note</span>
@@ -515,8 +516,9 @@
<button @click="open = !open"
class="fab-button"
:aria-expanded="open"
aria-label="Create new post">
<svg class="w-7 h-7 transition-transform duration-200" :class="{ 'rotate-45': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5" stroke-linecap="round">
aria-label="Create new post"
aria-haspopup="menu">
<svg class="w-7 h-7 transition-transform duration-200" :class="{ 'rotate-45': open }" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5" stroke-linecap="round" aria-hidden="true">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
</button>

View File

@@ -90,7 +90,7 @@ withSidebar: true
{% if post.data.summary %}
<p class="text-sm text-surface-600 dark:text-surface-400 mb-2 line-clamp-2">{{ post.data.summary }}</p>
{% endif %}
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<time class="font-mono" datetime="{{ post.data.published or post.date }}">
{{ (post.data.published or post.date) | date("MMM d, yyyy") }}
</time>

View File

@@ -16,7 +16,7 @@ withSidebar: true
</p>
{% endif %}
{% if updated %}
<p class="text-sm text-surface-500 dark:text-surface-400 mt-2">
<p class="text-sm text-surface-600 dark:text-surface-400 mt-2">
Last updated: <time class="dt-updated font-mono text-sm" datetime="{{ updated | isoDate }}">{{ updated | dateDisplay }}</time>
</p>
{% endif %}
@@ -35,19 +35,19 @@ withSidebar: true
<div class="grid gap-4 sm:grid-cols-4 mb-6">
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100">{{ stats.total }}</div>
<div class="text-xs text-surface-500 dark:text-surface-400">Total posts</div>
<div class="text-xs text-surface-600 dark:text-surface-400">Total posts</div>
</div>
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold text-amber-600 dark:text-amber-400">{{ stats.aiCount }}</div>
<div class="text-xs text-surface-500 dark:text-surface-400">AI-involved</div>
<div class="text-xs text-surface-600 dark:text-surface-400">AI-involved</div>
</div>
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold text-emerald-600 dark:text-emerald-400">{{ stats.total - stats.aiCount }}</div>
<div class="text-xs text-surface-500 dark:text-surface-400">Human-only</div>
<div class="text-xs text-surface-600 dark:text-surface-400">Human-only</div>
</div>
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100">{{ stats.percentage }}%</div>
<div class="text-xs text-surface-500 dark:text-surface-400">AI ratio</div>
<div class="text-xs text-surface-600 dark:text-surface-400">AI ratio</div>
</div>
</div>
@@ -69,7 +69,7 @@ withSidebar: true
{# Post graph showing AI posts (highlighted) on the full year grid #}
<h3 class="text-lg font-semibold text-surface-900 dark:text-surface-100 mb-3">AI-Involved Posts Over Time</h3>
<p class="text-sm text-surface-500 dark:text-surface-400 mb-4">Highlighted days had posts with AI involvement (level 1+). Empty boxes represent days with no AI-involved posts.</p>
<p class="text-sm text-surface-600 dark:text-surface-400 mb-4">Highlighted days had posts with AI involvement (level 1+). Empty boxes represent days with no AI-involved posts.</p>
{% postGraph aiPostsList, { prefix: "ai", highlightColorLight: "#d97706", highlightColorDark: "#fbbf24" } %}
</section>
{% endif %}
@@ -82,7 +82,7 @@ withSidebar: true
{% if aiTextLevel or aiCodeLevel or aiTools %}
<aside class="mt-6 p-4 rounded-lg bg-surface-50 dark:bg-surface-800/50 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="flex items-center gap-2 mb-2">
<svg class="w-4 h-4 text-surface-500 dark:text-surface-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<svg class="w-4 h-4 text-surface-600 dark:text-surface-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714a2.25 2.25 0 00.659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 01-1.59.659H9.06a2.25 2.25 0 01-1.591-.659L5 14.5m14 0V17a2 2 0 01-2 2H7a2 2 0 01-2-2v-2.5"/>
</svg>
<strong class="text-sm font-semibold text-surface-700 dark:text-surface-300">AI Usage</strong>
@@ -105,7 +105,7 @@ withSidebar: true
{% endif %}
</div>
{% if aiDescription %}
<p class="mt-2 text-xs text-surface-500 dark:text-surface-400">{{ aiDescription }}</p>
<p class="mt-2 text-xs text-surface-600 dark:text-surface-400">{{ aiDescription }}</p>
{% endif %}
<p class="mt-2 text-xs"><a href="/ai/" class="text-accent-600 dark:text-accent-400 hover:underline">Learn more about AI usage on this site &rarr;</a></p>
</aside>

View File

@@ -13,7 +13,7 @@ withBlogSidebar: true
<h1 class="p-name text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100 mb-3 sm:mb-4">{{ title }}</h1>
{% else %}
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-medium text-surface-500 dark:text-surface-400">
<span class="text-sm font-medium text-surface-600 dark:text-surface-400">
{% if replyTo %}&#8617; Reply{% elif likedUrl %}&#9829; Like{% elif repostedUrl %}&#9851; Repost{% elif bookmarkedUrl %}&#128278; Bookmark{% else %}&#9998; Note{% endif %}
</span>
</div>
@@ -73,7 +73,7 @@ withBlogSidebar: true
{% set aiCodeLevel = aiCodeLevel or ai_code_level %}
{% set aiTools = aiTools or ai_tools %}
{% set aiDescription = aiDescription or ai_description %}
<details class="mt-4 text-xs text-surface-500 dark:text-surface-400">
<details class="mt-4 text-xs text-surface-600 dark:text-surface-400">
<summary class="cursor-pointer hover:text-surface-600 dark:hover:text-surface-300 list-none flex items-center gap-1.5 [&::-webkit-details-marker]:hidden">
<svg class="w-3.5 h-3.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714a2.25 2.25 0 00.659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 01-1.59.659H9.06a2.25 2.25 0 01-1.591-.659L5 14.5m14 0V17a2 2 0 01-2 2H7a2 2 0 01-2-2v-2.5"/>
@@ -115,7 +115,7 @@ withBlogSidebar: true
{% if externalSyndication.length or selfHostedApUrl %}
<footer class="post-footer mt-8 pt-6 border-t border-surface-200 dark:border-surface-700">
<div class="flex flex-wrap items-center gap-4">
<span class="text-sm text-surface-500 dark:text-surface-400">Also on:</span>
<span class="text-sm text-surface-600 dark:text-surface-400">Also on:</span>
<div class="flex flex-wrap gap-3">
{# Fediverse remote interaction button (self-hosted ActivityPub) #}
{% if selfHostedApUrl %}
@@ -269,9 +269,10 @@ withBlogSidebar: true
{# Lightbox overlay for article images #}
<template x-teleport="body">
<div x-show="open" x-transition.opacity.duration.200ms
role="dialog" aria-modal="true" aria-label="Image viewer"
class="fixed inset-0 z-[9999] flex items-center justify-center bg-black/90 backdrop-blur-sm"
@click.self="close()">
<button @click="close()" class="absolute top-4 right-4 text-white/70 hover:text-white text-3xl leading-none p-2 z-10" aria-label="Close">&times;</button>
<button x-ref="closeBtn" @click="close()" class="absolute top-4 right-4 text-white/70 hover:text-white text-3xl leading-none p-2 z-10" aria-label="Close lightbox">&times;</button>
<template x-if="images.length > 1">
<button @click="prev()" class="absolute left-4 top-1/2 -translate-y-1/2 text-white/70 hover:text-white text-4xl leading-none p-2 z-10" aria-label="Previous">&lsaquo;</button>
</template>

View File

@@ -72,7 +72,7 @@ permalink: "articles/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
Previous
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Previous
</span>
@@ -84,7 +84,7 @@ permalink: "articles/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>

View File

@@ -26,7 +26,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
<a href="/blog/" class="px-3 py-1.5 text-sm font-medium rounded-full bg-accent-600 text-white dark:bg-accent-700">All Posts <span class="opacity-75">({{ collections.posts.length }})</span></a>
{% for pt in enabledPostTypes %}
{% set collName = pt.label | lower %}
<a href="{{ pt.path }}" class="px-3 py-1.5 text-sm font-medium rounded-full bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-700 border border-surface-200 dark:border-surface-700 transition-colors">{{ pt.label }}{% if collections[collName] %} <span class="text-surface-400 dark:text-surface-500">({{ collections[collName].length }})</span>{% endif %}</a>
<a href="{{ pt.path }}" class="px-3 py-1.5 text-sm font-medium rounded-full bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-700 border border-surface-200 dark:border-surface-700 transition-colors">{{ pt.label }}{% if collections[collName] %} <span class="text-surface-600 dark:text-surface-400">({{ collections[collName].length }})</span>{% endif %}</a>
{% endfor %}
</nav>
<ul class="post-list">
@@ -82,7 +82,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
{% endif %}
</div>
{% unfurl likedUrl %}
<a class="u-like-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
<a class="u-like-of text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
{{ likedUrl }}
</a>
{% if post.templateContent %}
@@ -126,7 +126,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
</h2>
{% endif %}
{% unfurl bookmarkedUrl %}
<a class="u-bookmark-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
<a class="u-bookmark-of text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
{{ bookmarkedUrl }}
</a>
{% if post.templateContent %}
@@ -165,7 +165,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
{% endif %}
</div>
{% unfurl repostedUrl %}
<a class="u-repost-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
<a class="u-repost-of text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
{{ repostedUrl }}
</a>
{% if post.templateContent %}
@@ -204,7 +204,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
{% endif %}
</div>
{% unfurl replyToUrl %}
<a class="u-in-reply-to text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ replyToUrl }}">
<a class="u-in-reply-to text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ replyToUrl }}">
{{ replyToUrl }}
</a>
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
@@ -297,7 +297,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
{# ── Note card (unchanged) ── #}
<div class="post-header">
<a class="u-url" href="{{ post.url }}">
<time-difference><time class="dt-published text-sm text-surface-500 dark:text-surface-400 font-medium font-mono" datetime="{{ post.date | isoDate }}">
<time-difference><time class="dt-published text-sm text-surface-600 dark:text-surface-400 font-medium font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time></time-difference>
</a>
@@ -327,7 +327,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
{% 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") %}
<span class="inline-flex items-center gap-1 mt-2 px-1.5 py-0.5 rounded text-[10px] font-medium bg-surface-100 dark:bg-surface-700 text-surface-500 dark:text-surface-400" title="AI usage: Text level {{ postAiText or '' }}, Code level {{ postAiCode or '' }}">
<span class="inline-flex items-center gap-1 mt-2 px-1.5 py-0.5 rounded text-[10px] font-medium bg-surface-100 dark:bg-surface-700 text-surface-600 dark:text-surface-400" title="AI usage: Text level {{ postAiText or '' }}, Code level {{ postAiCode or '' }}">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714a2.25 2.25 0 00.659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 01-1.59.659H9.06a2.25 2.25 0 01-1.591-.659L5 14.5m14 0V17a2 2 0 01-2 2H7a2 2 0 01-2-2v-2.5"/></svg>
AI{% if postAiText %}: T{{ postAiText }}{% endif %}{% if postAiCode %}/C{{ postAiCode }}{% endif %}
</span>
@@ -349,7 +349,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
Previous
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Previous
</span>
@@ -361,7 +361,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>

View File

@@ -14,17 +14,19 @@ permalink: /blogroll/
<p class="text-surface-600 dark:text-surface-400">
Blogs I follow - <span x-text="blogs.length" class="font-medium"></span> feeds
</p>
<p class="text-xs text-surface-500 mt-2" x-show="status?.lastSync">
<p class="text-xs text-surface-600 dark:text-surface-400 mt-2" x-show="status?.lastSync">
Last synced: <span class="font-mono" x-text="formatDate(status?.lastSync, 'full')"></span>
</p>
</header>
{# Tab Navigation - All Blogs first, then one tab per category #}
<div class="mb-6 border-b border-surface-200 dark:border-surface-700">
<nav class="flex gap-1 overflow-x-auto -mb-px" aria-label="Tabs">
<nav class="flex gap-1 overflow-x-auto -mb-px" role="tablist" aria-label="Blogroll categories">
<button
@click="switchTab('blogs')"
:class="activeTab === 'blogs' ? 'border-orange-500 text-orange-600 dark:text-orange-400' : 'border-transparent text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'blogs' ? 'border-orange-500 text-orange-600 dark:text-orange-400' : 'border-transparent text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'blogs').toString()"
role="tab" id="blogroll-tab-blogs" aria-controls="blogroll-panel-blogs"
class="pb-3 px-3 border-b-2 font-medium text-sm transition-colors whitespace-nowrap flex-shrink-0"
>
All Blogs
@@ -32,7 +34,9 @@ permalink: /blogroll/
<template x-for="cat in categories" :key="cat.name">
<button
@click="switchTab('category:' + cat.name)"
:class="activeTab === 'category:' + cat.name ? 'border-orange-500 text-orange-600 dark:text-orange-400' : 'border-transparent text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'category:' + cat.name ? 'border-orange-500 text-orange-600 dark:text-orange-400' : 'border-transparent text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'category:' + cat.name).toString()"
role="tab" :id="'blogroll-tab-' + cat.name.toLowerCase().replace(/\s+/g, '-')" :aria-controls="'blogroll-panel-' + cat.name.toLowerCase().replace(/\s+/g, '-')"
class="pb-3 px-3 border-b-2 font-medium text-sm transition-colors whitespace-nowrap flex-shrink-0"
>
<span x-text="cat.name"></span>
@@ -46,8 +50,8 @@ permalink: /blogroll/
{# Main Content #}
<div class="main-content">
{# Loading State #}
<div x-show="loading" class="text-center py-12">
<svg class="w-8 h-8 mx-auto text-orange-600 animate-spin mb-4" fill="none" viewBox="0 0 24 24">
<div x-show="loading" class="text-center py-12" role="status">
<svg class="w-8 h-8 mx-auto text-orange-600 animate-spin mb-4" fill="none" viewBox="0 0 24 24" aria-hidden="true">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
@@ -55,13 +59,13 @@ permalink: /blogroll/
</div>
{# Error State #}
<div x-show="error" class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-6">
<div x-show="error" role="alert" class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-6">
<p class="text-red-700 dark:text-red-400" x-text="error"></p>
<button @click="fetchData()" class="mt-2 text-sm text-red-600 hover:text-red-700 underline">Try again</button>
</div>
{# All Blogs Tab #}
<div x-show="activeTab === 'blogs' && !loading" class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div x-show="activeTab === 'blogs' && !loading" role="tabpanel" id="blogroll-panel-blogs" aria-labelledby="blogroll-tab-blogs" class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<template x-for="blog in blogs" :key="blog.id">
<a
:href="blog.siteUrl || blog.feedUrl"
@@ -71,16 +75,16 @@ permalink: /blogroll/
>
<div class="flex items-center gap-3 mb-2">
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-orange-400 to-orange-600 flex items-center justify-center flex-shrink-0 overflow-hidden">
<img x-show="blog.photo" :src="blog.photo" class="w-10 h-10 object-cover" loading="lazy" />
<img x-show="blog.photo" :src="blog.photo" :alt="blog.title" class="w-10 h-10 object-cover" loading="lazy" />
<span x-show="!blog.photo" class="text-white text-sm font-bold" x-text="blog.title?.charAt(0)?.toUpperCase()"></span>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-medium text-surface-900 dark:text-surface-100 truncate group-hover:text-orange-600 dark:group-hover:text-orange-400 transition-colors" x-text="blog.title"></h3>
<p x-show="blog.category" class="text-xs text-surface-500 truncate" x-text="blog.category"></p>
<p x-show="blog.category" class="text-xs text-surface-600 dark:text-surface-400 truncate" x-text="blog.category"></p>
</div>
</div>
<p x-show="blog.description" class="text-sm text-surface-600 dark:text-surface-400 line-clamp-2 mb-3" x-text="blog.description"></p>
<div class="flex items-center gap-3 text-xs text-surface-500">
<div class="flex items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<span x-text="`${blog.itemCount || 0} posts`"></span>
<span :class="blog.status === 'active' ? 'text-green-500' : 'text-red-500'">
<span x-show="blog.status === 'active'" class="flex items-center gap-1">
@@ -99,15 +103,15 @@ permalink: /blogroll/
{# Empty State for Blogs #}
<div x-show="!loading && blogs.length === 0 && activeTab === 'blogs' && !error" class="text-center py-12">
<svg class="w-16 h-16 mx-auto text-surface-300 dark:text-surface-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-16 h-16 mx-auto text-surface-300 dark:text-surface-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"/>
</svg>
<p class="text-surface-600 dark:text-surface-400 text-lg">No blogs yet.</p>
<p class="text-surface-500 text-sm mt-2">Add blogs via the admin dashboard.</p>
<p class="text-surface-600 dark:text-surface-400 text-sm mt-2">Add blogs via the admin dashboard.</p>
</div>
{# Category Items Tab (one for each category) #}
<div x-show="activeTab.startsWith('category:') && !loading" class="space-y-4">
<div x-show="activeTab.startsWith('category:') && !loading" role="tabpanel" :id="'blogroll-panel-' + activeTab.replace('category:', '').toLowerCase().replace(/\s+/g, '-')" :aria-labelledby="'blogroll-tab-' + activeTab.replace('category:', '').toLowerCase().replace(/\s+/g, '-')" class="space-y-4">
<template x-for="item in categoryItems" :key="item.id">
<article class="bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 p-4 sm:p-6 hover:border-orange-400 dark:hover:border-orange-600 transition-colors shadow-sm">
<div class="flex items-start gap-4">
@@ -126,7 +130,7 @@ permalink: /blogroll/
Upcoming
</span>
</div>
<div class="flex flex-wrap items-center gap-2 text-sm text-surface-500 mb-3">
<div class="flex flex-wrap items-center gap-2 text-sm text-surface-600 dark:text-surface-400 mb-3">
<a
:href="item.blog?.siteUrl || '#'"
class="inline-flex items-center gap-1 px-2 py-0.5 bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-400 rounded-full hover:bg-orange-200 dark:hover:bg-orange-900/50 transition-colors"
@@ -167,7 +171,7 @@ permalink: /blogroll/
<a
x-show="item.blog?.siteUrl"
:href="item.blog?.siteUrl"
class="inline-flex items-center gap-2 text-sm text-surface-500 hover:text-surface-700 dark:hover:text-surface-300"
class="inline-flex items-center gap-2 text-sm text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300"
target="_blank"
rel="noopener"
>
@@ -221,11 +225,11 @@ permalink: /blogroll/
{# Empty State for Category Items #}
<div x-show="categoryItems.length === 0 && !error" class="text-center py-12">
<svg class="w-16 h-16 mx-auto text-surface-300 dark:text-surface-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-16 h-16 mx-auto text-surface-300 dark:text-surface-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"/>
</svg>
<p class="text-surface-600 dark:text-surface-400 text-lg">No posts in this category yet.</p>
<p class="text-surface-500 text-sm mt-2">Posts will appear once blogs are synced.</p>
<p class="text-surface-600 dark:text-surface-400 text-sm mt-2">Posts will appear once blogs are synced.</p>
</div>
</div>
</div>
@@ -256,11 +260,11 @@ permalink: /blogroll/
<div class="grid grid-cols-2 gap-3 text-center">
<div class="p-3 bg-surface-50 dark:bg-surface-800 rounded-lg">
<span class="text-2xl font-bold text-orange-600 dark:text-orange-400 block" x-text="status?.blogs?.count || 0"></span>
<span class="text-xs text-surface-500 uppercase">Blogs</span>
<span class="text-xs text-surface-600 dark:text-surface-400 uppercase">Blogs</span>
</div>
<div class="p-3 bg-surface-50 dark:bg-surface-800 rounded-lg">
<span class="text-2xl font-bold text-orange-600 dark:text-orange-400 block" x-text="status?.items?.count || 0"></span>
<span class="text-xs text-surface-500 uppercase">Posts</span>
<span class="text-xs text-surface-600 dark:text-surface-400 uppercase">Posts</span>
</div>
</div>
</div>
@@ -283,7 +287,7 @@ permalink: /blogroll/
class="w-full text-left px-3 py-2 rounded-lg text-sm transition-colors"
>
<span x-text="cat.name"></span>
<span class="text-surface-500" x-text="`(${cat.count})`"></span>
<span class="text-surface-600 dark:text-surface-400" x-text="`(${cat.count})`"></span>
</button>
</li>
</template>

View File

@@ -55,7 +55,7 @@ permalink: "bookmarks/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageN
{% set bookmarkedUrl = post.data.bookmarkOf or post.data.bookmark_of %}
{% if bookmarkedUrl %}
{% unfurl bookmarkedUrl %}
<a class="u-bookmark-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
<a class="u-bookmark-of text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
{{ bookmarkedUrl }}
</a>
{% endif %}
@@ -81,7 +81,7 @@ permalink: "bookmarks/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageN
Previous
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Previous
</span>
@@ -93,7 +93,7 @@ permalink: "bookmarks/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageN
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>

View File

@@ -32,7 +32,7 @@ eleventyComputed:
{% endfor %}
{% if categoryPosts.length > 0 %}
<p class="text-sm text-surface-500 dark:text-surface-400 mb-4">{{ categoryPosts.length }} post{% if categoryPosts.length != 1 %}s{% endif %}</p>
<p class="text-sm text-surface-600 dark:text-surface-400 mb-4">{{ categoryPosts.length }} post{% if categoryPosts.length != 1 %}s{% endif %}</p>
<ul class="post-list">
{% for post in categoryPosts %}
{% set postType = post.inputPath | replace("./content/", "") %}

View File

@@ -14,11 +14,13 @@ withSidebar: false
<div x-data="changelogApp()" x-init="init()">
{# Tab navigation #}
<div class="flex gap-1 mb-6 border-b border-surface-200 dark:border-surface-700 overflow-x-auto">
<div class="flex gap-1 mb-6 border-b border-surface-200 dark:border-surface-700 overflow-x-auto" role="tablist" aria-label="Changelog categories">
<template x-for="tab in tabs" :key="tab.key">
<button
@click="activeTab = tab.key"
:class="activeTab === tab.key ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === tab.key ? 'border-b-2 border-accent-500 text-accent-600 dark:text-accent-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === tab.key).toString()"
role="tab"
class="flex items-center gap-1.5 px-3 py-2 text-sm font-medium transition-colors -mb-px whitespace-nowrap flex-shrink-0"
>
<span x-text="tab.label"></span>
@@ -37,13 +39,13 @@ withSidebar: false
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
<span class="ml-3 text-surface-500">Loading changelog...</span>
<span class="ml-3 text-surface-600 dark:text-surface-400">Loading changelog...</span>
</div>
{# Commit list #}
<div x-show="!loading" x-cloak>
<template x-if="filteredCommits().length === 0">
<p class="text-surface-500 py-8 text-center">No recent activity in this category.</p>
<p class="text-surface-600 dark:text-surface-400 py-8 text-center">No recent activity in this category.</p>
</template>
<ul class="space-y-4">
@@ -64,12 +66,12 @@ withSidebar: false
<a :href="commit.repoUrl" target="_blank" rel="noopener"
class="text-xs px-2 py-0.5 rounded-full bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-400 hover:text-accent-600 dark:hover:text-accent-400"
x-text="commit.repoName"></a>
<span class="text-xs text-surface-500 font-mono" x-text="formatDate(commit.date)"></span>
<span class="text-xs text-surface-600 dark:text-surface-400 font-mono" x-text="formatDate(commit.date)"></span>
<span class="text-xs text-surface-400" x-text="'by ' + commit.author"></span>
</div>
<template x-if="commit.body">
<details class="mt-2">
<summary class="text-xs text-surface-500 cursor-pointer hover:text-surface-700 dark:hover:text-surface-300">Show details</summary>
<summary class="text-xs text-surface-600 dark:text-surface-400 cursor-pointer hover:text-surface-700 dark:hover:text-surface-300">Show details</summary>
<pre class="mt-1 text-xs text-surface-600 dark:text-surface-400 whitespace-pre-wrap break-words bg-surface-50 dark:bg-surface-800 rounded p-2" x-text="commit.body"></pre>
</details>
</template>

View File

@@ -60,6 +60,16 @@ a{color:#b45309}
img{max-width:100%;height:auto}
svg:not(:root):not([width]){width:1.25rem;height:1.25rem}
/* Focus indicators — visible in critical CSS before Tailwind loads */
a:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
.dark a:focus-visible{outline-color:#fbbf24}
button:focus-visible,[type="button"]:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:4px}
.dark button:focus-visible,.dark [type="button"]:focus-visible{outline-color:#fbbf24}
/* Skip link */
.skip-link{position:absolute;top:-100%;left:0;z-index:100;background:#b45309;color:#fff;padding:0.5rem 1rem;font-weight:600;text-decoration:none}
.skip-link:focus{top:0;outline:none}
/* Skeleton loader — visible until Tailwind stylesheet loads */
html.loading main.container>.page-content{display:none}
html:not(.loading) .page-skeleton{display:none}
@@ -67,3 +77,6 @@ html:not(.loading) .page-skeleton{display:none}
.skel-bone{background:#e8e5df;border-radius:.5rem;animation:skel-pulse 1.5s ease-in-out infinite}
.dark .skel-bone{background:#3f3b35}
.skel-circle{border-radius:50%}
/* Reduced motion — disable animations for users who prefer it */
@media(prefers-reduced-motion:reduce){.skel-bone{animation:none}*{transition-duration:0.01ms!important;animation-duration:0.01ms!important}}

View File

@@ -70,8 +70,11 @@ lite-youtube > .lty-playbtn {
}
lite-youtube:hover > .lty-playbtn,
lite-youtube .lty-playbtn:focus {
lite-youtube .lty-playbtn:focus-visible {
filter: none;
outline: 2px solid #fbbf24;
outline-offset: -2px;
border-radius: 4px;
}
/* Post-click styles */

View File

@@ -49,7 +49,7 @@ pre[class*="language-"] {
.token.prolog,
.token.doctype,
.token.cdata {
color: #6a737d;
color: #586069;
}
.token.punctuation {
@@ -57,7 +57,7 @@ pre[class*="language-"] {
}
.token.namespace {
opacity: 0.7;
opacity: 0.85;
}
.token.property,

View File

@@ -288,7 +288,7 @@
/* 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;
@apply mt-12 py-8 border-t border-surface-200 dark:border-surface-700 text-center text-sm text-surface-600 dark:text-surface-400;
}
.site-footer a {
@@ -501,6 +501,13 @@
.pagination-link:active:not(.disabled) {
transform: scale(0.97);
}
@media (prefers-reduced-motion: reduce) {
button:active:not(:disabled),
.pagination-link:active:not(.disabled) {
transform: none;
}
}
}
/* Video embeds */
@@ -630,7 +637,7 @@
transition: opacity 0.15s;
}
.prose :is(h2, h3, h4):hover > a.header-anchor::after {
opacity: 0.4;
opacity: 0.6;
}
.post-list li {
@@ -677,7 +684,7 @@
--pagefind-ui-primary: #fbbf24;
--pagefind-ui-text: #faf8f5;
--pagefind-ui-background: #0f0e0d;
--pagefind-ui-border: #3f3b35;
--pagefind-ui-border: #5c5750;
--pagefind-ui-tag: #2a2722;
}
@@ -692,7 +699,7 @@
.dark #search .pagefind-ui__search-input {
background-color: #1c1b19;
color: #faf8f5;
border-color: #3f3b35;
border-color: #5c5750;
}
#search .pagefind-ui__search-input:focus {

4
cv.njk
View File

@@ -78,7 +78,7 @@ pagefindIgnore: true
{% endif %}
{# Contact details #}
{% if cvLocality or cvCountry or cvOrg or cvUrl or cvEmail or cvKeyUrl %}
<div class="flex flex-wrap gap-x-4 gap-y-1 mt-4 text-sm text-surface-500 dark:text-surface-400">
<div class="flex flex-wrap gap-x-4 gap-y-1 mt-4 text-sm text-surface-600 dark:text-surface-400">
{% if cvLocality or cvCountry %}
<span>{% if cvLocality %}{{ cvLocality }}{% endif %}{% if cvLocality and cvCountry %}, {% endif %}{% if cvCountry %}{{ cvCountry }}{% endif %}</span>
{% endif %}
@@ -126,7 +126,7 @@ pagefindIgnore: true
{# Last Updated #}
{% if cv.lastUpdated %}
<p class="text-sm text-surface-500 text-center mt-8">
<p class="text-sm text-surface-600 dark:text-surface-400 text-center mt-8">
Last updated: <time class="font-mono text-sm" datetime="{{ cv.lastUpdated }}">{{ cv.lastUpdated | date("PPP") }}</time>
</p>
{% endif %}

View File

@@ -27,7 +27,7 @@ permalink: "digest/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumb
<h2 class="font-semibold text-surface-900 dark:text-surface-100 hover:text-accent-600 dark:hover:text-accent-400">
{{ d.label }}
</h2>
<p class="text-sm text-surface-500 dark:text-surface-400 mt-1">
<p class="text-sm text-surface-600 dark:text-surface-400 mt-1">
<time class="font-mono" datetime="{{ d.startDate | isoDate }}">{{ d.startDate | dateDisplay }}</time> &ndash; <time class="font-mono" datetime="{{ d.endDate | isoDate }}">{{ d.endDate | dateDisplay }}</time>
&middot; {{ d.posts.length }} post{% if d.posts.length != 1 %}s{% endif %}
</p>
@@ -36,7 +36,7 @@ permalink: "digest/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumb
{% set typeLabels = (typeLabels.push(key + " (" + posts.length + ")"), typeLabels) %}
{% endfor %}
{% if typeLabels.length %}
<p class="text-xs text-surface-400 dark:text-surface-500 mt-1">
<p class="text-xs text-surface-600 dark:text-surface-400 mt-1">
{{ typeLabels | join(", ") }}
</p>
{% endif %}
@@ -57,7 +57,7 @@ permalink: "digest/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumb
Previous
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Previous
</span>
@@ -69,7 +69,7 @@ permalink: "digest/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumb
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>

View File

@@ -39,7 +39,7 @@ permalink: "digest/{{ digest.slug }}/"
<section class="mb-8">
<h2 class="text-lg sm:text-xl font-semibold text-surface-800 dark:text-surface-200 mb-4 border-b border-surface-200 dark:border-surface-700 pb-2">
{{ typeInfo.label }}
<span class="text-sm font-normal text-surface-500 dark:text-surface-400">({{ typePosts.length }})</span>
<span class="text-sm font-normal text-surface-600 dark:text-surface-400">({{ typePosts.length }})</span>
</h2>
<ul class="space-y-4">
{% for post in typePosts %}
@@ -50,7 +50,7 @@ permalink: "digest/{{ digest.slug }}/"
<span class="text-red-500 flex-shrink-0">&#x2764;</span>
<div>
<a href="{{ targetUrl }}" class="text-accent-600 dark:text-accent-400 hover:underline break-all">{{ targetUrl }}</a>
<div class="text-sm text-surface-500 dark:text-surface-400 mt-1">
<div class="text-sm text-surface-600 dark:text-surface-400 mt-1">
<time class="dt-published font-mono text-sm" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
&middot; <a href="{{ post.url }}" class="hover:underline">Permalink</a>
</div>
@@ -67,7 +67,7 @@ permalink: "digest/{{ digest.slug }}/"
{% else %}
<a href="{{ targetUrl }}" class="text-accent-600 dark:text-accent-400 hover:underline break-all">{{ targetUrl }}</a>
{% endif %}
<div class="text-sm text-surface-500 dark:text-surface-400 mt-1">
<div class="text-sm text-surface-600 dark:text-surface-400 mt-1">
<time class="dt-published font-mono text-sm" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
&middot; <a href="{{ post.url }}" class="hover:underline">Permalink</a>
</div>
@@ -80,7 +80,7 @@ permalink: "digest/{{ digest.slug }}/"
<span class="text-green-500 flex-shrink-0">&#x1F501;</span>
<div>
<a href="{{ targetUrl }}" class="text-accent-600 dark:text-accent-400 hover:underline break-all">{{ targetUrl }}</a>
<div class="text-sm text-surface-500 dark:text-surface-400 mt-1">
<div class="text-sm text-surface-600 dark:text-surface-400 mt-1">
<time class="dt-published font-mono text-sm" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
&middot; <a href="{{ post.url }}" class="hover:underline">Permalink</a>
</div>
@@ -103,7 +103,7 @@ permalink: "digest/{{ digest.slug }}/"
{% elif post.templateContent %}
<p class="text-surface-700 dark:text-surface-300 text-sm">{{ post.templateContent | striptags | truncate(120) }}</p>
{% endif %}
<div class="text-sm text-surface-500 dark:text-surface-400 mt-1">
<div class="text-sm text-surface-600 dark:text-surface-400 mt-1">
<time class="dt-published font-mono text-sm" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
&middot; <a href="{{ post.url }}" class="hover:underline">Permalink</a>
</div>
@@ -117,7 +117,7 @@ permalink: "digest/{{ digest.slug }}/"
{% if post.templateContent %}
<p class="text-surface-700 dark:text-surface-300 text-sm mt-1">{{ post.templateContent | striptags | truncate(200) }}</p>
{% endif %}
<div class="text-sm text-surface-500 dark:text-surface-400 mt-1">
<div class="text-sm text-surface-600 dark:text-surface-400 mt-1">
<time class="dt-published font-mono text-sm" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
&middot; <a href="{{ post.url }}" class="hover:underline">Permalink</a>
</div>
@@ -126,7 +126,7 @@ permalink: "digest/{{ digest.slug }}/"
{% else %}
<div>
<p class="text-surface-700 dark:text-surface-300">{{ post.templateContent | striptags | truncate(200) }}</p>
<div class="text-sm text-surface-500 dark:text-surface-400 mt-1">
<div class="text-sm text-surface-600 dark:text-surface-400 mt-1">
<time class="dt-published font-mono text-sm" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
&middot; <a href="{{ post.url }}" class="hover:underline">Permalink</a>
</div>

View File

@@ -321,7 +321,7 @@ export default function (eleventyConfig) {
cacheOptions: {
duration: process.env.ELEVENTY_RUN_MODE === "build" ? "1d" : "30d",
},
concurrency: 4,
concurrency: 1,
defaultAttributes: {
loading: "lazy",
decoding: "async",

View File

@@ -55,7 +55,7 @@ permalink: "featured/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
<div class="post-header">
<span class="text-xs font-medium text-rose-600 dark:text-rose-400">Liked</span>
<a class="u-url ml-2" href="{{ post.url }}">
<time class="dt-published text-sm text-surface-500 dark:text-surface-400 font-mono" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
<time class="dt-published text-sm text-surface-600 dark:text-surface-400 font-mono" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
</a>
</div>
<a class="u-like-of text-sm text-surface-600 dark:text-surface-400 hover:underline break-all mt-2 inline-block" href="{{ likedUrl }}">{{ likedUrl }}</a>
@@ -67,7 +67,7 @@ permalink: "featured/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
{# ── Bookmark ── #}
<div class="post-header">
<span class="text-xs font-medium text-amber-600 dark:text-amber-400">Bookmarked</span>
<time class="dt-published text-sm text-surface-500 dark:text-surface-400 ml-2 font-mono" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
<time class="dt-published text-sm text-surface-600 dark:text-surface-400 ml-2 font-mono" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
</div>
{% if post.data.title %}
<h2 class="p-name text-lg font-semibold mt-2">
@@ -84,7 +84,7 @@ permalink: "featured/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
<div class="post-header">
<span class="text-xs font-medium text-rose-600 dark:text-rose-400">Reposted</span>
<a class="u-url ml-2" href="{{ post.url }}">
<time class="dt-published text-sm text-surface-500 dark:text-surface-400 font-mono" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
<time class="dt-published text-sm text-surface-600 dark:text-surface-400 font-mono" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
</a>
</div>
<a class="u-repost-of text-sm text-surface-600 dark:text-surface-400 hover:underline break-all mt-2 inline-block" href="{{ repostedUrl }}">{{ repostedUrl }}</a>
@@ -97,7 +97,7 @@ permalink: "featured/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
<div class="post-header">
<span class="text-xs font-medium text-rose-600 dark:text-rose-400">In reply to</span>
<a class="u-url ml-2" href="{{ post.url }}">
<time class="dt-published text-sm text-surface-500 dark:text-surface-400 font-mono" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
<time class="dt-published text-sm text-surface-600 dark:text-surface-400 font-mono" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
</a>
</div>
<a class="u-in-reply-to text-sm text-surface-600 dark:text-surface-400 hover:underline break-all mt-2 inline-block" href="{{ replyToUrl }}">{{ replyToUrl }}</a>
@@ -127,7 +127,7 @@ permalink: "featured/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
{# ── Note ── #}
<div class="post-header">
<a class="u-url" href="{{ post.url }}">
<time class="dt-published text-sm text-surface-500 dark:text-surface-400 font-medium font-mono" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
<time class="dt-published text-sm text-surface-600 dark:text-surface-400 font-medium font-mono" datetime="{{ post.date | isoDate }}">{{ post.date | dateDisplay }}</time>
</a>
{% if post.data.postType %}
<span class="px-2 py-0.5 bg-surface-100 dark:bg-surface-700 rounded text-xs ml-2">{{ post.data.postType }}</span>
@@ -158,7 +158,7 @@ permalink: "featured/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
Previous
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Previous
</span>
@@ -170,7 +170,7 @@ permalink: "featured/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNu
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>

View File

@@ -66,9 +66,9 @@ withSidebar: true
</h2>
<p class="text-surface-600 dark:text-surface-400">{{ funkwhaleActivity.nowPlaying.artist }}</p>
{% if funkwhaleActivity.nowPlaying.album %}
<p class="text-sm text-surface-500 mt-1">{{ funkwhaleActivity.nowPlaying.album }}</p>
<p class="text-sm text-surface-600 dark:text-surface-400 mt-1">{{ funkwhaleActivity.nowPlaying.album }}</p>
{% endif %}
<p class="text-xs text-surface-500 mt-2">{{ funkwhaleActivity.nowPlaying.relativeTime }}</p>
<p class="text-xs text-surface-600 dark:text-surface-400 mt-2">{{ funkwhaleActivity.nowPlaying.relativeTime }}</p>
</div>
</div>
</div>
@@ -86,31 +86,39 @@ withSidebar: true
</h2>
{# Tab buttons #}
<div class="flex gap-1 mb-6 border-b border-surface-200 dark:border-surface-700 overflow-x-auto">
<div class="flex gap-1 mb-6 border-b border-surface-200 dark:border-surface-700 overflow-x-auto" role="tablist" aria-label="Listening statistics period">
<button
@click="activeTab = 'all'"
:class="activeTab === 'all' ? 'border-b-2 border-purple-500 text-purple-600 dark:text-purple-400' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'all' ? 'border-b-2 border-purple-500 text-purple-600 dark:text-purple-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'all').toString()"
role="tab" id="fw-tab-all" aria-controls="fw-panel-all"
class="px-4 py-2 text-sm font-medium transition-colors -mb-px whitespace-nowrap"
>
All Time
</button>
<button
@click="activeTab = 'month'"
:class="activeTab === 'month' ? 'border-b-2 border-purple-500 text-purple-600 dark:text-purple-400' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'month' ? 'border-b-2 border-purple-500 text-purple-600 dark:text-purple-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'month').toString()"
role="tab" id="fw-tab-month" aria-controls="fw-panel-month"
class="px-4 py-2 text-sm font-medium transition-colors -mb-px whitespace-nowrap"
>
This Month
</button>
<button
@click="activeTab = 'week'"
:class="activeTab === 'week' ? 'border-b-2 border-purple-500 text-purple-600 dark:text-purple-400' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'week' ? 'border-b-2 border-purple-500 text-purple-600 dark:text-purple-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'week').toString()"
role="tab" id="fw-tab-week" aria-controls="fw-panel-week"
class="px-4 py-2 text-sm font-medium transition-colors -mb-px whitespace-nowrap"
>
This Week
</button>
<button
@click="activeTab = 'trends'"
:class="activeTab === 'trends' ? 'border-b-2 border-purple-500 text-purple-600 dark:text-purple-400' : 'text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'trends' ? 'border-b-2 border-purple-500 text-purple-600 dark:text-purple-400' : 'text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'trends').toString()"
role="tab" id="fw-tab-trends" aria-controls="fw-panel-trends"
class="px-4 py-2 text-sm font-medium transition-colors -mb-px whitespace-nowrap"
>
Trends
@@ -118,7 +126,7 @@ withSidebar: true
</div>
{# All Time Tab #}
<div x-show="activeTab === 'all'" x-cloak>
<div x-show="activeTab === 'all'" x-cloak role="tabpanel" id="fw-panel-all" aria-labelledby="fw-tab-all">
{% set summary = funkwhaleActivity.stats.summary.all %}
{% set topArtists = funkwhaleActivity.stats.topArtists.all %}
{% set topAlbums = funkwhaleActivity.stats.topAlbums.all %}
@@ -126,7 +134,7 @@ withSidebar: true
</div>
{# This Month Tab #}
<div x-show="activeTab === 'month'" x-cloak>
<div x-show="activeTab === 'month'" x-cloak role="tabpanel" id="fw-panel-month" aria-labelledby="fw-tab-month">
{% set summary = funkwhaleActivity.stats.summary.month %}
{% set topArtists = funkwhaleActivity.stats.topArtists.month %}
{% set topAlbums = funkwhaleActivity.stats.topAlbums.month %}
@@ -134,7 +142,7 @@ withSidebar: true
</div>
{# This Week Tab #}
<div x-show="activeTab === 'week'" x-cloak>
<div x-show="activeTab === 'week'" x-cloak role="tabpanel" id="fw-panel-week" aria-labelledby="fw-tab-week">
{% set summary = funkwhaleActivity.stats.summary.week %}
{% set topArtists = funkwhaleActivity.stats.topArtists.week %}
{% set topAlbums = funkwhaleActivity.stats.topAlbums.week %}
@@ -142,7 +150,7 @@ withSidebar: true
</div>
{# Trends Tab #}
<div x-show="activeTab === 'trends'" x-cloak>
<div x-show="activeTab === 'trends'" x-cloak role="tabpanel" id="fw-panel-trends" aria-labelledby="fw-tab-trends">
{% if funkwhaleActivity.stats.trends and funkwhaleActivity.stats.trends.length %}
<div class="bg-surface-50 dark:bg-surface-800 rounded-lg p-6 border border-surface-200 dark:border-surface-700 shadow-sm">
<h3 class="text-lg font-semibold text-surface-900 dark:text-surface-100 mb-4">Daily Listening (Last 30 Days)</h3>
@@ -161,7 +169,7 @@ withSidebar: true
></div>
{% endfor %}
</div>
<div class="flex justify-between text-xs text-surface-500 mt-2">
<div class="flex justify-between text-xs text-surface-600 dark:text-surface-400 mt-2">
<span>{{ funkwhaleActivity.stats.trends[0].date }}</span>
<span>{{ funkwhaleActivity.stats.trends[funkwhaleActivity.stats.trends.length - 1].date }}</span>
</div>
@@ -210,7 +218,7 @@ withSidebar: true
</div>
<div class="text-right flex-shrink-0">
<span class="text-xs text-surface-500">{{ listening.relativeTime }}</span>
<span class="text-xs text-surface-600 dark:text-surface-400">{{ listening.relativeTime }}</span>
{% if listening.duration %}
<span class="text-xs text-surface-400 block">{{ listening.duration }}</span>
{% endif %}
@@ -258,7 +266,7 @@ withSidebar: true
</h3>
<p class="text-sm text-surface-600 dark:text-surface-400 truncate">{{ favorite.artist }}</p>
{% if favorite.album %}
<p class="text-xs text-surface-500 truncate">{{ favorite.album }}</p>
<p class="text-xs text-surface-600 dark:text-surface-400 truncate">{{ favorite.album }}</p>
{% endif %}
</div>
</div>

View File

@@ -43,7 +43,7 @@ withSidebar: true
<p class="text-sm text-surface-600 dark:text-surface-400 mb-4">{{ repo.description }}</p>
{% endif %}
<div class="flex flex-wrap items-center gap-3 text-sm text-surface-500 mb-4">
<div class="flex flex-wrap items-center gap-3 text-sm text-surface-600 dark:text-surface-400 mb-4">
{% if repo.language %}
<span class="flex items-center gap-1">
<span class="w-3 h-3 rounded-full bg-surface-500"></span>
@@ -109,7 +109,7 @@ withSidebar: true
<a href="{{ commit.url }}" class="text-surface-900 dark:text-surface-100 hover:text-emerald-600 dark:hover:text-emerald-400" target="_blank" rel="noopener">
{{ commit.message }}
</a>
<p class="text-xs text-surface-500 mt-1">
<p class="text-xs text-surface-600 dark:text-surface-400 mt-1">
<a href="{{ commit.repoUrl }}" class="hover:underline" target="_blank" rel="noopener">{{ commit.repo }}</a>
· <time class="font-mono" datetime="{{ commit.date }}">{{ commit.date | date("MMM d, yyyy") }}</time>
</p>
@@ -144,7 +144,7 @@ withSidebar: true
<a href="{{ item.url }}" class="text-surface-900 dark:text-surface-100 hover:text-emerald-600 dark:hover:text-emerald-400" target="_blank" rel="noopener">
{{ item.title }}
</a>
<p class="text-xs text-surface-500 mt-1">
<p class="text-xs text-surface-600 dark:text-surface-400 mt-1">
<a href="{{ item.repoUrl }}" class="hover:underline" target="_blank" rel="noopener">{{ item.repo }}</a>
#{{ item.number }}
· <time class="font-mono" datetime="{{ item.date }}">{{ item.date | date("MMM d, yyyy") }}</time>
@@ -179,7 +179,7 @@ withSidebar: true
<p class="text-sm text-surface-600 dark:text-surface-400 mb-3">{{ repo.description | truncate(100) }}</p>
{% endif %}
<div class="flex flex-wrap items-center gap-3 text-sm text-surface-500">
<div class="flex flex-wrap items-center gap-3 text-sm text-surface-600 dark:text-surface-400">
{% if repo.language %}
<span class="flex items-center gap-1">
<span class="w-3 h-3 rounded-full bg-surface-500"></span>
@@ -242,7 +242,7 @@ withSidebar: true
{% endfor %}
</div>
<div class="flex flex-wrap items-center gap-3 text-sm text-surface-500">
<div class="flex flex-wrap items-center gap-3 text-sm text-surface-600 dark:text-surface-400">
{% if repo.language %}
<span class="flex items-center gap-1">
<span class="w-3 h-3 rounded-full bg-surface-500"></span>

View File

@@ -14,5 +14,5 @@ withSidebar: true
{% if collections.posts and collections.posts.length %}
{% postGraph collections.posts, { limit: 0 } %}
{% else %}
<p class="text-surface-500 dark:text-surface-400">No posts found.</p>
<p class="text-surface-600 dark:text-surface-400">No posts found.</p>
{% endif %}

View File

@@ -17,17 +17,21 @@ permalink: /interactions/
{# Tab navigation for Outbound/Inbound #}
<div x-data="interactionsApp()" x-init="init()">
{# Tab buttons #}
<div class="flex border-b border-surface-200 dark:border-surface-700 mb-6">
<div class="flex border-b border-surface-200 dark:border-surface-700 mb-6" role="tablist" aria-label="Interaction direction">
<button
@click="activeTab = 'outbound'"
:class="activeTab === 'outbound' ? 'border-rose-500 text-rose-600 dark:text-rose-400' : 'border-transparent text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'outbound' ? 'border-rose-500 text-rose-600 dark:text-rose-400' : 'border-transparent text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'outbound').toString()"
role="tab" id="interactions-tab-outbound" aria-controls="interactions-panel-outbound"
class="px-4 py-3 text-sm font-medium border-b-2 -mb-px transition-colors">
My Activity
<span class="ml-1 text-xs text-surface-400">(outbound)</span>
</button>
<button
@click="activeTab = 'inbound'"
:class="activeTab === 'inbound' ? 'border-rose-500 text-rose-600 dark:text-rose-400' : 'border-transparent text-surface-500 hover:text-surface-700 dark:hover:text-surface-300'"
:class="activeTab === 'inbound' ? 'border-rose-500 text-rose-600 dark:text-rose-400' : 'border-transparent text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300'"
:aria-selected="(activeTab === 'inbound').toString()"
role="tab" id="interactions-tab-inbound" aria-controls="interactions-panel-inbound"
class="px-4 py-3 text-sm font-medium border-b-2 -mb-px transition-colors">
Received
<span class="ml-1 text-xs text-surface-400">(inbound)</span>
@@ -36,7 +40,7 @@ permalink: /interactions/
</div>
{# ===== OUTBOUND TAB - My Activity ===== #}
<div x-show="activeTab === 'outbound'" x-transition>
<div x-show="activeTab === 'outbound'" x-transition role="tabpanel" id="interactions-panel-outbound" aria-labelledby="interactions-tab-outbound">
<p class="text-surface-600 dark:text-surface-400 text-sm mb-6">Content I've interacted with across the web.</p>
<div class="grid gap-4 sm:gap-6 md:grid-cols-2 lg:grid-cols-3">
@@ -50,7 +54,7 @@ permalink: /interactions/
</div>
<div>
<h2 class="text-lg font-semibold text-surface-900 dark:text-surface-100 group-hover:text-rose-600 dark:group-hover:text-rose-400">Likes</h2>
<p class="text-sm text-surface-500">{{ collections.likes.length }} item{% if collections.likes.length != 1 %}s{% endif %}</p>
<p class="text-sm text-surface-600 dark:text-surface-400">{{ collections.likes.length }} item{% if collections.likes.length != 1 %}s{% endif %}</p>
</div>
</div>
<p class="text-surface-600 dark:text-surface-400 text-sm">Content I've appreciated across the web.</p>
@@ -66,7 +70,7 @@ permalink: /interactions/
</div>
<div>
<h2 class="text-lg font-semibold text-surface-900 dark:text-surface-100 group-hover:text-rose-600 dark:group-hover:text-rose-400">Replies</h2>
<p class="text-sm text-surface-500">{{ collections.replies.length }} item{% if collections.replies.length != 1 %}s{% endif %}</p>
<p class="text-sm text-surface-600 dark:text-surface-400">{{ collections.replies.length }} item{% if collections.replies.length != 1 %}s{% endif %}</p>
</div>
</div>
<p class="text-surface-600 dark:text-surface-400 text-sm">My responses to posts across the web.</p>
@@ -82,7 +86,7 @@ permalink: /interactions/
</div>
<div>
<h2 class="text-lg font-semibold text-surface-900 dark:text-surface-100 group-hover:text-amber-600 dark:group-hover:text-amber-400">Bookmarks</h2>
<p class="text-sm text-surface-500">{{ collections.bookmarks.length }} item{% if collections.bookmarks.length != 1 %}s{% endif %}</p>
<p class="text-sm text-surface-600 dark:text-surface-400">{{ collections.bookmarks.length }} item{% if collections.bookmarks.length != 1 %}s{% endif %}</p>
</div>
</div>
<p class="text-surface-600 dark:text-surface-400 text-sm">Links I've saved for later.</p>
@@ -98,7 +102,7 @@ permalink: /interactions/
</div>
<div>
<h2 class="text-lg font-semibold text-surface-900 dark:text-surface-100 group-hover:text-rose-600 dark:group-hover:text-rose-400">Reposts</h2>
<p class="text-sm text-surface-500">{{ collections.reposts.length }} item{% if collections.reposts.length != 1 %}s{% endif %}</p>
<p class="text-sm text-surface-600 dark:text-surface-400">{{ collections.reposts.length }} item{% if collections.reposts.length != 1 %}s{% endif %}</p>
</div>
</div>
<p class="text-surface-600 dark:text-surface-400 text-sm">Content I've shared from others.</p>
@@ -114,7 +118,7 @@ permalink: /interactions/
</div>
<div>
<h2 class="text-lg font-semibold text-surface-900 dark:text-surface-100 group-hover:text-purple-600 dark:group-hover:text-purple-400">Photos</h2>
<p class="text-sm text-surface-500">{{ collections.photos.length }} item{% if collections.photos.length != 1 %}s{% endif %}</p>
<p class="text-sm text-surface-600 dark:text-surface-400">{{ collections.photos.length }} item{% if collections.photos.length != 1 %}s{% endif %}</p>
</div>
</div>
<p class="text-surface-600 dark:text-surface-400 text-sm">Photo posts and images.</p>
@@ -137,7 +141,7 @@ permalink: /interactions/
</div>
{# ===== INBOUND TAB - Received Webmentions ===== #}
<div x-show="activeTab === 'inbound'" x-transition>
<div x-show="activeTab === 'inbound'" x-transition role="tabpanel" id="interactions-panel-inbound" aria-labelledby="interactions-tab-inbound">
<div class="flex items-center justify-between mb-6">
<p class="text-surface-600 dark:text-surface-400 text-sm">Webmentions and interactions others have made with my content.</p>
<button
@@ -154,7 +158,7 @@ permalink: /interactions/
{# Loading state #}
<div x-show="loading && !webmentions.length" class="text-center py-12">
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-rose-500"></div>
<p class="mt-4 text-surface-500">Loading webmentions...</p>
<p class="mt-4 text-surface-600 dark:text-surface-400">Loading webmentions...</p>
</div>
{# Setup required state — shown when webmentions proxy is not configured #}
@@ -263,7 +267,7 @@ permalink: /interactions/
<svg class="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
</span>
<a :href="wm.url || '#'" target="_blank" rel="noopener" class="text-xs text-surface-500 hover:underline">
<a :href="wm.url || '#'" target="_blank" rel="noopener" class="text-xs text-surface-600 dark:text-surface-400 hover:underline">
<time class="font-mono text-sm" :datetime="wm.published || wm['wm-received']" x-text="formatDate(wm.published || wm['wm-received'])"></time>
</a>
</div>
@@ -272,7 +276,7 @@ permalink: /interactions/
<div x-show="wm.content?.text" class="text-surface-700 dark:text-surface-300 text-sm mt-2" x-text="truncateText(wm.content?.text, 280)"></div>
{# Target URL - which of my posts this is about #}
<div class="mt-2 text-xs text-surface-500">
<div class="mt-2 text-xs text-surface-600 dark:text-surface-400">
<span>on </span>
<a :href="wm['wm-target']" class="text-rose-600 dark:text-rose-400 hover:underline" x-text="formatTargetUrl(wm['wm-target'])"></a>
</div>
@@ -282,7 +286,7 @@ permalink: /interactions/
</template>
{# Empty state #}
<div x-show="!loading && filteredWebmentions.length === 0" class="text-center py-12 text-surface-500">
<div x-show="!loading && filteredWebmentions.length === 0" class="text-center py-12 text-surface-600 dark:text-surface-400">
<p>No webmentions found for this filter.</p>
</div>
@@ -291,7 +295,7 @@ permalink: /interactions/
<nav class="pagination" aria-label="Webmentions pagination">
<div class="pagination-info">
Page <span x-text="currentPage"></span> of <span x-text="totalPages"></span>
<span class="text-surface-400 dark:text-surface-500 ml-1">(<span x-text="filteredWebmentions.length"></span> total)</span>
<span class="text-surface-600 dark:text-surface-400 ml-1">(<span x-text="filteredWebmentions.length"></span> total)</span>
</div>
<div class="pagination-links">
<button

View File

@@ -11,6 +11,7 @@ document.addEventListener("alpine:init", () => {
alt: "",
images: [],
currentIndex: 0,
triggerElement: null,
init() {
const container = this.$root;
@@ -21,14 +22,24 @@ document.addEventListener("alpine:init", () => {
this.images.forEach((img, i) => {
img.style.cursor = "zoom-in";
img.setAttribute("tabindex", "0");
img.setAttribute("role", "button");
img.setAttribute("aria-label", (img.alt || "Image") + " — click to enlarge");
img.addEventListener("click", (e) => {
e.preventDefault();
this.show(i);
});
img.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
this.show(i);
}
});
});
},
show(index) {
this.triggerElement = this.images[index];
this.currentIndex = index;
const img = this.images[index];
// Use the largest source available
@@ -48,12 +59,22 @@ document.addEventListener("alpine:init", () => {
this.alt = img.alt || "";
this.open = true;
document.body.style.overflow = "hidden";
// Move focus to close button for keyboard users
this.$nextTick(() => {
const closeBtn = document.querySelector('[x-ref="closeBtn"]');
if (closeBtn) closeBtn.focus();
});
},
close() {
this.open = false;
this.src = "";
document.body.style.overflow = "";
// Return focus to the image that triggered the lightbox
if (this.triggerElement) {
this.triggerElement.focus();
this.triggerElement = null;
}
},
next() {

View File

@@ -78,9 +78,12 @@ class TimeDifference extends HTMLElement {
const relative = rtf.format(value, unit);
// Store original text as title for hover tooltip
const originalText = time.textContent.trim();
if (!time.hasAttribute("title")) {
time.setAttribute("title", time.textContent.trim());
time.setAttribute("title", originalText);
}
// aria-label provides the full context: "2 days ago (March 5, 2026)"
time.setAttribute("aria-label", relative + " (" + originalText + ")");
time.textContent = relative;
} catch {
// Intl.RelativeTimeFormat not supported, keep static text

View File

@@ -197,22 +197,26 @@
items.forEach((item) => {
const author = item.author || {};
const li = document.createElement('li');
li.className = 'inline';
const link = document.createElement('a');
link.href = author.url || '#';
link.className = 'facepile-avatar';
link.title = author.name || 'Anonymous';
link.setAttribute('aria-label', (author.name || 'Anonymous') + ' (opens in new tab)');
link.target = '_blank';
link.rel = 'noopener';
link.dataset.new = 'true';
const img = document.createElement('img');
img.src = author.photo || '/images/default-avatar.svg';
img.alt = author.name || 'Anonymous';
img.alt = '';
img.className = 'w-8 h-8 rounded-full ring-2 ring-white dark:ring-surface-900';
img.loading = 'lazy';
link.appendChild(img);
row.appendChild(link);
li.appendChild(link);
row.appendChild(li);
});
}
@@ -278,7 +282,7 @@
const dateLink = document.createElement('a');
dateLink.href = item.url || '#';
dateLink.className = 'text-xs text-surface-500 hover:underline';
dateLink.className = 'text-xs text-surface-600 dark:text-surface-400 hover:underline';
dateLink.target = '_blank';
dateLink.rel = 'noopener';
@@ -396,8 +400,9 @@
header.className = 'text-sm font-semibold text-surface-600 dark:text-surface-400 uppercase tracking-wide mb-3';
header.textContent = `0 ${type === 'likes' ? 'Likes' : 'Reposts'}`;
const row = document.createElement('div');
const row = document.createElement('ul');
row.className = 'facepile';
row.setAttribute('role', 'list');
section.appendChild(header);
section.appendChild(row);
@@ -446,6 +451,8 @@
const section = document.createElement('section');
section.className = 'webmentions mt-8 pt-8 border-t border-surface-200 dark:border-surface-700';
section.id = 'webmentions';
section.setAttribute('aria-live', 'polite');
section.setAttribute('aria-label', 'Webmentions');
const header = document.createElement('h2');
header.className = 'text-xl font-bold text-surface-900 dark:text-surface-100 mb-6';

View File

@@ -109,8 +109,8 @@ export function renderCard(url, metadata) {
<a href="${escapeHtml(url)}" rel="noopener" target="_blank" class="flex no-underline text-inherit hover:text-inherit">
<div class="flex-1 p-3 sm:p-4 min-w-0">
<p class="font-semibold text-sm sm:text-base text-surface-900 dark:text-surface-100 truncate m-0">${escapeHtml(title)}</p>
${desc ? `<p class="text-xs sm:text-sm text-surface-500 dark:text-surface-400 mt-1 m-0 line-clamp-2">${escapeHtml(desc)}</p>` : ""}
<p class="text-xs text-surface-400 dark:text-surface-500 mt-2 m-0">${faviconHtml}${escapeHtml(domain)}</p>
${desc ? `<p class="text-xs sm:text-sm text-surface-600 dark:text-surface-400 mt-1 m-0 line-clamp-2">${escapeHtml(desc)}</p>` : ""}
<p class="text-xs text-surface-400 dark:text-surface-400 mt-2 m-0">${faviconHtml}${escapeHtml(domain)}</p>
</div>
${imgHtml}
</a>

View File

@@ -53,7 +53,7 @@ permalink: "likes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
{% set likedUrl = post.data.likeOf or post.data.like_of %}
{% if likedUrl %}
{% unfurl likedUrl %}
<a class="u-like-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
<a class="u-like-of text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
{{ likedUrl }}
</a>
{% endif %}
@@ -82,7 +82,7 @@ permalink: "likes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
Previous
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Previous
</span>
@@ -94,7 +94,7 @@ permalink: "likes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>

View File

@@ -99,9 +99,9 @@ withSidebar: true
</h2>
<p class="text-surface-600 dark:text-surface-400">{{ fwNowPlaying.artist }}</p>
{% if fwNowPlaying.album %}
<p class="text-sm text-surface-500 mt-1">{{ fwNowPlaying.album }}</p>
<p class="text-sm text-surface-600 dark:text-surface-400 mt-1">{{ fwNowPlaying.album }}</p>
{% endif %}
<p class="text-xs text-surface-500 mt-2">{{ fwNowPlaying.relativeTime }}</p>
<p class="text-xs text-surface-600 dark:text-surface-400 mt-2">{{ fwNowPlaying.relativeTime }}</p>
</div>
</div>
</div>
@@ -154,9 +154,9 @@ withSidebar: true
</h2>
<p class="text-surface-600 dark:text-surface-400">{{ lfmNowPlaying.artist }}</p>
{% if lfmNowPlaying.album %}
<p class="text-sm text-surface-500 mt-1">{{ lfmNowPlaying.album }}</p>
<p class="text-sm text-surface-600 dark:text-surface-400 mt-1">{{ lfmNowPlaying.album }}</p>
{% endif %}
<p class="text-xs text-surface-500 mt-2">{{ lfmNowPlaying.relativeTime }}</p>
<p class="text-xs text-surface-600 dark:text-surface-400 mt-2">{{ lfmNowPlaying.relativeTime }}</p>
</div>
</div>
</div>
@@ -187,15 +187,15 @@ withSidebar: true
<div class="grid grid-cols-3 gap-4 text-center">
<div>
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100">{{ funkwhaleActivity.stats.summary.all.totalPlays | default(0) }}</div>
<div class="text-xs text-surface-500 uppercase">Plays</div>
<div class="text-xs text-surface-600 dark:text-surface-400 uppercase">Plays</div>
</div>
<div>
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100">{{ funkwhaleActivity.stats.summary.all.uniqueArtists | default(0) }}</div>
<div class="text-xs text-surface-500 uppercase">Artists</div>
<div class="text-xs text-surface-600 dark:text-surface-400 uppercase">Artists</div>
</div>
<div>
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100">{{ funkwhaleActivity.stats.summary.all.uniqueTracks | default(0) }}</div>
<div class="text-xs text-surface-500 uppercase">Tracks</div>
<div class="text-xs text-surface-600 dark:text-surface-400 uppercase">Tracks</div>
</div>
</div>
{# Top Artists #}
@@ -206,7 +206,7 @@ withSidebar: true
{% for artist in funkwhaleActivity.stats.topArtists.all | head(5) %}
<div class="flex justify-between text-sm">
<span class="text-surface-600 dark:text-surface-400 truncate">{{ artist.name }}</span>
<span class="text-surface-500 ml-2">{{ artist.playCount }}</span>
<span class="text-surface-600 dark:text-surface-400 ml-2">{{ artist.playCount }}</span>
</div>
{% endfor %}
</div>
@@ -225,15 +225,15 @@ withSidebar: true
<div class="grid grid-cols-3 gap-4 text-center">
<div>
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100">{{ lastfmActivity.stats.summary.all.totalPlays | default(0) }}</div>
<div class="text-xs text-surface-500 uppercase">Scrobbles</div>
<div class="text-xs text-surface-600 dark:text-surface-400 uppercase">Scrobbles</div>
</div>
<div>
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100">{{ lastfmActivity.stats.summary.all.uniqueArtists | default(0) }}</div>
<div class="text-xs text-surface-500 uppercase">Artists</div>
<div class="text-xs text-surface-600 dark:text-surface-400 uppercase">Artists</div>
</div>
<div>
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100">{{ lastfmActivity.stats.summary.all.lovedCount | default(0) }}</div>
<div class="text-xs text-surface-500 uppercase">Loved</div>
<div class="text-xs text-surface-600 dark:text-surface-400 uppercase">Loved</div>
</div>
</div>
{# Top Artists from Last.fm #}
@@ -244,7 +244,7 @@ withSidebar: true
{% for artist in lastfmActivity.stats.topArtists.all | head(5) %}
<div class="flex justify-between text-sm">
<span class="text-surface-600 dark:text-surface-400 truncate">{{ artist.name }}</span>
<span class="text-surface-500 ml-2">{{ artist.playCount }}</span>
<span class="text-surface-600 dark:text-surface-400 ml-2">{{ artist.playCount }}</span>
</div>
{% endfor %}
</div>
@@ -294,7 +294,7 @@ withSidebar: true
<div class="text-right flex-shrink-0">
<span class="inline-block px-2 py-0.5 text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-400 rounded-full mb-1">Funkwhale</span>
<span class="text-xs text-surface-500 block">{{ listening.relativeTime }}</span>
<span class="text-xs text-surface-600 dark:text-surface-400 block">{{ listening.relativeTime }}</span>
<button
class="share-post-btn mt-1"
data-share-url="{{ listening.trackUrl }}"
@@ -351,7 +351,7 @@ withSidebar: true
<div class="text-right flex-shrink-0">
<span class="inline-block px-2 py-0.5 text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-400 rounded-full mb-1">Last.fm</span>
<span class="text-xs text-surface-500 block">{{ scrobble.relativeTime }}</span>
<span class="text-xs text-surface-600 dark:text-surface-400 block">{{ scrobble.relativeTime }}</span>
<button
class="share-post-btn mt-1"
data-share-url="{{ scrobble.trackUrl }}"
@@ -391,7 +391,7 @@ withSidebar: true
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
Loved Tracks
<span class="text-sm font-normal text-surface-500">(Last.fm)</span>
<span class="text-sm font-normal text-surface-600 dark:text-surface-400">(Last.fm)</span>
</h2>
<div class="grid gap-3 sm:gap-4 md:grid-cols-2">
@@ -452,7 +452,7 @@ withSidebar: true
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
Favorite Tracks
<span class="text-sm font-normal text-surface-500">(Funkwhale)</span>
<span class="text-sm font-normal text-surface-600 dark:text-surface-400">(Funkwhale)</span>
</h2>
<div class="grid gap-3 sm:gap-4 md:grid-cols-2">
@@ -478,7 +478,7 @@ withSidebar: true
</h3>
<p class="text-sm text-surface-600 dark:text-surface-400 truncate">{{ favorite.artist }}</p>
{% if favorite.album %}
<p class="text-xs text-surface-500 truncate">{{ favorite.album }}</p>
<p class="text-xs text-surface-600 dark:text-surface-400 truncate">{{ favorite.album }}</p>
{% endif %}
</div>
<button

View File

@@ -10,10 +10,10 @@ withSidebar: true
<p class="text-surface-600 dark:text-surface-400">
Aggregated content from my favorite feeds
</p>
<p class="text-xs text-surface-500 mt-2" x-show="lastUpdated">
<p class="text-xs text-surface-600 dark:text-surface-400 mt-2" x-show="lastUpdated">
Last updated: <span class="font-mono" x-text="formatDate(lastUpdated, 'full')"></span>
<button @click="refresh()" class="ml-2 text-accent-600 hover:text-accent-700 dark:text-accent-400 rounded" :disabled="loading">
<svg class="w-3 h-3 inline" :class="{ 'animate-spin': loading }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<button @click="refresh()" class="ml-2 text-accent-600 hover:text-accent-700 dark:text-accent-400 rounded" :disabled="loading" aria-label="Refresh news">
<svg class="w-3 h-3 inline" :class="{ 'animate-spin': loading }" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
</button>
@@ -21,8 +21,8 @@ withSidebar: true
</header>
{# Loading State #}
<div x-show="loading && items.length === 0" class="text-center py-12">
<svg class="w-8 h-8 mx-auto text-accent-600 animate-spin mb-4" fill="none" viewBox="0 0 24 24">
<div x-show="loading && items.length === 0" class="text-center py-12" role="status">
<svg class="w-8 h-8 mx-auto text-accent-600 animate-spin mb-4" fill="none" viewBox="0 0 24 24" aria-hidden="true">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
@@ -30,7 +30,7 @@ withSidebar: true
</div>
{# Error State #}
<div x-show="error" class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-6">
<div x-show="error" role="alert" class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-6">
<p class="text-red-700 dark:text-red-400" x-text="error"></p>
<button @click="refresh()" class="mt-2 text-sm text-red-600 hover:text-red-700 underline">Try again</button>
</div>
@@ -45,6 +45,7 @@ withSidebar: true
<button
@click="viewMode = 'list'"
:class="viewMode === 'list' ? 'bg-accent-600 text-white' : 'bg-surface-100 dark:bg-surface-800 text-surface-700 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-700'"
:aria-pressed="(viewMode === 'list').toString()"
class="px-3 py-2 rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
title="List view"
>
@@ -56,6 +57,7 @@ withSidebar: true
<button
@click="viewMode = 'card'"
:class="viewMode === 'card' ? 'bg-accent-600 text-white' : 'bg-surface-100 dark:bg-surface-800 text-surface-700 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-700'"
:aria-pressed="(viewMode === 'card').toString()"
class="px-3 py-2 rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
title="Card view"
>
@@ -67,6 +69,7 @@ withSidebar: true
<button
@click="viewMode = 'full'"
:class="viewMode === 'full' ? 'bg-accent-600 text-white' : 'bg-surface-100 dark:bg-surface-800 text-surface-700 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-700'"
:aria-pressed="(viewMode === 'full').toString()"
class="px-3 py-2 rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
title="Expanded view"
>
@@ -79,7 +82,9 @@ withSidebar: true
{# Feed Filter Dropdown #}
<div class="relative" x-show="feeds.length > 1">
<label for="news-feed-filter" class="sr-only">Filter by feed source</label>
<select
id="news-feed-filter"
x-model="filterFeed"
class="appearance-none bg-surface-100 dark:bg-surface-800 border border-surface-300 dark:border-surface-600 rounded-lg px-4 py-2 pr-8 text-sm text-surface-700 dark:text-surface-300 transition-colors"
>
@@ -88,7 +93,7 @@ withSidebar: true
<option :value="feed.id" x-text="feed.title"></option>
</template>
</select>
<svg class="absolute right-2 top-1/2 transform -translate-y-1/2 w-4 h-4 text-surface-500 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="absolute right-2 top-1/2 transform -translate-y-1/2 w-4 h-4 text-surface-600 dark:text-surface-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
@@ -97,11 +102,11 @@ withSidebar: true
{# Stats Bar #}
<div class="flex flex-wrap gap-4 mb-6 p-4 bg-surface-50 dark:bg-surface-800/50 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm text-sm">
<div class="flex items-center gap-2">
<span class="text-surface-500">Feeds:</span>
<span class="text-surface-600 dark:text-surface-400">Feeds:</span>
<span class="font-medium font-mono text-surface-900 dark:text-surface-100" x-text="status?.stats?.feedsCount || feeds.length"></span>
</div>
<div class="flex items-center gap-2">
<span class="text-surface-500">Items:</span>
<span class="text-surface-600 dark:text-surface-400">Items:</span>
<span class="font-medium font-mono text-surface-900 dark:text-surface-100" x-text="status?.stats?.itemsCount || items.length"></span>
</div>
<div x-show="status?.status === 'syncing'" class="flex items-center gap-2 text-orange-600 dark:text-orange-400">
@@ -144,7 +149,7 @@ withSidebar: true
></a>
</h2>
<p x-show="item.description" class="text-sm text-surface-600 dark:text-surface-400 line-clamp-2 mb-2" x-text="item.description"></p>
<div class="flex flex-wrap items-center gap-2 text-xs text-surface-500">
<div class="flex flex-wrap items-center gap-2 text-xs text-surface-600 dark:text-surface-400">
<a
:href="item.sourceUrl || getFeedUrl(item.feedId) || item.link"
class="inline-flex items-center gap-1 px-2 py-0.5 bg-surface-100 dark:bg-surface-700 rounded-full hover:bg-surface-200 dark:hover:bg-surface-600 transition-colors"
@@ -210,7 +215,7 @@ withSidebar: true
></a>
</h2>
<p x-show="item.description" class="text-sm text-surface-600 dark:text-surface-400 line-clamp-3 mb-3" x-text="item.description"></p>
<div class="flex items-center justify-between text-xs text-surface-500">
<div class="flex items-center justify-between text-xs text-surface-600 dark:text-surface-400">
<span class="truncate max-w-[60%]" x-text="truncate(item.sourceTitle || item.feedTitle, 20)"></span>
<time class="font-mono text-sm" :datetime="item.pubDate" x-text="formatDate(item.pubDate)"></time>
</div>
@@ -264,7 +269,7 @@ withSidebar: true
<span class="font-medium text-surface-700 dark:text-surface-300" x-text="item.sourceTitle || item.feedTitle"></span>
</a>
<span x-show="item.author" class="text-surface-600 dark:text-surface-400" x-text="'by ' + item.author"></span>
<time :datetime="item.pubDate" class="font-mono text-sm text-surface-500" x-text="formatDate(item.pubDate, 'long')"></time>
<time :datetime="item.pubDate" class="font-mono text-sm text-surface-600 dark:text-surface-400" x-text="formatDate(item.pubDate, 'long')"></time>
</div>
<h2 class="text-xl sm:text-2xl font-bold text-surface-900 dark:text-surface-100 mb-4">
@@ -349,11 +354,11 @@ withSidebar: true
{# Empty State #}
<div x-show="!loading && items.length === 0 && !error" class="text-center py-12">
<svg class="w-16 h-16 mx-auto text-surface-300 dark:text-surface-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-16 h-16 mx-auto text-surface-300 dark:text-surface-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"/>
</svg>
<p class="text-surface-600 dark:text-surface-400 text-lg">No news items yet.</p>
<p class="text-surface-500 text-sm mt-2">Add some RSS feeds to get started.</p>
<p class="text-surface-600 dark:text-surface-400 text-sm mt-2">Add some RSS feeds to get started.</p>
</div>
</div>

View File

@@ -28,7 +28,7 @@ permalink: "notes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
<li class="h-entry post-card border-l-[3px] border-l-teal-400 dark:border-l-teal-500">
<div class="post-header">
<a class="u-url" href="{{ post.url }}">
<time class="dt-published text-sm text-surface-500 dark:text-surface-400 font-medium font-mono" datetime="{{ post.date | isoDate }}">
<time class="dt-published text-sm text-surface-600 dark:text-surface-400 font-medium font-mono" datetime="{{ post.date | isoDate }}">
{{ post.date | dateDisplay }}
</time>
</a>
@@ -69,7 +69,7 @@ permalink: "notes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
Previous
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Previous
</span>
@@ -81,7 +81,7 @@ permalink: "notes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>

View File

@@ -77,7 +77,7 @@ permalink: "photos/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumb
Previous
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Previous
</span>
@@ -89,7 +89,7 @@ permalink: "photos/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumb
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>

View File

@@ -14,10 +14,10 @@ permalink: /podroll/
<p class="text-surface-600 dark:text-surface-400">
My podcast subscriptions - recent episodes from <span x-text="sources.length" class="font-medium font-mono"></span> podcasts
</p>
<p class="text-xs text-surface-500 mt-2" x-show="status?.episodes?.lastSync">
<p class="text-xs text-surface-600 dark:text-surface-400 mt-2" x-show="status?.episodes?.lastSync">
Last synced: <span class="font-mono" x-text="formatDate(status?.episodes?.lastSync, 'full')"></span>
<button @click="refresh()" class="ml-2 text-orange-600 hover:text-orange-700 dark:text-orange-400 transition-colors rounded" :disabled="loading">
<svg class="w-3 h-3 inline" :class="{ 'animate-spin': loading }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<button @click="refresh()" class="ml-2 text-orange-600 hover:text-orange-700 dark:text-orange-400 transition-colors rounded" :disabled="loading" aria-label="Refresh episodes">
<svg class="w-3 h-3 inline" :class="{ 'animate-spin': loading }" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
</button>
@@ -28,8 +28,8 @@ permalink: /podroll/
{# Main Content - Episodes #}
<div class="main-content">
{# Loading State #}
<div x-show="loading && episodes.length === 0" class="text-center py-12">
<svg class="w-8 h-8 mx-auto text-orange-600 animate-spin mb-4" fill="none" viewBox="0 0 24 24">
<div x-show="loading && episodes.length === 0" class="text-center py-12" role="status">
<svg class="w-8 h-8 mx-auto text-orange-600 animate-spin mb-4" fill="none" viewBox="0 0 24 24" aria-hidden="true">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
@@ -37,7 +37,7 @@ permalink: /podroll/
</div>
{# Error State #}
<div x-show="error" class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-6">
<div x-show="error" role="alert" class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-6">
<p class="text-red-700 dark:text-red-400" x-text="error"></p>
<button @click="refresh()" class="mt-2 text-sm text-red-600 hover:text-red-700 underline">Try again</button>
</div>
@@ -45,7 +45,9 @@ permalink: /podroll/
{# Filter by Podcast #}
<div x-show="episodes.length > 0" class="mb-6">
<div class="relative">
<label for="podroll-filter" class="sr-only">Filter by podcast</label>
<select
id="podroll-filter"
x-model="filterPodcast"
class="w-full sm:w-auto appearance-none bg-surface-100 dark:bg-surface-800 border border-surface-300 dark:border-surface-600 rounded-lg px-4 py-2 pr-10 text-sm text-surface-700 dark:text-surface-300"
>
@@ -54,7 +56,7 @@ permalink: /podroll/
<option :value="source.title" x-text="source.title"></option>
</template>
</select>
<svg class="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-surface-500 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-surface-600 dark:text-surface-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
@@ -70,7 +72,7 @@ permalink: /podroll/
<h2 class="font-semibold text-lg text-surface-900 dark:text-surface-100 mb-1">
<a :href="episode.url" class="hover:text-orange-600 dark:hover:text-orange-400 transition-colors" target="_blank" rel="noopener" x-text="episode.title"></a>
</h2>
<div class="flex flex-wrap items-center gap-2 text-sm text-surface-500">
<div class="flex flex-wrap items-center gap-2 text-sm text-surface-600 dark:text-surface-400">
<a
x-show="episode.podcast"
:href="episode.podcast?.url || '#'"
@@ -130,7 +132,7 @@ permalink: /podroll/
<a
x-show="episode.podcast?.feedUrl"
:href="episode.podcast?.feedUrl"
class="inline-flex items-center gap-2 text-sm text-surface-500 hover:text-surface-700 dark:hover:text-surface-300 transition-colors"
class="inline-flex items-center gap-2 text-sm text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300 transition-colors"
target="_blank"
rel="noopener"
title="Subscribe to feed"
@@ -187,11 +189,11 @@ permalink: /podroll/
{# Empty State #}
<div x-show="!loading && episodes.length === 0 && !error" class="text-center py-12">
<svg class="w-16 h-16 mx-auto text-surface-300 dark:text-surface-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-16 h-16 mx-auto text-surface-300 dark:text-surface-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/>
</svg>
<p class="text-surface-600 dark:text-surface-400 text-lg">No podcast episodes yet.</p>
<p class="text-surface-500 text-sm mt-2">Episodes will appear once the sync completes.</p>
<p class="text-surface-600 dark:text-surface-400 text-sm mt-2">Episodes will appear once the sync completes.</p>
</div>
</div>
@@ -204,10 +206,10 @@ permalink: /podroll/
<path d="M4 4.44v2.83c7.03 0 12.73 5.7 12.73 12.73h2.83c0-8.59-6.97-15.56-15.56-15.56zm0 5.66v2.83c3.9 0 7.07 3.17 7.07 7.07h2.83c0-5.47-4.43-9.9-9.9-9.9z"/>
</svg>
Subscriptions
<span class="text-sm font-normal font-mono text-surface-500" x-text="'(' + sources.length + ')'"></span>
<span class="text-sm font-normal font-mono text-surface-600 dark:text-surface-400" x-text="'(' + sources.length + ')'"></span>
</h3>
<div x-show="sources.length === 0 && !loading" class="text-sm text-surface-500 text-center py-4">
<div x-show="sources.length === 0 && !loading" class="text-sm text-surface-600 dark:text-surface-400 text-center py-4">
No subscriptions loaded yet.
</div>
@@ -225,7 +227,7 @@ permalink: /podroll/
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-surface-900 dark:text-surface-100 truncate" x-text="source.title"></p>
<p x-show="source.category" class="text-xs text-surface-500 truncate" x-text="source.category"></p>
<p x-show="source.category" class="text-xs text-surface-600 dark:text-surface-400 truncate" x-text="source.category"></p>
</div>
<a
:href="source.xmlUrl"

View File

@@ -20,8 +20,8 @@ permalink: /readlater/
{# Main Content #}
<div class="main-content">
{# Loading State #}
<div x-show="loading" class="text-center py-12">
<svg class="w-8 h-8 mx-auto text-orange-600 animate-spin mb-4" fill="none" viewBox="0 0 24 24">
<div x-show="loading" class="text-center py-12" role="status">
<svg class="w-8 h-8 mx-auto text-orange-600 animate-spin mb-4" fill="none" viewBox="0 0 24 24" aria-hidden="true">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
@@ -29,7 +29,7 @@ permalink: /readlater/
</div>
{# Error State #}
<div x-show="error" class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-6">
<div x-show="error" role="alert" class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-6">
<p class="text-red-700 dark:text-red-400" x-text="error"></p>
<button @click="fetchData()" class="mt-2 text-sm text-red-600 hover:text-red-700 underline">Try again</button>
</div>
@@ -38,7 +38,9 @@ permalink: /readlater/
<div x-show="!loading && items.length > 0" class="mb-6">
<div class="flex flex-wrap items-center gap-3">
<div class="relative">
<label for="readlater-source" class="sr-only">Filter by source</label>
<select
id="readlater-source"
x-model="selectedSource"
@change="fetchData()"
class="appearance-none bg-surface-50 dark:bg-surface-800 border border-surface-300 dark:border-surface-600 rounded-lg pl-3 pr-8 py-2 text-sm"
@@ -53,7 +55,9 @@ permalink: /readlater/
</svg>
</div>
<div class="relative flex-1 max-w-xs">
<label for="readlater-search" class="sr-only">Search reading list</label>
<input
id="readlater-search"
type="search"
x-model.debounce.300ms="searchQuery"
@input="fetchData()"
@@ -91,7 +95,7 @@ permalink: /readlater/
<h2 class="font-semibold text-surface-900 dark:text-surface-100 mb-1">
<a :href="item.url" class="hover:text-orange-600 dark:hover:text-orange-400 transition-colors" target="_blank" rel="noopener" x-text="item.title"></a>
</h2>
<div class="flex flex-wrap items-center gap-2 text-sm text-surface-500">
<div class="flex flex-wrap items-center gap-2 text-sm text-surface-600 dark:text-surface-400">
<span
class="inline-flex items-center px-2 py-0.5 bg-surface-100 dark:bg-surface-700 rounded-full text-xs"
x-text="item.source"
@@ -132,11 +136,11 @@ permalink: /readlater/
{# Empty State #}
<div x-show="!loading && items.length === 0 && !error" class="text-center py-12">
<svg class="w-16 h-16 mx-auto text-surface-300 dark:text-surface-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="w-16 h-16 mx-auto text-surface-300 dark:text-surface-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"/>
</svg>
<p class="text-surface-600 dark:text-surface-400 text-lg">No saved items yet.</p>
<p class="text-surface-500 text-sm mt-2">Save articles from around the web using the bookmark button.</p>
<p class="text-surface-600 dark:text-surface-400 text-sm mt-2">Save articles from around the web using the bookmark button.</p>
</div>
</div>
@@ -148,11 +152,11 @@ permalink: /readlater/
<div class="grid grid-cols-2 gap-3 text-center">
<div class="p-3 bg-surface-50 dark:bg-surface-800 rounded-lg">
<span class="text-2xl font-bold text-orange-600 dark:text-orange-400 block" x-text="items.length"></span>
<span class="text-xs text-surface-500 uppercase">Saved</span>
<span class="text-xs text-surface-600 dark:text-surface-400 uppercase">Saved</span>
</div>
<div class="p-3 bg-surface-50 dark:bg-surface-800 rounded-lg">
<span class="text-2xl font-bold text-orange-600 dark:text-orange-400 block" x-text="sources.length"></span>
<span class="text-xs text-surface-500 uppercase">Sources</span>
<span class="text-xs text-surface-600 dark:text-surface-400 uppercase">Sources</span>
</div>
</div>
</div>

View File

@@ -60,8 +60,8 @@ permalink: "replies/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
{% set protocol = replyTo | protocolType %}
{% unfurl replyTo %}
<p class="mt-2 text-sm flex items-center gap-2 flex-wrap">
<span class="text-surface-500">In reply to:</span>
<a class="u-in-reply-to text-xs text-surface-400 dark:text-surface-500 hover:underline break-all" href="{{ replyTo }}">
<span class="text-surface-600">In reply to:</span>
<a class="u-in-reply-to text-xs text-surface-600 dark:text-surface-400 hover:underline break-all" href="{{ replyTo }}">
{{ replyTo | replace("https://", "") | replace("http://", "") | truncate(60) }}
</a>
{% if protocol == "atmosphere" %}
@@ -111,7 +111,7 @@ permalink: "replies/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
Previous
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Previous
</span>
@@ -123,7 +123,7 @@ permalink: "replies/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>

View File

@@ -58,7 +58,7 @@ permalink: "reposts/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
{% set repostedUrl = post.data.repostOf or post.data.repost_of %}
{% if repostedUrl %}
{% unfurl repostedUrl %}
<a class="u-repost-of text-xs text-surface-400 dark:text-surface-500 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
<a class="u-repost-of text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
{{ repostedUrl }}
</a>
{% endif %}
@@ -87,7 +87,7 @@ permalink: "reposts/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
Previous
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Previous
</span>
@@ -99,7 +99,7 @@ permalink: "reposts/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNum
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</a>
{% else %}
<span class="pagination-link disabled">
<span class="pagination-link disabled" aria-disabled="true">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>

View File

@@ -14,7 +14,7 @@ pagefindIgnore: true
<noscript>
<div class="p-6 bg-surface-100 dark:bg-surface-800 rounded-lg mt-4">
<p class="text-surface-700 dark:text-surface-300">Search requires JavaScript to be enabled. Please enable JavaScript in your browser settings to use the search feature.</p>
<p class="text-surface-500 text-sm mt-2">Alternatively, you can browse content via the <a href="/blog/" class="text-accent-600 dark:text-accent-400 hover:underline">blog archive</a> or <a href="/categories/" class="text-accent-600 dark:text-accent-400 hover:underline">categories</a>.</p>
<p class="text-surface-600 dark:text-surface-400 text-sm mt-2">Alternatively, you can browse content via the <a href="/blog/" class="text-accent-600 dark:text-accent-400 hover:underline">blog archive</a> or <a href="/categories/" class="text-accent-600 dark:text-accent-400 hover:underline">categories</a>.</p>
</div>
</noscript>
@@ -23,16 +23,19 @@ pagefindIgnore: true
// Support ?q= query parameter and auto-focus
window.addEventListener("DOMContentLoaded", () => {
const input = document.querySelector("#search input[type='text']");
if (input) {
// Pagefind generates the input without a label — add one
input.setAttribute("aria-label", "Search all content");
}
const params = new URLSearchParams(window.location.search);
const query = params.get("q");
if (query) {
const input = document.querySelector("#search input[type='text']");
if (input) {
input.value = query;
input.dispatchEvent(new Event("input", { bubbles: true }));
}
} else {
const input = document.querySelector("#search input[type='text']");
if (input) input.focus();
}
});

View File

@@ -33,7 +33,7 @@ eleventyImport:
<p class="text-surface-600 dark:text-surface-400 mt-2">{{ page.data.title }}</p>
{% endif %}
{% if page.data.updated %}
<p class="text-sm text-surface-500 mt-2">
<p class="text-sm text-surface-600 dark:text-surface-400 mt-2">
Updated: <time class="font-mono text-sm" datetime="{{ page.data.updated | isoDate }}">{{ page.data.updated | dateDisplay }}</time>
</p>
{% endif %}
@@ -49,7 +49,7 @@ eleventyImport:
<li><code>@rmdes/indiekit-post-type-page</code> — registers the "page" post type with Indiekit, using root-level URL paths (<code>/slug</code> instead of <code>/type/YYYY/MM/DD/slug</code>)</li>
<li><code>@rmdes/indiekit-endpoint-posts</code> — publishing UI that sends the <code>h=page</code> Micropub type so pages are created at root level</li>
</ul>
<p class="text-surface-500 dark:text-surface-500 text-xs mt-3">
<p class="text-surface-600 dark:text-surface-400 text-xs mt-3">
Once both plugins are installed, "Page" appears as a post type in the Indiekit admin UI, and pages are published directly at <code>/slug</code>.
</p>
</div>

View File

@@ -38,7 +38,7 @@ eleventyExcludeFromCollections: true
<template x-if="loading">
<div class="text-center py-12">
<div class="inline-block w-8 h-8 border-4 border-emerald-200 border-t-emerald-600 rounded-full animate-spin"></div>
<p class="mt-4 text-surface-500">Loading starred repositories&hellip;</p>
<p class="mt-4 text-surface-600 dark:text-surface-400">Loading starred repositories&hellip;</p>
</div>
</template>
@@ -53,13 +53,15 @@ eleventyExcludeFromCollections: true
{# ===== TAB BAR ===== #}
<div class="mb-4 -mx-4 px-4 overflow-x-auto scrollbar-thin">
<div class="flex gap-1 min-w-max border-b border-surface-200 dark:border-surface-700">
<div class="flex gap-1 min-w-max border-b border-surface-200 dark:border-surface-700" role="tablist" aria-label="Starred repository lists">
{# All tab #}
<button
@click="activeTab = 'all'; resetView()"
:class="activeTab === 'all'
? 'border-emerald-600 text-emerald-700 dark:text-emerald-400'
: 'border-transparent text-surface-500 hover:text-surface-700 dark:hover:text-surface-300 hover:border-surface-300'"
: 'border-transparent text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300 hover:border-surface-300'"
:aria-selected="(activeTab === 'all').toString()"
role="tab"
class="px-3 py-2 text-sm font-medium border-b-2 whitespace-nowrap transition-colors"
>
All
@@ -72,7 +74,9 @@ eleventyExcludeFromCollections: true
@click="activeTab = list.slug; resetView()"
:class="activeTab === list.slug
? 'border-emerald-600 text-emerald-700 dark:text-emerald-400'
: 'border-transparent text-surface-500 hover:text-surface-700 dark:hover:text-surface-300 hover:border-surface-300'"
: 'border-transparent text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300 hover:border-surface-300'"
:aria-selected="(activeTab === list.slug).toString()"
role="tab"
class="px-3 py-2 text-sm font-medium border-b-2 whitespace-nowrap transition-colors"
>
<span x-text="list.name"></span>
@@ -85,7 +89,9 @@ eleventyExcludeFromCollections: true
@click="activeTab = 'uncategorized'; resetView()"
:class="activeTab === 'uncategorized'
? 'border-emerald-600 text-emerald-700 dark:text-emerald-400'
: 'border-transparent text-surface-500 hover:text-surface-700 dark:hover:text-surface-300 hover:border-surface-300'"
: 'border-transparent text-surface-600 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-300 hover:border-surface-300'"
:aria-selected="(activeTab === 'uncategorized').toString()"
role="tab"
class="px-3 py-2 text-sm font-medium border-b-2 whitespace-nowrap transition-colors"
>
Uncategorized
@@ -177,7 +183,7 @@ eleventyExcludeFromCollections: true
</div>
{# ===== RESULTS SUMMARY ===== #}
<div class="mb-4 text-sm text-surface-500">
<div class="mb-4 text-sm text-surface-600 dark:text-surface-400">
<span x-text="resultSummary"></span>
</div>
@@ -207,12 +213,12 @@ eleventyExcludeFromCollections: true
<span class="text-xs px-2 py-0.5 bg-surface-100 dark:bg-surface-700 text-surface-700 dark:text-surface-300 rounded" x-text="topic"></span>
</template>
<template x-if="repo.topics.length > 5">
<span class="text-xs px-2 py-0.5 bg-surface-100 dark:bg-surface-700 text-surface-500 rounded" x-text="'+' + (repo.topics.length - 5)"></span>
<span class="text-xs px-2 py-0.5 bg-surface-100 dark:bg-surface-700 text-surface-600 dark:text-surface-400 rounded" x-text="'+' + (repo.topics.length - 5)"></span>
</template>
</div>
</template>
<div class="flex flex-wrap items-center gap-3 text-xs text-surface-500">
<div class="flex flex-wrap items-center gap-3 text-xs text-surface-600 dark:text-surface-400">
<template x-if="repo.language">
<span class="flex items-center gap-1">
<span class="w-2.5 h-2.5 rounded-full" :style="'background:' + languageColor(repo.language)"></span>
@@ -243,7 +249,7 @@ eleventyExcludeFromCollections: true
{# ===== EMPTY FILTERED STATE ===== #}
<template x-if="sortedStars.length === 0">
<div class="text-center py-12">
<p class="text-surface-500">No repos match your current filters.</p>
<p class="text-surface-600 dark:text-surface-400">No repos match your current filters.</p>
<button @click="clearFilters()" class="mt-2 text-sm text-emerald-600 dark:text-emerald-400 hover:underline">Clear all filters</button>
</div>
</template>

View File

@@ -52,7 +52,7 @@ pagefindIgnore: true
{{ post.url }}
</a>
</td>
<td class="p-2 text-xs text-surface-500 font-mono">
<td class="p-2 text-xs text-surface-600 dark:text-surface-400 font-mono">
{% if legacyUrls.length %}
{% for legacyUrl in legacyUrls %}
<div>{{ legacyUrl }}</div>
@@ -94,7 +94,7 @@ pagefindIgnore: true
{% for newUrl, oldUrls in aliasEntries | head(20) %}
<tr>
<td class="p-2 text-xs break-all">{{ newUrl }}</td>
<td class="p-2 text-xs break-all text-surface-500">
<td class="p-2 text-xs break-all text-surface-600 dark:text-surface-400">
{% for oldUrl in oldUrls %}
<div>{{ oldUrl }}</div>
{% endfor %}
@@ -115,7 +115,7 @@ pagefindIgnore: true
<ul class="space-y-1 font-mono text-xs">
{% for wm in webmentions | head(30) %}
<li class="p-2 bg-surface-50 dark:bg-surface-800/50 rounded">
<span class="text-surface-500">{{ wm["wm-property"] }}:</span>
<span class="text-surface-600 dark:text-surface-400">{{ wm["wm-property"] }}:</span>
<span class="break-all">{{ wm["wm-target"] }}</span>
</li>
{% endfor %}

View File

@@ -15,11 +15,13 @@ withSidebar: true
{# Multi-channel tabs #}
{% if youtubeChannel.isMultiChannel and youtubeChannel.channels.length > 1 %}
<div class="mb-6">
<div class="flex flex-wrap gap-2 border-b border-surface-200 dark:border-surface-700">
<div class="flex flex-wrap gap-2 border-b border-surface-200 dark:border-surface-700" role="tablist" aria-label="YouTube channels">
{% for channel in youtubeChannel.channels %}
<button
@click="activeChannel = {{ loop.index0 }}"
:class="activeChannel === {{ loop.index0 }} ? 'border-red-500 text-red-600 dark:text-red-400' : 'border-transparent text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100'"
:aria-selected="(activeChannel === {{ loop.index0 }}).toString()"
role="tab" id="yt-tab-{{ loop.index0 }}" aria-controls="yt-panel-{{ loop.index0 }}"
class="px-4 py-2 font-medium border-b-2 -mb-px transition-colors"
>
{{ channel.configName or channel.title }}
@@ -31,7 +33,7 @@ withSidebar: true
{# Channel sections #}
{% for channel in youtubeChannel.channels %}
<div x-show="activeChannel === {{ loop.index0 }}" {% if not loop.first %}x-cloak{% endif %}>
<div x-show="activeChannel === {{ loop.index0 }}" {% if not loop.first %}x-cloak{% endif %} role="tabpanel" id="yt-panel-{{ loop.index0 }}" aria-labelledby="yt-tab-{{ loop.index0 }}">
{# Channel Header #}
<section class="mb-8">
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:gap-5 p-4 sm:p-6 bg-gradient-to-br from-red-500/10 to-red-600/5 dark:from-red-900/20 dark:to-red-800/10 rounded-xl sm:rounded-2xl border border-red-500/20">
@@ -57,7 +59,7 @@ withSidebar: true
</a>
</h2>
{% if channel.customUrl %}
<p class="text-sm text-surface-500">{{ channel.customUrl }}</p>
<p class="text-sm text-surface-600 dark:text-surface-400">{{ channel.customUrl }}</p>
{% endif %}
<div class="flex flex-wrap items-center gap-4 mt-2 text-sm text-surface-600 dark:text-surface-400">
<span class="flex items-center gap-1">
@@ -220,7 +222,7 @@ withSidebar: true
{{ video.title }}
</a>
</h3>
<div class="flex items-center gap-3 text-sm text-surface-500">
<div class="flex items-center gap-3 text-sm text-surface-600 dark:text-surface-400">
<span class="flex items-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>