mirror of
https://github.com/svemagie/blog-eleventy-indiekit.git
synced 2026-04-02 16:44:56 +02:00
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:
@@ -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" %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">← Previous</span>
|
||||
<span class="text-[10px] sm:text-xs font-semibold uppercase tracking-wide text-surface-600 dark:text-surface-400 block mb-2">← 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 →</span>
|
||||
<span class="text-[10px] sm:text-xs font-semibold uppercase tracking-wide text-surface-600 dark:text-surface-400 block mb-2">Next →</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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
{% if item.type %} · <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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" %}
|
||||
|
||||
@@ -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 %}
|
||||
<li class="inline">
|
||||
<a href="{{ like.author.url }}"
|
||||
class="facepile-avatar"
|
||||
title="{{ like.author.name }}"
|
||||
aria-label="{{ like.author.name }} (opens in new tab)"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
<img
|
||||
src="{{ like.author.photo or '/images/default-avatar.svg' }}"
|
||||
alt="{{ like.author.name }}"
|
||||
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 %}
|
||||
<li class="inline">
|
||||
<a href="{{ repost.author.url }}"
|
||||
class="facepile-avatar"
|
||||
title="{{ repost.author.name }}"
|
||||
aria-label="{{ repost.author.name }} (opens in new tab)"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
<img
|
||||
src="{{ repost.author.photo or '/images/default-avatar.svg' }}"
|
||||
alt="{{ repost.author.name }}"
|
||||
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 %}
|
||||
<li class="inline">
|
||||
<a href="{{ bookmark.author.url }}"
|
||||
class="facepile-avatar"
|
||||
title="{{ bookmark.author.name }}"
|
||||
aria-label="{{ bookmark.author.name }} (opens in new tab)"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
<img
|
||||
src="{{ bookmark.author.photo or '/images/default-avatar.svg' }}"
|
||||
alt="{{ bookmark.author.name }}"
|
||||
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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">♥</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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 %}">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 →</a></p>
|
||||
</aside>
|
||||
|
||||
@@ -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 %}↩ Reply{% elif likedUrl %}♥ Like{% elif repostedUrl %}♻ Repost{% elif bookmarkedUrl %}🔖 Bookmark{% else %}✎ 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">×</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">×</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">‹</button>
|
||||
</template>
|
||||
|
||||
@@ -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>
|
||||
|
||||
18
blog.njk
18
blog.njk
@@ -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>
|
||||
|
||||
46
blogroll.njk
46
blogroll.njk
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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/", "") %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
4
cv.njk
@@ -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 %}
|
||||
|
||||
@@ -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> – <time class="font-mono" datetime="{{ d.endDate | isoDate }}">{{ d.endDate | dateDisplay }}</time>
|
||||
· {{ 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>
|
||||
|
||||
14
digest.njk
14
digest.njk
@@ -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">❤</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>
|
||||
· <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>
|
||||
· <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">🔁</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>
|
||||
· <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>
|
||||
· <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>
|
||||
· <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>
|
||||
· <a href="{{ post.url }}" class="hover:underline">Permalink</a>
|
||||
</div>
|
||||
|
||||
@@ -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",
|
||||
|
||||
14
featured.njk
14
featured.njk
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
10
github.njk
10
github.njk
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
33
news.njk
33
news.njk
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
30
podroll.njk
30
podroll.njk
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
24
starred.njk
24
starred.njk
@@ -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…</p>
|
||||
<p class="mt-4 text-surface-600 dark:text-surface-400">Loading starred repositories…</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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
10
youtube.njk
10
youtube.njk
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user