7 Commits

Author SHA1 Message Date
4c36900314 remove space
All checks were successful
CI / update (push) Successful in 28s
2025-12-17 22:04:45 +01:00
80186fe737 fix Freudenreiche Geheimnisse spelling
All checks were successful
CI / update (push) Successful in 26s
2025-12-16 20:19:48 +01:00
50eaf2787d refactor: remove verbose debug logging from cospend API endpoints
All checks were successful
CI / update (push) Successful in 26s
Removed excessive console.log statements from recurring payments processing and monthly expenses aggregation. Error logging (console.error) is retained for troubleshooting.
2025-12-16 16:40:39 +01:00
7e3cbb0397 remove mario-kart route
All checks were successful
CI / update (push) Successful in 1m59s
2025-12-16 16:01:25 +01:00
b788a615ba fix: ensure Bible verses are prerendered and served statically
Some checks failed
CI / update (push) Has been cancelled
- Fetch full verse data at build time in +page.server.ts
- Pass preloaded verseData to BibleModal instead of fetching client-side
- Remove onMount fetch logic from BibleModal component
- Add VerseData interface to type definitions
- Update handleCitationClick to pass verseData prop

This ensures all Bible verses are embedded in static HTML during build,
eliminating runtime API calls and improving performance.
2025-12-16 16:01:04 +01:00
2be2e1977b feat: enhance rosary with interactive Bible citations and improved mystery selection
All checks were successful
CI / update (push) Successful in 25s
- Add clickable Bible reference buttons that open modal with full verses
- Create BibleModal component with backdrop blur and styled close button
- Implement build-time data fetching for Bible texts while maintaining reactivity
- Redesign mystery selector with responsive grid (3-in-row/4-in-row/2×2)
- Add "Heutige" badge to indicate today's auto-selected mystery
- Reposition luminous mysteries toggle below mystery selector
- Integrate Bible reference and counter buttons side-by-side
- Restructure Bible API under /api/glaube/bibel/ for better organization
2025-12-16 15:45:40 +01:00
01dd736bbc ß -> ss
All checks were successful
CI / update (push) Successful in 23s
2025-12-16 13:47:25 +01:00
23 changed files with 809 additions and 2916 deletions

View File

@@ -70,7 +70,7 @@ async function authorization({ event, resolve }) {
// Bible verse functionality for error pages // Bible verse functionality for error pages
async function getRandomVerse(fetch: typeof globalThis.fetch): Promise<any> { async function getRandomVerse(fetch: typeof globalThis.fetch): Promise<any> {
try { try {
const response = await fetch('/api/bible-quote'); const response = await fetch('/api/glaube/bibel/zufallszitat');
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }

View File

@@ -0,0 +1,259 @@
<script lang="ts">
import type { VerseData } from '$lib/data/mysteryDescriptions';
export let reference: string = '';
export let title: string = '';
export let verseData: VerseData | null = null;
export let onClose: () => void;
let book: string = verseData?.book || '';
let chapter: number = verseData?.chapter || 0;
let verses: Array<{ verse: number; text: string }> = verseData?.verses || [];
let loading = false;
let error = verseData ? '' : 'Keine Versdaten verfügbar';
function handleBackdropClick(event: MouseEvent) {
if (event.target === event.currentTarget) {
onClose();
}
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
onClose();
}
}
</script>
<svelte:window on:keydown={handleKeydown} />
<div class="modal-backdrop" on:click={handleBackdropClick} role="presentation">
<div class="modal-content">
<div class="modal-header">
<div class="header-content">
{#if title}
<h3 class="modal-title">
{#if title.includes(':')}
{title.split(':')[0]}:<br>{title.split(':')[1]}
{:else}
{title}
{/if}
</h3>
{/if}
<p class="modal-reference">{reference}</p>
</div>
<button class="close-button" on:click={onClose} aria-label="Schließen">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="modal-body">
{#if loading}
<p class="loading">Lädt...</p>
{:else if error}
<p class="error">{error}</p>
{:else if verses.length > 0}
<div class="verses">
{#each verses as verse}
<p class="verse">
<span class="verse-number">{verse.verse}</span>
<span class="verse-text">{verse.text}</span>
</p>
{/each}
</div>
{:else}
<p class="error">Keine Verse gefunden</p>
{/if}
</div>
</div>
</div>
<style>
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
backdrop-filter: blur(10px);
background: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
animation: show-backdrop 200ms ease forwards;
}
@keyframes show-backdrop {
from {
backdrop-filter: blur(0px);
background: rgba(0, 0, 0, 0);
}
to {
backdrop-filter: blur(10px);
background: rgba(0, 0, 0, 0.3);
}
}
@media(prefers-color-scheme: light) {
.modal-backdrop {
background: rgba(255, 255, 255, 0.3);
}
@keyframes show-backdrop {
from {
backdrop-filter: blur(0px);
background: rgba(255, 255, 255, 0);
}
to {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.3);
}
}
}
.modal-content {
background: var(--nord0);
border-radius: 12px;
max-width: 600px;
width: 100%;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
position: relative;
}
@media(prefers-color-scheme: light) {
.modal-content {
background: var(--nord6);
}
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 1.5rem;
border-bottom: 1px solid var(--nord3);
}
@media(prefers-color-scheme: light) {
.modal-header {
border-bottom: 1px solid var(--nord4);
}
}
.header-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.modal-title {
margin: 0;
color: var(--nord10);
font-size: 1.3rem;
font-weight: 700;
}
.modal-reference {
margin: 0;
color: var(--nord8);
font-size: 1rem;
font-weight: 600;
}
.close-button {
position: absolute;
top: -1rem;
right: -1rem;
background-color: var(--nord11);
border: none;
cursor: pointer;
padding: 1rem;
border-radius: 1000px;
color: white;
transition: 200ms;
box-shadow: 0 0 1em 0.2em rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
z-index: 1001;
}
.close-button svg {
width: 2rem;
height: 2rem;
}
.close-button:hover {
background-color: var(--nord0);
transform: scale(1.2, 1.2);
box-shadow: 0 0 1em 0.4em rgba(0, 0, 0, 0.3);
}
.close-button:active {
transition: 50ms;
scale: 0.8 0.8;
}
.modal-body {
padding: 1rem;
overflow-y: auto;
}
.loading,
.error {
text-align: center;
color: var(--nord4);
font-style: italic;
}
@media(prefers-color-scheme: light) {
.loading,
.error {
color: var(--nord2);
}
}
.error {
color: var(--nord11);
}
.verses {
display: flex;
flex-direction: column;
gap: 0;
}
.verse {
display: flex;
gap: 0.75rem;
line-height: 1.6;
color: var(--nord4);
}
@media(prefers-color-scheme: light) {
.verse {
color: var(--nord0);
}
}
.verse-number {
color: var(--nord10);
font-weight: 700;
min-width: 2rem;
font-size: 0.9rem;
}
.verse-text {
flex: 1;
font-size: 1.1rem;
}
</style>

View File

@@ -10,9 +10,6 @@ export let onClick;
<style> <style>
.counter-button { .counter-button {
position: absolute;
bottom: 0.5rem;
right: 0.5rem;
width: 3rem; width: 3rem;
height: 3rem; height: 3rem;
border-radius: 50%; border-radius: 50%;

View File

@@ -47,7 +47,7 @@
<v lang="la">Et resurréxit tértia die,</v> <v lang="la">Et resurréxit tértia die,</v>
<v lang="de">Er ist auferstanden am dritten Tage,</v> <v lang="de">Er ist auferstanden am dritten Tage,</v>
<v lang="la">secúndum Scriptúras.</v> <v lang="la">secúndum Scriptúras.</v>
<v lang="de">gemäß der Schrift;</v> <v lang="de">gemäss der Schrift;</v>
<v lang="la">Et ascéndit in cáelum:</v> <v lang="la">Et ascéndit in cáelum:</v>
<v lang="de">Er ist aufgefahren in den Himmel</v> <v lang="de">Er ist aufgefahren in den Himmel</v>
<v lang="la">sedet ad déxteram Patris.</v> <v lang="la">sedet ad déxteram Patris.</v>

View File

@@ -16,7 +16,7 @@
<v lang="la"><i><sup></sup></i> Grátias ágimus tibi</v> <v lang="la"><i><sup></sup></i> Grátias ágimus tibi</v>
<v lang="de"><i><sup></sup></i> Wir sagen Dir Dank</v> <v lang="de"><i><sup></sup></i> Wir sagen Dir Dank</v>
<v lang="la">propter magnam glóriam tuam.</v> <v lang="la">propter magnam glóriam tuam.</v>
<v lang="de">ob Deiner großen Herrlichkeit.</v> <v lang="de">ob Deiner grossen Herrlichkeit.</v>
<v lang="la">Dómine Deus, Rex cæléstis,</v> <v lang="la">Dómine Deus, Rex cæléstis,</v>
<v lang="de">Herr und Gott, König des Himmels,</v> <v lang="de">Herr und Gott, König des Himmels,</v>
<v lang="la">Deus Pater omnípotens.</v> <v lang="la">Deus Pater omnípotens.</v>

View File

@@ -12,7 +12,7 @@
<v lang="la">tuque, Prínceps milítæ cæléstis,</v> <v lang="la">tuque, Prínceps milítæ cæléstis,</v>
<v lang="de">Du aber, Fürst der himmlischen Heerscharen,</v> <v lang="de">Du aber, Fürst der himmlischen Heerscharen,</v>
<v lang="la">Sátanam aliósque spíritus malígnos,</v> <v lang="la">Sátanam aliósque spíritus malígnos,</v>
<v lang="de">stoße den Satan und die anderen bösen Geister,</v> <v lang="de">stosse den Satan und die anderen bösen Geister,</v>
<v lang="la">qui ad perditiónem animárum</v> <v lang="la">qui ad perditiónem animárum</v>
<v lang="la">pervagántur in múndo,</v> <v lang="la">pervagántur in múndo,</v>
<v lang="de">die in der Welt umhergehen,</v> <v lang="de">die in der Welt umhergehen,</v>

View File

@@ -12,11 +12,11 @@
<v lang="la">concéde, quæsumus;</v> <v lang="la">concéde, quæsumus;</v>
<v lang="de">verleihe uns, wir bitten dich,</v> <v lang="de">verleihe uns, wir bitten dich,</v>
<v lang="la">út, hæc mystéria sanctíssimo beátæ Maríæ Vírginis Rosário recoléntes;</v> <v lang="la">út, hæc mystéria sanctíssimo beátæ Maríæ Vírginis Rosário recoléntes;</v>
<v lang="de">daß wir, indem wir die Geheimisse des heiligen Rosenkranzes der allerseligsten Jungfrau ehren,</v> <v lang="de">dass wir, indem wir die Geheimisse des heiligen Rosenkranzes der allerseligsten Jungfrau ehren,</v>
<v lang="la">ét imitémur quód cóntinent,</v> <v lang="la">ét imitémur quód cóntinent,</v>
<v lang="de">was sie enthalten nachahmen</v> <v lang="de">was sie enthalten nachahmen</v>
<v lang="la">ét quód promíttunt, assequámur.</v> <v lang="la">ét quód promíttunt, assequámur.</v>
<v lang="de">und dadurch erlangen, was uns in denselben verheißen ist.</v> <v lang="de">und dadurch erlangen, was uns in denselben verheissen ist.</v>
<v lang="la">Pér eúmdem Chrístum Dóminum nóstrum.</v> <v lang="la">Pér eúmdem Chrístum Dóminum nóstrum.</v>
<v lang="de">Durch unsern Herrn Jesus Christus.</v> <v lang="de">Durch unsern Herrn Jesus Christus.</v>
<v lang="la">Ámen.</v> <v lang="la">Ámen.</v>

View File

@@ -1,11 +1,11 @@
<p> <p>
<v lang="la">Salve, Regína,</v> <v lang="la">Salve, Regína,</v>
<v lang="de">Sei gegrüßt, o Königin,</v> <v lang="de">Sei gegrüsst, o Königin,</v>
<v lang="la">máter misericórdiae;</v> <v lang="la">máter misericórdiae;</v>
<v lang="de">Mutter der Barmherzigkeit,</v> <v lang="de">Mutter der Barmherzigkeit,</v>
<v lang="la">Víta, dulcédo et spes nóstra, sálve.</v> <v lang="la">Víta, dulcédo et spes nóstra, sálve.</v>
<v lang="de">unser Leben, unsre Wonne</v> <v lang="de">unser Leben, unsre Wonne</v>
<v lang="de">und unsere Hoffnung, sei gegrüßt!</v> <v lang="de">und unsere Hoffnung, sei gegrüsst!</v>
</p> </p>
<p> <p>
<v lang="la">Ad te clamámus, éxsules fílii Hévae.</v> <v lang="la">Ad te clamámus, éxsules fílii Hévae.</v>
@@ -23,5 +23,5 @@
<v lang="la">nóbis post hoc exsílíum osténde.</v> <v lang="la">nóbis post hoc exsílíum osténde.</v>
<v lang="de">die gebenedeite Frucht deines Leibes.</v> <v lang="de">die gebenedeite Frucht deines Leibes.</v>
<v lang="la">O clémens, o pía, o dúlcis Vírgo María.</v> <v lang="la">O clémens, o pía, o dúlcis Vírgo María.</v>
<v lang="de">O gütige, o milde, o süße Jungfrau Maria.</v> <v lang="de">O gütige, o milde, o süsse Jungfrau Maria.</v>
</p> </p>

View File

@@ -0,0 +1,107 @@
export interface MysteryReference {
title: string;
reference: string;
}
export interface VerseData {
book: string;
chapter: number;
verses: Array<{ verse: number; text: string }>;
}
export interface MysteryDescription extends MysteryReference {
text: string;
verseData?: VerseData | null;
}
// Only store references - texts will be fetched at build time
export const mysteryReferences = {
lichtreichen: [
{
title: "Das erste lichtreiche Geheimnis: Die Taufe im Jordan.",
reference: "Mt 3, 16-17"
},
{
title: "Das zweite lichtreiche Geheimnis: Die Hochzeit von Kana.",
reference: "Joh 2, 1-5"
},
{
title: "Das dritte lichtreiche Geheimnis: Die Verkündigung des Reiches Gottes.",
reference: "Mk 1, 15"
},
{
title: "Das vierte lichtreiche Geheimnis: Die Verklärung.",
reference: "Mt 17, 1-2"
},
{
title: "Das fünfte lichtreiche Geheimnis: Die heiligste Eucharistie (Das Altarssakrament).",
reference: "Mt 26, 26"
}
],
freudenreich: [
{
title: "Das erste freudenreiche Geheimnis: Die Verkündigung des Erzengles Gabriel an die Jungfrau Maria.",
reference: "Lk 1, 26-27"
},
{
title: "Das zweite freudenreiche Geheimnis: Der Besuch Marias bei Elisabeth.",
reference: "Lk 1, 39-42"
},
{
title: "Das dritte freudenreiche Geheimnis: Die Geburt Jesu im Stall von Bethlehem.",
reference: "Lk 2, 1-7"
},
{
title: "Das vierte freudenreiche Geheimnis: Jesus wird von Maria und Josef im Tempel dargebracht.",
reference: "Lk 2, 21-24"
},
{
title: "Das fünfte freudenreiche Geheimnis: Jesus wird im Tempel wiedergefunden.",
reference: "Lk 2, 41-47"
}
],
schmerzhaften: [
{
title: "Das erste schmerzhafte Geheimnis: Die Todesangst Jesu.",
reference: "Mt 26, 36-39"
},
{
title: "Das zweite schmerzhafte Geheimnis: Die Geißelung Jesu.",
reference: "Mt 27, 26"
},
{
title: "Das dritte schmerzhafte Geheimnis: Die Dornenkrönung.",
reference: "Mt 27, 27-29"
},
{
title: "Das vierte schmerzhafte Geheimnis: Jesus trägt das schwere Kreuz.",
reference: "Mk 15, 21-22"
},
{
title: "Das fünfte schmerzhafte Geheimnis: Die Kreuzigung Jesu.",
reference: "Lk 23, 33-46"
}
],
glorreichen: [
{
title: "Das erste glorreiche Geheimnis: Die Auferstehung Jesu.",
reference: "Lk 24, 1-6"
},
{
title: "Das zweite glorreiche Geheimnis: Die Himmerfahrt Jesu.",
reference: "Mk 16, 19"
},
{
title: "Das dritte glorreiche Geheimnis: Die Herabkunft des Heiligen Geistes im Abendmahlssaal.",
reference: "Apg 2, 1-4"
},
{
title: "Das vierte glorreiche Geheimnis: Die Aufnahme Marias in den Himmel.",
reference: "Lk 1, 48-49"
},
{
title: "Das fünfte glorreiche Geheimnis: Die Krönung Marias zur Königin des Himmels und der Erde.",
reference: "Offb 12, 1"
}
]
} as const;

View File

@@ -77,7 +77,6 @@ export const GET: RequestHandler = async ({ url, locals }) => {
]; ];
const results = await Payment.aggregate(pipeline); const results = await Payment.aggregate(pipeline);
console.log('Aggregation results:', results);
// Transform data into chart-friendly format // Transform data into chart-friendly format
const monthsMap = new Map(); const monthsMap = new Map();

View File

@@ -18,11 +18,10 @@ export const POST: RequestHandler = async ({ request }) => {
} }
await dbConnect(); await dbConnect();
try { try {
const now = new Date(); const now = new Date();
console.log(`[Cron] Starting recurring payments processing at ${now.toISOString()}`);
// Find all active recurring payments that are due // Find all active recurring payments that are due
const duePayments = await RecurringPayment.find({ const duePayments = await RecurringPayment.find({
isActive: true, isActive: true,
@@ -34,16 +33,12 @@ export const POST: RequestHandler = async ({ request }) => {
] ]
}); });
console.log(`[Cron] Found ${duePayments.length} due recurring payments`);
const results = []; const results = [];
let successCount = 0; let successCount = 0;
let failureCount = 0; let failureCount = 0;
for (const recurringPayment of duePayments) { for (const recurringPayment of duePayments) {
try { try {
console.log(`[Cron] Processing recurring payment: ${recurringPayment.title} (${recurringPayment._id})`);
// Create the payment // Create the payment
const payment = await Payment.create({ const payment = await Payment.create({
title: `${recurringPayment.title} (Auto)`, title: `${recurringPayment.title} (Auto)`,
@@ -89,8 +84,6 @@ export const POST: RequestHandler = async ({ request }) => {
success: true success: true
}); });
console.log(`[Cron] Successfully processed: ${recurringPayment.title}, next execution: ${nextExecutionDate.toISOString()}`);
} catch (paymentError) { } catch (paymentError) {
console.error(`[Cron] Error processing recurring payment ${recurringPayment._id}:`, paymentError); console.error(`[Cron] Error processing recurring payment ${recurringPayment._id}:`, paymentError);
failureCount++; failureCount++;
@@ -104,8 +97,6 @@ export const POST: RequestHandler = async ({ request }) => {
} }
} }
console.log(`[Cron] Completed processing. Success: ${successCount}, Failures: ${failureCount}`);
return json({ return json({
success: true, success: true,
timestamp: now.toISOString(), timestamp: now.toISOString(),

View File

@@ -33,7 +33,6 @@ export const POST: RequestHandler = async ({ request, locals }) => {
switch (action) { switch (action) {
case 'execute': case 'execute':
console.log(`[API] Manual execution requested by ${auth.user.nickname}`);
await recurringPaymentScheduler.executeNow(); await recurringPaymentScheduler.executeNow();
return json({ return json({
success: true, success: true,

View File

@@ -0,0 +1,122 @@
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
interface BibleVerse {
bookName: string;
abbreviation: string;
bookNumber: number;
chapter: number;
verseNumber: number;
text: string;
}
// Cache for parsed verses to avoid reading file repeatedly
let cachedVerses: BibleVerse[] | null = null;
async function loadVerses(fetch: typeof globalThis.fetch): Promise<BibleVerse[]> {
if (cachedVerses) {
return cachedVerses;
}
try {
const response = await fetch('/allioli.tsv');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const content = await response.text();
const lines = content.trim().split('\n');
cachedVerses = lines.map(line => {
const [bookName, abbreviation, bookNumber, chapter, verseNumber, text] = line.split('\t');
return {
bookName,
abbreviation,
bookNumber: parseInt(bookNumber),
chapter: parseInt(chapter),
verseNumber: parseInt(verseNumber),
text
};
});
return cachedVerses;
} catch (err) {
console.error('Error loading Bible verses:', err);
throw new Error('Failed to load Bible verses');
}
}
function parseReference(reference: string): { bookRef: string; isFullName: boolean; chapter: number; startVerse: number; endVerse: number } | null {
// Parse various reference formats:
// "Mt 3, 16-17", "Mt3:16-17", "Mt 3:16-17", "Lk1:3", "Matthäus 3, 16-17"
// Match book name (letters and umlauts), optional space, chapter, separator (: or ,), optional space, verse(s)
const match = reference.match(/^([A-Za-zäöüÄÖÜß]+)\s*(\d+)[\s,:]+(\d+)(?:[-:](\d+))?$/);
if (!match) {
return null;
}
const [, bookRef, chapterStr, startVerseStr, endVerseStr] = match;
// If book reference is longer than 5 characters, assume it's a full name
// Otherwise, assume it's an abbreviation
const isFullName = bookRef.length > 5;
return {
bookRef,
isFullName,
chapter: parseInt(chapterStr),
startVerse: parseInt(startVerseStr),
endVerse: endVerseStr ? parseInt(endVerseStr) : parseInt(startVerseStr)
};
}
function getVersesByReference(verses: BibleVerse[], reference: string): BibleVerse[] {
const parsed = parseReference(reference);
if (!parsed) {
return [];
}
return verses.filter(v => {
// Match based on whether we're using full name or abbreviation
const bookMatches = parsed.isFullName
? v.bookName === parsed.bookRef
: v.abbreviation === parsed.bookRef;
return bookMatches &&
v.chapter === parsed.chapter &&
v.verseNumber >= parsed.startVerse &&
v.verseNumber <= parsed.endVerse;
});
}
export const GET: RequestHandler = async ({ params, fetch }) => {
const reference = params.reference;
if (!reference) {
return error(400, 'Missing reference parameter');
}
try {
const verses = await loadVerses(fetch);
const matchedVerses = getVersesByReference(verses, reference);
if (matchedVerses.length === 0) {
return error(404, 'No verses found for the given reference');
}
// Extract book and chapter from first verse (they're all the same)
const firstVerse = matchedVerses[0];
return json({
reference,
book: firstVerse.bookName,
chapter: firstVerse.chapter,
verses: matchedVerses.map(v => ({
verse: v.verseNumber,
text: v.text
}))
});
} catch (err) {
console.error('Error fetching Bible verses:', err);
return error(500, 'Failed to fetch Bible verses');
}
};

View File

@@ -4,8 +4,8 @@ import type { RequestHandler } from './$types';
interface BibleVerse { interface BibleVerse {
bookName: string; bookName: string;
abbreviation: string; abbreviation: string;
bookNumber: number;
chapter: number; chapter: number;
verse: number;
verseNumber: number; verseNumber: number;
text: string; text: string;
} }
@@ -25,14 +25,14 @@ async function loadVerses(fetch: typeof globalThis.fetch): Promise<BibleVerse[]>
} }
const content = await response.text(); const content = await response.text();
const lines = content.trim().split('\n'); const lines = content.trim().split('\n');
cachedVerses = lines.map(line => { cachedVerses = lines.map(line => {
const [bookName, abbreviation, chapter, verse, verseNumber, text] = line.split('\t'); const [bookName, abbreviation, bookNumber, chapter, verseNumber, text] = line.split('\t');
return { return {
bookName, bookName,
abbreviation, abbreviation,
bookNumber: parseInt(bookNumber),
chapter: parseInt(chapter), chapter: parseInt(chapter),
verse: parseInt(verse),
verseNumber: parseInt(verseNumber), verseNumber: parseInt(verseNumber),
text text
}; };
@@ -58,7 +58,7 @@ export const GET: RequestHandler = async ({ fetch }) => {
try { try {
const verses = await loadVerses(fetch); const verses = await loadVerses(fetch);
const randomVerse = getRandomVerse(verses); const randomVerse = getRandomVerse(verses);
return json({ return json({
text: randomVerse.text, text: randomVerse.text,
reference: formatVerse(randomVerse), reference: formatVerse(randomVerse),
@@ -70,4 +70,4 @@ export const GET: RequestHandler = async ({ fetch }) => {
console.error('Error fetching random Bible verse:', err); console.error('Error fetching random Bible verse:', err);
return error(500, 'Failed to fetch Bible verse'); return error(500, 'Failed to fetch Bible verse');
} }
}; };

View File

@@ -74,7 +74,7 @@ h1{
</Gebet> </Gebet>
<Gebet name={"Glória"} is_bilingue={true}> <Gebet name={"Glória"} is_bilingue={true}>
<p slot="intro">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 schließt mit einer Huldigung an die Heiligste Dreifaltigkeit, wobei man sich mit dem großen Kreuze bezeichnet.</p> <p slot="intro">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.</p>
<Gloria /> <Gloria />
</Gebet> </Gebet>

View File

@@ -74,16 +74,16 @@ h4{
<h2>Der Osterfestkreis</h2> <h2>Der Osterfestkreis</h2>
<div class=schott> <div class=schott>
<p> <p>
Der Weihnachtsfestkreis schließt mit der Woche vor Septuagesima. Der Weihnachtsfestkreis schliesst mit der Woche vor Septuagesima.
Der ersehnte Erlöser ist gekommen und hat in seiner ersten Ankunft zugleich seine zweite, die am Gerichtstage erfolgen wird, begründet und begonnen. Der ersehnte Erlöser ist gekommen und hat in seiner ersten Ankunft zugleich seine zweite, die am Gerichtstage erfolgen wird, begründet und begonnen.
</p> </p>
<p> <p>
Jetzt ist die Zeit des anstrengten Kampfes gegen Sünde, Welt und Fleisch gekommen, die Zeit der mühevollen Aussaat, des sturmumtobten Wachsens. Jetzt ist die Zeit des anstrengten Kampfes gegen Sünde, Welt und Fleisch gekommen, die Zeit der mühevollen Aussaat, des sturmumtobten Wachsens.
Durch Kampf zum Sieg, durch Sterben zum Leben, zur Auferstehung, zur Vollherrschaft Christi und schließlich zur Verklärung im Osterlichte! Durch Kampf zum Sieg, durch Sterben zum Leben, zur Auferstehung, zur Vollherrschaft Christi und schliesslich zur Verklärung im Osterlichte!
Christus soll in uns den Thron seiner Herrschaft errichten, einer Herrschaft, die uns nicht erdrückt, sondern erhöht; nicht beraubt, sondern bereichert; nicht einschränkt, sondern innerlich weitet und uns einmal mitherrschen läßt im ewigen Ostern des Himmels. Christus soll in uns den Thron seiner Herrschaft errichten, einer Herrschaft, die uns nicht erdrückt, sondern erhöht; nicht beraubt, sondern bereichert; nicht einschränkt, sondern innerlich weitet und uns einmal mitherrschen lässt im ewigen Ostern des Himmels.
</p> </p>
<p> <p>
Der Osterfestkreis umfaßt drei Abschnitte: Der Osterfestkreis umfasst drei Abschnitte:
die Zeit der Vorbereitung: Vorfasten- und Fastenzeit; die Zeit der Vorbereitung: Vorfasten- und Fastenzeit;
die eigentliche Festzeit: Oster- und Pfingstfest; die eigentliche Festzeit: Oster- und Pfingstfest;
endlich die Zeit nach Pfingsten endlich die Zeit nach Pfingsten
@@ -94,15 +94,15 @@ endlich die Zeit nach Pfingsten
<h3> 1. Die Vorfastenzeit</h3> <h3> 1. Die Vorfastenzeit</h3>
<div class=schott > <div class=schott >
<p> <p>
Sie umfaßt die Sonntage Septuagesima, Sexagesima und Quinquagesima. Sie umfasst die Sonntage Septuagesima, Sexagesima und Quinquagesima.
Diese Namen bezeichnen nicht die genauen Abstände bis zum Osterfest, sonder deuten auf die rund berechnete 70tägige, 60tägige, 50tägige Vorbereitungzeit auf Ostern. Diese Namen bezeichnen nicht die genauen Abstände bis zum Osterfest, sonder deuten auf die rund berechnete 70tägige, 60tägige, 50tägige Vorbereitungzeit auf Ostern.
</p> </p>
<p> <p>
Der Name Septuagesima weckt die Erinnerung an die 70 Jahre der Gefangenschaft, welche die Juden zur Strafe für ihre Untreue fern von Jerusalem, zu Babylon, verbringen mußten, bevor sie wieder ins Gelobte Land zurückkerhen durften. Der Name Septuagesima weckt die Erinnerung an die 70 Jahre der Gefangenschaft, welche die Juden zur Strafe für ihre Untreue fern von Jerusalem, zu Babylon, verbringen mussten, bevor sie wieder ins Gelobte Land zurückkerhen durften.
So mahnt uns diese Zeit an unsre eigene Pilgerschaft aus der Fremde, aus der gottfernen Welt (Babylon), zum wahren Vaterland (Jerusalem). So mahnt uns diese Zeit an unsre eigene Pilgerschaft aus der Fremde, aus der gottfernen Welt (Babylon), zum wahren Vaterland (Jerusalem).
Diese Pilgerschaft ist für uns eine beständiger Kampf gegen die Feinde unsres Heiles. Diese Pilgerschaft ist für uns eine beständiger Kampf gegen die Feinde unsres Heiles.
Für den göttlichen Heiland bedeutet das öffentliche Wirken Mühsal und Leiden und schließlich den Tod; Für den göttlichen Heiland bedeutet das öffentliche Wirken Mühsal und Leiden und schliesslich den Tod;
so muß sich auch unser Leben, soll es dem seinen nachgebildet werden, auf Kämpfe, selbst auf ein geistiges Sterben gefaßt machen; so muss sich auch unser Leben, soll es dem seinen nachgebildet werden, auf Kämpfe, selbst auf ein geistiges Sterben gefasst machen;
erst dann wird es mit dem Heiland zum endlichen Triumph gelangen. erst dann wird es mit dem Heiland zum endlichen Triumph gelangen.
</p> </p>
</div> </div>
@@ -123,14 +123,14 @@ endlich die Zeit nach Pfingsten
<h3> Epistel </h3> <h3> Epistel </h3>
<div class="epistel bibel"> <div class="epistel bibel">
<ol><i>1 Cor. 9</i> <ol><i>1 Cor. 9</i>
<li value=24>Wisset ihr nicht, daß die, welche in der Rennbahn laufen, zwar alle laufen, aber nur einer erlangt den Preis? Laufet fo, daß ihr ihn erlanget!</li> <li value=24>Wisset ihr nicht, dass die, welche in der Rennbahn laufen, zwar alle laufen, aber nur einer erlangt den Preis? Laufet fo, dass ihr ihn erlanget!</li>
<li>Jeder aber, der im Kampfspiele ringt, enthält sich von allem, und zwar jene, um eine vergängliche Krone zu empfangen, wir aber eine unvergängliche.</li> <li>Jeder aber, der im Kampfspiele ringt, enthält sich von allem, und zwar jene, um eine vergängliche Krone zu empfangen, wir aber eine unvergängliche.</li>
<li>Ich laufe demnach, nicht wie in's Ungewisse; ich kämpfe, nicht indem ich Luftstreiche thue,</li> <li>Ich laufe demnach, nicht wie in's Ungewisse; ich kämpfe, nicht indem ich Luftstreiche thue,</li>
<li>sondern ich züchtige meinen Leib, und bringe ihn in die Botmäßigkeit, damit ich nicht etwa, nachdem ich anderen gepredigt habe, selbst verworfen werde.</li> <li>sondern ich züchtige meinen Leib, und bringe ihn in die Botmässigkeit, damit ich nicht etwa, nachdem ich anderen gepredigt habe, selbst verworfen werde.</li>
<i>1 Cor. 10</i> <i>1 Cor. 10</i>
<li value=1>Denn ich will euch nicht in Unwissenheit lassen, Brüder! Daß unsere Väter alle unter der Wolke waren, und alle durch das Meer hindurch gingen,</li> <li value=1>Denn ich will euch nicht in Unwissenheit lassen, Brüder! Dass unsere Väter alle unter der Wolke waren, und alle durch das Meer hindurch gingen,</li>
<li>und alle auf Moses getauft wurden, in der Wolke und in dem Meere,</li> <li>und alle auf Moses getauft wurden, in der Wolke und in dem Meere,</li>
<li>und alle dieselbe geistige Speise aßen,</li> <li>und alle dieselbe geistige Speise assen,</li>
<li>und alle dieselbe geistigen Trank tranken (sie tranken nämlich aus einem geistigen, sie begleitenden Felsen, der Felsen aber war Christus);</li> <li>und alle dieselbe geistigen Trank tranken (sie tranken nämlich aus einem geistigen, sie begleitenden Felsen, der Felsen aber war Christus);</li>
<li>aber an der Mehrzahl von ihnen hatte Gott kein Wohlgefallen; denn sie wurden niedergestreckt in der Wüste.</li> <li>aber an der Mehrzahl von ihnen hatte Gott kein Wohlgefallen; denn sie wurden niedergestreckt in der Wüste.</li>
</ol> </ol>
@@ -141,14 +141,14 @@ endlich die Zeit nach Pfingsten
<ol><i>Matth. 20</i> <ol><i>Matth. 20</i>
<li>Das Himmelreich ist gleich einem Hausvater, der am frühen Morgen ausging, um Arbeiter in seinen Weinberg zu dingen.</li> <li>Das Himmelreich ist gleich einem Hausvater, der am frühen Morgen ausging, um Arbeiter in seinen Weinberg zu dingen.</li>
<li>Nachdem er nun mit den Arbeitern um einen Denar für den Tag übereingekommen war, sandte er sie in seinen Weinberg.</li> <li>Nachdem er nun mit den Arbeitern um einen Denar für den Tag übereingekommen war, sandte er sie in seinen Weinberg.</li>
<li>Und als er um die dritte Stunde ausging, sah er andere au dem Markte müßig stehen,</li> <li>Und als er um die dritte Stunde ausging, sah er andere au dem Markte müssig stehen,</li>
<li>und sprach zu ihnen: Gehet auch ihr in meinen Weinberg, und was recht ist, werde ich euch geben.</li> <li>und sprach zu ihnen: Gehet auch ihr in meinen Weinberg, und was recht ist, werde ich euch geben.</li>
<li>Sie aber gingen hin. Abermals ging er um die sechste und neunte Stunde aus, und that ebenso.</li> <li>Sie aber gingen hin. Abermals ging er um die sechste und neunte Stunde aus, und that ebenso.</li>
<li>Um die elfte Stunde aber ging er aus, und fand andere andere stehen, und sprach zu ihnen: Was stehet ihr hier den ganzen Tag müßig?</li> <li>Um die elfte Stunde aber ging er aus, und fand andere andere stehen, und sprach zu ihnen: Was stehet ihr hier den ganzen Tag müssig?</li>
<li>Sie antworteten ihm: Weil uns niemand gedungen hat. Da sprach er zu ihnen: Gehet auch ihr in meinen Weinberg,</li> <li>Sie antworteten ihm: Weil uns niemand gedungen hat. Da sprach er zu ihnen: Gehet auch ihr in meinen Weinberg,</li>
<li>Als es nun Abend geworden, sagte der Herr des Weinberges zu seinem Verwalter: Rufe die Arbeiter, und gib ihnen den Lohn, von den letzten anfangend, bis zu den ersten.</li> <li>Als es nun Abend geworden, sagte der Herr des Weinberges zu seinem Verwalter: Rufe die Arbeiter, und gib ihnen den Lohn, von den letzten anfangend, bis zu den ersten.</li>
<li>Da nun die kamen, welche um die elfte Stunde eingetreten waren, empfingen sie jeder einen Denar.</li> <li>Da nun die kamen, welche um die elfte Stunde eingetreten waren, empfingen sie jeder einen Denar.</li>
<li>Wie aber auch die ersten kamen, meinten sie, daß sie mehr empfangen würden, aber auch sie erhielten jeder einen Denar.</li> <li>Wie aber auch die ersten kamen, meinten sie, dass sie mehr empfangen würden, aber auch sie erhielten jeder einen Denar.</li>
<li>Und da sie ihn empfingen, murrten sie wider den Hausvater.</li> <li>Und da sie ihn empfingen, murrten sie wider den Hausvater.</li>
<li>und sprachen: Siese letzten haben eine einzige Stunde gearbeitet, und du hast sie uns gleich gehalten, die wir die Last und Hitze des Tages getragen haben.</li> <li>und sprachen: Siese letzten haben eine einzige Stunde gearbeitet, und du hast sie uns gleich gehalten, die wir die Last und Hitze des Tages getragen haben.</li>
<li>Er aber antowrtete einem aus ihnen, und sprach: Freund! ich thue dir nicht Unrecht; bist du nicht auf einen Denar mit mir eins geworden?</li> <li>Er aber antowrtete einem aus ihnen, und sprach: Freund! ich thue dir nicht Unrecht; bist du nicht auf einen Denar mit mir eins geworden?</li>
@@ -168,7 +168,7 @@ endlich die Zeit nach Pfingsten
Wir sollen gleich den Sportlern verzichten für das Heil der Seelen, dem Heil der eigenen Seele. Wir sollen gleich den Sportlern verzichten für das Heil der Seelen, dem Heil der eigenen Seele.
</p> </p>
<p> <p>
Diese Bildnis der Spiele im Stadion waren vermutlich ein gutes Bildnis für die Korinter. So hatten sie alle zwei Jahre Sportspiele von April bis Anfang Mai, welche wie auch Fußball heute noch, vieles der Gesellschaft lahmlegte. Diese Bildnis der Spiele im Stadion waren vermutlich ein gutes Bildnis für die Korinter. So hatten sie alle zwei Jahre Sportspiele von April bis Anfang Mai, welche wie auch Fussball heute noch, vieles der Gesellschaft lahmlegte.
Auch die Gläubigen gehörten damals wie heute zu den Begeisterten solcher Spiele. Auch die Gläubigen gehörten damals wie heute zu den Begeisterten solcher Spiele.
</p> </p>
@@ -194,7 +194,7 @@ endlich die Zeit nach Pfingsten
So beging er zwei Todsünden durch einen Blick auf eine Frau: Ehebruch und Mord am Manne dieser Frau. So beging er zwei Todsünden durch einen Blick auf eine Frau: Ehebruch und Mord am Manne dieser Frau.
</p> </p>
<p> <p>
Als Kind hat er Löwen und Bären mit bloßen Händen besiegt, aber nun wird dieser einst mutige und starke Mann bezwungen wegen seinem <em>ßiggang</em>. Als Kind hat er Löwen und Bären mit blossen Händen besiegt, aber nun wird dieser einst mutige und starke Mann bezwungen wegen seinem <em>ssiggang</em>.
Wie man zu sagen pflegt: Wer man kein Beschäftigugn welche Platz einnimmt so wird der Teufel selbst den ganzen Platz einnehmen, den wir freigelassen haben. Wie man zu sagen pflegt: Wer man kein Beschäftigugn welche Platz einnimmt so wird der Teufel selbst den ganzen Platz einnehmen, den wir freigelassen haben.
Es wäre besser gewesen für David, noch in der Ängstlichkeit der Flucht vor Saulus zu sein, als in seinem Palast in Jerusalem. Es wäre besser gewesen für David, noch in der Ängstlichkeit der Flucht vor Saulus zu sein, als in seinem Palast in Jerusalem.
</p> </p>
@@ -208,10 +208,10 @@ endlich die Zeit nach Pfingsten
</div> </div>
<p> <p>
ßiggang hat ihn besiegt und der Gedanke, dass er nichts mehr zu erobern hatte. ssiggang hat ihn besiegt und der Gedanke, dass er nichts mehr zu erobern hatte.
Es gibt immer etwas weiteres zu erobern. Es gibt immer eine Ecke in unserer Seele die uns, und damit Gott, nicht gehört. Es gibt immer etwas weiteres zu erobern. Es gibt immer eine Ecke in unserer Seele die uns, und damit Gott, nicht gehört.
Es gibt immer etwas, was man besser tun kann, es gibt immer schlechte Gewohnheiten die man ablegen muss. Es gibt immer etwas, was man besser tun kann, es gibt immer schlechte Gewohnheiten die man ablegen muss.
In dieser Vorfastenzeit geht es darum sich diesem deutlicher bewusst zu werden und seinen Kampf gegen diese Müßigkeit für die kommende Fastenzeit zu planen. In dieser Vorfastenzeit geht es darum sich diesem deutlicher bewusst zu werden und seinen Kampf gegen diese Müssigkeit für die kommende Fastenzeit zu planen.
</p> </p>
<p> <p>
Als Beispiel hilft hier das Königreich Spanien. Jahrhunderte von Kampf um die Anwesenheit der Muslime zu bekämpfen. Als sie endlich die letzte Stadt, welche unter der Vollmacht der Muslime war, erobert hatten, hat die Vorsehung ihnen noch etwas zu erobern gegeben: Als Beispiel hilft hier das Königreich Spanien. Jahrhunderte von Kampf um die Anwesenheit der Muslime zu bekämpfen. Als sie endlich die letzte Stadt, welche unter der Vollmacht der Muslime war, erobert hatten, hat die Vorsehung ihnen noch etwas zu erobern gegeben:
@@ -226,7 +226,7 @@ endlich die Zeit nach Pfingsten
<p> <p>
Von den Ägyptern durch die Wunder Gottes befreit, sind sie nun auf dem weiten Weg zum versprochenen Land. Von den Ägyptern durch die Wunder Gottes befreit, sind sie nun auf dem weiten Weg zum versprochenen Land.
Sie schicken Kundschafter in dieses Land. Diese Kundschafter gehen und verbringen 40 Tage dort. Sie schicken Kundschafter in dieses Land. Diese Kundschafter gehen und verbringen 40 Tage dort.
Diese Kundschafter finden ein wunderbares Land. Es fließt Honig und Milch. Aber es ist nicht unbevölkert. Es gibt viele, starke Stämme. Diese Kundschafter finden ein wunderbares Land. Es fliesst Honig und Milch. Aber es ist nicht unbevölkert. Es gibt viele, starke Stämme.
Die Kundschafter haben Angst, sie verbreiteten Lügen über dieses Land da sie Angst haben zu fallen im Versuch es einzunehmen. Die Kundschafter haben Angst, sie verbreiteten Lügen über dieses Land da sie Angst haben zu fallen im Versuch es einzunehmen.
Das Volk will murren, beklagen. Sie wollen nicht kämpfen. Das Volk will murren, beklagen. Sie wollen nicht kämpfen.
</p> </p>
@@ -251,7 +251,7 @@ endlich die Zeit nach Pfingsten
Diese Knechtschaft soll abgelegt werden um so zu einem kämpferischen Geist zu kommen. Das wird auch bestätigt im Johannesevangelium wo steht: Diese Knechtschaft soll abgelegt werden um so zu einem kämpferischen Geist zu kommen. Das wird auch bestätigt im Johannesevangelium wo steht:
<div class=bibel> <div class=bibel>
<ol><i> Johannes 15</i> <ol><i> Johannes 15</i>
<li value=15>Ich nenne euch nun nicht mehr Knechte, denn der Knecht weiß nicht, was sein Herr tut; euch aber habe ich Freunde genannt; denn alles, was ich von meinem Vater gehört, habe ich euch kundgetan. <li value=15>Ich nenne euch nun nicht mehr Knechte, denn der Knecht weiss nicht, was sein Herr tut; euch aber habe ich Freunde genannt; denn alles, was ich von meinem Vater gehört, habe ich euch kundgetan.
</li> </li>
</ol> </ol>
</div> </div>
@@ -280,7 +280,7 @@ endlich die Zeit nach Pfingsten
<li value=10>Simon Petrus also, der sein Schwert hatte, zog es und schlug den Knecht des Hohenpriesters, und hieb ihm sein rechtes Ohr ab. Der Name des Knechtes aber war Malchus.</li> <li value=10>Simon Petrus also, der sein Schwert hatte, zog es und schlug den Knecht des Hohenpriesters, und hieb ihm sein rechtes Ohr ab. Der Name des Knechtes aber war Malchus.</li>
<i>Matthäus 26</i> <i>Matthäus 26</i>
<li value=35>Da sprach Petrus zu ihm: Wenn ich auch mit dir sterben müsste, werde ich dich doch nicht verleugnen. In gleicher Weise sprachen auch alle Jünger.</li> <li value=35>Da sprach Petrus zu ihm: Wenn ich auch mit dir sterben müsste, werde ich dich doch nicht verleugnen. In gleicher Weise sprachen auch alle Jünger.</li>
<li value=69>Petrus aber saß draußen in dem Hofe; und eine Magd trat zu ihm hin, und sprach: Du warest auch bei Jesus, dem Galiläer!</li> <li value=69>Petrus aber sass draussen in dem Hofe; und eine Magd trat zu ihm hin, und sprach: Du warest auch bei Jesus, dem Galiläer!</li>
<li>Doch er leugnete vor allen, und sprach: Ich weiss nicht, was du sagst.</li> <li>Doch er leugnete vor allen, und sprach: Ich weiss nicht, was du sagst.</li>
</ol> </ol>
</div> </div>

View File

@@ -0,0 +1,64 @@
import { mysteryReferences, type MysteryDescription, type VerseData } from '$lib/data/mysteryDescriptions';
import type { PageServerLoad } from './$types';
export const prerender = true;
async function fetchBibleData(reference: string, fetch: typeof globalThis.fetch): Promise<{ text: string; verseData: VerseData | null }> {
try {
const response = await fetch(`/api/glaube/bibel/${encodeURIComponent(reference)}`);
if (!response.ok) {
console.error(`Failed to fetch reference ${reference}:`, response.status);
return { text: '', verseData: null };
}
const data = await response.json();
// Format the verses into a single text with guillemets
let text = '';
if (data.verses && data.verses.length > 0) {
text = `«${data.verses.map((v: { verse: number; text: string }) => v.text).join(' ')}»`;
}
// Store the full verse data for the modal
const verseData: VerseData = {
book: data.book,
chapter: data.chapter,
verses: data.verses
};
return { text, verseData };
} catch (err) {
console.error(`Error fetching reference ${reference}:`, err);
return { text: '', verseData: null };
}
}
export const load: PageServerLoad = async ({ fetch }) => {
// Fetch Bible texts for all mysteries at build time
const mysteryDescriptions: Record<string, MysteryDescription[]> = {
lichtreichen: [],
freudenreich: [],
schmerzhaften: [],
glorreichen: []
};
// Process each mystery type
for (const [mysteryType, references] of Object.entries(mysteryReferences)) {
const descriptions: MysteryDescription[] = [];
for (const ref of references) {
const { text, verseData } = await fetchBibleData(ref.reference, fetch);
descriptions.push({
title: ref.title,
reference: ref.reference,
text,
verseData
});
}
mysteryDescriptions[mysteryType] = descriptions;
}
return {
mysteryDescriptions
};
};

View File

@@ -12,6 +12,9 @@ import SalveRegina from "$lib/components/prayers/SalveRegina.svelte";
import RosaryFinalPrayer from "$lib/components/prayers/RosaryFinalPrayer.svelte"; import RosaryFinalPrayer from "$lib/components/prayers/RosaryFinalPrayer.svelte";
import BenedictusMedal from "$lib/components/BenedictusMedal.svelte"; import BenedictusMedal from "$lib/components/BenedictusMedal.svelte";
import CounterButton from "$lib/components/CounterButton.svelte"; import CounterButton from "$lib/components/CounterButton.svelte";
import BibleModal from "$lib/components/BibleModal.svelte";
export let data;
// Mystery variations for each type of rosary // Mystery variations for each type of rosary
const mysteries = { const mysteries = {
@@ -24,7 +27,7 @@ const mysteries = {
], ],
schmerzhaften: [ schmerzhaften: [
"Jesus, der für uns Blut geschwitzt hat.", "Jesus, der für uns Blut geschwitzt hat.",
"Jesus, der für uns gegeißelt worden ist.", "Jesus, der für uns gegeisselt worden ist.",
"Jesus, der für uns mit Dornen gekrönt worden ist.", "Jesus, der für uns mit Dornen gekrönt worden ist.",
"Jesus, der für uns das schwere Kreuz getragen hat.", "Jesus, der für uns das schwere Kreuz getragen hat.",
"Jesus, der für uns gekreuzigt worden ist." "Jesus, der für uns gekreuzigt worden ist."
@@ -62,7 +65,7 @@ const mysteriesLatin = {
], ],
glorreichen: [ glorreichen: [
"Jesus, qui resurréxit a mórtuis.", "Jesus, qui resurréxit a mórtuis.",
"Jesus, qui ascendit in cælum .", "Jesus, qui ascendit in cælum.",
"Jesus, qui misit Spíritum Sanctum.", "Jesus, qui misit Spíritum Sanctum.",
"Jesus, qui te, virgo, in cælum assúmpsit.", "Jesus, qui te, virgo, in cælum assúmpsit.",
"Jesus, qui te, virgo, in cælo coronávit." "Jesus, qui te, virgo, in cælo coronávit."
@@ -87,7 +90,7 @@ const mysteryTitles = {
], ],
schmerzhaften: [ schmerzhaften: [
"Todesangst", "Todesangst",
"Geißelung", "Geisselung",
"Dornenkrönung", "Dornenkrönung",
"Kreuzweg", "Kreuzweg",
"Kreuzigung" "Kreuzigung"
@@ -144,9 +147,14 @@ function getMysteryForWeekday(date, includeLuminous) {
// Determine which mystery to use based on current weekday // Determine which mystery to use based on current weekday
let selectedMystery = getMysteryForWeekday(new Date(), includeLuminous); let selectedMystery = getMysteryForWeekday(new Date(), includeLuminous);
let todaysMystery = selectedMystery; // Track today's auto-selected mystery
let currentMysteries = mysteries[selectedMystery]; let currentMysteries = mysteries[selectedMystery];
let currentMysteriesLatin = mysteriesLatin[selectedMystery]; let currentMysteriesLatin = mysteriesLatin[selectedMystery];
let currentMysteryTitles = mysteryTitles[selectedMystery]; let currentMysteryTitles = mysteryTitles[selectedMystery];
let currentMysteryDescriptions = data.mysteryDescriptions[selectedMystery] || [];
// Reactive statement to update mystery descriptions when selectedMystery changes
$: currentMysteryDescriptions = data.mysteryDescriptions[selectedMystery] || [];
// Function to switch mysteries // Function to switch mysteries
function selectMystery(mysteryType) { function selectMystery(mysteryType) {
@@ -159,7 +167,7 @@ function selectMystery(mysteryType) {
// Function to handle toggle change // Function to handle toggle change
function handleToggleChange() { function handleToggleChange() {
// Recalculate the default mystery for today // Recalculate the default mystery for today
const todaysMystery = getMysteryForWeekday(new Date(), includeLuminous); todaysMystery = getMysteryForWeekday(new Date(), includeLuminous);
// Update to today's mystery // Update to today's mystery
selectMystery(todaysMystery); selectMystery(todaysMystery);
} }
@@ -178,6 +186,12 @@ let decadeCounters = {
secret5: 0 secret5: 0
}; };
// Modal state for displaying Bible citations
let showModal = false;
let selectedReference = '';
let selectedTitle = '';
let selectedVerseData = null;
// Function to advance the counter for a specific decade // Function to advance the counter for a specific decade
function advanceDecade(decadeNum) { function advanceDecade(decadeNum) {
const key = `secret${decadeNum}`; const key = `secret${decadeNum}`;
@@ -211,6 +225,14 @@ function advanceDecade(decadeNum) {
} }
} }
// Function to handle citation click
function handleCitationClick(reference, title = '', verseData = null) {
selectedReference = reference;
selectedTitle = title;
selectedVerseData = verseData;
showModal = true;
}
// Map sections to their vertical positions in the SVG // Map sections to their vertical positions in the SVG
const sectionPositions = { const sectionPositions = {
cross: 35, cross: 35,
@@ -662,12 +684,12 @@ onMount(() => {
.prayer-section.decade { .prayer-section.decade {
scroll-snap-align: start; scroll-snap-align: start;
min-height: 50vh; /* Only decades need minimum height for scroll-snap */ min-height: 50vh; /* Only decades need minimum height for scroll-snap */
padding-bottom: 4.5rem; /* Extra space for the counter button */ padding-bottom: 2rem;
} }
@media (max-width: 1023px) { @media (max-width: 1023px) {
.prayer-section.decade { .prayer-section.decade {
padding-bottom: 3.5rem; /* Adjusted for mobile padding */ padding-bottom: 1.5rem;
} }
.prayer-section { .prayer-section {
padding: 0.5rem; padding: 0.5rem;
@@ -796,35 +818,26 @@ h1 {
/* Luminous mysteries toggle */ /* Luminous mysteries toggle */
.luminous-toggle { .luminous-toggle {
text-align: center; display: flex;
justify-content: center;
margin-bottom: 2rem; margin-bottom: 2rem;
padding: 1rem; max-width: 1200px;
background: var(--nord1);
border-radius: 8px;
max-width: 600px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
@media(prefers-color-scheme: light) {
.luminous-toggle {
background: var(--nord5);
}
}
.luminous-toggle label { .luminous-toggle label {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; gap: 0.75rem;
gap: 1rem;
cursor: pointer; cursor: pointer;
font-size: 1.1rem; font-size: 0.95rem;
color: var(--nord4); color: var(--nord4);
} }
@media(prefers-color-scheme: light) { @media(prefers-color-scheme: light) {
.luminous-toggle label { .luminous-toggle label {
color: var(--nord0); color: var(--nord2);
} }
} }
@@ -845,6 +858,7 @@ h1 {
transition: background 0.3s ease; transition: background 0.3s ease;
outline: none; outline: none;
border: none; border: none;
flex-shrink: 0;
} }
@media(prefers-color-scheme: light) { @media(prefers-color-scheme: light) {
@@ -874,34 +888,43 @@ h1 {
transform: translateX(20px); transform: translateX(20px);
} }
.luminous-toggle .toggle-description {
margin-top: 1rem;
font-size: 0.95rem;
color: var(--nord8);
line-height: 1.6;
text-align: center;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
@media(prefers-color-scheme: light) {
.luminous-toggle .toggle-description {
color: var(--nord3);
}
}
/* Mystery selector grid */ /* Mystery selector grid */
.mystery-selector { .mystery-selector {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(3, 1fr);
gap: 1.5rem; gap: 1.5rem;
margin-bottom: 3rem; margin-bottom: 3rem;
max-width: 1000px; max-width: 750px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.mystery-selector.four-mysteries {
grid-template-columns: repeat(2, 1fr);
max-width: 500px;
}
@media (min-width: 1024px) {
.mystery-selector.four-mysteries {
grid-template-columns: repeat(4, 1fr);
max-width: 900px;
}
}
@media (max-width: 768px) {
.mystery-selector:not(.four-mysteries) {
grid-template-columns: 1fr;
max-width: 400px;
}
}
@media (max-width: 500px) {
.mystery-selector.four-mysteries {
grid-template-columns: 1fr;
max-width: 400px;
}
}
.mystery-button { .mystery-button {
background: var(--nord1); background: var(--nord1);
border: 2px solid transparent; border: 2px solid transparent;
@@ -914,6 +937,7 @@ h1 {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
position: relative;
} }
@media(prefers-color-scheme: light) { @media(prefers-color-scheme: light) {
@@ -929,20 +953,18 @@ h1 {
.mystery-button.selected { .mystery-button.selected {
border-color: var(--nord10); border-color: var(--nord10);
background: var(--nord2); transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
} }
@media(prefers-color-scheme: light) { .mystery-button:nth-child(1):hover,
.mystery-button.selected { .mystery-button:nth-child(1).selected { background: var(--nord15); }
border-color: var(--nord10); .mystery-button:nth-child(2):hover,
background: var(--nord5); .mystery-button:nth-child(2).selected { background: var(--nord13); }
} .mystery-button:nth-child(3):hover,
} .mystery-button:nth-child(3).selected { background: var(--nord14); }
.mystery-button:nth-child(4):hover,
.mystery-button:nth-child(1):hover { background: var(--nord15); } .mystery-button:nth-child(4).selected { background: var(--nord12); }
.mystery-button:nth-child(2):hover { background: var(--nord13); }
.mystery-button:nth-child(3):hover { background: var(--nord14); }
.mystery-button:nth-child(4):hover { background: var(--nord12); }
.mystery-button svg { .mystery-button svg {
width: 80px; width: 80px;
@@ -978,44 +1000,126 @@ h1 {
font-weight: 700; font-weight: 700;
} }
/* Today's mystery badge */
.today-badge {
position: absolute;
top: 1rem;
right: 1rem;
background: var(--nord11);
color: white;
padding: 0.4rem 0.8rem;
border-radius: 4px;
font-size: 0.85rem;
font-weight: 600;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
z-index: 10;
}
/* Highlighted bead (orange for counting) */ /* Highlighted bead (orange for counting) */
.rosary-visualization :global(.counted-bead) { .rosary-visualization :global(.counted-bead) {
fill: var(--nord13) !important; fill: var(--nord13) !important;
filter: drop-shadow(0 0 8px var(--nord13)); filter: drop-shadow(0 0 8px var(--nord13));
} }
/* Mystery description styling */
.mystery-description {
margin: 1.5rem 0 1.5rem 0;
display: flex;
flex-direction: column;
gap: 0.75rem;
align-items: center;
}
.mystery-title {
font-weight: 700;
color: var(--nord10);
font-size: 1.1rem;
text-align: center;
}
.decade-buttons {
display: flex;
flex-direction: row;
gap: 1rem;
justify-content: flex-end;
align-items: center;
margin-top: 1.5rem;
}
.bible-reference-text {
color: var(--nord8);
font-size: 0.9rem;
font-weight: 600;
}
@media(prefers-color-scheme: light) {
.bible-reference-text {
color: var(--nord10);
}
}
.bible-reference-button {
background: var(--nord3);
border: 2px solid var(--nord2);
color: var(--nord6);
font-size: 1.2rem;
cursor: pointer;
padding: 0;
width: 3rem;
height: 3rem;
border-radius: 50%;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.bible-reference-button:hover {
background: var(--nord8);
border-color: var(--nord9);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.bible-reference-button:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
@media(prefers-color-scheme: light) {
.bible-reference-button {
background: var(--nord5);
border-color: var(--nord4);
color: var(--nord0);
}
.bible-reference-button:hover {
background: var(--nord4);
border-color: var(--nord3);
}
}
</style> </style>
<svelte:head> <svelte:head>
<title>Rosenkranz - Interaktiv</title> <title>Interaktiver Rosenkranz</title>
<meta name="description" content="Interaktive digitale Version des Rosenkranzes zum Mitbeten. Scrolle durch die Gebete und folge der Visualisierung."> <meta name="description" content="Interaktive digitale Version des Rosenkranzes zum Mitbeten. Scrolle durch die Gebete und folge der Visualisierung.">
</svelte:head> </svelte:head>
<div class="page-container"> <div class="page-container">
<h1>Interaktiver Rosenkranz</h1> <h1>Interaktiver Rosenkranz</h1>
<!-- Luminous Mysteries Toggle -->
<div class="luminous-toggle">
<label>
<input type="checkbox" bind:checked={includeLuminous} on:change={handleToggleChange} />
<span>Lichtreiche Geheimnisse einbeziehen</span>
</label>
<p class="toggle-description">
Die Geheimnisse werden automatisch nach dem Wochenplan ausgewählt.
{#if includeLuminous}
Mit lichtreichen Geheimnissen: Do=Lichtreich, andere Tage folgen dem traditionellen Plan.
{:else}
Traditioneller Plan ohne lichtreiche Geheimnisse.
{/if}
Sie können jederzeit manuell ein anderes Geheimnis wählen.
</p>
</div>
<h2 style="text-align:center;">Geheimnisse</h2>
<!-- Mystery Selector --> <!-- Mystery Selector -->
<div class="mystery-selector"> <div class="mystery-selector" class:four-mysteries={includeLuminous}>
<button <button
class="mystery-button" class="mystery-button"
class:selected={selectedMystery === 'freudenreich'} class:selected={selectedMystery === 'freudenreich'}
on:click={() => selectMystery('freudenreich')} on:click={() => selectMystery('freudenreich')}
> >
{#if todaysMystery === 'freudenreich'}
<span class="today-badge">Heutige</span>
{/if}
<svg viewBox="-10 0 2058 2048"> <svg viewBox="-10 0 2058 2048">
<path d="M1935 90q0 32 -38 91q-21 29 -56 90q-20 55 -63 164q-35 86 -95 143q-22 -21 -43 -45q51 -49 85 -139q49 -130 61 -152q-126 48 -152 63q-76 46 -95 128q-27 -18 -58 -25q28 -104 97 -149q31 -20 138 -52q90 -28 137 -74l29 -39q22 -30 32 -30q21 0 21 26zM1714 653 q-90 30 -113 43q-65 36 -65 90q0 19 20 119q23 116 23 247q0 169 -103 299q-111 141 -275 141q-254 0 -283 87q-16 104 -31 207q-27 162 -76 162q-21 0 -41 -20q-16 -19 -32 -37q-10 3 -33 22q-18 15 -39 15q-28 0 -50 -44.5t-30 -44.5q-10 0 -35.5 11.5t-41.5 11.5 q-47 0 -58.5 -45.5t-21.5 -45.5t-29.5 2.5t-29.5 2.5q-46 0 -46 -30q0 -16 14 -44.5t14 -44.5q0 -8 -46.5 -25.5t-46.5 -48.5q0 -34 35.5 -52t99.5 -31q91 -19 103 -22q113 -32 171 -93q37 -39 105 -165q34 -64 43 -82q26 -53 31 -85q-129 -67 -224 -76q-33 0 -96 -11 q-36 -13 -36 -41q0 -7 2 -19.5t2 -19.5q0 -20 -67.5 -42t-67.5 -64q0 -11 8.5 -30t8.5 -30q0 -15 -79 -39t-79 -63q0 -16 9 -45t9 -45q0 -20 -29 -43q-23 -17 -46 -33q-49 -44 -49 -215q0 -8 1 -15q91 53 194 68l282 16q202 12 304 59q143 65 143 210q0 15 -2 44t-2 44 q0 122 78 122q73 0 108 -133q16 -70 32 -139q21 -81 57 -119q46 -51 130 -51q71 0 122 61q90 107 154 149zM1597 636q-25 -22 -77 -91q-30 -40 -75 -40q-91 0 -131 115q-30 106 -59 213q-44 115 -144 115q-146 0 -146 -180q0 -16 2.5 -46.5t2.5 -46.5q0 -62 -19 -87 q-70 -92 -303 -115q-173 -9 -347 -18q-55 -6 -116 -30v34q0 27 57.5 73.5t57.5 91.5q0 16 -10.5 45t-10.5 44q1 1 7 1q3 0 7 1q146 36 146 105q0 13 -8.5 32.5t-8.5 27.5h10q5 0 9 1q61 15 86 36q32 28 28 85q173 15 372 107q-7 77 -80 215q-67 128 -127 195 q-67 74 -169 104q-96 24 -193 47q-10 3 -29 13q86 18 86 70q0 19 -19 62q15 -5 33 -5q42 0 59 26q8 11 22 61l-1 3q10 0 34.5 -11.5t42.5 -11.5q55 0 88 84q38 -32 64 -32q37 0 66 41q25 -53 33 -151q10 -112 23 -154q43 -136 337 -136q116 0 215 -108q105 -114 105 -277 q0 -23 -12 -112l-28 -207q-4 -30 -4 -42q0 -97 124 -147zM1506 605q0 38 -38 38q-39 0 -39 -38t39 -38q38 0 38 38z" /> <path d="M1935 90q0 32 -38 91q-21 29 -56 90q-20 55 -63 164q-35 86 -95 143q-22 -21 -43 -45q51 -49 85 -139q49 -130 61 -152q-126 48 -152 63q-76 46 -95 128q-27 -18 -58 -25q28 -104 97 -149q31 -20 138 -52q90 -28 137 -74l29 -39q22 -30 32 -30q21 0 21 26zM1714 653 q-90 30 -113 43q-65 36 -65 90q0 19 20 119q23 116 23 247q0 169 -103 299q-111 141 -275 141q-254 0 -283 87q-16 104 -31 207q-27 162 -76 162q-21 0 -41 -20q-16 -19 -32 -37q-10 3 -33 22q-18 15 -39 15q-28 0 -50 -44.5t-30 -44.5q-10 0 -35.5 11.5t-41.5 11.5 q-47 0 -58.5 -45.5t-21.5 -45.5t-29.5 2.5t-29.5 2.5q-46 0 -46 -30q0 -16 14 -44.5t14 -44.5q0 -8 -46.5 -25.5t-46.5 -48.5q0 -34 35.5 -52t99.5 -31q91 -19 103 -22q113 -32 171 -93q37 -39 105 -165q34 -64 43 -82q26 -53 31 -85q-129 -67 -224 -76q-33 0 -96 -11 q-36 -13 -36 -41q0 -7 2 -19.5t2 -19.5q0 -20 -67.5 -42t-67.5 -64q0 -11 8.5 -30t8.5 -30q0 -15 -79 -39t-79 -63q0 -16 9 -45t9 -45q0 -20 -29 -43q-23 -17 -46 -33q-49 -44 -49 -215q0 -8 1 -15q91 53 194 68l282 16q202 12 304 59q143 65 143 210q0 15 -2 44t-2 44 q0 122 78 122q73 0 108 -133q16 -70 32 -139q21 -81 57 -119q46 -51 130 -51q71 0 122 61q90 107 154 149zM1597 636q-25 -22 -77 -91q-30 -40 -75 -40q-91 0 -131 115q-30 106 -59 213q-44 115 -144 115q-146 0 -146 -180q0 -16 2.5 -46.5t2.5 -46.5q0 -62 -19 -87 q-70 -92 -303 -115q-173 -9 -347 -18q-55 -6 -116 -30v34q0 27 57.5 73.5t57.5 91.5q0 16 -10.5 45t-10.5 44q1 1 7 1q3 0 7 1q146 36 146 105q0 13 -8.5 32.5t-8.5 27.5h10q5 0 9 1q61 15 86 36q32 28 28 85q173 15 372 107q-7 77 -80 215q-67 128 -127 195 q-67 74 -169 104q-96 24 -193 47q-10 3 -29 13q86 18 86 70q0 19 -19 62q15 -5 33 -5q42 0 59 26q8 11 22 61l-1 3q10 0 34.5 -11.5t42.5 -11.5q55 0 88 84q38 -32 64 -32q37 0 66 41q25 -53 33 -151q10 -112 23 -154q43 -136 337 -136q116 0 215 -108q105 -114 105 -277 q0 -23 -12 -112l-28 -207q-4 -30 -4 -42q0 -97 124 -147zM1506 605q0 38 -38 38q-39 0 -39 -38t39 -38q38 0 38 38z" />
<path d="m 1724.44,1054.6641 c -31.1769,-18 -37.7653,-42.5884 -19.7653,-73.76528 5.3333,-9.2376 12.354,-16.7312 21.0621,-22.4808 6.2201,-4.1068 44.7886,-7.2427 115.7055,-9.4077 70.9168,-2.1649 110.128,-1.0807 117.6336,3.2526 30.0222,17.3334 35.5333,42.45448 16.5333,75.36348 -7.3333,12.7017 -16.1754,20.6833 -26.5263,23.9448 -24.5645,1.2137 -56.7805,3.0135 -96.648,5.3994 -72.6282,5.7957 -115.2931,5.0269 -127.9949,-2.3065 z" /> <path d="m 1724.44,1054.6641 c -31.1769,-18 -37.7653,-42.5884 -19.7653,-73.76528 5.3333,-9.2376 12.354,-16.7312 21.0621,-22.4808 6.2201,-4.1068 44.7886,-7.2427 115.7055,-9.4077 70.9168,-2.1649 110.128,-1.0807 117.6336,3.2526 30.0222,17.3334 35.5333,42.45448 16.5333,75.36348 -7.3333,12.7017 -16.1754,20.6833 -26.5263,23.9448 -24.5645,1.2137 -56.7805,3.0135 -96.648,5.3994 -72.6282,5.7957 -115.2931,5.0269 -127.9949,-2.3065 z" />
@@ -1024,7 +1128,7 @@ h1 {
<path d="m 1184.6228,1956.284 c -4.807,-8.0003 -6.8298,-42.7561 -6.0684,-104.2674 0.7614,-61.5113 2.7093,-100.0139 5.8437,-115.508 3.1343,-15.4941 11.8445,-27.5329 26.1306,-36.117 30.2866,-18.198 54.7006,-11.868 73.242,18.99 5.4937,9.1432 8.145,43.3269 7.9537,102.5512 -0.081,52.9359 -1.4296,89.5231 -4.0464,109.7617 -2.276,16.9226 -11.1284,30.0192 -26.5575,39.29 -33.1439,19.9148 -58.643,15.0146 -76.4977,-14.7005 z" /> <path d="m 1184.6228,1956.284 c -4.807,-8.0003 -6.8298,-42.7561 -6.0684,-104.2674 0.7614,-61.5113 2.7093,-100.0139 5.8437,-115.508 3.1343,-15.4941 11.8445,-27.5329 26.1306,-36.117 30.2866,-18.198 54.7006,-11.868 73.242,18.99 5.4937,9.1432 8.145,43.3269 7.9537,102.5512 -0.081,52.9359 -1.4296,89.5231 -4.0464,109.7617 -2.276,16.9226 -11.1284,30.0192 -26.5575,39.29 -33.1439,19.9148 -58.643,15.0146 -76.4977,-14.7005 z" />
<path d="m 1773.3127,1737.6952 c -9.0153,-2.4157 -34.6139,-26.0118 -76.7955,-70.7882 -42.1816,-44.7764 -67.5266,-73.826 -76.035,-87.1489 -8.5084,-13.3228 -10.6057,-28.0334 -6.2922,-44.1323 9.145,-34.1293 31.1041,-46.5353 65.8774,-37.2179 10.3033,2.7609 35.9565,25.5088 76.9595,68.2441 36.7142,38.1352 61.1596,65.3907 73.3362,81.7668 10.1182,13.7541 12.8479,29.3245 8.1892,46.7113 -10.0077,37.3492 -31.7542,51.5375 -65.2396,42.5651 z" /> <path d="m 1773.3127,1737.6952 c -9.0153,-2.4157 -34.6139,-26.0118 -76.7955,-70.7882 -42.1816,-44.7764 -67.5266,-73.826 -76.035,-87.1489 -8.5084,-13.3228 -10.6057,-28.0334 -6.2922,-44.1323 9.145,-34.1293 31.1041,-46.5353 65.8774,-37.2179 10.3033,2.7609 35.9565,25.5088 76.9595,68.2441 36.7142,38.1352 61.1596,65.3907 73.3362,81.7668 10.1182,13.7541 12.8479,29.3245 8.1892,46.7113 -10.0077,37.3492 -31.7542,51.5375 -65.2396,42.5651 z" />
</svg> </svg>
<h3>Freudenreich</h3> <h3>Freudenreiche</h3>
</button> </button>
<button <button
@@ -1032,6 +1136,9 @@ h1 {
class:selected={selectedMystery === 'schmerzhaften'} class:selected={selectedMystery === 'schmerzhaften'}
on:click={() => selectMystery('schmerzhaften')} on:click={() => selectMystery('schmerzhaften')}
> >
{#if todaysMystery === 'schmerzhaften'}
<span class="today-badge">Heutige</span>
{/if}
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 512 512" ><path d="M255.094 24.875c-16.73 9.388-34.47 42.043-41.688 59.47-14.608-2.407-28.87-3.664-42.562-3.75-11.446-.074-22.49.68-33.03 2.218-16.34-8.284-34.766-29.065-42.626-50-9.324 15.704-9.558 42.313-5.782 64.593-19.443 9.72-35.107 23.633-45.53 41.688-7.262 12.577-11.5 26.34-12.97 40.875 13.294-25.904 35-46.957 65.656-54.345-34.99 31.783-59.85 87.186-51.5 129.406-1.2 22.87-9.48 37.647-24.75 44.595 16.335 4.59 35.497 3.343 49.438-1.28 24.94 34.82 60.818 67.882 105.063 94.342-6.952 17.613-16.677 49.21-16.47 66.032 10.846-13.178 37.433-40.585 61.72-42.783 23.656 10.27 47.35 17.698 70.312 22.313 12.423 17.25 12.895 38.867 7.375 53.594 16.402-9.2 33.82-33.187 39.938-48 47.1 1.423 88.046-10.534 114.718-35.563 17.536 5.52 30.744 15.707 39.813 30.5.243-19.578-8.05-44.353-18-60.31 13.42-28.268 12.786-61.81.5-96.158l.405.47c9.976-11.804 18.304-33.19 18.063-52.907-8.535 10.373-20.727 15.14-36.75 14.188-13.56-22.597-31.81-44.812-54.032-65.375 10.56-19.27 30.402-36.43 44.156-47.97-18.985-5.337-67.794 5.2-80.78 17.782l5.906 8.5c5.637 11.99 9.503 24.423 11.093 37.063-26.323-37.275-70.72-74.72-114.905-95.625-15.894-25.424-19.322-56.118-12.78-73.563zm-82.875 97.063c1.13-.015 2.258-.008 3.405 0 31.56.2 68.888 8.842 107 25.656-8.8 20.095-14.74 44.482-10 61.344 13.33-18.637 37.313-34.22 55.406-37.5 55.904 34.315 96.215 78.718 111.658 118.718l.093.22c16.088 37.88 13.36 85.186-26.56 117.312 4.79-11.41 7.986-23.828 9.5-36.438-14.078 10.012-33.524 15.304-56.314 15.97-1.954-17.242-9.117-52.874-22.28-65.72 1.565 16.122-8.11 46.272-26.22 61.063-31.916-6.495-66.794-19.67-101.03-39.438-9.538-5.506-18.65-11.307-27.314-17.344-3.444-23.614 7.842-53.562 20.563-64.03-18.967-.234-46.71 22.156-59.313 32.75-40.974-38.47-64.14-81.11-61.25-115 16.275-1.708 36.144.927 51.72 8-3.92-15.382-18.553-31.733-34.407-44.344 14.757-13.826 37.7-20.852 65.344-21.22z"/></svg> <svg viewBox="0 0 512 512" ><path d="M255.094 24.875c-16.73 9.388-34.47 42.043-41.688 59.47-14.608-2.407-28.87-3.664-42.562-3.75-11.446-.074-22.49.68-33.03 2.218-16.34-8.284-34.766-29.065-42.626-50-9.324 15.704-9.558 42.313-5.782 64.593-19.443 9.72-35.107 23.633-45.53 41.688-7.262 12.577-11.5 26.34-12.97 40.875 13.294-25.904 35-46.957 65.656-54.345-34.99 31.783-59.85 87.186-51.5 129.406-1.2 22.87-9.48 37.647-24.75 44.595 16.335 4.59 35.497 3.343 49.438-1.28 24.94 34.82 60.818 67.882 105.063 94.342-6.952 17.613-16.677 49.21-16.47 66.032 10.846-13.178 37.433-40.585 61.72-42.783 23.656 10.27 47.35 17.698 70.312 22.313 12.423 17.25 12.895 38.867 7.375 53.594 16.402-9.2 33.82-33.187 39.938-48 47.1 1.423 88.046-10.534 114.718-35.563 17.536 5.52 30.744 15.707 39.813 30.5.243-19.578-8.05-44.353-18-60.31 13.42-28.268 12.786-61.81.5-96.158l.405.47c9.976-11.804 18.304-33.19 18.063-52.907-8.535 10.373-20.727 15.14-36.75 14.188-13.56-22.597-31.81-44.812-54.032-65.375 10.56-19.27 30.402-36.43 44.156-47.97-18.985-5.337-67.794 5.2-80.78 17.782l5.906 8.5c5.637 11.99 9.503 24.423 11.093 37.063-26.323-37.275-70.72-74.72-114.905-95.625-15.894-25.424-19.322-56.118-12.78-73.563zm-82.875 97.063c1.13-.015 2.258-.008 3.405 0 31.56.2 68.888 8.842 107 25.656-8.8 20.095-14.74 44.482-10 61.344 13.33-18.637 37.313-34.22 55.406-37.5 55.904 34.315 96.215 78.718 111.658 118.718l.093.22c16.088 37.88 13.36 85.186-26.56 117.312 4.79-11.41 7.986-23.828 9.5-36.438-14.078 10.012-33.524 15.304-56.314 15.97-1.954-17.242-9.117-52.874-22.28-65.72 1.565 16.122-8.11 46.272-26.22 61.063-31.916-6.495-66.794-19.67-101.03-39.438-9.538-5.506-18.65-11.307-27.314-17.344-3.444-23.614 7.842-53.562 20.563-64.03-18.967-.234-46.71 22.156-59.313 32.75-40.974-38.47-64.14-81.11-61.25-115 16.275-1.708 36.144.927 51.72 8-3.92-15.382-18.553-31.733-34.407-44.344 14.757-13.826 37.7-20.852 65.344-21.22z"/></svg>
</svg> </svg>
@@ -1043,6 +1150,9 @@ h1 {
class:selected={selectedMystery === 'glorreichen'} class:selected={selectedMystery === 'glorreichen'}
on:click={() => selectMystery('glorreichen')} on:click={() => selectMystery('glorreichen')}
> >
{#if todaysMystery === 'glorreichen'}
<span class="today-badge">Heutige</span>
{/if}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-10 0 2060 2048"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-10 0 2060 2048">
<path <path
d="M1968 505l-119 632q101 61 101 163q0 149 -228 212q-171 47 -356 47h-682q-47 0 -111 -8q-210 -26 -293 -55q-180 -62 -180 -196q0 -124 101 -163l-119 -632h37q87 0 170 43q-18 85 -18 103q0 116 75 130q31 -47 77 -129l40 147q49 -37 95 -37t100 37q9 -38 31 -113 d="M1968 505l-119 632q101 61 101 163q0 149 -228 212q-171 47 -356 47h-682q-47 0 -111 -8q-210 -26 -293 -55q-180 -62 -180 -196q0 -124 101 -163l-119 -632h37q87 0 170 43q-18 85 -18 103q0 116 75 130q31 -47 77 -129l40 147q49 -37 95 -37t100 37q9 -38 31 -113
@@ -1063,6 +1173,9 @@ q0 -31 22 -54.5t52 -23.5q31 0 52.5 23.5t21.5 54.5zM596 888q0 34 -34 34q-30 0 -30
class:selected={selectedMystery === 'lichtreichen'} class:selected={selectedMystery === 'lichtreichen'}
on:click={() => selectMystery('lichtreichen')} on:click={() => selectMystery('lichtreichen')}
> >
{#if todaysMystery === 'lichtreichen'}
<span class="today-badge">Heutige</span>
{/if}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-10 0 2156 2048"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-10 0 2156 2048">
<path <path
d="M1668 383q0 14 -48.5 92.5t-64.5 96t-41 17.5q-53 0 -53 -54q0 -16 46 -92q41 -68 60 -92q16 -20 43 -20q58 0 58 52zM688 535q0 54 -54 54q-16 0 -30 -7q-10 -5 -66 -95.5t-56 -103.5q0 -52 57 -52q22 0 34 11q20 31 53 81q62 90 62 112zM2064 842q0 59 -56 100 d="M1668 383q0 14 -48.5 92.5t-64.5 96t-41 17.5q-53 0 -53 -54q0 -16 46 -92q41 -68 60 -92q16 -20 43 -20q58 0 58 52zM688 535q0 54 -54 54q-16 0 -30 -7q-10 -5 -66 -95.5t-56 -103.5q0 -52 57 -52q22 0 34 11q20 31 53 81q62 90 62 112zM2064 842q0 59 -56 100
@@ -1076,6 +1189,14 @@ l536 389l-209 -629zM1671 934l-370 267l150 436l-378 -271l-371 271q8 -34 15 -68q10
{/if} {/if}
</div> </div>
<!-- Luminous Mysteries Toggle -->
<div class="luminous-toggle">
<label>
<input type="checkbox" bind:checked={includeLuminous} on:change={handleToggleChange} />
<span>Lichtreiche Geheimnisse einbeziehen</span>
</label>
</div>
<div class="rosary-layout"> <div class="rosary-layout">
<!-- Sidebar: Rosary Visualization --> <!-- Sidebar: Rosary Visualization -->
<div class="rosary-sidebar"> <div class="rosary-sidebar">
@@ -1242,14 +1363,29 @@ l536 389l-209 -629zM1671 934l-370 267l150 436l-378 -271l-371 271q8 -34 15 -68q10
data-section="secret{decadeNum}" data-section="secret{decadeNum}"
> >
<h2>{decadeNum}. Gesätz: {currentMysteryTitles[decadeNum - 1]}</h2> <h2>{decadeNum}. Gesätz: {currentMysteryTitles[decadeNum - 1]}</h2>
<!-- Mystery description with Bible reference button -->
<h3>Ave Maria <span class="repeat-count">(10×)</span></h3> <h3>Ave Maria <span class="repeat-count">(10×)</span></h3>
<AveMaria <AveMaria
mysteryLatin={currentMysteriesLatin[decadeNum - 1]} mysteryLatin={currentMysteriesLatin[decadeNum - 1]}
mystery={currentMysteries[decadeNum - 1]} mystery={currentMysteries[decadeNum - 1]}
/> />
<!-- Counter button --> <!-- Bible reference and counter buttons -->
<CounterButton onClick={() => advanceDecade(decadeNum)} /> <div class="decade-buttons">
{#if currentMysteryDescriptions[decadeNum - 1]}
{@const description = currentMysteryDescriptions[decadeNum - 1]}
<span class="bible-reference-text">{description.reference}</span>
<button
class="bible-reference-button"
on:click={() => handleCitationClick(description.reference, description.title, description.verseData)}
aria-label="Bibelstelle anzeigen"
>
📖
</button>
{/if}
<CounterButton onClick={() => advanceDecade(decadeNum)} />
</div>
</div> </div>
<!-- Transition prayers (Gloria, Fatima, Paternoster) --> <!-- Transition prayers (Gloria, Fatima, Paternoster) -->
@@ -1388,7 +1524,7 @@ Der Plan ohne lichtreiche Geheimnisse ist wie folgt:
<h3>Die schmerzhaften Geheimnisse <i>(über das Leiden und Sterben Jesu)</i></h3> <h3>Die schmerzhaften Geheimnisse <i>(über das Leiden und Sterben Jesu)</i></h3>
<ol><!-- dolorosa --> <ol><!-- dolorosa -->
<li>... Jesus, der für uns Blut geschwitzt hat.</li> <li>... Jesus, der für uns Blut geschwitzt hat.</li>
<li>... Jesus, der für uns gegeißelt worden ist.</li> <li>... Jesus, der für uns gegeisselt worden ist.</li>
<li>... Jesus, der für uns mit Dornen gekrönt worden ist.</li> <li>... Jesus, der für uns mit Dornen gekrönt worden ist.</li>
<li>... Jesus, der für uns das schwere Kreuz getragen hat.</li> <li>... Jesus, der für uns das schwere Kreuz getragen hat.</li>
<li>... Jesus, der für uns gekreuzigt worden ist.</li> <li>... Jesus, der für uns gekreuzigt worden ist.</li>
@@ -1437,3 +1573,8 @@ Anders als die Geheimnisse in Deutsch ist es üblich beim beten des Rosenkranzes
</ol> </ol>
</div> </div>
</div> </div>
<!-- Bible citation modal -->
{#if showModal}
<BibleModal reference={selectedReference} title={selectedTitle} verseData={selectedVerseData} onClose={() => showModal = false} />
{/if}

View File

@@ -1,24 +0,0 @@
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';
import { dbConnect } from '$utils/db';
import { MarioKartTournament } from '$models/MarioKartTournament';
export const load: PageServerLoad = async () => {
try {
await dbConnect();
const tournaments = await MarioKartTournament.find()
.sort({ createdAt: -1 })
.lean({ flattenMaps: true });
// Convert MongoDB documents to plain objects for serialization
const serializedTournaments = JSON.parse(JSON.stringify(tournaments));
return {
tournaments: serializedTournaments
};
} catch (err) {
console.error('Error loading tournaments:', err);
throw error(500, 'Failed to load tournaments');
}
};

View File

@@ -1,569 +0,0 @@
<script>
import { goto } from '$app/navigation';
import { invalidateAll } from '$app/navigation';
let { data } = $props();
let tournaments = $state(data.tournaments);
let showCreateModal = $state(false);
let newTournamentName = $state('');
let roundsPerMatch = $state(3);
let matchSize = $state(2);
let loading = $state(false);
async function createTournament() {
if (!newTournamentName.trim()) {
alert('Please enter a tournament name');
return;
}
loading = true;
try {
const response = await fetch('/api/mario-kart/tournaments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: newTournamentName,
roundsPerMatch,
matchSize
})
});
if (response.ok) {
const data = await response.json();
showCreateModal = false;
newTournamentName = '';
goto(`/mario-kart/${data.tournament._id}`);
} else {
const error = await response.json();
alert(error.error || 'Failed to create tournament');
}
} catch (error) {
console.error('Failed to create tournament:', error);
alert('Failed to create tournament');
} finally {
loading = false;
}
}
async function deleteTournament(id, name) {
if (!confirm(`Are you sure you want to delete "${name}"?`)) {
return;
}
try {
const response = await fetch(`/api/mario-kart/tournaments/${id}`, {
method: 'DELETE'
});
if (response.ok) {
await invalidateAll();
} else {
const error = await response.json();
alert(error.error || 'Failed to delete tournament');
}
} catch (error) {
console.error('Failed to delete tournament:', error);
alert('Failed to delete tournament');
}
}
function getStatusBadge(status) {
const badges = {
setup: { text: 'Setup', class: 'badge-blue' },
group_stage: { text: 'Group Stage', class: 'badge-yellow' },
bracket: { text: 'Bracket', class: 'badge-purple' },
completed: { text: 'Completed', class: 'badge-green' }
};
return badges[status] || badges.setup;
}
function formatDate(dateString) {
return new Date(dateString).toLocaleDateString();
}
</script>
<div class="container">
<div class="header">
<div class="header-content">
<h1>Mario Kart Tournament Tracker</h1>
<p>Manage your company Mario Kart tournaments</p>
</div>
<button class="btn-primary" onclick={() => showCreateModal = true}>
Create Tournament
</button>
</div>
{#if tournaments.length === 0}
<div class="empty-state">
<div class="empty-icon">🏁</div>
<h2>No tournaments yet</h2>
<p>Create your first Mario Kart tournament to get started!</p>
<button class="btn-primary" onclick={() => showCreateModal = true}>
Create Your First Tournament
</button>
</div>
{:else}
<div class="tournaments-grid">
{#each tournaments as tournament}
<div class="tournament-card">
<div class="card-header">
<h3>{tournament.name}</h3>
<span class="badge {getStatusBadge(tournament.status).class}">
{getStatusBadge(tournament.status).text}
</span>
</div>
<div class="card-stats">
<div class="stat">
<span class="stat-icon">👥</span>
<span>{tournament.contestants.length} contestants</span>
</div>
{#if tournament.groups.length > 0}
<div class="stat">
<span class="stat-icon">🎮</span>
<span>{tournament.groups.length} groups</span>
</div>
{/if}
<div class="stat">
<span class="stat-icon">🔄</span>
<span>{tournament.roundsPerMatch} rounds/match</span>
</div>
</div>
<div class="card-footer">
<span class="date">Created {formatDate(tournament.createdAt)}</span>
<div class="actions">
<a href="/mario-kart/{tournament._id}" class="btn-view">View</a>
<button
class="btn-delete"
onclick={() => deleteTournament(tournament._id, tournament.name)}
>
Delete
</button>
</div>
</div>
</div>
{/each}
</div>
{/if}
</div>
{#if showCreateModal}
<div class="modal-overlay" onclick={() => showCreateModal = false}>
<div class="modal" onclick={(e) => e.stopPropagation()}>
<div class="modal-header">
<h2>Create New Tournament</h2>
<button class="close-btn" onclick={() => showCreateModal = false}>×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="tournament-name">Tournament Name</label>
<input
id="tournament-name"
type="text"
bind:value={newTournamentName}
placeholder="e.g., Company Championship 2024"
class="input"
/>
</div>
<div class="form-group">
<label for="rounds-per-match">Rounds per Match</label>
<input
id="rounds-per-match"
type="number"
bind:value={roundsPerMatch}
min="1"
max="10"
class="input"
/>
<small>How many races should each match have?</small>
</div>
<div class="form-group">
<label for="match-size">Match Size (Contestants per Match)</label>
<input
id="match-size"
type="number"
bind:value={matchSize}
min="2"
max="12"
class="input"
/>
<small>How many contestants compete simultaneously? (2 for 1v1, 4 for 4-player matches)</small>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick={() => showCreateModal = false}>
Cancel
</button>
<button
class="btn-primary"
onclick={createTournament}
disabled={loading}
>
{loading ? 'Creating...' : 'Create Tournament'}
</button>
</div>
</div>
</div>
{/if}
<style>
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.header-content h1 {
font-size: 2rem;
font-weight: 800;
color: #1f2937;
margin: 0 0 0.5rem 0;
}
.header-content p {
color: #6b7280;
margin: 0;
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
background: white;
border-radius: 1rem;
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
}
.empty-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
.empty-state h2 {
font-size: 1.5rem;
color: #1f2937;
margin-bottom: 0.5rem;
}
.empty-state p {
color: #6b7280;
margin-bottom: 2rem;
}
.tournaments-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.5rem;
}
.tournament-card {
background: white;
border-radius: 1rem;
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
padding: 1.5rem;
transition: transform 0.2s, box-shadow 0.2s;
}
.tournament-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 1rem;
gap: 1rem;
}
.card-header h3 {
font-size: 1.25rem;
font-weight: 600;
color: #1f2937;
margin: 0;
flex: 1;
}
.badge {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
}
.badge-blue {
background: #dbeafe;
color: #1e40af;
}
.badge-yellow {
background: #fef3c7;
color: #92400e;
}
.badge-purple {
background: #e9d5ff;
color: #6b21a8;
}
.badge-green {
background: #d1fae5;
color: #065f46;
}
.card-stats {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
padding: 1rem;
background: #f9fafb;
border-radius: 0.5rem;
}
.stat {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
}
.stat-icon {
font-size: 1.25rem;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
}
.date {
font-size: 0.875rem;
color: #6b7280;
}
.actions {
display: flex;
gap: 0.5rem;
}
.btn-primary {
background: #3b82f6;
color: white;
border: none;
padding: 0.625rem 1.25rem;
border-radius: 0.5rem;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.btn-primary:hover:not(:disabled) {
background: #2563eb;
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-secondary {
background: white;
color: #374151;
border: 1px solid #d1d5db;
padding: 0.625rem 1.25rem;
border-radius: 0.5rem;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.btn-secondary:hover {
background: #f9fafb;
}
.btn-view {
background: #10b981;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
text-decoration: none;
font-size: 0.875rem;
font-weight: 500;
transition: background 0.2s;
}
.btn-view:hover {
background: #059669;
}
.btn-delete {
background: #ef4444;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.btn-delete:hover {
background: #dc2626;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 50;
padding: 1rem;
}
.modal {
background: white;
border-radius: 1rem;
max-width: 500px;
width: 100%;
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
.modal-header h2 {
font-size: 1.5rem;
font-weight: 600;
color: #1f2937;
margin: 0;
}
.close-btn {
background: none;
border: none;
font-size: 2rem;
color: #9ca3af;
cursor: pointer;
line-height: 1;
padding: 0;
width: 2rem;
height: 2rem;
}
.close-btn:hover {
color: #4b5563;
}
.modal-body {
padding: 1.5rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-group label {
display: block;
font-weight: 500;
color: #374151;
margin-bottom: 0.5rem;
}
.input {
width: 100%;
padding: 0.625rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 1rem;
box-sizing: border-box;
}
.input:focus {
outline: none;
border-color: #3b82f6;
ring: 2px;
ring-color: rgba(59, 130, 246, 0.5);
}
.form-group small {
display: block;
color: #6b7280;
font-size: 0.875rem;
margin-top: 0.25rem;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding: 1.5rem;
border-top: 1px solid #e5e7eb;
}
@media (max-width: 768px) {
.container {
padding: 1rem;
}
.header {
flex-direction: column;
align-items: stretch;
}
.tournaments-grid {
grid-template-columns: 1fr;
}
.card-footer {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.actions {
justify-content: stretch;
}
.btn-view,
.btn-delete {
flex: 1;
text-align: center;
}
}
</style>

View File

@@ -1,43 +0,0 @@
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';
import { dbConnect } from '$utils/db';
import { MarioKartTournament } from '$models/MarioKartTournament';
export const load: PageServerLoad = async ({ params }) => {
try {
await dbConnect();
// Use lean with flattenMaps option to convert Map objects to plain objects
const tournament = await MarioKartTournament.findById(params.id).lean({ flattenMaps: true });
if (!tournament) {
throw error(404, 'Tournament not found');
}
console.log('=== SERVER LOAD DEBUG ===');
console.log('Raw tournament bracket:', tournament.bracket);
if (tournament.bracket?.rounds) {
console.log('First bracket round matches:', tournament.bracket.rounds[0]?.matches);
}
console.log('=== END SERVER LOAD DEBUG ===');
// Convert _id and other MongoDB ObjectIds to strings for serialization
const serializedTournament = JSON.parse(JSON.stringify(tournament));
console.log('=== SERIALIZED DEBUG ===');
if (serializedTournament.bracket?.rounds) {
console.log('Serialized first bracket round matches:', serializedTournament.bracket.rounds[0]?.matches);
}
console.log('=== END SERIALIZED DEBUG ===');
return {
tournament: serializedTournament
};
} catch (err: any) {
if (err.status === 404) {
throw err;
}
console.error('Error loading tournament:', err);
throw error(500, 'Failed to load tournament');
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -52,7 +52,7 @@ h1{
<p> <p>
Die Fensterprobe ist eine Methode um den optimalen Knetzustand eines Teiges zu bestimmen. Die Fensterprobe ist eine Methode um den optimalen Knetzustand eines Teiges zu bestimmen.
Dazu wird ein kleines, ca. Walnussgrosses Stück Teig zwischen den Fingern auseinandergezogen. Ist der Teig elastisch und reißt nicht bis der Teig so dünn ist, dass man leicht licht durchsehen kann, so ist der Teig optimal verknetet. Dazu wird ein kleines, ca. Walnussgrosses Stück Teig zwischen den Fingern auseinandergezogen. Ist der Teig elastisch und reisst nicht bis der Teig so dünn ist, dass man leicht licht durchsehen kann, so ist der Teig optimal verknetet.
</p> </p>
<p> <p>
Teig lässt sich leichter verkneten wenn er noch trockener ist. Daher lohnt es sich zunächst etwa 10% der Flüssigkeit zurückzuhalten und erst nach und nach zuzugeben nachdem der Teig bereits für einige Minuten geknetet wurde. Teig lässt sich leichter verkneten wenn er noch trockener ist. Daher lohnt es sich zunächst etwa 10% der Flüssigkeit zurückzuhalten und erst nach und nach zuzugeben nachdem der Teig bereits für einige Minuten geknetet wurde.