Compare commits

2 Commits

Author SHA1 Message Date
d9e5b96026 refactor: extract language toggle into reusable components
All checks were successful
CI / update (push) Successful in 27s
Create Toggle and LanguageToggle components to reduce code duplication
and enable shared state across pages.

- Add Toggle.svelte: Generic iOS-style toggle with customizable accent color
- Add LanguageToggle.svelte: Language-specific toggle with localStorage persistence
- Refactor rosary page to use new toggle components
- Add language toggle to gebete page
- Toggle state persists across both pages via localStorage
- Reduce min-height of Ave Maria decades in monolingual mode (50vh → 30vh)
2025-12-26 17:34:34 +01:00
f2c50fa6a5 add language toggle 2025-12-26 17:29:41 +01:00
15 changed files with 542 additions and 250 deletions

View File

@@ -0,0 +1,39 @@
<script>
import { onMount } from 'svelte';
import { getLanguageContext } from '$lib/contexts/languageContext.js';
import Toggle from './Toggle.svelte';
// Get the language context (must be created by parent page)
const { showLatin } = getLanguageContext();
// Local state for the checkbox
let showBilingual = true;
// Flag to prevent saving before we've loaded from localStorage
let hasLoadedFromStorage = false;
// Sync checkbox with context
$: $showLatin = showBilingual;
// Save to localStorage whenever it changes (but only after initial load)
$: if (typeof localStorage !== 'undefined' && hasLoadedFromStorage) {
localStorage.setItem('rosary_showBilingual', showBilingual.toString());
}
onMount(() => {
// Load from localStorage
const saved = localStorage.getItem('rosary_showBilingual');
if (saved !== null) {
showBilingual = saved === 'true';
}
// Now allow saving
hasLoadedFromStorage = true;
});
</script>
<Toggle
bind:checked={showBilingual}
label="Lateinisch und Deutsch anzeigen"
accentColor="var(--nord14)"
/>

View File

@@ -0,0 +1,85 @@
<script>
export let checked = false;
export let label = "";
export let accentColor = "var(--nord14)"; // Default to nord14, can be overridden
</script>
<style>
.toggle-wrapper {
display: flex;
justify-content: center;
margin-bottom: 2rem;
max-width: 1200px;
margin-left: auto;
margin-right: auto;
}
.toggle-wrapper label {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
font-size: 0.95rem;
color: var(--nord4);
}
@media(prefers-color-scheme: light) {
.toggle-wrapper label {
color: var(--nord2);
}
}
.toggle-wrapper span {
user-select: none;
}
/* iOS-style toggle switch */
.toggle-wrapper input[type="checkbox"] {
appearance: none;
-webkit-appearance: none;
width: 51px;
height: 31px;
background: var(--nord2);
border-radius: 31px;
position: relative;
cursor: pointer;
transition: background 0.3s ease;
outline: none;
border: none;
flex-shrink: 0;
}
@media(prefers-color-scheme: light) {
.toggle-wrapper input[type="checkbox"] {
background: var(--nord4);
}
}
.toggle-wrapper input[type="checkbox"]:checked {
background: var(--accent-color);
}
.toggle-wrapper input[type="checkbox"]::before {
content: '';
position: absolute;
width: 27px;
height: 27px;
border-radius: 50%;
top: 2px;
left: 2px;
background: white;
transition: transform 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.toggle-wrapper input[type="checkbox"]:checked::before {
transform: translateX(20px);
}
</style>
<div class="toggle-wrapper" style="--accent-color: {accentColor}">
<label>
<input type="checkbox" bind:checked on:change />
<span>{label}</span>
</label>
</div>

View File

@@ -1,9 +1,12 @@
<script> <script>
import Prayer from './Prayer.svelte';
export let mystery = ""; // For rosary mysteries (German) export let mystery = ""; // For rosary mysteries (German)
export let mysteryLatin = ""; // For rosary mysteries (Latin) export let mysteryLatin = ""; // For rosary mysteries (Latin)
</script> </script>
<p> <Prayer>
<p>
<v lang="la">Ave María, grátia plena. Dóminus tecum,</v> <v lang="la">Ave María, grátia plena. Dóminus tecum,</v>
<v lang="de">Gegrüsset seist du Maria, voll der Gnade; der Herr ist mit dir;</v> <v lang="de">Gegrüsset seist du Maria, voll der Gnade; der Herr ist mit dir;</v>
<v lang="la">benedícta tu in muliéribus,</v> <v lang="la">benedícta tu in muliéribus,</v>
@@ -16,10 +19,11 @@
{#if mystery} {#if mystery}
<v lang="de" class="mystery-text">{mystery}</v> <v lang="de" class="mystery-text">{mystery}</v>
{/if} {/if}
</p> </p>
<p> <p>
<v lang="la">Sancta María, mater Dei, ora pro nobis peccatóribus,</v> <v lang="la">Sancta María, mater Dei, ora pro nobis peccatóribus,</v>
<v lang="de">Heilige Maria, Mutter Gottes, bitte für uns Sünder</v> <v lang="de">Heilige Maria, Mutter Gottes, bitte für uns Sünder</v>
<v lang="la">nunc, et in hora mortis nostræ. Amen.</v> <v lang="la">nunc, et in hora mortis nostræ. Amen.</v>
<v lang="de">jetzt und in der Stunde unseres Todes! Amen.</v> <v lang="de">jetzt und in der Stunde unseres Todes! Amen.</v>
</p> </p>
</Prayer>

View File

@@ -1,4 +1,9 @@
<p> <script>
import Prayer from './Prayer.svelte';
</script>
<Prayer>
<p>
<v lang="la">Credo in unum <i><sup></sup></i> Deum, Patrem omnipoténtem,</v> <v lang="la">Credo in unum <i><sup></sup></i> Deum, Patrem omnipoténtem,</v>
<v lang="de">Ich glaub an den einen <i><sup></sup></i> Gott. Den allmächtigen Vater,</v> <v lang="de">Ich glaub an den einen <i><sup></sup></i> Gott. Den allmächtigen Vater,</v>
<v lang="la">factórem cæli et terræ,</v> <v lang="la">factórem cæli et terræ,</v>
@@ -82,4 +87,5 @@
<v lang="de">Ich erwarte die Auferstehung der Toten.</v> <v lang="de">Ich erwarte die Auferstehung der Toten.</v>
<v lang="la"><i></i> Et vitam ventúri sǽculi. Amen.</v> <v lang="la"><i></i> Et vitam ventúri sǽculi. Amen.</v>
<v lang="de"><i></i> Und das Leben der zukünftigen Welt. Amen.</v> <v lang="de"><i></i> Und das Leben der zukünftigen Welt. Amen.</v>
</p> </p>
</Prayer>

View File

@@ -1,4 +1,9 @@
<p> <script>
import Prayer from './Prayer.svelte';
</script>
<Prayer>
<p>
<v lang="la">Ó mí Jésú, dímitte nóbís débita nostra,</v> <v lang="la">Ó mí Jésú, dímitte nóbís débita nostra,</v>
<v lang="de">O mein Jesus, verzeih' uns unsere Sünden,</v> <v lang="de">O mein Jesus, verzeih' uns unsere Sünden,</v>
<v lang="la">líberá nós ab igne ínferní,</v> <v lang="la">líberá nós ab igne ínferní,</v>
@@ -9,4 +14,5 @@
<v lang="de">besonders jene,</v> <v lang="de">besonders jene,</v>
<v lang="la">quæ maximé indigent misericordiá tuá. Amen.</v> <v lang="la">quæ maximé indigent misericordiá tuá. Amen.</v>
<v lang="de">die Deiner Barmherzigkeit am meisten bedürfen. Amen.</v> <v lang="de">die Deiner Barmherzigkeit am meisten bedürfen. Amen.</v>
</p> </p>
</Prayer>

View File

@@ -1,4 +1,9 @@
<p> <script>
import Prayer from './Prayer.svelte';
</script>
<Prayer>
<p>
<v lang="la">Glória in excélsis <i><sup></sup></i> Deo.</v> <v lang="la">Glória in excélsis <i><sup></sup></i> Deo.</v>
<v lang="de">Ehre sei <i><sup></sup></i> Gott in der Höhe.</v> <v lang="de">Ehre sei <i><sup></sup></i> Gott in der Höhe.</v>
<v lang="la">Et in terra pax homínibus</v> <v lang="la">Et in terra pax homínibus</v>
@@ -49,4 +54,5 @@
<v lang="de">Mit dem Hl. Geiste,</v> <v lang="de">Mit dem Hl. Geiste,</v>
<v lang="la"><i></i> in glória Dei Patris. Amen.</v> <v lang="la"><i></i> in glória Dei Patris. Amen.</v>
<v lang="de"><i></i> in der Herrlichkeit Gottes des Vaters. Amen.</v> <v lang="de"><i></i> in der Herrlichkeit Gottes des Vaters. Amen.</v>
</p> </p>
</Prayer>

View File

@@ -1,8 +1,14 @@
<p> <script>
import Prayer from './Prayer.svelte';
</script>
<Prayer>
<p>
<v lang="la">Glória Patri, et Fílio, et Spirítui Sancto.</v> <v lang="la">Glória Patri, et Fílio, et Spirítui Sancto.</v>
<v lang="de">Ehre sei dem Vater und dem Sohne und dem Hl. Geiste.</v> <v lang="de">Ehre sei dem Vater und dem Sohne und dem Hl. Geiste.</v>
<v lang="la">Sicut erat in princípio, et nunc, et semper:</v> <v lang="la">Sicut erat in princípio, et nunc, et semper:</v>
<v lang="de">Wie es war am Anfang, so auch jetzt und allezeit</v> <v lang="de">Wie es war am Anfang, so auch jetzt und allezeit</v>
<v lang="la">et in sǽcula sæculórum. Amen.</v> <v lang="la">et in sǽcula sæculórum. Amen.</v>
<v lang="de">und in Ewigkeit. Amen.</v> <v lang="de">und in Ewigkeit. Amen.</v>
</p> </p>
</Prayer>

View File

@@ -1,4 +1,10 @@
<p> <script>
import Prayer from './Prayer.svelte';
</script>
<Prayer>
<p>
<v lang="la">In nómine <i></i> Patris, et Fílii, et Spíritus Sancti. Amen.</v> <v lang="la">In nómine <i></i> Patris, et Fílii, et Spíritus Sancti. Amen.</v>
<v lang="de">Im Namen des <i></i> Vaters und des Sohnes und des Heiligen Geistes. Amen.</v> <v lang="de">Im Namen des <i></i> Vaters und des Sohnes und des Heiligen Geistes. Amen.</v>
</p> </p>
</Prayer>

View File

@@ -1,4 +1,9 @@
<p> <script>
import Prayer from './Prayer.svelte';
</script>
<Prayer>
<p>
<v lang="la">Pater noster, qui es in cælis</v> <v lang="la">Pater noster, qui es in cælis</v>
<v lang="de">Vater unser, der Du bist im Himmel,</v> <v lang="de">Vater unser, der Du bist im Himmel,</v>
<v lang="la">Sanctificétur nomen tuum</v> <v lang="la">Sanctificétur nomen tuum</v>
@@ -17,4 +22,5 @@
<v lang="de">und führe uns nicht in Versuchung.</v> <v lang="de">und führe uns nicht in Versuchung.</v>
<v lang="la">Sed líbera nos a malo. Amen.</v> <v lang="la">Sed líbera nos a malo. Amen.</v>
<v lang="de">Sondern erlöse uns von dem Übel. Amen.</v> <v lang="de">Sondern erlöse uns von dem Übel. Amen.</v>
</p> </p>
</Prayer>

View File

@@ -0,0 +1,126 @@
<script>
import { getLanguageContext } from '$lib/contexts/languageContext.js';
export let latinPrimary = true; // Controls which language is shown prominently
// Get context if available (graceful fallback for standalone usage)
let showLatinStore;
try {
const context = getLanguageContext();
showLatinStore = context.showLatin;
} catch {
showLatinStore = null;
}
$: showLatin = showLatinStore ? $showLatinStore : true;
</script>
<style>
.prayer-wrapper :global(p) {
display: flex;
flex-direction: column;
}
/* Reverse order when German is primary */
.prayer-wrapper.german-primary :global(p) {
flex-direction: column-reverse;
}
.prayer-wrapper :global(v) {
margin: 0;
display: block;
}
/* Latin primary (default) */
.prayer-wrapper :global(v:lang(la)) {
color: var(--nord6);
}
.prayer-wrapper :global(v:lang(de)) {
color: grey;
}
.prayer-wrapper :global(i) {
font-style: normal;
color: var(--nord11);
font-weight: 900;
}
@media(prefers-color-scheme: light) {
.prayer-wrapper :global(v:lang(la)) {
color: black;
}
}
/* German primary mode */
.prayer-wrapper.german-primary :global(v:lang(de)) {
color: var(--nord6);
}
.prayer-wrapper.german-primary :global(v:lang(la)) {
color: grey;
}
@media(prefers-color-scheme: light) {
.prayer-wrapper.german-primary :global(v:lang(de)) {
color: black;
}
}
/* Mystery text styling */
.prayer-wrapper :global(v.mystery-text:lang(la)) {
color: var(--nord11) !important;
font-weight: 700;
font-size: 1.1em;
}
.prayer-wrapper :global(v.mystery-text:lang(de)) {
color: var(--nord12) !important;
font-weight: 700;
font-size: 0.95em;
}
.prayer-wrapper.german-primary :global(v.mystery-text:lang(de)) {
color: var(--nord11) !important;
font-weight: 700;
font-size: 1.1em;
}
.prayer-wrapper.german-primary :global(v.mystery-text:lang(la)) {
color: var(--nord12) !important;
font-weight: 700;
font-size: 0.95em;
}
/* Hide Latin in monolingual mode */
.prayer-wrapper.monolingual :global(v:lang(la)) {
display: none;
}
/* German gets primary styling in monolingual mode */
.prayer-wrapper.monolingual :global(v:lang(de)) {
color: var(--nord6);
}
@media(prefers-color-scheme: light) {
.prayer-wrapper.monolingual :global(v:lang(de)) {
color: black;
}
}
/* Hide Latin mystery text in monolingual mode */
.prayer-wrapper.monolingual :global(v.mystery-text:lang(la)) {
display: none;
}
/* German mystery text gets prominent styling in monolingual mode */
.prayer-wrapper.monolingual :global(v.mystery-text:lang(de)) {
color: var(--nord11) !important;
font-weight: 700;
font-size: 1.1em;
}
</style>
<div class="prayer-wrapper" class:german-primary={!latinPrimary} class:monolingual={!showLatin}>
<slot></slot>
</div>

View File

@@ -1,8 +1,13 @@
<p> <script>
import Prayer from './Prayer.svelte';
</script>
<Prayer>
<p>
<v lang="la">Orémus:</v> <v lang="la">Orémus:</v>
<v lang="de">Lasset uns beten:</v> <v lang="de">Lasset uns beten:</v>
</p> </p>
<p> <p>
<v lang="la">Déus, cújus Unigénitus,</v> <v lang="la">Déus, cújus Unigénitus,</v>
<v lang="de">O Gott, dessen eingeborner Sohn</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="la">pér vítam, mórtem ét resurrectiónem súam</v>
@@ -21,4 +26,5 @@
<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>
<v lang="de">Amen.</v> <v lang="de">Amen.</v>
</p> </p>
</Prayer>

View File

@@ -1,4 +1,9 @@
<p> <script>
import Prayer from './Prayer.svelte';
</script>
<Prayer>
<p>
<v lang="la">Salve, Regína,</v> <v lang="la">Salve, Regína,</v>
<v lang="de">Sei gegrüsst, 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>
@@ -6,8 +11,8 @@
<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üsst!</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>
<v lang="de">Zu dir rufen wir verbannte Kinder Evas;</v> <v lang="de">Zu dir rufen wir verbannte Kinder Evas;</v>
<v lang="la">Ad te suspirámus,</v> <v lang="la">Ad te suspirámus,</v>
@@ -24,4 +29,5 @@
<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üsse Jungfrau Maria.</v> <v lang="de">O gütige, o milde, o süsse Jungfrau Maria.</v>
</p> </p>
</Prayer>

View File

@@ -0,0 +1,18 @@
import { setContext, getContext } from 'svelte';
import { writable } from 'svelte/store';
const LANGUAGE_CONTEXT_KEY = Symbol('language');
export function createLanguageContext() {
const showLatin = writable(true); // true = bilingual, false = monolingual
setContext(LANGUAGE_CONTEXT_KEY, {
showLatin
});
return { showLatin };
}
export function getLanguageContext() {
return getContext(LANGUAGE_CONTEXT_KEY);
}

View File

@@ -1,7 +1,9 @@
<script> <script>
import { createLanguageContext } from "$lib/contexts/languageContext.js";
import "$lib/css/christ.css"; import "$lib/css/christ.css";
import "$lib/css/nordtheme.css"; import "$lib/css/nordtheme.css";
import Gebet from "./Gebet.svelte"; import Gebet from "./Gebet.svelte";
import LanguageToggle from "$lib/components/LanguageToggle.svelte";
import Kreuzzeichen from "$lib/components/prayers/Kreuzzeichen.svelte"; import Kreuzzeichen from "$lib/components/prayers/Kreuzzeichen.svelte";
import GloriaPatri from "$lib/components/prayers/GloriaPatri.svelte"; import GloriaPatri from "$lib/components/prayers/GloriaPatri.svelte";
import Paternoster from "$lib/components/prayers/Paternoster.svelte"; import Paternoster from "$lib/components/prayers/Paternoster.svelte";
@@ -13,6 +15,9 @@
import MichaelGebet from "$lib/components/prayers/MichaelGebet.svelte"; import MichaelGebet from "$lib/components/prayers/MichaelGebet.svelte";
import BruderKlausGebet from "$lib/components/prayers/BruderKlausGebet.svelte"; import BruderKlausGebet from "$lib/components/prayers/BruderKlausGebet.svelte";
import JosephGebet from "$lib/components/prayers/JosephGebet.svelte"; import JosephGebet from "$lib/components/prayers/JosephGebet.svelte";
// Create language context for prayer components
createLanguageContext();
</script> </script>
<style> <style>
.ccontainer{ .ccontainer{
@@ -41,6 +46,7 @@ h1{
</style> </style>
<h1>Gebete</h1> <h1>Gebete</h1>
<LanguageToggle />
<div class="ccontainer"> <div class="ccontainer">
<div class=container> <div class=container>

View File

@@ -1,5 +1,6 @@
<script> <script>
import { onMount } from "svelte"; import { onMount } from "svelte";
import { createLanguageContext } from "$lib/contexts/languageContext.js";
import "$lib/css/christ.css"; import "$lib/css/christ.css";
import "$lib/css/rosenkranz.css"; import "$lib/css/rosenkranz.css";
import Kreuzzeichen from "$lib/components/prayers/Kreuzzeichen.svelte"; import Kreuzzeichen from "$lib/components/prayers/Kreuzzeichen.svelte";
@@ -13,6 +14,8 @@ 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"; import BibleModal from "$lib/components/BibleModal.svelte";
import Toggle from "$lib/components/Toggle.svelte";
import LanguageToggle from "$lib/components/LanguageToggle.svelte";
export let data; export let data;
@@ -114,6 +117,17 @@ const mysteryTitles = {
// Toggle for including Luminous mysteries // Toggle for including Luminous mysteries
let includeLuminous = true; let includeLuminous = true;
// Flag to prevent saving before we've loaded from localStorage
let hasLoadedFromStorage = false;
// Create language context for prayer components (LanguageToggle will use this)
createLanguageContext();
// Save luminous toggle state to localStorage whenever it changes (but only after initial load)
$: if (typeof localStorage !== 'undefined' && hasLoadedFromStorage) {
localStorage.setItem('rosary_includeLuminous', includeLuminous.toString());
}
// Function to get the appropriate mystery for a given weekday // Function to get the appropriate mystery for a given weekday
function getMysteryForWeekday(date, includeLuminous) { function getMysteryForWeekday(date, includeLuminous) {
const dayOfWeek = date.getDay(); // 0 = Sunday, 1 = Monday, etc. const dayOfWeek = date.getDay(); // 0 = Sunday, 1 = Monday, etc.
@@ -254,6 +268,20 @@ const sectionPositions = {
}; };
onMount(() => { onMount(() => {
// Load toggle state from localStorage
const savedIncludeLuminous = localStorage.getItem('rosary_includeLuminous');
if (savedIncludeLuminous !== null) {
includeLuminous = savedIncludeLuminous === 'true';
}
// Recalculate mystery based on loaded includeLuminous value
todaysMystery = getMysteryForWeekday(new Date(), includeLuminous);
selectMystery(todaysMystery);
// Now allow saving to localStorage
hasLoadedFromStorage = true;
let scrollLock = null; // Track which side initiated the scroll: 'prayer', 'svg', or 'click' let scrollLock = null; // Track which side initiated the scroll: 'prayer', 'svg', or 'click'
let scrollLockTimeout = null; let scrollLockTimeout = null;
@@ -736,10 +764,18 @@ onMount(() => {
padding-bottom: 2rem; padding-bottom: 2rem;
} }
/* Reduce min-height in monolingual mode since content is shorter */
.prayer-section.decade:has(:global(.prayer-wrapper.monolingual)) {
min-height: 30vh;
}
@media (max-width: 1023px) { @media (max-width: 1023px) {
.prayer-section.decade { .prayer-section.decade {
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
} }
.prayer-section.decade:has(:global(.prayer-wrapper.monolingual)) {
min-height: 20vh;
}
.prayer-section { .prayer-section {
padding: 0.5rem; padding: 0.5rem;
} }
@@ -865,78 +901,6 @@ h1 {
margin-bottom: 2rem; margin-bottom: 2rem;
} }
/* Luminous mysteries toggle */
.luminous-toggle {
display: flex;
justify-content: center;
margin-bottom: 2rem;
max-width: 1200px;
margin-left: auto;
margin-right: auto;
}
.luminous-toggle label {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
font-size: 0.95rem;
color: var(--nord4);
}
@media(prefers-color-scheme: light) {
.luminous-toggle label {
color: var(--nord2);
}
}
.luminous-toggle span {
user-select: none;
}
/* iOS-style toggle switch */
.luminous-toggle input[type="checkbox"] {
appearance: none;
-webkit-appearance: none;
width: 51px;
height: 31px;
background: var(--nord2);
border-radius: 31px;
position: relative;
cursor: pointer;
transition: background 0.3s ease;
outline: none;
border: none;
flex-shrink: 0;
}
@media(prefers-color-scheme: light) {
.luminous-toggle input[type="checkbox"] {
background: var(--nord4);
}
}
.luminous-toggle input[type="checkbox"]:checked {
background: var(--nord14);
}
.luminous-toggle input[type="checkbox"]::before {
content: '';
position: absolute;
width: 27px;
height: 27px;
border-radius: 50%;
top: 2px;
left: 2px;
background: white;
transition: transform 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.luminous-toggle input[type="checkbox"]:checked::before {
transform: translateX(20px);
}
/* Mystery selector grid */ /* Mystery selector grid */
.mystery-selector { .mystery-selector {
display: grid; display: grid;
@@ -1263,12 +1227,14 @@ l536 389l-209 -629zM1671 934l-370 267l150 436l-378 -271l-371 271q8 -34 15 -68q10
</div> </div>
<!-- Luminous Mysteries Toggle --> <!-- Luminous Mysteries Toggle -->
<div class="luminous-toggle"> <Toggle
<label> bind:checked={includeLuminous}
<input type="checkbox" bind:checked={includeLuminous} on:change={handleToggleChange} /> label="Lichtreiche Geheimnisse einbeziehen"
<span>Lichtreiche Geheimnisse einbeziehen</span> on:change={handleToggleChange}
</label> />
</div>
<!-- Language Toggle -->
<LanguageToggle />
<div class="rosary-layout"> <div class="rosary-layout">
<!-- Sidebar: Rosary Visualization --> <!-- Sidebar: Rosary Visualization -->