rosary: progressive enhancement for no-JS browsers
All checks were successful
CI / update (push) Successful in 1m33s

SVG beads are now anchor links to prayer sections, with CSS :has(:target)
highlighting the active bead. Inline mystery images render in each decade
by default and hide when JS takes over. StreakCounter uses a form action
fallback for logged-in users and hides entirely for anonymous no-JS users.
Show images toggle now works via ?images= URL param like the other toggles.
This commit is contained in:
2026-02-13 12:56:33 +01:00
parent a0146927b6
commit 96a91ed8dd
5 changed files with 177 additions and 38 deletions

View File

@@ -10,9 +10,10 @@ let streak = $state<ReturnType<typeof getRosaryStreak> | null>(null);
interface Props { interface Props {
streakData?: { length: number; lastPrayed: string | null } | null; streakData?: { length: number; lastPrayed: string | null } | null;
lang?: 'de' | 'en'; lang?: 'de' | 'en';
isLoggedIn?: boolean;
} }
let { streakData = null, lang = 'de' }: Props = $props(); let { streakData = null, lang = 'de', isLoggedIn = false }: Props = $props();
const isEnglish = $derived(lang === 'en'); const isEnglish = $derived(lang === 'en');
@@ -42,23 +43,25 @@ async function pray() {
} }
</script> </script>
<div class="streak-container"> <div class="streak-container" class:no-js-hidden={!isLoggedIn}>
<div class="streak-display"> <div class="streak-display">
<StreakAura value={displayLength} {burst} /> <StreakAura value={displayLength} {burst} />
<span class="streak-label">{labels.days}</span> <span class="streak-label">{labels.days}</span>
</div> </div>
<button <form method="POST" action="?/pray" onsubmit={(e) => { e.preventDefault(); pray(); }}>
class="streak-button" <button
onclick={pray} class="streak-button"
disabled={prayedToday} type="submit"
aria-label={labels.ariaLabel} disabled={prayedToday}
> aria-label={labels.ariaLabel}
{#if prayedToday} >
{labels.prayedToday} {#if prayedToday}
{:else} {labels.prayedToday}
{labels.prayed} {:else}
{/if} {labels.prayed}
</button> {/if}
</button>
</form>
</div> </div>
<style> <style>
@@ -119,6 +122,15 @@ async function pray() {
opacity: 0.7; opacity: 0.7;
} }
/* Hide for non-logged-in users without JS (no form action available) */
.no-js-hidden {
display: none;
}
:global(html.js-enabled) .no-js-hidden {
display: flex;
}
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
.streak-button:disabled { .streak-button:disabled {
background: var(--nord4); background: var(--nord4);

View File

@@ -1,5 +1,5 @@
import { mysteryVerseDataDe, mysteryVerseDataEn } from '$lib/data/mysteryVerseData'; import { mysteryVerseDataDe, mysteryVerseDataEn } from '$lib/data/mysteryVerseData';
import type { PageServerLoad } from './$types'; import type { PageServerLoad, Actions } from './$types';
interface StreakData { interface StreakData {
length: number; length: number;
@@ -43,13 +43,16 @@ export const load: PageServerLoad = async ({ url, fetch, locals, params }) => {
const luminousParam = url.searchParams.get('luminous'); const luminousParam = url.searchParams.get('luminous');
const latinParam = url.searchParams.get('latin'); const latinParam = url.searchParams.get('latin');
const mysteryParam = url.searchParams.get('mystery'); const mysteryParam = url.searchParams.get('mystery');
const imagesParam = url.searchParams.get('images');
const hasUrlLuminous = luminousParam !== null; const hasUrlLuminous = luminousParam !== null;
const hasUrlLatin = latinParam !== null; const hasUrlLatin = latinParam !== null;
const hasUrlMystery = mysteryParam !== null; const hasUrlMystery = mysteryParam !== null;
const hasUrlImages = imagesParam !== null;
const initialLuminous = hasUrlLuminous ? luminousParam !== '0' : true; const initialLuminous = hasUrlLuminous ? luminousParam !== '0' : true;
const initialLatin = hasUrlLatin ? latinParam !== '0' : true; const initialLatin = hasUrlLatin ? latinParam !== '0' : true;
const initialShowImages = hasUrlImages ? imagesParam !== '0' : true;
const todaysMystery = getMysteryForWeekday(new Date(), initialLuminous); const todaysMystery = getMysteryForWeekday(new Date(), initialLuminous);
@@ -78,12 +81,46 @@ export const load: PageServerLoad = async ({ url, fetch, locals, params }) => {
return { return {
mysteryDescriptions: params.faithLang === 'faith' ? mysteryVerseDataEn : mysteryVerseDataDe, mysteryDescriptions: params.faithLang === 'faith' ? mysteryVerseDataEn : mysteryVerseDataDe,
streakData, streakData,
isLoggedIn: !!session?.user?.nickname,
initialMystery, initialMystery,
todaysMystery, todaysMystery,
initialLuminous, initialLuminous,
initialLatin, initialLatin,
hasUrlMystery, hasUrlMystery,
hasUrlLuminous, hasUrlLuminous,
hasUrlLatin hasUrlLatin,
initialShowImages,
hasUrlImages
}; };
}; };
export const actions: Actions = {
pray: async ({ locals, fetch }) => {
const session = await locals.auth();
if (!session?.user?.nickname) {
return { success: false };
}
const res = await fetch('/api/glaube/rosary-streak');
const current = res.ok ? await res.json() : { length: 0, lastPrayed: null };
const today = new Date().toISOString().split('T')[0];
if (current.lastPrayed === today) {
return { success: true, alreadyPrayed: true };
}
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const yesterdayStr = yesterday.toISOString().split('T')[0];
const newLength = current.lastPrayed === yesterdayStr ? current.length + 1 : 1;
await fetch('/api/glaube/rosary-streak', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ length: newLength, lastPrayed: today })
});
return { success: true };
}
};

View File

@@ -29,8 +29,8 @@ let { data } = $props();
// Toggle for including Luminous mysteries (initialized from URL param or default) // Toggle for including Luminous mysteries (initialized from URL param or default)
let includeLuminous = $state(data.initialLuminous); let includeLuminous = $state(data.initialLuminous);
// Toggle for showing mystery images // Toggle for showing mystery images (initialized from URL param or default)
let showImages = $state(true); let showImages = $state(data.initialShowImages);
// Flag to prevent saving before we've loaded from localStorage // Flag to prevent saving before we've loaded from localStorage
let hasLoadedFromStorage = $state(false); let hasLoadedFromStorage = $state(false);
@@ -76,11 +76,12 @@ function selectMystery(mysteryType) {
} }
// Build URLs preserving full state (for no-JS fallback) // Build URLs preserving full state (for no-JS fallback)
function buildHref({ mystery = selectedMystery, luminous = includeLuminous, latin = data.initialLatin } = {}) { function buildHref({ mystery = selectedMystery, luminous = includeLuminous, latin = data.initialLatin, images = showImages } = {}) {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.set('mystery', mystery); params.set('mystery', mystery);
if (!luminous) params.set('luminous', '0'); if (!luminous) params.set('luminous', '0');
if (!latin) params.set('latin', '0'); if (!latin) params.set('latin', '0');
if (!images) params.set('images', '0');
return `?${params.toString()}`; return `?${params.toString()}`;
} }
@@ -91,6 +92,7 @@ function mysteryHref(mystery) {
// Toggle hrefs navigate to opposite state (for no-JS self-submit) // Toggle hrefs navigate to opposite state (for no-JS self-submit)
let luminousToggleHref = $derived(buildHref({ luminous: !includeLuminous })); let luminousToggleHref = $derived(buildHref({ luminous: !includeLuminous }));
let latinToggleHref = $derived(buildHref({ latin: !data.initialLatin })); let latinToggleHref = $derived(buildHref({ latin: !data.initialLatin }));
let imagesToggleHref = $derived(buildHref({ images: !showImages }));
// When luminous toggle changes, update today's mystery and fix invalid selection // When luminous toggle changes, update today's mystery and fix invalid selection
$effect(() => { $effect(() => {
@@ -264,9 +266,11 @@ onMount(() => {
includeLuminous = savedIncludeLuminous === 'true'; includeLuminous = savedIncludeLuminous === 'true';
} }
} }
const savedShowImages = localStorage.getItem('rosary_showImages'); if (!data.hasUrlImages) {
if (savedShowImages !== null) { const savedShowImages = localStorage.getItem('rosary_showImages');
showImages = savedShowImages === 'true'; if (savedShowImages !== null) {
showImages = savedShowImages === 'true';
}
} }
// If no mystery was specified in URL, recompute based on loaded preferences // If no mystery was specified in URL, recompute based on loaded preferences
@@ -283,6 +287,9 @@ onMount(() => {
// Now allow saving to localStorage // Now allow saving to localStorage
hasLoadedFromStorage = true; hasLoadedFromStorage = true;
// Mark JS as active so no-JS fallback images hide
document.documentElement.classList.add('js-enabled');
// PiP resize handler — show/hide when crossing the breakpoint // PiP resize handler — show/hide when crossing the breakpoint
const onPipResize = () => { const onPipResize = () => {
if (!rosaryPipEl) return; if (!rosaryPipEl) return;
@@ -306,6 +313,7 @@ onMount(() => {
return () => { return () => {
cleanupScrollSync(); cleanupScrollSync();
window.removeEventListener('resize', onPipResize); window.removeEventListener('resize', onPipResize);
document.documentElement.classList.remove('js-enabled');
}; };
}); });
</script> </script>
@@ -407,6 +415,7 @@ onMount(() => {
.prayer-section { .prayer-section {
scroll-snap-align: start; scroll-snap-align: start;
scroll-margin-top: 3rem;
padding: 2rem; padding: 2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
background: var(--accent-dark); background: var(--accent-dark);
@@ -415,6 +424,40 @@ onMount(() => {
position: relative; position: relative;
} }
/* No-JS: highlight SVG beads when a prayer section is :target */
.rosary-layout:has(#start1:target) :global(.bead[data-section="start1"]),
.rosary-layout:has(#start2:target) :global(.bead[data-section="start2"]),
.rosary-layout:has(#start3:target) :global(.bead[data-section="start3"]),
.rosary-layout:has(#secret1:target) :global(.bead[data-section="secret1"]),
.rosary-layout:has(#secret2:target) :global(.bead[data-section="secret2"]),
.rosary-layout:has(#secret3:target) :global(.bead[data-section="secret3"]),
.rosary-layout:has(#secret4:target) :global(.bead[data-section="secret4"]),
.rosary-layout:has(#secret5:target) :global(.bead[data-section="secret5"]),
.rosary-layout:has(#final_salve:target) :global(.bead[data-section="final_salve"]),
.rosary-layout:has(#final_schlussgebet:target) :global(.bead[data-section="final_schlussgebet"]),
.rosary-layout:has(#final_michael:target) :global(.bead[data-section="final_michael"]) {
fill: var(--nord11) !important;
filter: drop-shadow(0 0 8px var(--nord11));
}
.rosary-layout:has(#lbead1:target) :global(.large-bead[data-section="lbead1"]),
.rosary-layout:has(#lbead2:target) :global(.large-bead[data-section="lbead2"]),
.rosary-layout:has(#secret1_transition:target) :global(.large-bead[data-section="secret1_transition"]),
.rosary-layout:has(#secret2_transition:target) :global(.large-bead[data-section="secret2_transition"]),
.rosary-layout:has(#secret3_transition:target) :global(.large-bead[data-section="secret3_transition"]),
.rosary-layout:has(#secret4_transition:target) :global(.large-bead[data-section="secret4_transition"]),
.rosary-layout:has(#final_transition:target) :global(.large-bead[data-section="final_transition"]),
.rosary-layout:has(#final_paternoster:target) :global(.large-bead[data-section="final_paternoster"]) {
fill: var(--nord13) !important;
filter: drop-shadow(0 0 10px var(--nord13));
}
.rosary-layout:has(#cross:target) :global([data-section="cross"] .cross-symbol),
.rosary-layout:has(#final_cross:target) :global([data-section="final_cross"] .cross-symbol) {
fill: var(--nord11) !important;
filter: drop-shadow(0 0 10px var(--nord11));
}
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
.prayer-section { .prayer-section {
background: var(--nord5); background: var(--nord5);
@@ -597,6 +640,28 @@ h1 {
height: 50vh; height: 50vh;
} }
/* Inline mystery images: visible without JS, hidden when JS takes over */
.decade-inline-image {
margin: 1rem auto;
text-align: center;
}
.decade-inline-image img {
max-width: 100%;
max-height: 40vh;
border-radius: 8px;
}
.decade-inline-image figcaption {
font-size: 0.85rem;
color: var(--nord4);
margin-top: 0.5rem;
}
:global(html.js-enabled) .decade-inline-image {
display: none;
}
/* Mystery images: third grid column (desktop), PiP (mobile) */ /* Mystery images: third grid column (desktop), PiP (mobile) */
.mystery-image-column { .mystery-image-column {
display: none; display: none;
@@ -633,7 +698,7 @@ h1 {
<!-- Toggle Controls & Streak Counter --> <!-- Toggle Controls & Streak Counter -->
<div class="controls-row"> <div class="controls-row">
<StreakCounter streakData={data.streakData} lang={data.lang} /> <StreakCounter streakData={data.streakData} lang={data.lang} isLoggedIn={data.isLoggedIn} />
<div class="toggle-controls"> <div class="toggle-controls">
<!-- Luminous Mysteries Toggle (link for no-JS, enhanced with onclick for JS) --> <!-- Luminous Mysteries Toggle (link for no-JS, enhanced with onclick for JS) -->
<Toggle <Toggle
@@ -645,6 +710,7 @@ h1 {
<Toggle <Toggle
bind:checked={showImages} bind:checked={showImages}
label={labels.showImages} label={labels.showImages}
href={imagesToggleHref}
/> />
<!-- Language Toggle (link for no-JS, enhanced with onclick for JS) --> <!-- Language Toggle (link for no-JS, enhanced with onclick for JS) -->
@@ -669,6 +735,7 @@ h1 {
<!-- Cross & Credo --> <!-- Cross & Credo -->
<div <div
class="prayer-section" class="prayer-section"
id="cross"
bind:this={sectionElements.cross} bind:this={sectionElements.cross}
data-section="cross" data-section="cross"
> >
@@ -686,6 +753,7 @@ h1 {
<!-- First Large Bead --> <!-- First Large Bead -->
<div <div
class="prayer-section" class="prayer-section"
id="lbead1"
bind:this={sectionElements.lbead1} bind:this={sectionElements.lbead1}
data-section="lbead1" data-section="lbead1"
> >
@@ -696,6 +764,7 @@ h1 {
<!-- First Ave Maria (Faith) --> <!-- First Ave Maria (Faith) -->
<div <div
class="prayer-section" class="prayer-section"
id="start1"
bind:this={sectionElements.start1} bind:this={sectionElements.start1}
data-section="start1" data-section="start1"
> >
@@ -710,6 +779,7 @@ h1 {
<!-- Second Ave Maria (Hope) --> <!-- Second Ave Maria (Hope) -->
<div <div
class="prayer-section" class="prayer-section"
id="start2"
bind:this={sectionElements.start2} bind:this={sectionElements.start2}
data-section="start2" data-section="start2"
> >
@@ -724,6 +794,7 @@ h1 {
<!-- Third Ave Maria (Love) --> <!-- Third Ave Maria (Love) -->
<div <div
class="prayer-section" class="prayer-section"
id="start3"
bind:this={sectionElements.start3} bind:this={sectionElements.start3}
data-section="start3" data-section="start3"
> >
@@ -738,6 +809,7 @@ h1 {
<!-- Gloria Patri before decades --> <!-- Gloria Patri before decades -->
<div <div
class="prayer-section" class="prayer-section"
id="lbead2"
bind:this={sectionElements.lbead2} bind:this={sectionElements.lbead2}
data-section="lbead2" data-section="lbead2"
> >
@@ -752,11 +824,20 @@ h1 {
<!-- Ave Maria decade (Gesätz) --> <!-- Ave Maria decade (Gesätz) -->
<div <div
class="prayer-section decade" class="prayer-section decade"
id={`secret${decadeNum}`}
bind:this={sectionElements[`secret${decadeNum}`]} bind:this={sectionElements[`secret${decadeNum}`]}
data-section={`secret${decadeNum}`} data-section={`secret${decadeNum}`}
> >
<h2>{decadeNum}. {labels.decade}: {currentMysteryTitles[decadeNum - 1]}</h2> <h2>{decadeNum}. {labels.decade}: {currentMysteryTitles[decadeNum - 1]}</h2>
{#if showImages && allMysteryImages[selectedMystery]?.get(decadeNum)}
{@const img = allMysteryImages[selectedMystery].get(decadeNum)}
<figure class="decade-inline-image">
<img src={img.src} alt={isEnglish ? img.title : img.titleDe} loading="lazy" />
<figcaption>{img.artist ? `${img.artist}, ` : ''}<em>{isEnglish ? img.title : img.titleDe}</em>{img.year ? `, ${img.year}` : ''}</figcaption>
</figure>
{/if}
<h3>{labels.hailMary} <span class="repeat-count">(10×)</span></h3> <h3>{labels.hailMary} <span class="repeat-count">(10×)</span></h3>
<AveMaria <AveMaria
mysteryLatin={currentMysteriesLatin[decadeNum - 1]} mysteryLatin={currentMysteriesLatin[decadeNum - 1]}
@@ -784,6 +865,7 @@ h1 {
{#if decadeNum < 5} {#if decadeNum < 5}
<div <div
class="prayer-section" class="prayer-section"
id={`secret${decadeNum}_transition`}
bind:this={sectionElements[`secret${decadeNum}_transition`]} bind:this={sectionElements[`secret${decadeNum}_transition`]}
data-section={`secret${decadeNum}_transition`} data-section={`secret${decadeNum}_transition`}
> >
@@ -802,6 +884,7 @@ h1 {
<!-- Final prayers after 5th decade --> <!-- Final prayers after 5th decade -->
<div <div
class="prayer-section" class="prayer-section"
id="final_transition"
bind:this={sectionElements.final_transition} bind:this={sectionElements.final_transition}
data-section="final_transition" data-section="final_transition"
> >
@@ -816,6 +899,7 @@ h1 {
<div <div
class="prayer-section" class="prayer-section"
id="final_salve"
bind:this={sectionElements.final_salve} bind:this={sectionElements.final_salve}
data-section="final_salve" data-section="final_salve"
> >
@@ -825,6 +909,7 @@ h1 {
<div <div
class="prayer-section" class="prayer-section"
id="final_schlussgebet"
bind:this={sectionElements.final_schlussgebet} bind:this={sectionElements.final_schlussgebet}
data-section="final_schlussgebet" data-section="final_schlussgebet"
> >
@@ -834,6 +919,7 @@ h1 {
<div <div
class="prayer-section" class="prayer-section"
id="final_michael"
bind:this={sectionElements.final_michael} bind:this={sectionElements.final_michael}
data-section="final_michael" data-section="final_michael"
> >
@@ -843,6 +929,7 @@ h1 {
<div <div
class="prayer-section" class="prayer-section"
id="final_paternoster"
bind:this={sectionElements.final_paternoster} bind:this={sectionElements.final_paternoster}
data-section="final_paternoster" data-section="final_paternoster"
> >
@@ -852,6 +939,7 @@ h1 {
<div <div
class="prayer-section" class="prayer-section"
id="final_cross"
bind:this={sectionElements.final_cross} bind:this={sectionElements.final_cross}
data-section="final_cross" data-section="final_cross"
> >

View File

@@ -128,34 +128,34 @@
class="cross-symbol" class:active-cross={activeSection === 'final_cross'} /> class="cross-symbol" class:active-cross={activeSection === 'final_cross'} />
</g> </g>
<!-- Invisible hitboxes for larger tap targets --> <!-- Invisible hitboxes for larger tap targets (anchor links for no-JS fallback) -->
<g class="hitboxes"> <g class="hitboxes">
<!-- Cross hitbox --> <!-- Cross hitbox -->
<rect x="-15" y="-30" width="80" height="80" data-section="cross" /> <a href="#cross"><rect x="-15" y="-30" width="80" height="80" data-section="cross" /></a>
<!-- Individual bead hitboxes --> <!-- Individual bead hitboxes -->
<circle cx="25" cy={pos.lbead1} r="25" data-section="lbead1" /> <a href="#lbead1"><circle cx="25" cy={pos.lbead1} r="25" data-section="lbead1" /></a>
<circle cx="25" cy={pos.start1} r="20" data-section="start1" /> <a href="#start1"><circle cx="25" cy={pos.start1} r="20" data-section="start1" /></a>
<circle cx="25" cy={pos.start2} r="20" data-section="start2" /> <a href="#start2"><circle cx="25" cy={pos.start2} r="20" data-section="start2" /></a>
<circle cx="25" cy={pos.start3} r="20" data-section="start3" /> <a href="#start3"><circle cx="25" cy={pos.start3} r="20" data-section="start3" /></a>
<circle cx="25" cy={pos.lbead2} r="25" data-section="lbead2" /> <a href="#lbead2"><circle cx="25" cy={pos.lbead2} r="25" data-section="lbead2" /></a>
<!-- Decade hitboxes --> <!-- Decade hitboxes -->
{#each [1, 2, 3, 4, 5] as d (d)} {#each [1, 2, 3, 4, 5] as d (d)}
{@const decadePos = pos[`secret${d}`]} {@const decadePos = pos[`secret${d}`]}
<rect x="-15" y={decadePos - 2} width="80" height={DECADE_OFFSET + 9 * BEAD_SPACING + 12} data-section={`secret${d}`} /> <a href={`#secret${d}`}><rect x="-15" y={decadePos - 2} width="80" height={DECADE_OFFSET + 9 * BEAD_SPACING + 12} data-section={`secret${d}`} /></a>
{/each} {/each}
<!-- Transition bead hitboxes --> <!-- Transition bead hitboxes -->
{#each [1, 2, 3, 4] as d (d)} {#each [1, 2, 3, 4] as d (d)}
<circle cx="25" cy={pos[`secret${d}_transition`]} r="25" data-section={`secret${d}_transition`} /> <a href={`#secret${d}_transition`}><circle cx="25" cy={pos[`secret${d}_transition`]} r="25" data-section={`secret${d}_transition`} /></a>
{/each} {/each}
<circle cx="25" cy={pos.final_transition} r="25" data-section="final_transition" /> <a href="#final_transition"><circle cx="25" cy={pos.final_transition} r="25" data-section="final_transition" /></a>
<circle cx="25" cy={pos.final_salve} r="20" data-section="final_salve" /> <a href="#final_salve"><circle cx="25" cy={pos.final_salve} r="20" data-section="final_salve" /></a>
<circle cx="25" cy={pos.final_schlussgebet} r="20" data-section="final_schlussgebet" /> <a href="#final_schlussgebet"><circle cx="25" cy={pos.final_schlussgebet} r="20" data-section="final_schlussgebet" /></a>
<circle cx="25" cy={pos.final_michael} r="20" data-section="final_michael" /> <a href="#final_michael"><circle cx="25" cy={pos.final_michael} r="20" data-section="final_michael" /></a>
<circle cx="25" cy={pos.final_paternoster} r="25" data-section="final_paternoster" /> <a href="#final_paternoster"><circle cx="25" cy={pos.final_paternoster} r="25" data-section="final_paternoster" /></a>
<rect x="-15" y={pos.final_cross - 50} width="80" height="80" data-section="final_cross" /> <a href="#final_cross"><rect x="-15" y={pos.final_cross - 50} width="80" height="80" data-section="final_cross" /></a>
</g> </g>
</svg> </svg>

View File

@@ -194,6 +194,7 @@ export function setupScrollSync({
} }
// Handle clicks on SVG elements to jump to prayers // Handle clicks on SVG elements to jump to prayers
// preventDefault() overrides the anchor-link fallback when JS is enabled
const handleSvgClick = (e) => { const handleSvgClick = (e) => {
const svgContainer = getSvgContainer(); const svgContainer = getSvgContainer();
const sectionElements = getSectionElements(); const sectionElements = getSectionElements();
@@ -201,6 +202,7 @@ export function setupScrollSync({
while (target && target !== svgContainer) { while (target && target !== svgContainer) {
const section = target.dataset.section; const section = target.dataset.section;
if (section && sectionElements[section]) { if (section && sectionElements[section]) {
e.preventDefault();
setActiveSection(section); setActiveSection(section);
setScrollLock('click', 1500); setScrollLock('click', 1500);