faith: progressive enhancement for all faith pages without JS
All checks were successful
CI / update (push) Successful in 1m29s

- Rosary: mystery selection, luminous toggle, and latin toggle fall back
  to URL params (?mystery=, ?luminous=, ?latin=) for no-JS navigation
- Prayers/Angelus: latin toggle uses URL param fallback
- Search on prayers page hidden without JS (requires DOM queries)
- Toggle component supports href prop for link-based no-JS self-submit
- LanguageSelector uses <a> links with computed paths and :focus-within
  dropdown for no-JS; displays correct language via server-provided prop
- Recipe language links use translated slugs from $page.data
- URL params cleaned via replaceState after hydration to avoid clutter
This commit is contained in:
2026-02-04 14:14:11 +01:00
parent 1c100a4534
commit 7d6a80442a
13 changed files with 347 additions and 90 deletions

View File

@@ -1,4 +1,5 @@
<script>
import { onMount } from 'svelte';
import { browser } from '$app/environment';
import { createLanguageContext } from "$lib/contexts/languageContext.js";
import "$lib/css/christ.css";
@@ -22,7 +23,7 @@
let { data } = $props();
// Create language context for prayer components
const langContext = createLanguageContext({ urlLang: data.lang });
const langContext = createLanguageContext({ urlLang: data.lang, initialLatin: data.initialLatin });
// Update lang store when data.lang changes (e.g., after navigation)
$effect(() => {
@@ -57,9 +58,19 @@
textMatch: isEnglish ? 'Match in prayer text' : 'Treffer im Gebetstext'
});
// Search state
// JS-only search (hidden without JS)
let jsEnabled = $state(false);
let searchQuery = $state('');
onMount(() => {
jsEnabled = true;
// Clean up URL params after hydration (state is now in component state)
if (window.location.search) {
history.replaceState({}, '', window.location.pathname);
}
});
// Match results: 'primary' (name/terms), 'secondary' (text only), or null (no match)
/** @type {Map<string, 'primary' | 'secondary'>} */
let matchResults = $state(/** @type {Map<string, 'primary' | 'secondary'>} */ (new Map()));
@@ -165,6 +176,7 @@
// Helper to get match class for a prayer
function getMatchClass(id) {
if (!jsEnabled) return '';
const match = matchResults.get(id);
if (!searchQuery.trim()) return '';
if (match === 'primary') return '';
@@ -202,6 +214,9 @@
joseph: { bilingue: false },
confiteor: { bilingue: true }
};
// Toggle href for no-JS fallback (navigates to opposite latin state)
const latinToggleHref = $derived(data.initialLatin ? '?latin=0' : '?');
</script>
<svelte:head>
@@ -264,18 +279,33 @@ h1{
color: var(--nord0);
}
}
/* Search is hidden without JS */
.js-only {
display: none;
}
.js-enabled .js-only {
display: block;
}
</style>
<div class:js-enabled={jsEnabled}>
<h1>{labels.title}</h1>
<div class="toggle-controls">
<LanguageToggle />
<LanguageToggle
initialLatin={data.initialLatin}
hasUrlLatin={data.hasUrlLatin}
href={latinToggleHref}
/>
</div>
<SearchInput
bind:value={searchQuery}
placeholder={labels.searchPlaceholder}
clearTitle={labels.clearSearch}
/>
<div class="js-only">
<SearchInput
bind:value={searchQuery}
placeholder={labels.searchPlaceholder}
clearTitle={labels.clearSearch}
/>
</div>
<div class="ccontainer">
<div class=container>
@@ -317,3 +347,4 @@ h1{
{/each}
</div>
</div>
</div>