feat: graceful no-JS fallback and noscript handling

- Add <noscript><style> in base.njk that unhides x-cloak/x-show content,
  hides FAB and tab buttons when JS is disabled (content stacks instead)
- Add noscript message on search page with links to blog/categories
- Add noscript banner on interactions page explaining inbound tab needs JS
This commit is contained in:
Ricardo
2026-02-19 17:35:21 +01:00
parent 03ace58be5
commit 656a70eb0e
3 changed files with 27 additions and 0 deletions

View File

@@ -76,6 +76,21 @@
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>[x-cloak] { display: none !important; }</style> <style>[x-cloak] { display: none !important; }</style>
{# Graceful no-JS fallback: show content that Alpine would normally control #}
<noscript>
<style>
/* Override x-cloak so hidden content is visible without Alpine */
[x-cloak] { display: block !important; }
/* Show all tab panels stacked (Alpine x-show tabs) */
[x-show] { display: block !important; }
/* Hide JS-only interactive controls */
.fab-container, .fab-button, .fab-backdrop, .fab-menu { display: none !important; }
/* Hide tab button rows - content shows stacked instead */
[x-data] > .flex.border-b { display: none !important; }
/* Hide loading spinners and JS-only buttons */
[x-show*="loading"], button[\\@click*="fetch"], button[\\@click*="loadMore"] { display: none !important; }
</style>
</noscript>
<link rel="canonical" href="{{ site.url }}{{ page.url }}"> <link rel="canonical" href="{{ site.url }}{{ page.url }}">
<link rel="alternate" type="application/rss+xml" href="/feed.xml" title="RSS Feed"> <link rel="alternate" type="application/rss+xml" href="/feed.xml" title="RSS Feed">
<link rel="alternate" type="application/json" href="/feed.json" title="JSON Feed"> <link rel="alternate" type="application/json" href="/feed.json" title="JSON Feed">

View File

@@ -8,6 +8,12 @@ permalink: /interactions/
<p class="text-surface-600 dark:text-surface-400">Activity across the IndieWeb - both my engagement and responses to my content.</p> <p class="text-surface-600 dark:text-surface-400">Activity across the IndieWeb - both my engagement and responses to my content.</p>
</div> </div>
<noscript>
<div class="p-4 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg mb-6">
<p class="text-amber-800 dark:text-amber-200 text-sm">The inbound webmentions tab requires JavaScript to load data from the API. Enable JavaScript for the full interactive experience. Outbound interactions are shown below.</p>
</div>
</noscript>
{# Tab navigation for Outbound/Inbound #} {# Tab navigation for Outbound/Inbound #}
<div x-data="interactionsApp()" x-init="init()"> <div x-data="interactionsApp()" x-init="init()">
{# Tab buttons #} {# Tab buttons #}

View File

@@ -11,6 +11,12 @@ pagefindIgnore: true
</div> </div>
<div id="search"></div> <div id="search"></div>
<noscript>
<div class="p-6 bg-surface-100 dark:bg-surface-800 rounded-lg mt-4">
<p class="text-surface-700 dark:text-surface-300">Search requires JavaScript to be enabled. Please enable JavaScript in your browser settings to use the search feature.</p>
<p class="text-surface-500 text-sm mt-2">Alternatively, you can browse content via the <a href="/blog/" class="text-primary-600 dark:text-primary-400 hover:underline">blog archive</a> or <a href="/categories/" class="text-primary-600 dark:text-primary-400 hover:underline">categories</a>.</p>
</div>
</noscript>
<script> <script>
initPagefind("#search", { showSubResults: true }); initPagefind("#search", { showSubResults: true });