Compare commits
9 Commits
21f130e280
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
4c36900314
|
|||
|
80186fe737
|
|||
|
50eaf2787d
|
|||
|
7e3cbb0397
|
|||
|
b788a615ba
|
|||
|
2be2e1977b
|
|||
|
01dd736bbc
|
|||
|
48e89305e0
|
|||
|
f4d6f195b3
|
23
CLAUDE.md
Normal file
23
CLAUDE.md
Normal file
@@ -0,0 +1,23 @@
|
||||
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
|
||||
|
||||
## Available MCP Tools:
|
||||
|
||||
### 1. list-sections
|
||||
|
||||
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
|
||||
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
|
||||
|
||||
### 2. get-documentation
|
||||
|
||||
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
|
||||
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
|
||||
|
||||
### 3. svelte-autofixer
|
||||
|
||||
Analyzes Svelte code and returns issues and suggestions.
|
||||
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
|
||||
|
||||
### 4. playground-link
|
||||
|
||||
Generates a Svelte Playground link with the provided code.
|
||||
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
|
||||
@@ -44,5 +44,10 @@
|
||||
"mongoose": "^8.0.0",
|
||||
"node-cron": "^4.2.1",
|
||||
"sharp": "^0.33.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ async function authorization({ event, resolve }) {
|
||||
// Bible verse functionality for error pages
|
||||
async function getRandomVerse(fetch: typeof globalThis.fetch): Promise<any> {
|
||||
try {
|
||||
const response = await fetch('/api/bible-quote');
|
||||
const response = await fetch('/api/glaube/bibel/zufallszitat');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
259
src/lib/components/BibleModal.svelte
Normal file
259
src/lib/components/BibleModal.svelte
Normal 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>
|
||||
@@ -10,9 +10,6 @@ export let onClick;
|
||||
|
||||
<style>
|
||||
.counter-button {
|
||||
position: absolute;
|
||||
bottom: 0.5rem;
|
||||
right: 0.5rem;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 50%;
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<v lang="la">Et resurréxit tértia die,</v>
|
||||
<v lang="de">Er ist auferstanden am dritten Tage,</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="de">Er ist aufgefahren in den Himmel</v>
|
||||
<v lang="la">sedet ad déxteram Patris.</v>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<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="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="de">Herr und Gott, König des Himmels,</v>
|
||||
<v lang="la">Deus Pater omnípotens.</v>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<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="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">pervagántur in múndo,</v>
|
||||
<v lang="de">die in der Welt umhergehen,</v>
|
||||
|
||||
24
src/lib/components/prayers/RosaryFinalPrayer.svelte
Normal file
24
src/lib/components/prayers/RosaryFinalPrayer.svelte
Normal file
@@ -0,0 +1,24 @@
|
||||
<p>
|
||||
<v lang="la">Orémus:</v>
|
||||
<v lang="de">Lasset uns beten:</v>
|
||||
</p>
|
||||
<p>
|
||||
<v lang="la">Déus, cújus Unigénitus,</v>
|
||||
<v lang="de">O Gott, dessen eingeborner Sohn</v>
|
||||
<v lang="la">pér vítam, mórtem ét resurrectiónem súam</v>
|
||||
<v lang="de">durch sein Leben, seinen Tod und seine Auferstehung</v>
|
||||
<v lang="la">nóbis salútis ætérnæ præmia comparávit:</v>
|
||||
<v lang="de">uns die Belohnung des ewigen Lebens verdient hat,</v>
|
||||
<v lang="la">concéde, quæsumus;</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="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="de">was sie enthalten nachahmen</v>
|
||||
<v lang="la">ét quód promíttunt, assequámur.</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="de">Durch unsern Herrn Jesus Christus.</v>
|
||||
<v lang="la">Ámen.</v>
|
||||
<v lang="de">Amen.</v>
|
||||
</p>
|
||||
@@ -1,11 +1,11 @@
|
||||
<p>
|
||||
<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="de">Mutter der Barmherzigkeit,</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">und unsere Hoffnung, sei gegrüßt!</v>
|
||||
<v lang="de">und unsere Hoffnung, sei gegrüsst!</v>
|
||||
</p>
|
||||
<p>
|
||||
<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="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="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>
|
||||
|
||||
107
src/lib/data/mysteryDescriptions.ts
Normal file
107
src/lib/data/mysteryDescriptions.ts
Normal 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;
|
||||
@@ -77,7 +77,6 @@ export const GET: RequestHandler = async ({ url, locals }) => {
|
||||
];
|
||||
|
||||
const results = await Payment.aggregate(pipeline);
|
||||
console.log('Aggregation results:', results);
|
||||
|
||||
// Transform data into chart-friendly format
|
||||
const monthsMap = new Map();
|
||||
|
||||
@@ -18,11 +18,10 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
}
|
||||
|
||||
await dbConnect();
|
||||
|
||||
|
||||
try {
|
||||
const now = new Date();
|
||||
console.log(`[Cron] Starting recurring payments processing at ${now.toISOString()}`);
|
||||
|
||||
|
||||
// Find all active recurring payments that are due
|
||||
const duePayments = await RecurringPayment.find({
|
||||
isActive: true,
|
||||
@@ -34,16 +33,12 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
]
|
||||
});
|
||||
|
||||
console.log(`[Cron] Found ${duePayments.length} due recurring payments`);
|
||||
|
||||
const results = [];
|
||||
let successCount = 0;
|
||||
let failureCount = 0;
|
||||
|
||||
for (const recurringPayment of duePayments) {
|
||||
try {
|
||||
console.log(`[Cron] Processing recurring payment: ${recurringPayment.title} (${recurringPayment._id})`);
|
||||
|
||||
// Create the payment
|
||||
const payment = await Payment.create({
|
||||
title: `${recurringPayment.title} (Auto)`,
|
||||
@@ -89,8 +84,6 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
success: true
|
||||
});
|
||||
|
||||
console.log(`[Cron] Successfully processed: ${recurringPayment.title}, next execution: ${nextExecutionDate.toISOString()}`);
|
||||
|
||||
} catch (paymentError) {
|
||||
console.error(`[Cron] Error processing recurring payment ${recurringPayment._id}:`, paymentError);
|
||||
failureCount++;
|
||||
@@ -104,8 +97,6 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Cron] Completed processing. Success: ${successCount}, Failures: ${failureCount}`);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
timestamp: now.toISOString(),
|
||||
|
||||
@@ -33,7 +33,6 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
|
||||
switch (action) {
|
||||
case 'execute':
|
||||
console.log(`[API] Manual execution requested by ${auth.user.nickname}`);
|
||||
await recurringPaymentScheduler.executeNow();
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
122
src/routes/api/glaube/bibel/[reference]/+server.ts
Normal file
122
src/routes/api/glaube/bibel/[reference]/+server.ts
Normal 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');
|
||||
}
|
||||
};
|
||||
@@ -4,8 +4,8 @@ import type { RequestHandler } from './$types';
|
||||
interface BibleVerse {
|
||||
bookName: string;
|
||||
abbreviation: string;
|
||||
bookNumber: number;
|
||||
chapter: number;
|
||||
verse: number;
|
||||
verseNumber: number;
|
||||
text: string;
|
||||
}
|
||||
@@ -25,14 +25,14 @@ async function loadVerses(fetch: typeof globalThis.fetch): Promise<BibleVerse[]>
|
||||
}
|
||||
const content = await response.text();
|
||||
const lines = content.trim().split('\n');
|
||||
|
||||
|
||||
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 {
|
||||
bookName,
|
||||
abbreviation,
|
||||
abbreviation,
|
||||
bookNumber: parseInt(bookNumber),
|
||||
chapter: parseInt(chapter),
|
||||
verse: parseInt(verse),
|
||||
verseNumber: parseInt(verseNumber),
|
||||
text
|
||||
};
|
||||
@@ -58,7 +58,7 @@ export const GET: RequestHandler = async ({ fetch }) => {
|
||||
try {
|
||||
const verses = await loadVerses(fetch);
|
||||
const randomVerse = getRandomVerse(verses);
|
||||
|
||||
|
||||
return json({
|
||||
text: randomVerse.text,
|
||||
reference: formatVerse(randomVerse),
|
||||
@@ -70,4 +70,4 @@ export const GET: RequestHandler = async ({ fetch }) => {
|
||||
console.error('Error fetching random Bible verse:', err);
|
||||
return error(500, 'Failed to fetch Bible verse');
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -74,7 +74,7 @@ h1{
|
||||
</Gebet>
|
||||
|
||||
<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 />
|
||||
</Gebet>
|
||||
|
||||
|
||||
@@ -74,16 +74,16 @@ h4{
|
||||
<h2>Der Osterfestkreis</h2>
|
||||
<div class=schott>
|
||||
<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.
|
||||
</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.
|
||||
Durch Kampf zum Sieg, durch Sterben zum Leben, zur Auferstehung, zur Vollherrschaft Christi und schließlich 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.
|
||||
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ässt im ewigen Ostern des Himmels.
|
||||
</p>
|
||||
<p>
|
||||
Der Osterfestkreis umfaßt drei Abschnitte:
|
||||
Der Osterfestkreis umfasst drei Abschnitte:
|
||||
die Zeit der Vorbereitung: Vorfasten- und Fastenzeit;
|
||||
die eigentliche Festzeit: Oster- und Pfingstfest;
|
||||
endlich die Zeit nach Pfingsten
|
||||
@@ -94,15 +94,15 @@ endlich die Zeit nach Pfingsten
|
||||
<h3> 1. Die Vorfastenzeit</h3>
|
||||
<div class=schott >
|
||||
<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.
|
||||
</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).
|
||||
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;
|
||||
so muß sich auch unser Leben, soll es dem seinen nachgebildet werden, auf Kämpfe, selbst auf ein geistiges Sterben gefaßt machen;
|
||||
Für den göttlichen Heiland bedeutet das öffentliche Wirken Mühsal und Leiden und schliesslich den Tod;
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
@@ -123,14 +123,14 @@ endlich die Zeit nach Pfingsten
|
||||
<h3> Epistel </h3>
|
||||
<div class="epistel bibel">
|
||||
<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>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>
|
||||
<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 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>aber an der Mehrzahl von ihnen hatte Gott kein Wohlgefallen; denn sie wurden niedergestreckt in der Wüste.</li>
|
||||
</ol>
|
||||
@@ -141,14 +141,14 @@ endlich die Zeit nach Pfingsten
|
||||
<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>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>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>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>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 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>
|
||||
@@ -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.
|
||||
</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.
|
||||
</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.
|
||||
</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>Müß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>Mü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.
|
||||
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>
|
||||
@@ -208,10 +208,10 @@ endlich die Zeit nach Pfingsten
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Müßiggang hat ihn besiegt und der Gedanke, dass er nichts mehr zu erobern hatte.
|
||||
Mü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, 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>
|
||||
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>
|
||||
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.
|
||||
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.
|
||||
Das Volk will murren, beklagen. Sie wollen nicht kämpfen.
|
||||
</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:
|
||||
<div class=bibel>
|
||||
<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>
|
||||
</ol>
|
||||
</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>
|
||||
<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=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>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
64
src/routes/glaube/rosenkranz/+page.server.ts
Normal file
64
src/routes/glaube/rosenkranz/+page.server.ts
Normal 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
|
||||
};
|
||||
};
|
||||
@@ -9,8 +9,12 @@ import AveMaria from "$lib/components/prayers/AveMaria.svelte";
|
||||
import GloriaPatri from "$lib/components/prayers/GloriaPatri.svelte";
|
||||
import FatimaGebet from "$lib/components/prayers/FatimaGebet.svelte";
|
||||
import SalveRegina from "$lib/components/prayers/SalveRegina.svelte";
|
||||
import RosaryFinalPrayer from "$lib/components/prayers/RosaryFinalPrayer.svelte";
|
||||
import BenedictusMedal from "$lib/components/BenedictusMedal.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
|
||||
const mysteries = {
|
||||
@@ -23,7 +27,7 @@ const mysteries = {
|
||||
],
|
||||
schmerzhaften: [
|
||||
"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 das schwere Kreuz getragen hat.",
|
||||
"Jesus, der für uns gekreuzigt worden ist."
|
||||
@@ -61,7 +65,7 @@ const mysteriesLatin = {
|
||||
],
|
||||
glorreichen: [
|
||||
"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 te, virgo, in cælum assúmpsit.",
|
||||
"Jesus, qui te, virgo, in cælo coronávit."
|
||||
@@ -75,6 +79,38 @@ const mysteriesLatin = {
|
||||
]
|
||||
};
|
||||
|
||||
// Short titles for mysteries (for display in headings)
|
||||
const mysteryTitles = {
|
||||
freudenreich: [
|
||||
"Verkündigung",
|
||||
"Heimsuchung",
|
||||
"Geburt",
|
||||
"Darstellung",
|
||||
"Wiederfindung"
|
||||
],
|
||||
schmerzhaften: [
|
||||
"Todesangst",
|
||||
"Geisselung",
|
||||
"Dornenkrönung",
|
||||
"Kreuzweg",
|
||||
"Kreuzigung"
|
||||
],
|
||||
glorreichen: [
|
||||
"Auferstehung",
|
||||
"Himmelfahrt",
|
||||
"Geistsendung",
|
||||
"Aufnahme Mariens",
|
||||
"Krönung Mariens"
|
||||
],
|
||||
lichtreichen: [
|
||||
"Taufe",
|
||||
"Hochzeit zu Kana",
|
||||
"Verkündigung des Reiches",
|
||||
"Verklärung",
|
||||
"Einsetzung der Eucharistie"
|
||||
]
|
||||
};
|
||||
|
||||
// Toggle for including Luminous mysteries
|
||||
let includeLuminous = true;
|
||||
|
||||
@@ -111,20 +147,27 @@ function getMysteryForWeekday(date, includeLuminous) {
|
||||
|
||||
// Determine which mystery to use based on current weekday
|
||||
let selectedMystery = getMysteryForWeekday(new Date(), includeLuminous);
|
||||
let todaysMystery = selectedMystery; // Track today's auto-selected mystery
|
||||
let currentMysteries = mysteries[selectedMystery];
|
||||
let currentMysteriesLatin = mysteriesLatin[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 selectMystery(mysteryType) {
|
||||
selectedMystery = mysteryType;
|
||||
currentMysteries = mysteries[mysteryType];
|
||||
currentMysteriesLatin = mysteriesLatin[mysteryType];
|
||||
currentMysteryTitles = mysteryTitles[mysteryType];
|
||||
}
|
||||
|
||||
// Function to handle toggle change
|
||||
function handleToggleChange() {
|
||||
// Recalculate the default mystery for today
|
||||
const todaysMystery = getMysteryForWeekday(new Date(), includeLuminous);
|
||||
todaysMystery = getMysteryForWeekday(new Date(), includeLuminous);
|
||||
// Update to today's mystery
|
||||
selectMystery(todaysMystery);
|
||||
}
|
||||
@@ -143,6 +186,12 @@ let decadeCounters = {
|
||||
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 advanceDecade(decadeNum) {
|
||||
const key = `secret${decadeNum}`;
|
||||
@@ -176,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
|
||||
const sectionPositions = {
|
||||
cross: 35,
|
||||
@@ -627,12 +684,12 @@ onMount(() => {
|
||||
.prayer-section.decade {
|
||||
scroll-snap-align: start;
|
||||
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) {
|
||||
.prayer-section.decade {
|
||||
padding-bottom: 3.5rem; /* Adjusted for mobile padding */
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
.prayer-section {
|
||||
padding: 0.5rem;
|
||||
@@ -761,35 +818,26 @@ h1 {
|
||||
|
||||
/* Luminous mysteries toggle */
|
||||
.luminous-toggle {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 2rem;
|
||||
padding: 1rem;
|
||||
background: var(--nord1);
|
||||
border-radius: 8px;
|
||||
max-width: 600px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
.luminous-toggle {
|
||||
background: var(--nord5);
|
||||
}
|
||||
}
|
||||
|
||||
.luminous-toggle label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem;
|
||||
cursor: pointer;
|
||||
font-size: 1.1rem;
|
||||
font-size: 0.95rem;
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
.luminous-toggle label {
|
||||
color: var(--nord0);
|
||||
color: var(--nord2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,6 +858,7 @@ h1 {
|
||||
transition: background 0.3s ease;
|
||||
outline: none;
|
||||
border: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
@@ -839,34 +888,43 @@ h1 {
|
||||
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 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
max-width: 1000px;
|
||||
max-width: 750px;
|
||||
margin-left: 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 {
|
||||
background: var(--nord1);
|
||||
border: 2px solid transparent;
|
||||
@@ -879,6 +937,7 @@ h1 {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: light) {
|
||||
@@ -894,20 +953,18 @@ h1 {
|
||||
|
||||
.mystery-button.selected {
|
||||
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.selected {
|
||||
border-color: var(--nord10);
|
||||
background: var(--nord5);
|
||||
}
|
||||
}
|
||||
|
||||
.mystery-button:nth-child(1):hover { background: var(--nord15); }
|
||||
.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:nth-child(1):hover,
|
||||
.mystery-button:nth-child(1).selected { background: var(--nord15); }
|
||||
.mystery-button:nth-child(2):hover,
|
||||
.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(4).selected { background: var(--nord12); }
|
||||
|
||||
.mystery-button svg {
|
||||
width: 80px;
|
||||
@@ -943,44 +1000,126 @@ h1 {
|
||||
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) */
|
||||
.rosary-visualization :global(.counted-bead) {
|
||||
fill: var(--nord13) !important;
|
||||
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>
|
||||
<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.">
|
||||
</svelte:head>
|
||||
|
||||
<div class="page-container">
|
||||
<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 -->
|
||||
<div class="mystery-selector">
|
||||
<div class="mystery-selector" class:four-mysteries={includeLuminous}>
|
||||
<button
|
||||
class="mystery-button"
|
||||
class:selected={selectedMystery === 'freudenreich'}
|
||||
on:click={() => selectMystery('freudenreich')}
|
||||
>
|
||||
{#if todaysMystery === 'freudenreich'}
|
||||
<span class="today-badge">Heutige</span>
|
||||
{/if}
|
||||
<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="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" />
|
||||
@@ -989,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 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>
|
||||
<h3>Freudenreich</h3>
|
||||
<h3>Freudenreiche</h3>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -997,6 +1136,9 @@ h1 {
|
||||
class:selected={selectedMystery === '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 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>
|
||||
@@ -1008,6 +1150,9 @@ h1 {
|
||||
class:selected={selectedMystery === '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">
|
||||
<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
|
||||
@@ -1028,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'}
|
||||
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">
|
||||
<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
|
||||
@@ -1041,6 +1189,14 @@ l536 389l-209 -629zM1671 934l-370 267l150 436l-378 -271l-371 271q8 -34 15 -68q10
|
||||
{/if}
|
||||
</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">
|
||||
<!-- Sidebar: Rosary Visualization -->
|
||||
<div class="rosary-sidebar">
|
||||
@@ -1153,7 +1309,7 @@ l536 389l-209 -629zM1671 934l-370 267l150 436l-378 -271l-371 271q8 -34 15 -68q10
|
||||
bind:this={sectionElements.start1}
|
||||
data-section="start1"
|
||||
>
|
||||
<h3>Ave Maria</h3>
|
||||
<h3>Ave Maria: Glaube</h3>
|
||||
<AveMaria
|
||||
mysteryLatin="Jesus, qui adáugeat nobis fidem"
|
||||
mystery="Jesus, der in uns den Glauben vermehre"
|
||||
@@ -1166,7 +1322,7 @@ l536 389l-209 -629zM1671 934l-370 267l150 436l-378 -271l-371 271q8 -34 15 -68q10
|
||||
bind:this={sectionElements.start2}
|
||||
data-section="start2"
|
||||
>
|
||||
<h3>Ave Maria</h3>
|
||||
<h3>Ave Maria: Hoffnung</h3>
|
||||
<AveMaria
|
||||
mysteryLatin="Jesus, qui corróboret nobis spem"
|
||||
mystery="Jesus, der in uns die Hoffnung stärke"
|
||||
@@ -1179,7 +1335,7 @@ l536 389l-209 -629zM1671 934l-370 267l150 436l-378 -271l-371 271q8 -34 15 -68q10
|
||||
bind:this={sectionElements.start3}
|
||||
data-section="start3"
|
||||
>
|
||||
<h3>Ave Maria</h3>
|
||||
<h3>Ave Maria: Liebe</h3>
|
||||
<AveMaria
|
||||
mysteryLatin="Jesus, qui perficiat in nobis caritátem"
|
||||
mystery="Jesus, der in uns die Liebe entzünde"
|
||||
@@ -1206,15 +1362,30 @@ l536 389l-209 -629zM1671 934l-370 267l150 436l-378 -271l-371 271q8 -34 15 -68q10
|
||||
bind:this={sectionElements[`secret${decadeNum}`]}
|
||||
data-section="secret{decadeNum}"
|
||||
>
|
||||
<h2>{decadeNum}. Gesätz</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>
|
||||
<AveMaria
|
||||
mysteryLatin={currentMysteriesLatin[decadeNum - 1]}
|
||||
mystery={currentMysteries[decadeNum - 1]}
|
||||
/>
|
||||
|
||||
<!-- Counter button -->
|
||||
<CounterButton onClick={() => advanceDecade(decadeNum)} />
|
||||
<!-- Bible reference and counter buttons -->
|
||||
<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>
|
||||
|
||||
<!-- Transition prayers (Gloria, Fatima, Paternoster) -->
|
||||
@@ -1252,6 +1423,11 @@ l536 389l-209 -629zM1671 934l-370 267l150 436l-378 -271l-371 271q8 -34 15 -68q10
|
||||
|
||||
<h3>Salve Regina</h3>
|
||||
<SalveRegina />
|
||||
|
||||
<h3>Schlussgebet</h3>
|
||||
<RosaryFinalPrayer />
|
||||
|
||||
<h3 style="text-align: center; font-size: 2.5rem; margin-top: 2rem;">♱</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1348,7 +1524,7 @@ Der Plan ohne lichtreiche Geheimnisse ist wie folgt:
|
||||
<h3>Die schmerzhaften Geheimnisse <i>(über das Leiden und Sterben Jesu)</i></h3>
|
||||
<ol><!-- dolorosa -->
|
||||
<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 das schwere Kreuz getragen hat.</li>
|
||||
<li>... Jesus, der für uns gekreuzigt worden ist.</li>
|
||||
@@ -1397,3 +1573,8 @@ Anders als die Geheimnisse in Deutsch ist es üblich beim beten des Rosenkranzes
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bible citation modal -->
|
||||
{#if showModal}
|
||||
<BibleModal reference={selectedReference} title={selectedTitle} verseData={selectedVerseData} onClose={() => showModal = false} />
|
||||
{/if}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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
@@ -52,7 +52,7 @@ h1{
|
||||
|
||||
<p>
|
||||
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>
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user