mirror of
https://github.com/svemagie/blog-eleventy-indiekit.git
synced 2026-04-02 08:44:56 +02:00
- Focus traps for fediverse modal and lightbox dialogs (C3, C4) - Search widget input label (C5) - Blogroll widget tab ARIA semantics (C6) - Footer social links "opens in new tab" warning (S5) - Reply context aria-label on aside (S8) - Photo alt text fallback includes post title (S10) - Post categories use list markup (M3) - Funkwhale now-playing bars aria-hidden (M7) - TOC uses static Tailwind classes instead of dynamic (M9) - Footer headings use proper aria heading roles (M15) - Header anchor opacity increased to 1 for contrast (M18) - Custom HTML widgets labeled as regions (M19) - Empty collection placeholder role=status (M22) - GitHub widget loading state announced (N5) - Subscribe icon contrast improved (m1) - All Permalink links have aria-label with post context (m3) - Podroll audio element aria-label (m4) - Obfuscated email link aria-label (m6) - Fediverse follow button uses aria-label (M10) Score: 53.6% → 92.9% (26/28 WCAG criteria passing) Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
296 lines
20 KiB
Plaintext
296 lines
20 KiB
Plaintext
---
|
|
layout: layouts/base.njk
|
|
withBlogSidebar: true
|
|
---
|
|
<article class="h-entry post" x-data="lightbox" @keydown.window="onKeydown($event)">
|
|
{# Support both camelCase (Indiekit Eleventy preset) and underscore (legacy) property names #}
|
|
{% set bookmarkedUrl = bookmarkOf or bookmark_of %}
|
|
{% set likedUrl = likeOf or like_of %}
|
|
{% set replyTo = inReplyTo or in_reply_to %}
|
|
{% set repostedUrl = repostOf or repost_of %}
|
|
|
|
{% if title %}
|
|
<h1 class="p-name text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100 mb-3 sm:mb-4">{{ title }}</h1>
|
|
{% else %}
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="text-sm font-medium text-surface-600 dark:text-surface-400">
|
|
{% if replyTo %}↩ Reply{% elif likedUrl %}♥ Like{% elif repostedUrl %}♻ Repost{% elif bookmarkedUrl %}🔖 Bookmark{% else %}✎ Note{% endif %}
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="post-meta mb-4 sm:mb-6">
|
|
<time-difference><time class="dt-published font-mono text-sm" datetime="{{ date.toISOString() }}">
|
|
{{ date | dateDisplay }}
|
|
</time></time-difference>
|
|
{% if category %}
|
|
<ul class="post-categories" role="list" aria-label="Categories">
|
|
{# Handle both string and array categories #}
|
|
{% if category is string %}
|
|
<li><a href="/categories/{{ category | slugify }}/" class="p-category">{{ category }}</a></li>
|
|
{% else %}
|
|
{% for cat in category %}
|
|
<li><a href="/categories/{{ cat | slugify }}/" class="p-category">{{ cat }}</a></li>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</ul>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# Bridgy syndication content - controls what gets posted to social networks #}
|
|
{# For interaction types (bookmarks, likes, replies, reposts), include the target URL #}
|
|
{% set bridgySummary = description or summary or (content | ogDescription(280)) %}
|
|
{% set interactionUrl = bookmarkedUrl or likedUrl or replyTo or repostedUrl %}
|
|
|
|
{% if bridgySummary or interactionUrl %}
|
|
<p class="p-summary e-bridgy-mastodon-content e-bridgy-bluesky-content hidden">{% if bookmarkedUrl %}🔖 {{ bookmarkedUrl }}{% if bridgySummary %} - {{ bridgySummary }}{% endif %}{% elif likedUrl %}❤️ {{ likedUrl }}{% if bridgySummary %} - {{ bridgySummary }}{% endif %}{% elif replyTo %}↩️ {{ replyTo }}{% if bridgySummary %} - {{ bridgySummary }}{% endif %}{% elif repostedUrl %}🔁 {{ repostedUrl }}{% if bridgySummary %} - {{ bridgySummary }}{% endif %}{% else %}{{ bridgySummary }}{% endif %}</p>
|
|
{% endif %}
|
|
|
|
{# Render photo(s) from frontmatter for photo posts - use eleventy:ignore to skip image transform #}
|
|
{% if photo %}
|
|
<div class="photo-gallery mb-6">
|
|
{% for img in photo %}
|
|
{% set photoUrl = img.url %}
|
|
{% if photoUrl and photoUrl[0] != '/' and 'http' not in photoUrl %}
|
|
{% set photoUrl = '/' + photoUrl %}
|
|
{% endif %}
|
|
<img src="{{ photoUrl }}" alt="{{ img.alt | default('Photo from: ' + title) }}" class="u-photo rounded-lg max-w-full" loading="lazy" eleventy:ignore>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set isInteraction = replyTo or likedUrl or repostedUrl or bookmarkedUrl %}
|
|
{% set hasContent = content and content | striptags | trim %}
|
|
<div class="e-content prose prose-surface dark:prose-invert max-w-none{% if isInteraction and hasContent %} border-l-[3px] border-l-accent-500 dark:border-l-accent-400 pl-4{% endif %}">
|
|
{{ content | safe }}
|
|
</div>
|
|
|
|
{# Rich reply context with h-cite microformat #}
|
|
{% include "components/reply-context.njk" %}
|
|
|
|
{# AI usage disclosure — always shown, collapsed by default, placed after reply context #}
|
|
{% set aiTextLevel = aiTextLevel or ai_text_level or "0" %}
|
|
{% set aiCodeLevel = aiCodeLevel or ai_code_level %}
|
|
{% set aiTools = aiTools or ai_tools %}
|
|
{% set aiDescription = aiDescription or ai_description %}
|
|
<details class="mt-4 text-xs text-surface-600 dark:text-surface-400">
|
|
<summary class="cursor-pointer hover:text-surface-600 dark:hover:text-surface-300 list-none flex items-center gap-1.5 [&::-webkit-details-marker]:hidden">
|
|
<svg class="w-3.5 h-3.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714a2.25 2.25 0 00.659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 01-1.59.659H9.06a2.25 2.25 0 01-1.591-.659L5 14.5m14 0V17a2 2 0 01-2 2H7a2 2 0 01-2-2v-2.5"/>
|
|
</svg>
|
|
<span>AI:
|
|
Text {% if aiTextLevel === "0" %}None{% elif aiTextLevel === "1" %}Editorial{% elif aiTextLevel === "2" %}Co-drafted{% elif aiTextLevel === "3" %}AI-generated{% endif %}{% if aiCodeLevel %} · Code {% if aiCodeLevel === "0" %}Human{% elif aiCodeLevel === "1" %}AI-assisted{% elif aiCodeLevel === "2" %}AI-generated{% endif %}{% endif %}{% if aiTools %} · {{ aiTools }}{% endif %}
|
|
</span>
|
|
</summary>
|
|
{% if aiDescription %}
|
|
<p class="mt-1 pl-5">{{ aiDescription }}</p>
|
|
{% endif %}
|
|
<p class="mt-1 pl-5"><a href="/ai/" class="hover:text-accent-600 dark:hover:text-accent-400 underline">Learn more about AI usage on this site</a></p>
|
|
</details>
|
|
|
|
{# Pending syndication targets (for services like IndieNews that require u-syndication before webmention) #}
|
|
{% if mpSyndicateTo %}
|
|
<div class="hidden">
|
|
{% for url in mpSyndicateTo %}
|
|
{% if "news.indieweb.org" in url %}
|
|
<a href="{{ url }}" class="u-syndication" rel="syndication">IndieNews</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Syndication Footer - shows where this post was also published #}
|
|
{# Separate self-hosted AP URLs from external syndication targets #}
|
|
{% set externalSyndication = [] %}
|
|
{% set selfHostedApUrl = "" %}
|
|
{% if syndication %}
|
|
{% for url in syndication %}
|
|
{% if url.indexOf(site.url) == 0 %}
|
|
{% set selfHostedApUrl = url %}
|
|
{% else %}
|
|
{% set externalSyndication = (externalSyndication.push(url), externalSyndication) %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% if externalSyndication.length or selfHostedApUrl %}
|
|
<footer class="post-footer mt-8 pt-6 border-t border-surface-200 dark:border-surface-700">
|
|
<div class="flex flex-wrap items-center gap-4">
|
|
<span class="text-sm text-surface-600 dark:text-surface-400">Also on:</span>
|
|
<div class="flex flex-wrap gap-3">
|
|
{# Fediverse remote interaction button (self-hosted ActivityPub) #}
|
|
{% if selfHostedApUrl %}
|
|
<span x-data="fediverseInteract('{{ selfHostedApUrl }}', 'interact')" class="inline-flex">
|
|
<a class="u-syndication inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-[#a730b8]/10 text-[#a730b8] hover:bg-[#a730b8]/20 transition-colors text-sm font-medium cursor-pointer"
|
|
href="{{ selfHostedApUrl }}"
|
|
rel="syndication"
|
|
title="Interact from your fediverse instance (Shift+click to change)"
|
|
@click="handleClick($event)">
|
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M13.09 4.43L24 10.73v2.51L13.09 19.58v-2.51L21.83 12 13.09 6.98v-2.55zM13.09 9.49L17.44 12l-4.35 2.51V9.49z"/><path d="M10.91 4.43L0 10.73v2.51l8.74-5.03v10.09l2.18 1.28V4.43zM6.56 12L2.18 14.51l4.35 2.51V12z"/></svg>
|
|
<span>Fediverse</span>
|
|
</a>
|
|
{% set modalTitle = "Fediverse Interaction" %}
|
|
{% set modalDescription = "Choose your instance to like, boost, or reply." %}
|
|
{% include "components/fediverse-modal.njk" %}
|
|
</span>
|
|
{% endif %}
|
|
|
|
{# External syndication buttons #}
|
|
{% for url in externalSyndication %}
|
|
{% if "bsky.app" in url or "bluesky" in url %}
|
|
<a class="u-syndication inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-[#0085ff]/10 text-[#0085ff] hover:bg-[#0085ff]/20 transition-colors text-sm font-medium" href="{{ url }}" target="_blank" rel="noopener syndication" title="View on Bluesky">
|
|
<svg class="w-4 h-4" viewBox="0 0 568 501" fill="currentColor" aria-hidden="true">
|
|
<path d="M123.121 33.664C188.241 82.553 258.281 181.68 284 234.873c25.719-53.192 95.759-152.32 160.879-201.21C491.866-1.611 568-28.906 568 57.947c0 17.346-9.945 145.713-15.778 166.555-20.275 72.453-94.155 90.933-159.875 79.748C507.222 323.8 536.444 388.56 473.333 453.32c-119.86 122.992-172.272-30.859-185.702-70.281-2.462-7.227-3.614-10.608-3.631-7.733-.017-2.875-1.169.506-3.631 7.733-13.43 39.422-65.842 193.273-185.702 70.281-63.111-64.76-33.89-129.52 80.986-149.071-65.72 11.185-139.6-7.295-159.875-79.748C9.945 203.659 0 75.291 0 57.946 0-28.906 76.135-1.612 123.121 33.664Z"/>
|
|
</svg>
|
|
<span>Bluesky</span>
|
|
</a>
|
|
{% elif site.feeds.mastodon.instance and site.feeds.mastodon.instance in url %}
|
|
<a class="u-syndication inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-[#6364ff]/10 text-[#6364ff] hover:bg-[#6364ff]/20 transition-colors text-sm font-medium" href="{{ url }}" target="_blank" rel="noopener syndication" title="View on Mastodon">
|
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
<path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.668 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/>
|
|
</svg>
|
|
<span>Mastodon</span>
|
|
</a>
|
|
{% elif "linkedin.com" in url %}
|
|
<a class="u-syndication inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-[#0a66c2]/10 text-[#0a66c2] hover:bg-[#0a66c2]/20 transition-colors text-sm font-medium" href="{{ url }}" target="_blank" rel="noopener syndication" title="View on LinkedIn">
|
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
|
|
</svg>
|
|
<span>LinkedIn</span>
|
|
</a>
|
|
{% elif "news.indieweb.org" in url %}
|
|
<a class="u-syndication inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-[#ff5c00]/10 text-[#ff5c00] hover:bg-[#ff5c00]/20 transition-colors text-sm font-medium" href="{{ url }}" target="_blank" rel="noopener syndication" title="View on IndieNews">
|
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/><line x1="10" y1="6" x2="18" y2="6"/><line x1="10" y1="10" x2="18" y2="10"/><line x1="10" y1="14" x2="14" y2="14"/>
|
|
</svg>
|
|
<span>IndieNews</span>
|
|
</a>
|
|
{% elif "/@" in url %}
|
|
{# Mastodon-compatible instance (any URL with /@username pattern) #}
|
|
{% set mastoHandle = url | replace("https://", "") | replace("http://", "") %}
|
|
{% set mastoHandle = mastoHandle.split("/")[0] + "/" + mastoHandle.split("/")[1] %}
|
|
<a class="u-syndication inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-[#6364ff]/10 text-[#6364ff] hover:bg-[#6364ff]/20 transition-colors text-sm font-medium" href="{{ url }}" target="_blank" rel="noopener syndication" title="View on Mastodon">
|
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
<path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.668 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/>
|
|
</svg>
|
|
<span>{{ mastoHandle }}</span>
|
|
</a>
|
|
{% else %}
|
|
<a class="u-syndication inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-700 transition-colors text-sm font-medium" href="{{ url }}" target="_blank" rel="noopener syndication">
|
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
|
<polyline points="15 3 21 3 21 9"/>
|
|
<line x1="10" y1="14" x2="21" y2="3"/>
|
|
</svg>
|
|
<span>{{ url | replace("https://", "") | truncate(20) }}</span>
|
|
</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
{% endif %}
|
|
|
|
<a class="u-url" href="{{ page.url }}" hidden>Permalink</a>
|
|
|
|
{# Author h-card for IndieWeb authorship #}
|
|
<span class="p-author h-card hidden">
|
|
<a class="p-name u-url" href="{{ site.author.url }}">{{ site.author.name }}</a>
|
|
<img class="u-photo" src="{{ site.author.avatar }}" alt="{{ site.author.name }}" hidden>
|
|
</span>
|
|
|
|
{# Pagefind filter metadata — hidden elements for search filtering #}
|
|
<div hidden>
|
|
{% if replyTo %}<span data-pagefind-filter="type">Reply</span>
|
|
{% elif likedUrl %}<span data-pagefind-filter="type">Like</span>
|
|
{% elif repostedUrl %}<span data-pagefind-filter="type">Repost</span>
|
|
{% elif bookmarkedUrl %}<span data-pagefind-filter="type">Bookmark</span>
|
|
{% elif photo and photo.length %}<span data-pagefind-filter="type">Photo</span>
|
|
{% elif title %}<span data-pagefind-filter="type">Article</span>
|
|
{% else %}<span data-pagefind-filter="type">Note</span>
|
|
{% endif %}
|
|
<span data-pagefind-filter="year">{{ date | date("yyyy") }}</span>
|
|
{% if category %}
|
|
{% if category is string %}
|
|
<span data-pagefind-filter="category">{{ category }}</span>
|
|
{% else %}
|
|
{% for cat in category %}
|
|
<span data-pagefind-filter="category">{{ cat }}</span>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# JSON-LD Structured Data for SEO #}
|
|
{# Handle photo as potentially an array #}
|
|
{% set postImage = photo %}
|
|
{% if postImage %}
|
|
{# If photo is an array, use first element (check if first element looks like a URL) #}
|
|
{% if postImage[0] and (postImage[0] | length) > 10 %}
|
|
{% set postImage = postImage[0] %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% if not postImage or postImage == "" %}
|
|
{% set postImage = image or (content | extractFirstImage) %}
|
|
{% endif %}
|
|
{% set postDesc = description | default(content | ogDescription(160)) %}
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "Article",
|
|
"headline": {{ (title or "Untitled") | dump | safe }},
|
|
"url": "{{ site.url }}{{ page.url }}",
|
|
"mainEntityOfPage": {
|
|
"@type": "WebPage",
|
|
"@id": "{{ site.url }}{{ page.url }}"
|
|
},
|
|
"datePublished": "{{ date.toISOString() }}",
|
|
"dateModified": "{{ date.toISOString() }}",
|
|
"author": {
|
|
"@type": "Person",
|
|
"name": "{{ site.author.name }}",
|
|
"url": "{{ site.author.url }}"
|
|
},
|
|
"publisher": {
|
|
"@type": "Organization",
|
|
"name": "{{ site.name }}",
|
|
"url": "{{ site.url }}",
|
|
"logo": {
|
|
"@type": "ImageObject",
|
|
"url": "{{ site.url }}/images/og-default.png"
|
|
}
|
|
},
|
|
"description": {{ postDesc | dump | safe }}{% if postImage and postImage != "" and (postImage | length) > 10 %},
|
|
"image": ["{% if postImage.startsWith('http') %}{{ postImage }}{% elif '/' in postImage and postImage[0] == '/' %}{{ site.url }}{{ postImage }}{% else %}{{ site.url }}/{{ postImage }}{% endif %}"]{% endif %}{% if aiTextLevel or aiCodeLevel or aiTools %},
|
|
"usageInfo": "{{ site.url }}/ai"{% set _aiParts = [] %}{% if aiTextLevel %}{% set _textLabel %}{% if aiTextLevel === "0" %}None{% elif aiTextLevel === "1" %}Editorial assistance{% elif aiTextLevel === "2" %}Co-drafting{% elif aiTextLevel === "3" %}AI-generated{% endif %}{% endset %}{% set _aiParts = (_aiParts.push("Text: " + _textLabel), _aiParts) %}{% endif %}{% if aiCodeLevel %}{% set _codeLabel %}{% if aiCodeLevel === "0" %}Human-written{% elif aiCodeLevel === "1" %}AI-assisted{% elif aiCodeLevel === "2" %}AI-generated{% endif %}{% endset %}{% set _aiParts = (_aiParts.push("Code: " + _codeLabel), _aiParts) %}{% endif %}{% if aiTools %}{% set _aiParts = (_aiParts.push("Tools: " + aiTools), _aiParts) %}{% endif %},
|
|
"creativeWorkStatus": "{{ _aiParts | join(', ') }}"{% endif %}
|
|
}
|
|
</script>
|
|
|
|
{# Lightbox overlay for article images #}
|
|
<template x-teleport="body">
|
|
<div x-show="open" x-transition.opacity.duration.200ms
|
|
role="dialog" aria-modal="true" aria-label="Image viewer"
|
|
class="fixed inset-0 z-[9999] flex items-center justify-center bg-black/90 backdrop-blur-sm"
|
|
@click.self="close()">
|
|
<button x-ref="closeBtn" @click="close()" class="absolute top-4 right-4 text-white/70 hover:text-white text-3xl leading-none p-2 z-10" aria-label="Close lightbox">×</button>
|
|
<template x-if="images.length > 1">
|
|
<button @click="prev()" class="absolute left-4 top-1/2 -translate-y-1/2 text-white/70 hover:text-white text-4xl leading-none p-2 z-10" aria-label="Previous">‹</button>
|
|
</template>
|
|
<template x-if="images.length > 1">
|
|
<button @click="next()" class="absolute right-4 top-1/2 -translate-y-1/2 text-white/70 hover:text-white text-4xl leading-none p-2 z-10" aria-label="Next">›</button>
|
|
</template>
|
|
<img :src="src" :alt="alt" class="max-h-[90vh] max-w-[90vw] object-contain" @click.stop>
|
|
<div x-show="alt" x-text="alt" class="absolute bottom-4 left-1/2 -translate-x-1/2 text-white/80 text-sm max-w-2xl text-center px-4 py-2 bg-black/50 rounded-lg"></div>
|
|
</div>
|
|
</template>
|
|
</article>
|
|
|
|
{# Comments section #}
|
|
{% include "components/comments.njk" %}
|
|
|
|
{# Webmentions display - likes, reposts, replies #}
|
|
{% include "components/webmentions.njk" %}
|
|
|
|
{# Post Navigation - Previous/Next #}
|
|
{% include "components/post-navigation.njk" %}
|