feat: UI overhaul — accent color, Inter font, widget icons, diversified colors
- Add teal accent color scale and activate Inter font via @font-face - Neutralize nav/footer hovers from primary blue to surface neutrals - Apply accent color to hero subtitle, FAB, CTA buttons, card hovers - Fix reply post-type color from generic primary to distinctive sky blue - Create centralized icon macro (icon.njk) with 24 reusable SVG icons - Add per-widget-type icons and colored left-accent borders to all sidebars - Update .p-category tags from blue to neutral surface with border - Diversify color vocabulary: red (likes), amber (bookmarks/blogroll), green (reposts), purple (funkwhale), sky (replies), orange (subscribe) Confab-Link: http://localhost:8080/sessions/bd3f7012-c703-47e9-bfe2-2ad04ce1842d
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
{# Blog Sidebar - Shown on individual post pages #}
|
{# Blog Sidebar - Shown on individual post pages #}
|
||||||
{# Data-driven when homepageConfig.blogPostSidebar is configured, otherwise falls back to default widgets #}
|
{# Data-driven when homepageConfig.blogPostSidebar is configured, otherwise falls back to default widgets #}
|
||||||
{# Each widget is wrapped in a collapsible container with localStorage persistence #}
|
{# Each widget is wrapped in a collapsible container with localStorage persistence #}
|
||||||
|
{% from "components/icon.njk" import icon %}
|
||||||
|
|
||||||
{% if homepageConfig and homepageConfig.blogPostSidebar and homepageConfig.blogPostSidebar.length %}
|
{% if homepageConfig and homepageConfig.blogPostSidebar and homepageConfig.blogPostSidebar.length %}
|
||||||
{# === Data-driven mode: render configured widgets === #}
|
{# === Data-driven mode: render configured widgets === #}
|
||||||
@@ -28,6 +29,41 @@
|
|||||||
{% else %}{% set widgetTitle = widget.type %}
|
{% else %}{% set widgetTitle = widget.type %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Resolve widget icon and accent border #}
|
||||||
|
{% if widget.type == "social-activity" %}
|
||||||
|
{% set widgetIcon = "globe" %}{% set widgetIconClass = "w-5 h-5 text-[#0085ff]" %}{% set widgetBorder = "border-l-[3px] border-l-[#0085ff]" %}
|
||||||
|
{% elif widget.type == "github-repos" %}
|
||||||
|
{% set widgetIcon = "github" %}{% set widgetIconClass = "w-5 h-5 text-surface-800 dark:text-surface-200" %}{% set widgetBorder = "border-l-[3px] border-l-surface-400 dark:border-l-surface-500" %}
|
||||||
|
{% elif widget.type == "funkwhale" %}
|
||||||
|
{% set widgetIcon = "headphones" %}{% set widgetIconClass = "w-5 h-5 text-purple-500" %}{% set widgetBorder = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
|
||||||
|
{% elif widget.type == "blogroll" %}
|
||||||
|
{% set widgetIcon = "book-open" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
|
||||||
|
{% elif widget.type == "feedland" %}
|
||||||
|
{% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
|
||||||
|
{% elif widget.type == "subscribe" %}
|
||||||
|
{% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-orange-500" %}{% set widgetBorder = "border-l-[3px] border-l-orange-400 dark:border-l-orange-500" %}
|
||||||
|
{% elif widget.type == "fediverse-follow" %}
|
||||||
|
{% set widgetIcon = "user-plus" %}{% set widgetIconClass = "w-5 h-5 text-[#a730b8]" %}{% set widgetBorder = "border-l-[3px] border-l-[#a730b8]" %}
|
||||||
|
{% elif widget.type == "author-card" or widget.type == "author-card-compact" %}
|
||||||
|
{% set widgetIcon = "user" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "recent-posts" %}
|
||||||
|
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "categories" or widget.type == "post-categories" %}
|
||||||
|
{% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "recent-comments" %}
|
||||||
|
{% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "search" %}
|
||||||
|
{% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "webmentions" %}
|
||||||
|
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "toc" %}
|
||||||
|
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "share" %}
|
||||||
|
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% else %}
|
||||||
|
{% set widgetIcon = "" %}{% set widgetIconClass = "" %}{% set widgetBorder = "" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% set widgetKey = "post-widget-" + widget.type + "-" + loop.index0 %}
|
{% set widgetKey = "post-widget-" + widget.type + "-" + loop.index0 %}
|
||||||
{% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
|
{% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
|
||||||
|
|
||||||
@@ -36,13 +72,16 @@
|
|||||||
class="widget-collapsible mb-4"
|
class="widget-collapsible mb-4"
|
||||||
x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : {{ defaultOpen }} }"
|
x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : {{ defaultOpen }} }"
|
||||||
>
|
>
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden {{ widgetBorder }}">
|
||||||
<button
|
<button
|
||||||
class="widget-header w-full p-4"
|
class="widget-header w-full p-4"
|
||||||
@click="open = !open; localStorage.setItem('{{ widgetKey }}', open)"
|
@click="open = !open; localStorage.setItem('{{ widgetKey }}', open)"
|
||||||
:aria-expanded="open ? 'true' : 'false'"
|
:aria-expanded="open ? 'true' : 'false'"
|
||||||
>
|
>
|
||||||
<h3 class="widget-title font-bold text-lg">{{ widgetTitle }}</h3>
|
<h3 class="widget-title font-bold text-lg flex items-center gap-2">
|
||||||
|
{% if widgetIcon %}{{ icon(widgetIcon, widgetIconClass) }}{% endif %}
|
||||||
|
{{ widgetTitle }}
|
||||||
|
</h3>
|
||||||
<svg
|
<svg
|
||||||
class="widget-chevron"
|
class="widget-chevron"
|
||||||
:class="open && 'rotate-180'"
|
:class="open && 'rotate-180'"
|
||||||
@@ -130,7 +169,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Author</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -144,7 +183,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Table of Contents</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -158,7 +197,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Categories</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -172,7 +211,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Recent Posts</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -186,7 +225,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Webmentions</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -200,7 +239,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Share</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -212,9 +251,9 @@
|
|||||||
{# Subscribe #}
|
{# Subscribe #}
|
||||||
{% set widgetKey = "post-fb-subscribe" %}
|
{% set widgetKey = "post-fb-subscribe" %}
|
||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Subscribe</h3>
|
<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"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -228,7 +267,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Recent Comments</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{# Homepage Builder Sidebar — renders widgets from homepageConfig.sidebar #}
|
{# Homepage Builder Sidebar — renders widgets from homepageConfig.sidebar #}
|
||||||
{# Each widget is wrapped in a collapsible container with localStorage persistence #}
|
{# Each widget is wrapped in a collapsible container with localStorage persistence #}
|
||||||
|
{% from "components/icon.njk" import icon %}
|
||||||
|
|
||||||
{% if homepageConfig.sidebar and homepageConfig.sidebar.length %}
|
{% if homepageConfig.sidebar and homepageConfig.sidebar.length %}
|
||||||
{% for widget in homepageConfig.sidebar %}
|
{% for widget in homepageConfig.sidebar %}
|
||||||
|
|
||||||
@@ -20,6 +22,37 @@
|
|||||||
{% else %}{% set widgetTitle = widget.type %}
|
{% else %}{% set widgetTitle = widget.type %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Resolve widget icon and accent border #}
|
||||||
|
{% if widget.type == "social-activity" %}
|
||||||
|
{% set widgetIcon = "globe" %}{% set widgetIconClass = "w-5 h-5 text-[#0085ff]" %}{% set widgetBorder = "border-l-[3px] border-l-[#0085ff]" %}
|
||||||
|
{% elif widget.type == "github-repos" %}
|
||||||
|
{% set widgetIcon = "github" %}{% set widgetIconClass = "w-5 h-5 text-surface-800 dark:text-surface-200" %}{% set widgetBorder = "border-l-[3px] border-l-surface-400 dark:border-l-surface-500" %}
|
||||||
|
{% elif widget.type == "funkwhale" %}
|
||||||
|
{% set widgetIcon = "headphones" %}{% set widgetIconClass = "w-5 h-5 text-purple-500" %}{% set widgetBorder = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
|
||||||
|
{% elif widget.type == "blogroll" %}
|
||||||
|
{% set widgetIcon = "book-open" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
|
||||||
|
{% elif widget.type == "feedland" %}
|
||||||
|
{% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
|
||||||
|
{% elif widget.type == "subscribe" %}
|
||||||
|
{% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-orange-500" %}{% set widgetBorder = "border-l-[3px] border-l-orange-400 dark:border-l-orange-500" %}
|
||||||
|
{% elif widget.type == "fediverse-follow" %}
|
||||||
|
{% set widgetIcon = "user-plus" %}{% set widgetIconClass = "w-5 h-5 text-[#a730b8]" %}{% set widgetBorder = "border-l-[3px] border-l-[#a730b8]" %}
|
||||||
|
{% elif widget.type == "author-card" %}
|
||||||
|
{% set widgetIcon = "user" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "recent-posts" %}
|
||||||
|
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "categories" %}
|
||||||
|
{% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "recent-comments" %}
|
||||||
|
{% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "search" %}
|
||||||
|
{% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "webmentions" %}
|
||||||
|
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% else %}
|
||||||
|
{% set widgetIcon = "" %}{% set widgetIconClass = "" %}{% set widgetBorder = "" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% set widgetKey = "widget-" + widget.type + "-" + loop.index0 %}
|
{% set widgetKey = "widget-" + widget.type + "-" + loop.index0 %}
|
||||||
{% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
|
{% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
|
||||||
|
|
||||||
@@ -28,13 +61,16 @@
|
|||||||
class="widget-collapsible mb-4"
|
class="widget-collapsible mb-4"
|
||||||
x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : {{ defaultOpen }} }"
|
x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : {{ defaultOpen }} }"
|
||||||
>
|
>
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden {{ widgetBorder }}">
|
||||||
<button
|
<button
|
||||||
class="widget-header w-full p-4"
|
class="widget-header w-full p-4"
|
||||||
@click="open = !open; localStorage.setItem('{{ widgetKey }}', open)"
|
@click="open = !open; localStorage.setItem('{{ widgetKey }}', open)"
|
||||||
:aria-expanded="open ? 'true' : 'false'"
|
:aria-expanded="open ? 'true' : 'false'"
|
||||||
>
|
>
|
||||||
<h3 class="widget-title font-bold text-lg">{{ widgetTitle }}</h3>
|
<h3 class="widget-title font-bold text-lg flex items-center gap-2">
|
||||||
|
{% if widgetIcon %}{{ icon(widgetIcon, widgetIconClass) }}{% endif %}
|
||||||
|
{{ widgetTitle }}
|
||||||
|
</h3>
|
||||||
<svg
|
<svg
|
||||||
class="widget-chevron"
|
class="widget-chevron"
|
||||||
:class="open && 'rotate-180'"
|
:class="open && 'rotate-180'"
|
||||||
|
|||||||
67
_includes/components/icon.njk
Normal file
67
_includes/components/icon.njk
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
{#
|
||||||
|
Centralized UI icon macro
|
||||||
|
Usage: {% from "components/icon.njk" import icon %}
|
||||||
|
{{ icon("heart", "w-5 h-5 text-red-500") }}
|
||||||
|
|
||||||
|
All icons use stroke-width="2" unless they are filled icons.
|
||||||
|
Default size: w-5 h-5 (override via cssClass parameter)
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% macro icon(name, cssClass) %}
|
||||||
|
{% set cls = cssClass or "w-5 h-5" %}
|
||||||
|
{%- if name == "heart" -%}
|
||||||
|
<svg class="{{ cls }}" 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>
|
||||||
|
{%- elif name == "bookmark" -%}
|
||||||
|
<svg class="{{ cls }}" 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>
|
||||||
|
{%- elif name == "repost" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 1l4 4-4 4"/><path d="M3 11V9a4 4 0 014-4h14"/><path d="M7 23l-4-4 4-4"/><path d="M21 13v2a4 4 0 01-4 4H3"/></svg>
|
||||||
|
{%- elif name == "reply" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/></svg>
|
||||||
|
{%- elif name == "camera" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/><circle cx="12" cy="13" r="4"/></svg>
|
||||||
|
{%- elif name == "article" -%}
|
||||||
|
<svg class="{{ cls }}" 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 00-2 2v16a2 2 0 002 2h12a2 2 0 002-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"/></svg>
|
||||||
|
{%- elif name == "note" -%}
|
||||||
|
<svg class="{{ cls }}" 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 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
||||||
|
{%- elif name == "music" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||||||
|
{%- elif name == "tag" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>
|
||||||
|
{%- elif name == "rss" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 11a9 9 0 019 9"/><path d="M4 4a16 16 0 0116 16"/><circle cx="5" cy="19" r="1"/></svg>
|
||||||
|
{%- elif name == "chat" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
|
||||||
|
{%- elif name == "user" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||||||
|
{%- elif name == "search" -%}
|
||||||
|
<svg class="{{ cls }}" 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>
|
||||||
|
{%- elif name == "star" -%}
|
||||||
|
<svg class="{{ cls }}" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
||||||
|
{%- elif name == "external-link" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
||||||
|
{%- elif name == "chevron-down" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 9l-7 7-7-7"/></svg>
|
||||||
|
{%- elif name == "chevron-right" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg>
|
||||||
|
{%- elif name == "globe" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" 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>
|
||||||
|
{%- elif name == "github" -%}
|
||||||
|
<svg class="{{ cls }}" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"/></svg>
|
||||||
|
{%- elif name == "list" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
|
||||||
|
{%- elif name == "share" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
|
||||||
|
{%- elif name == "book-open" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/></svg>
|
||||||
|
{%- elif name == "headphones" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 18v-6a9 9 0 0118 0v6"/><path d="M21 19a2 2 0 01-2 2h-1a2 2 0 01-2-2v-3a2 2 0 012-2h3zM3 19a2 2 0 002 2h1a2 2 0 002-2v-3a2 2 0 00-2-2H3z"/></svg>
|
||||||
|
{%- elif name == "mail" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
|
||||||
|
{%- elif name == "podcast" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17.657 18.657A8 8 0 016.343 7.343"/><path d="M9.879 16.121A3 3 0 1012.015 11L11 17H9c-2 0-3-2-3-3l.879-.879z"/></svg>
|
||||||
|
{%- elif name == "user-plus" -%}
|
||||||
|
<svg class="{{ cls }}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/></svg>
|
||||||
|
{%- else -%}
|
||||||
|
<!-- Unknown icon: {{ name }} -->
|
||||||
|
{%- endif -%}
|
||||||
|
{% endmacro %}
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<h1 class="text-2xl sm:text-3xl md:text-4xl font-bold text-surface-900 dark:text-surface-100 mb-2">
|
<h1 class="text-2xl sm:text-3xl md:text-4xl font-bold text-surface-900 dark:text-surface-100 mb-2">
|
||||||
{{ authorName }}
|
{{ authorName }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-lg sm:text-xl text-primary-600 dark:text-primary-400 mb-3 sm:mb-4">
|
<p class="text-lg sm:text-xl text-accent-600 dark:text-accent-400 mb-3 sm:mb-4">
|
||||||
{{ authorTitle }}
|
{{ authorTitle }}
|
||||||
</p>
|
</p>
|
||||||
{% if authorBio %}
|
{% if authorBio %}
|
||||||
|
|||||||
@@ -32,14 +32,14 @@
|
|||||||
{% elif repostedUrl %}
|
{% elif repostedUrl %}
|
||||||
{% set borderClass = "border-l-[3px] border-l-green-400 dark:border-l-green-500" %}
|
{% set borderClass = "border-l-[3px] border-l-green-400 dark:border-l-green-500" %}
|
||||||
{% elif replyToUrl %}
|
{% elif replyToUrl %}
|
||||||
{% set borderClass = "border-l-[3px] border-l-primary-400 dark:border-l-primary-500" %}
|
{% set borderClass = "border-l-[3px] border-l-sky-400 dark:border-l-sky-500" %}
|
||||||
{% elif hasPhotos %}
|
{% elif hasPhotos %}
|
||||||
{% set borderClass = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
|
{% set borderClass = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set borderClass = "border-l-[3px] border-l-surface-300 dark:border-l-surface-600" %}
|
{% set borderClass = "border-l-[3px] border-l-surface-300 dark:border-l-surface-600" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<article class="h-entry p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-primary-400 dark:hover:border-primary-600 transition-colors {{ borderClass }}">
|
<article class="h-entry p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-accent-400 dark:hover:border-accent-600 transition-colors {{ borderClass }}">
|
||||||
|
|
||||||
{% if likedUrl %}
|
{% if likedUrl %}
|
||||||
{# ── Like card ── #}
|
{# ── Like card ── #}
|
||||||
@@ -134,13 +134,13 @@
|
|||||||
{# ── Reply card ── #}
|
{# ── Reply card ── #}
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<div class="flex-shrink-0 mt-1">
|
<div class="flex-shrink-0 mt-1">
|
||||||
<svg class="w-5 h-5 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="w-5 h-5 text-sky-500" 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"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0">
|
<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-500">
|
||||||
<span class="font-medium text-primary-600 dark:text-primary-400">In reply to</span>
|
<span class="font-medium text-sky-600 dark:text-sky-400">In reply to</span>
|
||||||
<time class="dt-published" datetime="{{ post.date | isoDate }}">
|
<time class="dt-published" datetime="{{ post.date | isoDate }}">
|
||||||
{{ post.date | dateDisplay }}
|
{{ post.date | dateDisplay }}
|
||||||
</time>
|
</time>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{# Sidebar — for blog listing pages (/blog/, /notes/, /articles/...) #}
|
{# Sidebar — for blog listing pages (/blog/, /notes/, /articles/...) #}
|
||||||
{# Data-driven when homepageConfig.blogListingSidebar is configured, otherwise falls back to default widgets #}
|
{# Data-driven when homepageConfig.blogListingSidebar is configured, otherwise falls back to default widgets #}
|
||||||
{# Each widget is wrapped in a collapsible container with localStorage persistence #}
|
{# Each widget is wrapped in a collapsible container with localStorage persistence #}
|
||||||
|
{% from "components/icon.njk" import icon %}
|
||||||
|
|
||||||
{% if homepageConfig and homepageConfig.blogListingSidebar and homepageConfig.blogListingSidebar.length %}
|
{% if homepageConfig and homepageConfig.blogListingSidebar and homepageConfig.blogListingSidebar.length %}
|
||||||
{# === Data-driven mode: render configured widgets === #}
|
{# === Data-driven mode: render configured widgets === #}
|
||||||
@@ -25,6 +26,37 @@
|
|||||||
{% else %}{% set widgetTitle = widget.type %}
|
{% else %}{% set widgetTitle = widget.type %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Resolve widget icon and accent border #}
|
||||||
|
{% if widget.type == "social-activity" %}
|
||||||
|
{% set widgetIcon = "globe" %}{% set widgetIconClass = "w-5 h-5 text-[#0085ff]" %}{% set widgetBorder = "border-l-[3px] border-l-[#0085ff]" %}
|
||||||
|
{% elif widget.type == "github-repos" %}
|
||||||
|
{% set widgetIcon = "github" %}{% set widgetIconClass = "w-5 h-5 text-surface-800 dark:text-surface-200" %}{% set widgetBorder = "border-l-[3px] border-l-surface-400 dark:border-l-surface-500" %}
|
||||||
|
{% elif widget.type == "funkwhale" %}
|
||||||
|
{% set widgetIcon = "headphones" %}{% set widgetIconClass = "w-5 h-5 text-purple-500" %}{% set widgetBorder = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
|
||||||
|
{% elif widget.type == "blogroll" %}
|
||||||
|
{% set widgetIcon = "book-open" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
|
||||||
|
{% elif widget.type == "feedland" %}
|
||||||
|
{% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-amber-500" %}{% set widgetBorder = "border-l-[3px] border-l-amber-400 dark:border-l-amber-500" %}
|
||||||
|
{% elif widget.type == "subscribe" %}
|
||||||
|
{% set widgetIcon = "rss" %}{% set widgetIconClass = "w-5 h-5 text-orange-500" %}{% set widgetBorder = "border-l-[3px] border-l-orange-400 dark:border-l-orange-500" %}
|
||||||
|
{% elif widget.type == "fediverse-follow" %}
|
||||||
|
{% set widgetIcon = "user-plus" %}{% set widgetIconClass = "w-5 h-5 text-[#a730b8]" %}{% set widgetBorder = "border-l-[3px] border-l-[#a730b8]" %}
|
||||||
|
{% elif widget.type == "author-card" or widget.type == "author-card-compact" %}
|
||||||
|
{% set widgetIcon = "user" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "recent-posts" %}
|
||||||
|
{% set widgetIcon = "list" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "categories" %}
|
||||||
|
{% set widgetIcon = "tag" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "recent-comments" %}
|
||||||
|
{% set widgetIcon = "chat" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "search" %}
|
||||||
|
{% set widgetIcon = "search" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% elif widget.type == "webmentions" %}
|
||||||
|
{% set widgetIcon = "share" %}{% set widgetIconClass = "w-5 h-5 text-surface-500" %}{% set widgetBorder = "" %}
|
||||||
|
{% else %}
|
||||||
|
{% set widgetIcon = "" %}{% set widgetIconClass = "" %}{% set widgetBorder = "" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% set widgetKey = "listing-widget-" + widget.type + "-" + loop.index0 %}
|
{% set widgetKey = "listing-widget-" + widget.type + "-" + loop.index0 %}
|
||||||
{% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
|
{% set defaultOpen = "true" if loop.index0 < 3 else "false" %}
|
||||||
|
|
||||||
@@ -33,13 +65,16 @@
|
|||||||
class="widget-collapsible mb-4"
|
class="widget-collapsible mb-4"
|
||||||
x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : {{ defaultOpen }} }"
|
x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : {{ defaultOpen }} }"
|
||||||
>
|
>
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden {{ widgetBorder }}">
|
||||||
<button
|
<button
|
||||||
class="widget-header w-full p-4"
|
class="widget-header w-full p-4"
|
||||||
@click="open = !open; localStorage.setItem('{{ widgetKey }}', open)"
|
@click="open = !open; localStorage.setItem('{{ widgetKey }}', open)"
|
||||||
:aria-expanded="open ? 'true' : 'false'"
|
:aria-expanded="open ? 'true' : 'false'"
|
||||||
>
|
>
|
||||||
<h3 class="widget-title font-bold text-lg">{{ widgetTitle }}</h3>
|
<h3 class="widget-title font-bold text-lg flex items-center gap-2">
|
||||||
|
{% if widgetIcon %}{{ icon(widgetIcon, widgetIconClass) }}{% endif %}
|
||||||
|
{{ widgetTitle }}
|
||||||
|
</h3>
|
||||||
<svg
|
<svg
|
||||||
class="widget-chevron"
|
class="widget-chevron"
|
||||||
:class="open && 'rotate-180'"
|
:class="open && 'rotate-180'"
|
||||||
@@ -123,7 +158,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Author</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -135,9 +170,9 @@
|
|||||||
{# Social Activity — Bluesky/Mastodon feeds #}
|
{# Social Activity — Bluesky/Mastodon feeds #}
|
||||||
{% set widgetKey = "listing-fb-social-activity" %}
|
{% set widgetKey = "listing-fb-social-activity" %}
|
||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Social Activity</h3>
|
<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"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -149,9 +184,9 @@
|
|||||||
{# GitHub Repos #}
|
{# GitHub Repos #}
|
||||||
{% set widgetKey = "listing-fb-github-repos" %}
|
{% set widgetKey = "listing-fb-github-repos" %}
|
||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : true }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">GitHub</h3>
|
<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"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -163,9 +198,9 @@
|
|||||||
{# Funkwhale — Now Playing / Listening Stats #}
|
{# Funkwhale — Now Playing / Listening Stats #}
|
||||||
{% set widgetKey = "listing-fb-funkwhale" %}
|
{% set widgetKey = "listing-fb-funkwhale" %}
|
||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Listening</h3>
|
<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"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -179,7 +214,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Recent Posts</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -192,9 +227,9 @@
|
|||||||
{% if blogrollStatus and blogrollStatus.source == "indiekit" %}
|
{% if blogrollStatus and blogrollStatus.source == "indiekit" %}
|
||||||
{% set widgetKey = "listing-fb-blogroll" %}
|
{% set widgetKey = "listing-fb-blogroll" %}
|
||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Blogroll</h3>
|
<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"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -208,9 +243,9 @@
|
|||||||
{% if blogrollStatus and blogrollStatus.source == "indiekit" %}
|
{% if blogrollStatus and blogrollStatus.source == "indiekit" %}
|
||||||
{% set widgetKey = "listing-fb-feedland" %}
|
{% set widgetKey = "listing-fb-feedland" %}
|
||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">FeedLand</h3>
|
<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"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -225,7 +260,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Recent Comments</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
@@ -239,7 +274,7 @@
|
|||||||
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
<div class="widget-collapsible mb-4" x-data="{ open: localStorage.getItem('{{ widgetKey }}') !== null ? localStorage.getItem('{{ widgetKey }}') === 'true' : false }">
|
||||||
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm overflow-hidden">
|
<div class="bg-white 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'">
|
<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">Categories</h3>
|
<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>
|
<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>
|
||||||
</button>
|
</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>
|
<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>
|
||||||
|
|||||||
@@ -194,7 +194,7 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="px-4 py-2 text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 rounded transition-colors">
|
class="px-4 py-2 text-sm font-medium text-white bg-accent-600 hover:bg-accent-700 rounded transition-colors">
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -60,11 +60,11 @@
|
|||||||
|
|
||||||
{% elif replyToUrl %}
|
{% elif replyToUrl %}
|
||||||
<div class="flex items-start gap-2">
|
<div class="flex items-start gap-2">
|
||||||
<svg class="w-4 h-4 text-primary-500 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="w-4 h-4 text-sky-500 flex-shrink-0 mt-0.5" 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"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<a href="{{ post.url }}" class="text-sm text-primary-600 dark:text-primary-400 hover:underline break-all line-clamp-1">
|
<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) }}
|
Reply to {{ replyToUrl | replace("https://", "") | truncate(40) }}
|
||||||
</a>
|
</a>
|
||||||
<time class="text-xs text-surface-500 block" datetime="{{ post.data.published }}">
|
<time class="text-xs text-surface-500 block" datetime="{{ post.data.published }}">
|
||||||
|
|||||||
@@ -343,23 +343,23 @@
|
|||||||
<div>
|
<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-500 dark:text-surface-400 mb-3">Navigate</h4>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
<li><a href="/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400">Home</a></li>
|
<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-primary-600 dark:hover:text-primary-400">About</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>
|
||||||
<li><a href="/cv/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400">CV</a></li>
|
<li><a href="/cv/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">CV</a></li>
|
||||||
<li><a href="/changelog/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400">Changelog</a></li>
|
<li><a href="/changelog/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Changelog</a></li>
|
||||||
<li><a href="/search/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400">Search</a></li>
|
<li><a href="/search/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Search</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{# Content #}
|
{# Content #}
|
||||||
<div>
|
<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-500 dark:text-surface-400 mb-3">Content</h4>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
<li><a href="/blog/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400">Blog</a></li>
|
<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 %}
|
{% for pt in enabledPostTypes %}
|
||||||
<li><a href="{{ pt.path }}" class="text-sm text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400">{{ pt.label }}</a></li>
|
<li><a href="{{ pt.path }}" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">{{ pt.label }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li><a href="/interactions/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400">Interactions</a></li>
|
<li><a href="/interactions/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Interactions</a></li>
|
||||||
<li><a href="/digest/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400">Digest</a></li>
|
<li><a href="/digest/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Digest</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{# Connect #}
|
{# Connect #}
|
||||||
@@ -367,7 +367,7 @@
|
|||||||
<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-500 dark:text-surface-400 mb-3">Connect</h4>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
{% for social in site.social %}
|
{% 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-primary-600 dark:hover:text-primary-400">{{ social.name }}</a></li>
|
<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>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -375,13 +375,13 @@
|
|||||||
<div>
|
<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-500 dark:text-surface-400 mb-3">Meta</h4>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
<li><a href="/feed.xml" class="text-sm text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400">RSS Feed</a></li>
|
<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-primary-600 dark:hover:text-primary-400">JSON 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>
|
||||||
<li><a href="/changelog/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400">Changelog</a></li>
|
<li><a href="/changelog/" class="text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 hover:underline">Changelog</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</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-primary-600 dark:hover:text-primary-400">Indiekit</a> + <a href="https://11ty.dev" class="hover:text-primary-600 dark:hover:text-primary-400">Eleventy</a></p>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<script>
|
<script>
|
||||||
@@ -486,7 +486,7 @@
|
|||||||
class="fab-menu">
|
class="fab-menu">
|
||||||
{% if mpUrl %}
|
{% if mpUrl %}
|
||||||
<a href="/posts/edit?url={{ mpUrl | urlencode }}" @click="open = false" class="fab-menu-item" rel="nofollow">
|
<a href="/posts/edit?url={{ mpUrl | urlencode }}" @click="open = false" class="fab-menu-item" rel="nofollow">
|
||||||
<svg class="w-5 h-5 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<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">
|
||||||
<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="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"/>
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ withSidebar: true
|
|||||||
<h1 class="text-2xl sm:text-3xl md:text-4xl font-bold text-surface-900 dark:text-surface-100 mb-2">
|
<h1 class="text-2xl sm:text-3xl md:text-4xl font-bold text-surface-900 dark:text-surface-100 mb-2">
|
||||||
{{ site.author.name }}
|
{{ site.author.name }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-lg sm:text-xl text-primary-600 dark:text-primary-400 mb-3 sm:mb-4">
|
<p class="text-lg sm:text-xl text-accent-600 dark:text-accent-400 mb-3 sm:mb-4">
|
||||||
{{ site.author.title }}
|
{{ site.author.title }}
|
||||||
</p>
|
</p>
|
||||||
{% if site.author.bio %}
|
{% if site.author.bio %}
|
||||||
@@ -79,7 +79,7 @@ withSidebar: true
|
|||||||
<h2 class="text-xl sm:text-2xl font-bold text-surface-900 dark:text-surface-100 mb-4 sm:mb-6">Recent Posts</h2>
|
<h2 class="text-xl sm:text-2xl font-bold text-surface-900 dark:text-surface-100 mb-4 sm:mb-6">Recent Posts</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{% for post in collections.posts | head(10) %}
|
{% for post in collections.posts | head(10) %}
|
||||||
<article class="p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-primary-400 dark:hover:border-primary-600 transition-colors">
|
<article class="p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-accent-400 dark:hover:border-accent-600 transition-colors">
|
||||||
<h3 class="font-semibold text-surface-900 dark:text-surface-100 mb-1">
|
<h3 class="font-semibold text-surface-900 dark:text-surface-100 mb-1">
|
||||||
<a href="{{ post.url }}" class="hover:text-primary-600 dark:hover:text-primary-400">
|
<a href="{{ post.url }}" class="hover:text-primary-600 dark:hover:text-primary-400">
|
||||||
{{ post.data.title or post.data.name or "Untitled" }}
|
{{ post.data.title or post.data.name or "Untitled" }}
|
||||||
@@ -110,27 +110,27 @@ withSidebar: true
|
|||||||
<section class="mb-8 sm:mb-12">
|
<section class="mb-8 sm:mb-12">
|
||||||
<h2 class="text-xl sm:text-2xl font-bold text-surface-900 dark:text-surface-100 mb-4 sm:mb-6">Explore</h2>
|
<h2 class="text-xl sm:text-2xl font-bold text-surface-900 dark:text-surface-100 mb-4 sm:mb-6">Explore</h2>
|
||||||
<div class="grid gap-3 sm:grid-cols-3">
|
<div class="grid gap-3 sm:grid-cols-3">
|
||||||
<a href="/blog/" class="p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-primary-400 dark:hover:border-primary-600 transition-colors text-center">
|
<a href="/blog/" class="p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-accent-400 dark:hover:border-accent-600 transition-colors text-center">
|
||||||
<div class="text-2xl mb-2">
|
<div class="text-2xl mb-2">
|
||||||
<svg class="w-8 h-8 mx-auto text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
<svg class="w-8 h-8 mx-auto text-accent-600 dark:text-accent-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<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"/>
|
<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"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span class="font-semibold text-surface-900 dark:text-surface-100">Blog</span>
|
<span class="font-semibold text-surface-900 dark:text-surface-100">Blog</span>
|
||||||
<p class="text-sm text-surface-600 dark:text-surface-400 mt-1">Articles, notes, and photos</p>
|
<p class="text-sm text-surface-600 dark:text-surface-400 mt-1">Articles, notes, and photos</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="/cv/" class="p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-primary-400 dark:hover:border-primary-600 transition-colors text-center">
|
<a href="/cv/" class="p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-accent-400 dark:hover:border-accent-600 transition-colors text-center">
|
||||||
<div class="text-2xl mb-2">
|
<div class="text-2xl mb-2">
|
||||||
<svg class="w-8 h-8 mx-auto text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
<svg class="w-8 h-8 mx-auto text-accent-600 dark:text-accent-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/>
|
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span class="font-semibold text-surface-900 dark:text-surface-100">CV</span>
|
<span class="font-semibold text-surface-900 dark:text-surface-100">CV</span>
|
||||||
<p class="text-sm text-surface-600 dark:text-surface-400 mt-1">Experience and projects</p>
|
<p class="text-sm text-surface-600 dark:text-surface-400 mt-1">Experience and projects</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="/about/" class="p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-primary-400 dark:hover:border-primary-600 transition-colors text-center">
|
<a href="/about/" class="p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-accent-400 dark:hover:border-accent-600 transition-colors text-center">
|
||||||
<div class="text-2xl mb-2">
|
<div class="text-2xl mb-2">
|
||||||
<svg class="w-8 h-8 mx-auto text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
<svg class="w-8 h-8 mx-auto text-accent-600 dark:text-accent-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
6
blog.njk
6
blog.njk
@@ -53,7 +53,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
|||||||
{% elif repostedUrl %}
|
{% elif repostedUrl %}
|
||||||
{% set borderClass = "border-l-[3px] border-l-green-400 dark:border-l-green-500" %}
|
{% set borderClass = "border-l-[3px] border-l-green-400 dark:border-l-green-500" %}
|
||||||
{% elif replyToUrl %}
|
{% elif replyToUrl %}
|
||||||
{% set borderClass = "border-l-[3px] border-l-primary-400 dark:border-l-primary-500" %}
|
{% set borderClass = "border-l-[3px] border-l-sky-400 dark:border-l-sky-500" %}
|
||||||
{% elif hasPhotos %}
|
{% elif hasPhotos %}
|
||||||
{% set borderClass = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
|
{% set borderClass = "border-l-[3px] border-l-purple-400 dark:border-l-purple-500" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -187,13 +187,13 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
|||||||
{# ── Reply card ── #}
|
{# ── Reply card ── #}
|
||||||
<div class="post-header flex items-start gap-3">
|
<div class="post-header flex items-start gap-3">
|
||||||
<div class="flex-shrink-0 mt-1">
|
<div class="flex-shrink-0 mt-1">
|
||||||
<svg class="w-5 h-5 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="w-5 h-5 text-sky-500" 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"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="post-meta">
|
<div class="post-meta">
|
||||||
<span class="font-medium text-primary-600 dark:text-primary-400">In reply to</span>
|
<span class="font-medium text-sky-600 dark:text-sky-400">In reply to</span>
|
||||||
<time-difference><time class="dt-published" datetime="{{ post.date | isoDate }}">
|
<time-difference><time class="dt-published" datetime="{{ post.date | isoDate }}">
|
||||||
{{ post.date | dateDisplay }}
|
{{ post.date | dateDisplay }}
|
||||||
</time></time-difference>
|
</time></time-difference>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
/* Covers: layout shell, header, dark mode toggle, font display, basic typography */
|
/* Covers: layout shell, header, dark mode toggle, font display, basic typography */
|
||||||
|
|
||||||
*,*::before,*::after{box-sizing:border-box}
|
*,*::before,*::after{box-sizing:border-box}
|
||||||
body{margin:0;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;line-height:1.5;-webkit-font-smoothing:antialiased}
|
body{margin:0;font-family:"Inter",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;line-height:1.5;-webkit-font-smoothing:antialiased}
|
||||||
|
|
||||||
/* Dark mode base */
|
/* Dark mode base */
|
||||||
body{background-color:#fff;color:#18181b}
|
body{background-color:#fff;color:#18181b}
|
||||||
|
|||||||
@@ -1,3 +1,69 @@
|
|||||||
|
/* Inter font — latin + latin-ext subsets, weights 400/500/600/700 */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(/fonts/inter-latin-ext-400-normal.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(/fonts/inter-latin-400-normal.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url(/fonts/inter-latin-ext-500-normal.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url(/fonts/inter-latin-500-normal.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 600;
|
||||||
|
src: url(/fonts/inter-latin-ext-600-normal.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 600;
|
||||||
|
src: url(/fonts/inter-latin-600-normal.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url(/fonts/inter-latin-ext-700-normal.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url(/fonts/inter-latin-700-normal.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
@@ -56,7 +122,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.site-title {
|
.site-title {
|
||||||
@apply text-xl font-bold text-surface-900 dark:text-white no-underline hover:text-primary-600 dark:hover:text-primary-400 transition-colors;
|
@apply text-xl font-bold text-surface-900 dark:text-white no-underline hover:text-surface-600 dark:hover:text-surface-300 transition-colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header actions (nav + theme toggle) */
|
/* Header actions (nav + theme toggle) */
|
||||||
@@ -70,7 +136,7 @@
|
|||||||
|
|
||||||
.site-nav > a,
|
.site-nav > a,
|
||||||
.site-nav .nav-dropdown-trigger {
|
.site-nav .nav-dropdown-trigger {
|
||||||
@apply text-surface-600 dark:text-surface-400 hover:text-primary-600 dark:hover:text-primary-400 no-underline transition-colors py-2;
|
@apply text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-100 no-underline transition-colors py-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navigation dropdown */
|
/* Navigation dropdown */
|
||||||
@@ -89,7 +155,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-dropdown-menu a {
|
.nav-dropdown-menu a {
|
||||||
@apply block px-4 py-2 text-sm text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-700 hover:text-primary-600 dark:hover:text-primary-400 no-underline;
|
@apply block px-4 py-2 text-sm text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-700 hover:text-surface-900 dark:hover:text-surface-100 no-underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-dropdown-divider {
|
.nav-dropdown-divider {
|
||||||
@@ -109,7 +175,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mobile-nav a {
|
.mobile-nav a {
|
||||||
@apply block px-4 py-3 text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 hover:text-primary-600 dark:hover:text-primary-400 no-underline transition-colors border-b border-surface-100 dark:border-surface-800 last:border-b-0;
|
@apply block px-4 py-3 text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 hover:text-surface-900 dark:hover:text-surface-100 no-underline transition-colors border-b border-surface-100 dark:border-surface-800 last:border-b-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile nav collapsible sections */
|
/* Mobile nav collapsible sections */
|
||||||
@@ -118,7 +184,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mobile-nav-toggle {
|
.mobile-nav-toggle {
|
||||||
@apply flex items-center justify-between w-full px-4 py-3 text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 hover:text-primary-600 dark:hover:text-primary-400 transition-colors bg-transparent border-none text-base text-left cursor-pointer;
|
@apply flex items-center justify-between w-full px-4 py-3 text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 hover:text-surface-900 dark:hover:text-surface-100 transition-colors bg-transparent border-none text-base text-left cursor-pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-nav-submenu {
|
.mobile-nav-submenu {
|
||||||
@@ -156,7 +222,7 @@
|
|||||||
|
|
||||||
/* Mobile theme toggle */
|
/* Mobile theme toggle */
|
||||||
.mobile-theme-toggle {
|
.mobile-theme-toggle {
|
||||||
@apply flex items-center justify-between w-full px-4 py-3 text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 hover:text-primary-600 dark:hover:text-primary-400 transition-colors bg-transparent border-none text-base text-left cursor-pointer border-t border-surface-200 dark:border-surface-700;
|
@apply flex items-center justify-between w-full px-4 py-3 text-surface-600 dark:text-surface-400 hover:bg-surface-100 dark:hover:bg-surface-800 hover:text-surface-900 dark:hover:text-surface-100 transition-colors bg-transparent border-none text-base text-left cursor-pointer border-t border-surface-200 dark:border-surface-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-theme-toggle .theme-label {
|
.mobile-theme-toggle .theme-label {
|
||||||
@@ -234,7 +300,7 @@
|
|||||||
|
|
||||||
/* Category tags */
|
/* Category tags */
|
||||||
.p-category {
|
.p-category {
|
||||||
@apply inline-block px-2 py-0.5 text-xs bg-primary-100 dark:bg-primary-900 text-primary-800 dark:text-primary-200 rounded;
|
@apply inline-block px-2 py-0.5 text-xs bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-300 rounded border border-surface-200 dark:border-surface-700 hover:border-surface-400 dark:hover:border-surface-500 transition-colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Webmention facepile - overlapping avatar display */
|
/* Webmention facepile - overlapping avatar display */
|
||||||
@@ -261,7 +327,7 @@
|
|||||||
|
|
||||||
/* Timeline for CV */
|
/* Timeline for CV */
|
||||||
.timeline {
|
.timeline {
|
||||||
@apply relative pl-6 border-l-2 border-primary-500;
|
@apply relative pl-6 border-l-2 border-accent-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-item {
|
.timeline-item {
|
||||||
@@ -270,7 +336,7 @@
|
|||||||
|
|
||||||
.timeline-item::before {
|
.timeline-item::before {
|
||||||
content: '';
|
content: '';
|
||||||
@apply absolute -left-[calc(1.5rem+5px)] top-1.5 w-3 h-3 bg-primary-500 rounded-full;
|
@apply absolute -left-[calc(1.5rem+5px)] top-1.5 w-3 h-3 bg-accent-500 rounded-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Skills badges */
|
/* Skills badges */
|
||||||
@@ -422,11 +488,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fab-button {
|
.fab-button {
|
||||||
@apply relative z-50 w-14 h-14 rounded-full bg-primary-600 hover:bg-primary-700 dark:bg-primary-500 dark:hover:bg-primary-600 text-white shadow-lg hover:shadow-xl transition-all duration-200 flex items-center justify-center;
|
@apply relative z-50 w-14 h-14 rounded-full bg-accent-600 hover:bg-accent-700 dark:bg-accent-500 dark:hover:bg-accent-600 text-white shadow-lg hover:shadow-xl transition-all duration-200 flex items-center justify-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fab-button:focus-visible {
|
.fab-button:focus-visible {
|
||||||
@apply outline-2 outline-offset-2 outline-primary-500;
|
@apply outline-2 outline-offset-2 outline-accent-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fab-menu {
|
.fab-menu {
|
||||||
@@ -434,7 +500,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fab-menu-item {
|
.fab-menu-item {
|
||||||
@apply flex items-center gap-3 px-4 py-3 rounded-xl bg-white dark:bg-surface-800 shadow-md hover:shadow-lg border border-surface-200 dark:border-surface-700 text-surface-700 dark:text-surface-200 hover:text-primary-600 dark:hover:text-primary-400 no-underline transition-all duration-150 text-sm font-medium;
|
@apply flex items-center gap-3 px-4 py-3 rounded-xl bg-white dark:bg-surface-800 shadow-md hover:shadow-lg border border-surface-200 dark:border-surface-700 text-surface-700 dark:text-surface-200 hover:text-accent-600 dark:hover:text-accent-400 no-underline transition-all duration-150 text-sm font-medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fab-menu-divider {
|
.fab-menu-divider {
|
||||||
@@ -442,7 +508,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.admin-nav-link {
|
.admin-nav-link {
|
||||||
@apply text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 no-underline transition-colors py-2 inline-flex items-center gap-1;
|
@apply text-accent-600 dark:text-accent-400 hover:text-accent-700 dark:hover:text-accent-300 no-underline transition-colors py-2 inline-flex items-center gap-1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -363,6 +363,12 @@ export default function (eleventyConfig) {
|
|||||||
"node_modules/@zachleat/filter-container/filter-container.js": "js/filter-container.js",
|
"node_modules/@zachleat/filter-container/filter-container.js": "js/filter-container.js",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Copy Inter font files (latin + latin-ext subsets, woff2 only for modern browsers)
|
||||||
|
eleventyConfig.addPassthroughCopy({
|
||||||
|
"node_modules/@fontsource/inter/files/inter-latin-*-normal.woff2": "fonts",
|
||||||
|
"node_modules/@fontsource/inter/files/inter-latin-ext-*-normal.woff2": "fonts",
|
||||||
|
});
|
||||||
|
|
||||||
// Watch for content changes
|
// Watch for content changes
|
||||||
eleventyConfig.addWatchTarget("./content/");
|
eleventyConfig.addWatchTarget("./content/");
|
||||||
eleventyConfig.addWatchTarget("./css/");
|
eleventyConfig.addWatchTarget("./css/");
|
||||||
|
|||||||
@@ -26,6 +26,19 @@ export default {
|
|||||||
900: "#1e3a8a",
|
900: "#1e3a8a",
|
||||||
950: "#172554",
|
950: "#172554",
|
||||||
},
|
},
|
||||||
|
accent: {
|
||||||
|
50: "#f0fdfa",
|
||||||
|
100: "#ccfbf1",
|
||||||
|
200: "#99f6e4",
|
||||||
|
300: "#5eead4",
|
||||||
|
400: "#2dd4bf",
|
||||||
|
500: "#14b8a6",
|
||||||
|
600: "#0d9488",
|
||||||
|
700: "#0f766e",
|
||||||
|
800: "#115e59",
|
||||||
|
900: "#134e4a",
|
||||||
|
950: "#042f2e",
|
||||||
|
},
|
||||||
surface: {
|
surface: {
|
||||||
50: "#fafafa",
|
50: "#fafafa",
|
||||||
100: "#f4f4f5",
|
100: "#f4f4f5",
|
||||||
@@ -42,6 +55,7 @@ export default {
|
|||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: [
|
sans: [
|
||||||
|
'"Inter"',
|
||||||
"system-ui",
|
"system-ui",
|
||||||
"-apple-system",
|
"-apple-system",
|
||||||
"BlinkMacSystemFont",
|
"BlinkMacSystemFont",
|
||||||
|
|||||||
Reference in New Issue
Block a user