prayers: add search and individual prayer pages
All checks were successful
CI / update (push) Successful in 1m22s
All checks were successful
CI / update (push) Successful in 1m22s
- Add SearchInput component for reusable search UI - Add search functionality to prayers list with two-tier results: - Primary matches (name/searchTerms) shown first - Secondary matches (text content) shown after with reduced opacity - Add individual prayer pages with language-appropriate slugs (e.g., /glaube/gebete/ave-maria, /faith/prayers/hail-mary) - Make prayer cards clickable to navigate to individual pages - Fix language visibility for prayers without Latin (BruderKlaus, Joseph) - Add Prayer wrapper to MichaelGebet for consistent styling - Use CSS columns for masonry layout with dynamic reordering
This commit is contained in:
12
TODO.md
Normal file
12
TODO.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
## Refactor Recipe Search Component
|
||||||
|
|
||||||
|
Refactor `src/lib/components/Search.svelte` to use the new `SearchInput.svelte` component for the visual input part. This will:
|
||||||
|
- Reduce code duplication between recipe search and prayer search
|
||||||
|
- Keep the visual styling consistent across the site
|
||||||
|
- Separate concerns: SearchInput handles the UI, Search.svelte handles recipe-specific filtering logic
|
||||||
|
|
||||||
|
Files involved:
|
||||||
|
- `src/lib/components/Search.svelte` - refactor to use SearchInput
|
||||||
|
- `src/lib/components/SearchInput.svelte` - the reusable input component
|
||||||
87
src/lib/components/SearchInput.svelte
Normal file
87
src/lib/components/SearchInput.svelte
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<script>
|
||||||
|
import "$lib/css/nordtheme.css";
|
||||||
|
|
||||||
|
let {
|
||||||
|
value = $bindable(''),
|
||||||
|
placeholder = 'Search...',
|
||||||
|
clearTitle = 'Clear search',
|
||||||
|
onClear = () => {}
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
function handleClear() {
|
||||||
|
value = '';
|
||||||
|
onClear();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
input {
|
||||||
|
all: unset;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background: var(--nord0);
|
||||||
|
color: #fff;
|
||||||
|
padding: 0.7rem 2rem;
|
||||||
|
border-radius: 1000px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
input::placeholder {
|
||||||
|
color: var(--nord6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
width: 500px;
|
||||||
|
max-width: 85vw;
|
||||||
|
position: relative;
|
||||||
|
margin: 2.5rem auto 1.2rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transition: 100ms;
|
||||||
|
filter: drop-shadow(0.4em 0.5em 0.4em rgba(0,0,0,0.4));
|
||||||
|
}
|
||||||
|
|
||||||
|
.search:hover,
|
||||||
|
.search:focus-within {
|
||||||
|
scale: 1.02 1.02;
|
||||||
|
filter: drop-shadow(0.4em 0.5em 1em rgba(0,0,0,0.6));
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button {
|
||||||
|
all: unset;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5em;
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
color: var(--nord6);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 180ms ease-in-out;
|
||||||
|
}
|
||||||
|
.search-button:hover {
|
||||||
|
color: white;
|
||||||
|
scale: 1.1 1.1;
|
||||||
|
}
|
||||||
|
.search-button:active {
|
||||||
|
transition: 50ms;
|
||||||
|
scale: 0.8 0.8;
|
||||||
|
}
|
||||||
|
.search-button svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="search">
|
||||||
|
<input type="text" {placeholder} bind:value>
|
||||||
|
{#if value}
|
||||||
|
<button type="button" class="search-button" onclick={handleClear}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<title>{clearTitle}</title>
|
||||||
|
<path d="M135.19 390.14a28.79 28.79 0 0021.68 9.86h246.26A29 29 0 00432 371.13V140.87A29 29 0 00403.13 112H156.87a28.84 28.84 0 00-21.67 9.84v0L46.33 256l88.86 134.11z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"/>
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -1,24 +1,30 @@
|
|||||||
<p>
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer hasLatin={false}>
|
||||||
|
<p>
|
||||||
<v lang="de">Mein Herr und mein Gott,</v>
|
<v lang="de">Mein Herr und mein Gott,</v>
|
||||||
<v lang="en">My Lord and my God,</v>
|
<v lang="en">My Lord and my God,</v>
|
||||||
<v lang="de">nimm alles von mir,</v>
|
<v lang="de">nimm alles von mir,</v>
|
||||||
<v lang="en">take from me everything</v>
|
<v lang="en">take from me everything</v>
|
||||||
<v lang="de">was mich hindert zu Dir.</v>
|
<v lang="de">was mich hindert zu Dir.</v>
|
||||||
<v lang="en">that distances me from Thee.</v>
|
<v lang="en">that distances me from Thee.</v>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<v lang="de">Mein Herr und mein Gott,</v>
|
<v lang="de">Mein Herr und mein Gott,</v>
|
||||||
<v lang="en">My Lord and my God,</v>
|
<v lang="en">My Lord and my God,</v>
|
||||||
<v lang="de">gib alles mir,</v>
|
<v lang="de">gib alles mir,</v>
|
||||||
<v lang="en">give me everything</v>
|
<v lang="en">give me everything</v>
|
||||||
<v lang="de">was mich führet zu Dir.</v>
|
<v lang="de">was mich führet zu Dir.</v>
|
||||||
<v lang="en">that brings me closer to Thee.</v>
|
<v lang="en">that brings me closer to Thee.</v>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<v lang="de">Mein Herr und mein Gott,</v>
|
<v lang="de">Mein Herr und mein Gott,</v>
|
||||||
<v lang="en">My Lord and my God,</v>
|
<v lang="en">My Lord and my God,</v>
|
||||||
<v lang="de">nimm mich mir</v>
|
<v lang="de">nimm mich mir</v>
|
||||||
<v lang="en">detach me from myself</v>
|
<v lang="en">detach me from myself</v>
|
||||||
<v lang="de">und gib mich ganz zu eigen Dir.</v>
|
<v lang="de">und gib mich ganz zu eigen Dir.</v>
|
||||||
<v lang="en">to give my all to Thee.</v>
|
<v lang="en">to give my all to Thee.</v>
|
||||||
</p>
|
</p>
|
||||||
|
</Prayer>
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
<p>
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer hasLatin={false}>
|
||||||
|
<p>
|
||||||
<v lang="de">Jungfräulicher Vater <i><sup>⚬</sup></i>Jesu,</v>
|
<v lang="de">Jungfräulicher Vater <i><sup>⚬</sup></i>Jesu,</v>
|
||||||
<v lang="en">Virgin Father of <i><sup>⚬</sup></i>Jesus,</v>
|
<v lang="en">Virgin Father of <i><sup>⚬</sup></i>Jesus,</v>
|
||||||
<v lang="de">Reinster Bräutigam <i><sup>⚬</sup></i>Mariä,</v>
|
<v lang="de">Reinster Bräutigam <i><sup>⚬</sup></i>Mariä,</v>
|
||||||
@@ -11,4 +16,5 @@
|
|||||||
<v lang="en">that we may fight victoriously in life</v>
|
<v lang="en">that we may fight victoriously in life</v>
|
||||||
<v lang="de">und die Krone von Ihm erhalten im Sterben.</v>
|
<v lang="de">und die Krone von Ihm erhalten im Sterben.</v>
|
||||||
<v lang="en">and receive the crown from Him at death.</v>
|
<v lang="en">and receive the crown from Him at death.</v>
|
||||||
</p>
|
</p>
|
||||||
|
</Prayer>
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
<p>
|
<script>
|
||||||
|
import Prayer from './Prayer.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Prayer>
|
||||||
|
<p>
|
||||||
<v lang="la">Sáncte Míchael Archángele,</v>
|
<v lang="la">Sáncte Míchael Archángele,</v>
|
||||||
<v lang="de">Heiliger Erzengel Michael,</v>
|
<v lang="de">Heiliger Erzengel Michael,</v>
|
||||||
<v lang="en">Saint Michael the Archangel,</v>
|
<v lang="en">Saint Michael the Archangel,</v>
|
||||||
@@ -29,4 +34,5 @@
|
|||||||
<v lang="la">divína virtúte, in inférnum detrúde. Amen.</v>
|
<v lang="la">divína virtúte, in inférnum detrúde. Amen.</v>
|
||||||
<v lang="de">durch die Kraft Gottes in die Hölle. Amen.</v>
|
<v lang="de">durch die Kraft Gottes in die Hölle. Amen.</v>
|
||||||
<v lang="en">Amen.</v>
|
<v lang="en">Amen.</v>
|
||||||
</p>
|
</p>
|
||||||
|
</Prayer>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import { getLanguageContext } from '$lib/contexts/languageContext.js';
|
import { getLanguageContext } from '$lib/contexts/languageContext.js';
|
||||||
|
|
||||||
let { latinPrimary = true, children } = $props<{ latinPrimary?: boolean, children?: Snippet }>();
|
let { latinPrimary = true, hasLatin = true, children } = $props<{ latinPrimary?: boolean, hasLatin?: boolean, children?: Snippet }>();
|
||||||
|
|
||||||
// Get context if available (graceful fallback for standalone usage)
|
// Get context if available (graceful fallback for standalone usage)
|
||||||
let showLatinStore;
|
let showLatinStore;
|
||||||
@@ -47,6 +47,12 @@
|
|||||||
.prayer-wrapper :global(v:lang(de)),
|
.prayer-wrapper :global(v:lang(de)),
|
||||||
.prayer-wrapper :global(v:lang(en)) { color: grey; }
|
.prayer-wrapper :global(v:lang(en)) { color: grey; }
|
||||||
|
|
||||||
|
/* No-Latin prayers: vernacular gets primary color */
|
||||||
|
.prayer-wrapper.no-latin :global(v:lang(de)),
|
||||||
|
.prayer-wrapper.no-latin :global(v:lang(en)) {
|
||||||
|
color: var(--nord6);
|
||||||
|
}
|
||||||
|
|
||||||
/* Vernacular primary overrides */
|
/* Vernacular primary overrides */
|
||||||
.prayer-wrapper.vernacular-primary :global(v:lang(de)),
|
.prayer-wrapper.vernacular-primary :global(v:lang(de)),
|
||||||
.prayer-wrapper.vernacular-primary :global(v:lang(en)) {
|
.prayer-wrapper.vernacular-primary :global(v:lang(en)) {
|
||||||
@@ -67,7 +73,9 @@
|
|||||||
.prayer-wrapper :global(v:lang(la)),
|
.prayer-wrapper :global(v:lang(la)),
|
||||||
.prayer-wrapper.vernacular-primary :global(v:lang(de)),
|
.prayer-wrapper.vernacular-primary :global(v:lang(de)),
|
||||||
.prayer-wrapper.vernacular-primary :global(v:lang(en)),
|
.prayer-wrapper.vernacular-primary :global(v:lang(en)),
|
||||||
.prayer-wrapper.monolingual :global(v:not(:lang(la))) {
|
.prayer-wrapper.monolingual :global(v:not(:lang(la))),
|
||||||
|
.prayer-wrapper.no-latin :global(v:lang(de)),
|
||||||
|
.prayer-wrapper.no-latin :global(v:lang(en)) {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,6 +153,7 @@
|
|||||||
class="prayer-wrapper"
|
class="prayer-wrapper"
|
||||||
class:vernacular-primary={!latinPrimary}
|
class:vernacular-primary={!latinPrimary}
|
||||||
class:monolingual={!showLatin}
|
class:monolingual={!showLatin}
|
||||||
|
class:no-latin={!hasLatin}
|
||||||
class:lang-de={urlLang === 'de'}
|
class:lang-de={urlLang === 'de'}
|
||||||
class:lang-en={urlLang === 'en'}
|
class:lang-en={urlLang === 'en'}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { browser } from '$app/environment';
|
||||||
import { createLanguageContext } from "$lib/contexts/languageContext.js";
|
import { createLanguageContext } from "$lib/contexts/languageContext.js";
|
||||||
import "$lib/css/christ.css";
|
import "$lib/css/christ.css";
|
||||||
import "$lib/css/nordtheme.css";
|
import "$lib/css/nordtheme.css";
|
||||||
import Gebet from "./Gebet.svelte";
|
import Gebet from "./Gebet.svelte";
|
||||||
import LanguageToggle from "$lib/components/LanguageToggle.svelte";
|
import LanguageToggle from "$lib/components/LanguageToggle.svelte";
|
||||||
|
import SearchInput from "$lib/components/SearchInput.svelte";
|
||||||
import Kreuzzeichen from "$lib/components/prayers/Kreuzzeichen.svelte";
|
import Kreuzzeichen from "$lib/components/prayers/Kreuzzeichen.svelte";
|
||||||
import GloriaPatri from "$lib/components/prayers/GloriaPatri.svelte";
|
import GloriaPatri from "$lib/components/prayers/GloriaPatri.svelte";
|
||||||
import Paternoster from "$lib/components/prayers/Paternoster.svelte";
|
import Paternoster from "$lib/components/prayers/Paternoster.svelte";
|
||||||
@@ -49,8 +51,157 @@
|
|||||||
michael: isEnglish ? 'Prayer to St. Michael the Archangel' : 'Gebet zum hl. Erzengel Michael',
|
michael: isEnglish ? 'Prayer to St. Michael the Archangel' : 'Gebet zum hl. Erzengel Michael',
|
||||||
bruderKlaus: isEnglish ? 'Prayer of St. Nicholas of Flüe' : 'Bruder Klaus Gebet',
|
bruderKlaus: isEnglish ? 'Prayer of St. Nicholas of Flüe' : 'Bruder Klaus Gebet',
|
||||||
joseph: isEnglish ? 'Prayer to St. Joseph by Pope St. Pius X' : 'Josephgebet des hl. Papst Pius X',
|
joseph: isEnglish ? 'Prayer to St. Joseph by Pope St. Pius X' : 'Josephgebet des hl. Papst Pius X',
|
||||||
confiteor: isEnglish ? 'The Confiteor' : 'Das Confiteor'
|
confiteor: isEnglish ? 'The Confiteor' : 'Das Confiteor',
|
||||||
|
searchPlaceholder: isEnglish ? 'Search prayers...' : 'Gebete suchen...',
|
||||||
|
clearSearch: isEnglish ? 'Clear search' : 'Suche löschen',
|
||||||
|
textMatch: isEnglish ? 'Match in prayer text' : 'Treffer im Gebetstext'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Search state
|
||||||
|
let searchQuery = $state('');
|
||||||
|
|
||||||
|
// 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()));
|
||||||
|
|
||||||
|
// Define prayers with their searchable terms and slugs for URLs
|
||||||
|
const prayers = $derived([
|
||||||
|
{ id: 'signOfCross', searchTerms: ['kreuzzeichen', 'sign of the cross', 'cross'], slug: isEnglish ? 'the-sign-of-the-cross' : 'das-heilige-kreuzzeichen' },
|
||||||
|
{ id: 'gloriaPatri', searchTerms: ['gloria patri', 'glory be', 'ehre sei'], slug: 'gloria-patri' },
|
||||||
|
{ id: 'paternoster', searchTerms: ['paternoster', 'our father', 'vater unser', 'pater noster'], slug: isEnglish ? 'our-father' : 'paternoster' },
|
||||||
|
{ id: 'credo', searchTerms: ['credo', 'creed', 'glaubensbekenntnis', 'nicene'], slug: isEnglish ? 'nicene-creed' : 'credo' },
|
||||||
|
{ id: 'aveMaria', searchTerms: ['ave maria', 'hail mary', 'gegrüsst seist du'], slug: isEnglish ? 'hail-mary' : 'ave-maria' },
|
||||||
|
{ id: 'salveRegina', searchTerms: ['salve regina', 'hail holy queen'], slug: 'salve-regina' },
|
||||||
|
{ id: 'fatima', searchTerms: ['fatima', 'fatimagebet'], slug: isEnglish ? 'fatima-prayer' : 'das-fatimagebet' },
|
||||||
|
{ id: 'gloria', searchTerms: ['gloria', 'glory', 'gloria in excelsis'], slug: 'gloria' },
|
||||||
|
{ id: 'michael', searchTerms: ['michael', 'archangel', 'erzengel', 'satan', 'devil', 'teufel'], slug: isEnglish ? 'prayer-to-st-michael-the-archangel' : 'gebet-zum-hl-erzengel-michael' },
|
||||||
|
{ id: 'bruderKlaus', searchTerms: ['bruder klaus', 'nicholas', 'niklaus', 'flüe'], slug: isEnglish ? 'prayer-of-st-nicholas-of-flue' : 'bruder-klaus-gebet' },
|
||||||
|
{ id: 'joseph', searchTerms: ['joseph', 'josef', 'pius'], slug: isEnglish ? 'prayer-to-st-joseph-by-pope-st-pius-x' : 'josephgebet-des-hl-papst-pius-x' },
|
||||||
|
{ id: 'confiteor', searchTerms: ['confiteor', 'i confess', 'ich bekenne', 'mea culpa'], slug: isEnglish ? 'the-confiteor' : 'das-confiteor' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Base URL for prayer links
|
||||||
|
const baseUrl = $derived(isEnglish ? '/faith/prayers' : '/glaube/gebete');
|
||||||
|
|
||||||
|
// Get prayer name by ID (reactive based on language)
|
||||||
|
function getPrayerName(id) {
|
||||||
|
const nameMap = {
|
||||||
|
signOfCross: labels.signOfCross,
|
||||||
|
gloriaPatri: labels.gloriaPatri,
|
||||||
|
paternoster: labels.paternoster,
|
||||||
|
credo: labels.credo,
|
||||||
|
aveMaria: labels.aveMaria,
|
||||||
|
salveRegina: labels.salveRegina,
|
||||||
|
fatima: labels.fatima,
|
||||||
|
gloria: labels.gloria,
|
||||||
|
michael: labels.michael,
|
||||||
|
bruderKlaus: labels.bruderKlaus,
|
||||||
|
joseph: labels.joseph,
|
||||||
|
confiteor: labels.confiteor
|
||||||
|
};
|
||||||
|
return nameMap[id] || id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize text for search comparison
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
function normalize(text) {
|
||||||
|
return text.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search the DOM and update match results
|
||||||
|
*/
|
||||||
|
function performSearch() {
|
||||||
|
if (!browser) return;
|
||||||
|
|
||||||
|
const query = searchQuery.trim();
|
||||||
|
const newResults = new Map();
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
// No search query - show all as primary
|
||||||
|
prayers.forEach(p => newResults.set(p.id, 'primary'));
|
||||||
|
} else {
|
||||||
|
const normalizedQuery = normalize(query);
|
||||||
|
|
||||||
|
prayers.forEach(prayer => {
|
||||||
|
const name = getPrayerName(prayer.id);
|
||||||
|
|
||||||
|
// Check name match
|
||||||
|
if (normalize(name).includes(normalizedQuery)) {
|
||||||
|
newResults.set(prayer.id, 'primary');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check search terms match
|
||||||
|
if (prayer.searchTerms.some(term => normalize(term).includes(normalizedQuery))) {
|
||||||
|
newResults.set(prayer.id, 'primary');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check DOM text content
|
||||||
|
const element = document.querySelector(`[data-prayer-id="${prayer.id}"]`);
|
||||||
|
if (element) {
|
||||||
|
const textContent = normalize(element.textContent || '');
|
||||||
|
if (textContent.includes(normalizedQuery)) {
|
||||||
|
newResults.set(prayer.id, 'secondary');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
matchResults = newResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run search when query changes (debounced)
|
||||||
|
$effect(() => {
|
||||||
|
if (browser) {
|
||||||
|
const query = searchQuery; // track dependency
|
||||||
|
const timer = setTimeout(performSearch, 50);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper to get match class for a prayer
|
||||||
|
function getMatchClass(id) {
|
||||||
|
const match = matchResults.get(id);
|
||||||
|
if (!searchQuery.trim()) return '';
|
||||||
|
if (match === 'primary') return '';
|
||||||
|
if (match === 'secondary') return 'secondary-match';
|
||||||
|
return 'no-match';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorted prayers array - primary matches first, then secondary, then hidden
|
||||||
|
const sortedPrayers = $derived.by(() => {
|
||||||
|
if (!searchQuery.trim()) return prayers;
|
||||||
|
|
||||||
|
return [...prayers].sort((a, b) => {
|
||||||
|
const matchA = matchResults.get(a.id);
|
||||||
|
const matchB = matchResults.get(b.id);
|
||||||
|
|
||||||
|
const orderA = matchA === 'primary' ? 0 : matchA === 'secondary' ? 1 : 2;
|
||||||
|
const orderB = matchB === 'primary' ? 0 : matchB === 'secondary' ? 1 : 2;
|
||||||
|
|
||||||
|
return orderA - orderB;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prayer metadata (bilingue status)
|
||||||
|
const prayerMeta = {
|
||||||
|
signOfCross: { bilingue: true },
|
||||||
|
gloriaPatri: { bilingue: true },
|
||||||
|
paternoster: { bilingue: true },
|
||||||
|
credo: { bilingue: true },
|
||||||
|
aveMaria: { bilingue: true },
|
||||||
|
salveRegina: { bilingue: true },
|
||||||
|
fatima: { bilingue: true },
|
||||||
|
gloria: { bilingue: true },
|
||||||
|
michael: { bilingue: true },
|
||||||
|
bruderKlaus: { bilingue: false },
|
||||||
|
joseph: { bilingue: false },
|
||||||
|
confiteor: { bilingue: true }
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -70,13 +221,9 @@
|
|||||||
@media (max-width: 800px) {
|
@media (max-width: 800px) {
|
||||||
.container{
|
.container{
|
||||||
column-count: 1;
|
column-count: 1;
|
||||||
padding-left: calc((100% - 600px ) * 0.5); /* ugly*/
|
padding-left: calc((100% - 600px) * 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:global(.container > *){
|
|
||||||
break-inside: avoid-column; /* Prevent children from splitting across columns */
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
h1{
|
h1{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
@@ -86,6 +233,37 @@ h1{
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Search result styling */
|
||||||
|
.prayer-wrapper {
|
||||||
|
position: relative;
|
||||||
|
break-inside: avoid-column;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.prayer-wrapper.no-match {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.prayer-wrapper.secondary-match {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.prayer-wrapper.secondary-match::before {
|
||||||
|
content: attr(data-match-label);
|
||||||
|
position: absolute;
|
||||||
|
top: 0.3em;
|
||||||
|
right: 0.3em;
|
||||||
|
font-size: 0.65em;
|
||||||
|
padding: 0.2em 0.5em;
|
||||||
|
background: var(--nord3);
|
||||||
|
color: var(--nord6);
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
@media(prefers-color-scheme: light) {
|
||||||
|
.prayer-wrapper.secondary-match::before {
|
||||||
|
background: var(--nord4);
|
||||||
|
color: var(--nord0);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<h1>{labels.title}</h1>
|
<h1>{labels.title}</h1>
|
||||||
|
|
||||||
@@ -93,56 +271,49 @@ h1{
|
|||||||
<LanguageToggle />
|
<LanguageToggle />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SearchInput
|
||||||
|
bind:value={searchQuery}
|
||||||
|
placeholder={labels.searchPlaceholder}
|
||||||
|
clearTitle={labels.clearSearch}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="ccontainer">
|
<div class="ccontainer">
|
||||||
<div class=container>
|
<div class=container>
|
||||||
|
{#each sortedPrayers as prayer (prayer.id)}
|
||||||
<Gebet name={labels.signOfCross} is_bilingue={true}>
|
<div class="prayer-wrapper {getMatchClass(prayer.id)}" data-match-label={labels.textMatch}>
|
||||||
<Kreuzzeichen />
|
{#if prayer.id === 'gloria'}
|
||||||
</Gebet>
|
<Gebet name={getPrayerName(prayer.id)} is_bilingue={true} id={prayer.id} href="{baseUrl}/{prayer.slug}">
|
||||||
|
|
||||||
<Gebet name={labels.gloriaPatri} is_bilingue={true}>
|
|
||||||
<GloriaPatri />
|
|
||||||
</Gebet>
|
|
||||||
|
|
||||||
<Gebet name={labels.paternoster} is_bilingue={true}>
|
|
||||||
<Paternoster />
|
|
||||||
</Gebet>
|
|
||||||
|
|
||||||
<Gebet name={labels.credo} is_bilingue={true}>
|
|
||||||
<Credo />
|
|
||||||
</Gebet>
|
|
||||||
|
|
||||||
<Gebet name={labels.aveMaria} is_bilingue={true}>
|
|
||||||
<AveMaria />
|
|
||||||
</Gebet>
|
|
||||||
|
|
||||||
<Gebet name={labels.salveRegina} is_bilingue={true}>
|
|
||||||
<SalveRegina />
|
|
||||||
</Gebet>
|
|
||||||
|
|
||||||
<Gebet name={labels.fatima} is_bilingue={true}>
|
|
||||||
<FatimaGebet />
|
|
||||||
</Gebet>
|
|
||||||
|
|
||||||
<Gebet name={labels.gloria} is_bilingue={true}>
|
|
||||||
<p slot="intro">{labels.gloriaIntro}</p>
|
<p slot="intro">{labels.gloriaIntro}</p>
|
||||||
<Gloria />
|
<Gloria />
|
||||||
</Gebet>
|
</Gebet>
|
||||||
|
{:else}
|
||||||
<Gebet name={labels.michael} is_bilingue={true}>
|
<Gebet name={getPrayerName(prayer.id)} is_bilingue={prayerMeta[prayer.id]?.bilingue ?? true} id={prayer.id} href="{baseUrl}/{prayer.slug}">
|
||||||
|
{#if prayer.id === 'signOfCross'}
|
||||||
|
<Kreuzzeichen />
|
||||||
|
{:else if prayer.id === 'gloriaPatri'}
|
||||||
|
<GloriaPatri />
|
||||||
|
{:else if prayer.id === 'paternoster'}
|
||||||
|
<Paternoster />
|
||||||
|
{:else if prayer.id === 'credo'}
|
||||||
|
<Credo />
|
||||||
|
{:else if prayer.id === 'aveMaria'}
|
||||||
|
<AveMaria />
|
||||||
|
{:else if prayer.id === 'salveRegina'}
|
||||||
|
<SalveRegina />
|
||||||
|
{:else if prayer.id === 'fatima'}
|
||||||
|
<FatimaGebet />
|
||||||
|
{:else if prayer.id === 'michael'}
|
||||||
<MichaelGebet />
|
<MichaelGebet />
|
||||||
</Gebet>
|
{:else if prayer.id === 'bruderKlaus'}
|
||||||
|
|
||||||
<Gebet name={labels.bruderKlaus} is_bilingue={false}>
|
|
||||||
<BruderKlausGebet />
|
<BruderKlausGebet />
|
||||||
</Gebet>
|
{:else if prayer.id === 'joseph'}
|
||||||
|
|
||||||
<Gebet name={labels.joseph} is_bilingue={false}>
|
|
||||||
<JosephGebet />
|
<JosephGebet />
|
||||||
</Gebet>
|
{:else if prayer.id === 'confiteor'}
|
||||||
|
|
||||||
<Gebet name={labels.confiteor} is_bilingue={true}>
|
|
||||||
<Confiteor />
|
<Confiteor />
|
||||||
</Gebet>
|
{/if}
|
||||||
|
</Gebet>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export let is_bilingue;
|
export let is_bilingue;
|
||||||
export let name;
|
export let name;
|
||||||
|
export let id = '';
|
||||||
|
export let href = '';
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
div.gebet{
|
div.gebet{
|
||||||
@@ -33,6 +35,22 @@ div.gebet{
|
|||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
.gebet_wrapper.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 150ms ease, box-shadow 150ms ease;
|
||||||
|
}
|
||||||
|
.gebet_wrapper.clickable:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
.gebet_wrapper.clickable:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
a.gebet-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
@media(prefers-color-scheme: light){
|
@media(prefers-color-scheme: light){
|
||||||
.gebet_wrapper{
|
.gebet_wrapper{
|
||||||
background-color: var(--accent-light);
|
background-color: var(--accent-light);
|
||||||
@@ -43,10 +61,22 @@ div.gebet{
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="gebet_wrapper">
|
{#if href}
|
||||||
|
<a {href} class="gebet-link">
|
||||||
|
<div class="gebet_wrapper clickable" data-prayer-id={id}>
|
||||||
|
<h2>{name}</h2>
|
||||||
|
<slot name="intro"></slot>
|
||||||
|
<div class="gebet" class:bilingue={is_bilingue}>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<div class="gebet_wrapper" data-prayer-id={id}>
|
||||||
<h2>{name}</h2>
|
<h2>{name}</h2>
|
||||||
<slot name="intro"></slot>
|
<slot name="intro"></slot>
|
||||||
<div class="gebet" class:bilingue={is_bilingue}>
|
<div class="gebet" class:bilingue={is_bilingue}>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import type { PageServerLoad } from "./$types";
|
||||||
|
import { error } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
// Valid prayer slugs (both languages)
|
||||||
|
const validSlugs = new Set([
|
||||||
|
'das-heilige-kreuzzeichen', 'the-sign-of-the-cross',
|
||||||
|
'gloria-patri',
|
||||||
|
'paternoster', 'our-father',
|
||||||
|
'credo', 'nicene-creed',
|
||||||
|
'ave-maria', 'hail-mary',
|
||||||
|
'salve-regina',
|
||||||
|
'das-fatimagebet', 'fatima-prayer',
|
||||||
|
'gloria',
|
||||||
|
'gebet-zum-hl-erzengel-michael', 'prayer-to-st-michael-the-archangel',
|
||||||
|
'bruder-klaus-gebet', 'prayer-of-st-nicholas-of-flue',
|
||||||
|
'josephgebet-des-hl-papst-pius-x', 'prayer-to-st-joseph-by-pope-st-pius-x',
|
||||||
|
'das-confiteor', 'the-confiteor'
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ params }) => {
|
||||||
|
if (!validSlugs.has(params.prayer)) {
|
||||||
|
throw error(404, 'Prayer not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
prayer: params.prayer
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<script>
|
||||||
|
import { createLanguageContext } from "$lib/contexts/languageContext.js";
|
||||||
|
import "$lib/css/christ.css";
|
||||||
|
import "$lib/css/nordtheme.css";
|
||||||
|
import LanguageToggle from "$lib/components/LanguageToggle.svelte";
|
||||||
|
import Kreuzzeichen from "$lib/components/prayers/Kreuzzeichen.svelte";
|
||||||
|
import GloriaPatri from "$lib/components/prayers/GloriaPatri.svelte";
|
||||||
|
import Paternoster from "$lib/components/prayers/Paternoster.svelte";
|
||||||
|
import Credo from "$lib/components/prayers/Credo.svelte";
|
||||||
|
import AveMaria from "$lib/components/prayers/AveMaria.svelte";
|
||||||
|
import SalveRegina from "$lib/components/prayers/SalveRegina.svelte";
|
||||||
|
import FatimaGebet from "$lib/components/prayers/FatimaGebet.svelte";
|
||||||
|
import Gloria from "$lib/components/prayers/Gloria.svelte";
|
||||||
|
import MichaelGebet from "$lib/components/prayers/MichaelGebet.svelte";
|
||||||
|
import BruderKlausGebet from "$lib/components/prayers/BruderKlausGebet.svelte";
|
||||||
|
import JosephGebet from "$lib/components/prayers/JosephGebet.svelte";
|
||||||
|
import Confiteor from "$lib/components/prayers/Confiteor.svelte";
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
|
||||||
|
const langContext = createLanguageContext({ urlLang: data.lang });
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
langContext.lang.set(data.lang);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isEnglish = $derived(data.lang === 'en');
|
||||||
|
|
||||||
|
// Prayer definitions with slugs
|
||||||
|
const prayerDefs = $derived({
|
||||||
|
'das-heilige-kreuzzeichen': { id: 'signOfCross', name: isEnglish ? 'The Sign of the Cross' : 'Das heilige Kreuzzeichen', bilingue: true },
|
||||||
|
'the-sign-of-the-cross': { id: 'signOfCross', name: isEnglish ? 'The Sign of the Cross' : 'Das heilige Kreuzzeichen', bilingue: true },
|
||||||
|
'gloria-patri': { id: 'gloriaPatri', name: 'Glória Patri', bilingue: true },
|
||||||
|
'paternoster': { id: 'paternoster', name: isEnglish ? 'Our Father' : 'Paternoster', bilingue: true },
|
||||||
|
'our-father': { id: 'paternoster', name: isEnglish ? 'Our Father' : 'Paternoster', bilingue: true },
|
||||||
|
'credo': { id: 'credo', name: isEnglish ? 'Nicene Creed' : 'Credo', bilingue: true },
|
||||||
|
'nicene-creed': { id: 'credo', name: isEnglish ? 'Nicene Creed' : 'Credo', bilingue: true },
|
||||||
|
'ave-maria': { id: 'aveMaria', name: isEnglish ? 'Hail Mary' : 'Ave Maria', bilingue: true },
|
||||||
|
'hail-mary': { id: 'aveMaria', name: isEnglish ? 'Hail Mary' : 'Ave Maria', bilingue: true },
|
||||||
|
'salve-regina': { id: 'salveRegina', name: 'Salve Regina', bilingue: true },
|
||||||
|
'das-fatimagebet': { id: 'fatima', name: isEnglish ? 'Fatima Prayer' : 'Das Fatimagebet', bilingue: true },
|
||||||
|
'fatima-prayer': { id: 'fatima', name: isEnglish ? 'Fatima Prayer' : 'Das Fatimagebet', bilingue: true },
|
||||||
|
'gloria': { id: 'gloria', name: 'Glória', bilingue: true },
|
||||||
|
'gebet-zum-hl-erzengel-michael': { id: 'michael', name: isEnglish ? 'Prayer to St. Michael the Archangel' : 'Gebet zum hl. Erzengel Michael', bilingue: true },
|
||||||
|
'prayer-to-st-michael-the-archangel': { id: 'michael', name: isEnglish ? 'Prayer to St. Michael the Archangel' : 'Gebet zum hl. Erzengel Michael', bilingue: true },
|
||||||
|
'bruder-klaus-gebet': { id: 'bruderKlaus', name: isEnglish ? 'Prayer of St. Nicholas of Flüe' : 'Bruder Klaus Gebet', bilingue: false },
|
||||||
|
'prayer-of-st-nicholas-of-flue': { id: 'bruderKlaus', name: isEnglish ? 'Prayer of St. Nicholas of Flüe' : 'Bruder Klaus Gebet', bilingue: false },
|
||||||
|
'josephgebet-des-hl-papst-pius-x': { id: 'joseph', name: isEnglish ? 'Prayer to St. Joseph by Pope St. Pius X' : 'Josephgebet des hl. Papst Pius X', bilingue: false },
|
||||||
|
'prayer-to-st-joseph-by-pope-st-pius-x': { id: 'joseph', name: isEnglish ? 'Prayer to St. Joseph by Pope St. Pius X' : 'Josephgebet des hl. Papst Pius X', bilingue: false },
|
||||||
|
'das-confiteor': { id: 'confiteor', name: isEnglish ? 'The Confiteor' : 'Das Confiteor', bilingue: true },
|
||||||
|
'the-confiteor': { id: 'confiteor', name: isEnglish ? 'The Confiteor' : 'Das Confiteor', bilingue: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
const prayer = $derived(prayerDefs[data.prayer]);
|
||||||
|
const prayerName = $derived(prayer?.name || data.prayer);
|
||||||
|
const isBilingue = $derived(prayer?.bilingue ?? true);
|
||||||
|
const prayerId = $derived(prayer?.id);
|
||||||
|
|
||||||
|
const gloriaIntro = $derived(isEnglish
|
||||||
|
? 'This ancient hymn begins with the words the angels used to celebrate the newborn Savior. It first praises God the Father, then God the Son; it concludes with homage to the Most Holy Trinity, during which one makes the sign of the cross.'
|
||||||
|
: 'Der uralte Gesang beginnt mit den Worten, mit denen die Engelscharen den neugeborenen Welterlöser feierten. Er preist zunächst Gott Vater, dann Gott Sohn; er schliesst mit einer Huldigung an die Heiligste Dreifaltigkeit, wobei man sich mit dem grossen Kreuze bezeichnet.');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{prayerName} - Bocken</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
.toggle-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.gebet {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
:global(.gebet v) {
|
||||||
|
margin: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
:global(.gebet v:lang(la)) {
|
||||||
|
color: var(--nord6);
|
||||||
|
}
|
||||||
|
:global(.bilingue v:lang(de)) {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
:global(.gebet i) {
|
||||||
|
font-style: normal;
|
||||||
|
color: var(--nord11);
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
.gebet-wrapper {
|
||||||
|
padding: 1.5em;
|
||||||
|
background-color: var(--accent-dark);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
.intro {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@media(prefers-color-scheme: light) {
|
||||||
|
.gebet-wrapper {
|
||||||
|
background-color: var(--accent-light);
|
||||||
|
}
|
||||||
|
:global(.gebet v:lang(la)) {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>{prayerName}</h1>
|
||||||
|
|
||||||
|
<div class="toggle-controls">
|
||||||
|
<LanguageToggle />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gebet-wrapper">
|
||||||
|
{#if prayerId === 'gloria'}
|
||||||
|
<p class="intro">{gloriaIntro}</p>
|
||||||
|
{/if}
|
||||||
|
<div class="gebet" class:bilingue={isBilingue}>
|
||||||
|
{#if prayerId === 'signOfCross'}
|
||||||
|
<Kreuzzeichen />
|
||||||
|
{:else if prayerId === 'gloriaPatri'}
|
||||||
|
<GloriaPatri />
|
||||||
|
{:else if prayerId === 'paternoster'}
|
||||||
|
<Paternoster />
|
||||||
|
{:else if prayerId === 'credo'}
|
||||||
|
<Credo />
|
||||||
|
{:else if prayerId === 'aveMaria'}
|
||||||
|
<AveMaria />
|
||||||
|
{:else if prayerId === 'salveRegina'}
|
||||||
|
<SalveRegina />
|
||||||
|
{:else if prayerId === 'fatima'}
|
||||||
|
<FatimaGebet />
|
||||||
|
{:else if prayerId === 'gloria'}
|
||||||
|
<Gloria />
|
||||||
|
{:else if prayerId === 'michael'}
|
||||||
|
<MichaelGebet />
|
||||||
|
{:else if prayerId === 'bruderKlaus'}
|
||||||
|
<BruderKlausGebet />
|
||||||
|
{:else if prayerId === 'joseph'}
|
||||||
|
<JosephGebet />
|
||||||
|
{:else if prayerId === 'confiteor'}
|
||||||
|
<Confiteor />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user