PageSourceSearch

https://shop.holysoft.de/js/funct.js?v=1785263780

js holysoft.de collected 2026-10-02 12:50:04 UTC 53,673 bytes, 1,823 lines download raw bytes

1'use strict';
2var warning="";
3var base="";
4var baseTitle="";
5var page="";
6var fhQuery="";
7var popupStatus = 0;  
8var scroll_container_padding_horizontal=0;
9var scrollwait=false;
10var runtimes=0;
11var active=[];
12var lazyLoader;
13
14function warnme(b64_warning) {
15	if(b64_warning!="") {
16		window.setTimeout('fancyAlert(\''+b64_warning+'\')',1);
17	}
18}
19
20function fancyAlert(b64_warning) {
21	let warning = "";
22	try {
23		warning = decodeBase64(b64_warning);
24	}
25	catch {
26		alert('Fehler in Hinweismodul');
27	}
28	
29	if(!warning || warning == "") {
30		return;
31	}
32	
33	$.confirm({
34		title: 'Hinweis',
35		content: warning,
36		animation: 'none',
37		typeAnimated: false,
38		boxWidth: 'clamp(300px, 30vw, 50%)',
39		useBootstrap: false,
40		buttons: {   
41			ok: {
42				text: "OK",
43				btnClass: 'btn-primary',
44				keys: ['enter','esc','space'],
45			},
46		}
47	});	
48	return;
49}
50
51function decodeBase64(base64) {
52    const text = atob(base64);
53    const length = text.length;
54    const bytes = new Uint8Array(length);
55    for (let i = 0; i < length; i++) {
56        bytes[i] = text.charCodeAt(i);
57    }
58    const decoder = new TextDecoder(); // default is utf-8
59    return decoder.decode(bytes);
60}
61
62
63
64function blur_it() {
65  var alla=document.getElementsByTagName('a');
66  if(alla) {
67    for(var i=0; i<alla.length;i++) {
68      if(alla[i])
69        alla[i].onfocus=new Function('if(this.blur)this.blur();');
70    }
71  }
72}
73
74/** Start-page featured carousel (#startRotate): init only when wide; re-run after narrow→wide resize. */
75function initFeaturedStartSlider() {
76	var slider = $('#startRotate.slider');
77	if (!slider.length) {
78		return;
79	}
80	if (!window.matchMedia || window.matchMedia('(max-width: 768px)').matches) {
81		return;
82	}
83	if (slider.data('featuredSliderBound')) {
84		var slidesFix = slider.find('ul li');
85		if (slidesFix.length > 1 && slidesFix.filter('.active').length === 0) {
86			slidesFix.removeClass('active');
87			slidesFix.first().addClass('active');
88		}
89		return;
90	}
91
92	var slides = slider.find('ul li');
93	var slideCount = slides.length;
94	var currentSlide = 0;
95	var autoplayInterval;
96	var isPlaying = true;
97
98	if (slideCount > 1) {
99		currentSlide = slides.index(slides.filter('.active'));
100		if (currentSlide === -1) {
101			currentSlide = 0;
102			slides.first().addClass('active');
103		}
104
105		slides.each(function(index) {
106			var slide = $(this);
107			var interactiveElements = slide.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
108
109			if (index === currentSlide) {
110				interactiveElements.each(function() {
111					$(this).removeAttr('tabindex');
112				});
113				slide.removeAttr('aria-hidden');
114			} else {
115				interactiveElements.each(function() {
116					$(this).attr('tabindex', '-1');
117				});
118				slide.attr('aria-hidden', 'true');
119			}
120		});
121
122		var paginationHtml = '<div class="simple-pagination">';
123		for (var i = 0; i < slideCount; i++) {
124			paginationHtml += '<button class="simple-dot' + (i === currentSlide ? ' active' : '') + '" data-slide="' + i + '" aria-label="Go to slide ' + (i + 1) + '"></button>';
125		}
126		paginationHtml += '</div>';
127		slider.append(paginationHtml);
128
129		function nextSlide() {
130			if (!isPlaying) {
131				return;
132			}
133			var nextIndex = (currentSlide + 1) % slideCount;
134			transitionToSlide(nextIndex);
135		}
136
137		function goToSlide(index) {
138			if (index === currentSlide) {
139				return;
140			}
141			transitionToSlide(index);
142		}
143
144		function transitionToSlide(index) {
145			var currentSlideEl = slides.eq(currentSlide);
146			currentSlideEl.removeClass('active');
147			var interactiveElements = currentSlideEl.find('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
148			interactiveElements.each(function() {
149				$(this).attr('tabindex', '-1');
150			});
151			currentSlideEl.attr('aria-hidden', 'true');
152
153			currentSlide = index;
154
155			var newSlideEl = slides.eq(currentSlide);
156			newSlideEl.addClass('active');
157			var activeInteractiveElements = newSlideEl.find('a, button, input, select, textarea, [tabindex="-1"]');
158			activeInteractiveElements.each(function() {
159				$(this).removeAttr('tabindex');
160			});
161			newSlideEl.removeAttr('aria-hidden');
162
163			updatePagination();
164		}
165
166		function updatePagination() {
167			slider.find('.simple-dot').removeClass('active').eq(currentSlide).addClass('active');
168		}
169
170		function startAutoplay() {
171			stopAutoplay();
172			autoplayInterval = setInterval(nextSlide, 8000);
173			slider.data('featuredAutoplayId', autoplayInterval);
174		}
175
176		function stopAutoplay() {
177			if (autoplayInterval) {
178				clearInterval(autoplayInterval);
179				autoplayInterval = null;
180			}
181			slider.removeData('featuredAutoplayId');
182		}
183
184		function attachPaginationEvents() {
185			slider.find('.simple-dot').each(function() {
186				var $button = $(this);
187				$button.on('click', function(e) {
188					e.preventDefault();
189					var slideIndex = parseInt($button.data('slide'), 10);
190					goToSlide(slideIndex);
191					stopAutoplay();
192					isPlaying = false;
193				});
194				$button.on('keydown', function(e) {
195					if (e.key === 'Enter' || e.key === ' ') {
196						e.preventDefault();
197						var slideIndex = parseInt($button.data('slide'), 10);
198						goToSlide(slideIndex);
199						stopAutoplay();
200						isPlaying = false;
201					}
202				});
203			});
204		}
205
206		attachPaginationEvents();
207		startAutoplay();
208	}
209
210	slider.data('featuredSliderBound', true);
211}
212
213function registerFeaturedSliderViewportListener() {
214	if (window.__featuredSliderMqRegistered) {
215		return;
216	}
217	window.__featuredSliderMqRegistered = true;
218	var mq = window.matchMedia('(max-width: 768px)');
219	function onMqChange() {
220		if (mq.matches) {
221			var s = $('#startRotate.slider');
222			var iv = s.data('featuredAutoplayId');
223			if (iv) {
224				clearInterval(iv);
225				s.removeData('featuredAutoplayId');
226			}
227			/* Allow a later widen to run full init again (autoplay + dots), without stacking handlers. */
228			s.find('.simple-pagination').remove();
229			s.removeData('featuredSliderBound');
230		} else {
231			initFeaturedStartSlider();
232		}
233	}
234	if (typeof mq.addEventListener === 'function') {
235		mq.addEventListener('change', onMqChange);
236	} else if (typeof mq.addListener === 'function') {
237		mq.addListener(onMqChange);
238	}
239}
240
241var rowScrollThrottled = _.throttle(callRowPaginationSingle, 250, { leading: true, trailing: true });
242
243function bindRowScrollEvents($rows) {
244	$rows.find('.content_row_scroll').on("scroll", rowScrollThrottled);
245}
246
247$(document).ready(function() {
248	initManualLoadMoreMode();
249
250	$(document).keyup(function(e){
251		if(e.which==27 && popupStatus==1){  
252			disablePopup();  
253		}
254	});
255
256	$('#input_top_search_button').click(function(r) {
257		if($('#input_top_search').val()=="Suche...") {
258			return false;			
259		}
260		document.getElementById('top_search').submit();
261	});
262		
263	$('#input_top_search').click(function(r) {
264		if($(this).val()=="Suche...") {
265			$(this).val('');
266		}
267	});
268	
269	$('.mobileMenuButton').on('click keydown', function(e) {
270		if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
271		if (e.type === 'keydown') e.preventDefault();
272		e.preventDefault();
273		$('.mm_toggle').toggle();
274	});
275   
276   $('.top_categories_click>p').on('click keydown', function(e) {
277		if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
278		if (e.type === 'keydown') e.preventDefault();
279		e.preventDefault();
280		$('.top_categories_list').toggle();
281   });
282   $('.top_info_click>p').on('click keydown', function(e) {
283		if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
284		if (e.type === 'keydown') e.preventDefault();
285		e.preventDefault();
286		$('.top_info_list').toggle();
287   });
288
289	if(isTouchDevice()) {
290		$('.top_login').on('click keydown', (e) => {
291			/* Burger nav (≤768px): submenu is always stacked in-panel — no desktop dropdown toggle. */
292			if (window.matchMedia('(max-width: 768px)').matches) {
293				return;
294			}
295			if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
296			if (e.type === 'keydown') e.preventDefault();
297			$('.top_usermenu').toggleClass('dd_visible');
298		});
299	}
300	else {
301		$('.top_login').removeClass('top_login_hover').addClass('top_login_hover');
302	}
303
304	initFeaturedStartSlider();
305	registerFeaturedSliderViewportListener();
306
307	if(fhQuery!='') {
308		openForm(fhQuery);
309	}
310 	$('#content').on('click keydown', '.scrollArrow', function(e) {
311		if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
312		if (e.type === 'keydown') e.preventDefault();
313		var targ=$(this).data('target');
314		var elcnt=$(this).data('elcnt');
315		if($(this).data('dir')=="left") {
316			var dir=-1;
317		} else {
318			var dir=1;
319		}
320		scrollRow("#"+targ,dir,elcnt);
321	});
322	$('.open_target_element').on('click keydown', function(e) {
323		if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
324		if (e.type === 'keydown') e.preventDefault();
325		e.preventDefault();
326		openTargetElement($(this));
327	});
328	
329	$('.news_menu_year').off('click keydown').on('click keydown', function (e) {
330		if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
331		if (e.type === 'keydown') e.preventDefault();
332		let jahr=$(this).data('jahr');
333		$('.news_menu_'+jahr).toggle();
334		var toggle_icon='-';
335		// var toggle_icon='▲';
336		if($('.news_menu_'+jahr).is(':hidden')) {
337			toggle_icon='+';
338			// var toggle_icon='▼';
339		}
340		$('.news_menu_toggle_icon_'+jahr).html(toggle_icon);		
341	});
342
343	$('.content_row_scroll').on("scroll", rowScrollThrottled);
344	$('.img_popup').off('click keydown').on('click keydown', function (e) {
345		if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
346		if (e.type === 'keydown') e.preventDefault();
347		showImage($(this));
348	});
349
350	callRowPaginationFull();
351	
352	$('.product_full_media_thumb').on('click keydown', function(e) {
353		if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
354		if (e.type === 'keydown') e.preventDefault();
355		setProdMainImg($(this));
356	});
357	
358	warnme(warning);
359});
360
361window.onpopstate = function(event) {
362	if(event.state && event.state.tt!=undefined) {
363		contentElementName=event.state.ce;
364		targetElementName=event.state.te;
365		targetTitle=event.state.tt;
366		document.title=baseTitle+" - "+targetTitle;
367		openTargetElement('',event.state);
368	}
369};
370
371function isManualLoadMoreMode() {
372	if (!window.matchMedia) return false;
373	var m = window.matchMedia('(max-width: 1024px)').matches;
374	var t = isTouchDevice();
375	return m && t;
376}
377var hsManualLoadModeInitialized = false;
378
379function syncScrollLoaderPosition() {
380	var scrollLoaderElement = $('#scrollLoader');
381	if (scrollLoaderElement.length < 1) {
382		return;
383	}
384	var visibleRows = $('.content_row').filter(function() {
385		return !$(this).hasClass('loaderhide');
386	});
387	if (visibleRows.length > 0) {
388		scrollLoaderElement.insertAfter(visibleRows.last());
389	} else {
390		var firstHidden = $('.content_row.loaderhide').first();
391		if (firstHidden.length > 0) {
392			scrollLoaderElement.insertBefore(firstHidden);
393		}
394	}
395}
396
397function normalizeManualRows() {
398	$('.content_row')
399		.not('.hs-manual-visible')
400		.addClass('loaderhide hs-manual-hidden')
401		.hide();
402	$('.content_row.hs-manual-visible')
403		.removeClass('loaderhide hs-manual-hidden')
404		.show();
405	syncScrollLoaderPosition();
406}
407
408function bootstrapRowLoaderState() {
409	if (!isManualLoadMoreMode()) {
410		return;
411	}
412	var initialVisibleBudget = (window.innerHeight && window.innerHeight < 750) ? 6 : 8;
413	var allRows = $('.content_row');
414	allRows
415		.removeClass('hs-manual-visible')
416		.addClass('loaderhide hs-manual-hidden')
417		.hide();
418	allRows
419		.slice(0, initialVisibleBudget)
420		.addClass('hs-manual-visible')
421		.removeClass('loaderhide hs-manual-hidden')
422		.show();
423	var visibleRows = allRows.slice(0, initialVisibleBudget);
424	visibleRows.each(function() {
425		callRowPaginationSingle($(this));
426	});
427	normalizeManualRows();
428}
429
430function initManualLoadMoreMode() {
431	if (!isManualLoadMoreMode()) {
432		hsManualLoadModeInitialized = false;
433		return;
434	}
435	// Bootstrap rows only once; keep trying to init the button if it wasn't in the DOM yet
436	if (!hsManualLoadModeInitialized) {
437		bootstrapRowLoaderState();
438		hsManualLoadModeInitialized = true;
439	}
440	var scrollLoader = $('#scrollLoader');
441	if (scrollLoader.length < 1) {
442		scrollLoader = $(document.getElementById('scrollLoader'));
443	}
444	if (scrollLoader.length > 0 && !scrollLoader.data('hs-loader-init')) {
445		ensureScrollLoaderButton(scrollLoader);
446		scrollLoader.data('hs-loader-init', true);
447	}
448}
449
450function loadNextContentRowAjax(scrollLoaderElement, count, onComplete) {
451	if (!scrollLoaderElement || scrollLoaderElement.length < 1) {
452		if (typeof onComplete === 'function') { onComplete(); }
453		return false;
454	}
455	if (!window.HS_ROW_INDEX || window.HS_ROW_INDEX.length === 0) {
456		if (typeof onComplete === 'function') { onComplete(); }
457		return false;
458	}
459	if (scrollLoaderElement.data('hs-ajax-loading')) {
460		return true;
461	}
462	var target = Math.max(1, Number(count) || 4);
463	var batch = window.HS_ROW_INDEX.splice(0, target);
464	if (batch.length === 0) {
465		if (typeof onComplete === 'function') { onComplete(); }
466		return false;
467	}
468	var serieIds = batch.map(function(r) { return r.serie_id; });
469	var offset = batch[0].id;
470	scrollLoaderElement.data('hs-ajax-loading', true);
471	$.ajax({
472		url: 'index.php?c=load-rows',
473		type: 'POST',
474		data: {
475			serie_ids: serieIds.join(','),
476			offset: offset
477		},
478		success: function(response) {
479			scrollLoaderElement.removeData('hs-ajax-loading');
480			if (response.html) {
481				var newRows = $(response.html);
482				newRows.removeClass('loaderhide hs-manual-hidden').show();
483				newRows.addClass('hs-manual-visible');
484				var $lastRow = $('.content_row').last();
485				if ($lastRow.length > 0) {
486					$lastRow.after(newRows);
487				} else {
488					scrollLoaderElement.after(newRows);
489				}
490				callRowPaginationSingle(newRows);
491				bindRowScrollEvents(newRows);
492			}
493			syncScrollLoaderPosition();
494			if ((!window.HS_ROW_INDEX || window.HS_ROW_INDEX.length === 0) &&
495			    (!window.HS_DEFERRED_ROWS || window.HS_DEFERRED_ROWS.length === 0)) {
496				scrollLoaderElement.remove();
497			}
498			if (typeof onComplete === 'function') { onComplete(); }
499		},
500		error: function() {
501			scrollLoaderElement.removeData('hs-ajax-loading');
502			if (typeof onComplete === 'function') { onComplete(); }
503		}
504	});
505	return true;
506}
507
508function loadNextContentRow(scrollLoaderElement) {
509	return loadNextContentRows(scrollLoaderElement, 1);
510}
511
512function loadNextContentRows(scrollLoaderElement, count) {
513	if (!scrollLoaderElement || scrollLoaderElement.length < 1) {
514		return false;
515	}
516	var target = Math.max(1, Number(count) || 1);
517	var loaded = 0;
518
519	// 1. Reveal any hidden DOM rows synchronously
520	for (var i = 0; i < target; i++) {
521		var nextHidden = $('.content_row.loaderhide').first();
522		if (nextHidden.length > 0) {
523			nextHidden.addClass('hs-manual-visible');
524			nextHidden.removeClass('loaderhide hs-manual-hidden').show();
525			callRowPaginationSingle(nextHidden);
526			bindRowScrollEvents(nextHidden);
527			loaded++;
528		} else {
529			break;
530		}
531	}
532
533	// 2. Reveal any pre-rendered deferred rows synchronously
534	var bufferLoaded = 0;
535	while (loaded < target && Array.isArray(window.HS_DEFERRED_ROWS) && window.HS_DEFERRED_ROWS.length > 0) {
536		var nextRowHtml = window.HS_DEFERRED_ROWS.shift();
537		if (nextRowHtml) {
538			var nextRow = $(nextRowHtml);
539			nextRow.removeClass('loaderhide hs-manual-hidden').show();
540			nextRow.addClass('hs-manual-visible');
541			var $lastRow = $('.content_row').last();
542			if ($lastRow.length > 0) {
543				$lastRow.after(nextRow);
544			} else {
545				scrollLoaderElement.after(nextRow);
546			}
547			callRowPaginationSingle(nextRow);
548			bindRowScrollEvents(nextRow);
549			loaded++;
550			bufferLoaded++;
551		} else {
552			break;
553		}
554	}
555
556	if (loaded > 0) {
557		syncScrollLoaderPosition();
558	}
559
560	// 3. If still need more, fetch remainder via AJAX (exactly one call)
561	if (loaded < target) {
562		var remaining = target - loaded;
563		return loadNextContentRowAjax(scrollLoaderElement, remaining);
564	}
565
566	return loaded > 0;
567}
568
569function ensureScrollLoaderButton(scrollLoaderElement) {
570	if (!scrollLoaderElement || scrollLoaderElement.length < 1) {
571		return;
572	}
573	syncScrollLoaderPosition();
574	scrollLoaderElement
575		.text('Mehr laden')
576		.attr('role', 'button')
577		.attr('tabindex', '0')
578		.attr('aria-label', 'Weitere Inhalte laden')
579		.css('cursor', 'pointer')
580		.off('click.loadMore keydown.loadMore')
581		.on('click.loadMore keydown.loadMore', function(e) {
582			if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
583			if (e.type === 'keydown') e.preventDefault();
584			var batchSize = isManualLoadMoreMode() ? 4 : 1;
585			var loaded = loadNextContentRows(scrollLoaderElement, batchSize);
586			if (!loaded) {
587				scrollLoaderElement.remove();
588			}
589		});
590}
591
592$(window).scroll(_.throttle(function() {
593	if (window.__hsCatchUpRunning) return;
594	var scrollLoaderElement = $('#scrollLoader');
595	if (scrollLoaderElement.length < 1) return;
596	if (scrollLoaderElement.data('hs-ajax-loading')) return;
597	if (isManualLoadMoreMode()) return;
598	// Detect bfcache back-button restoration on first scroll
599	if (!window.__hsFirstScrollDone) {
600		window.__hsFirstScrollDone = true;
601		var $sl = $('#scrollLoader');
602		if ($sl.length > 0 && $(window).scrollTop() > 0) {
603			var loaderTop = $sl.offset().top;
604			var viewportBottom = $(window).scrollTop() + $(window).height();
605			if (viewportBottom >= loaderTop) {
606				window.__hsBfcacheRestored = true;
607				setTimeout(function() {
608					window.__hsBfcacheRestored = false;
609					// After settling, trigger one load if loader is in viewport
610					var $sl = $('#scrollLoader');
611					if ($sl.length > 0 && !$sl.data('hs-ajax-loading') && !window.__hsCatchUpRunning) {
612						var slBottom = $sl.offset().top + $sl.outerHeight();
613						var winBottom = $(window).scrollTop() + $(window).height();
614						if (slBottom <= winBottom) {
615							loadNextContentRow($sl);
616						}
617					}
618				}, 3000);
619			}
620		}
621	}
622
623	var hiddenVisible = $('.loaderhide');
624	if (hiddenVisible.length > 0) {
625		var scrollLoaderElementBottom = scrollLoaderElement.offset().top + scrollLoaderElement.outerHeight();
626		var windowBottom = $(window).scrollTop() + $(window).height();
627		var windowTop = $(window).scrollTop();
628		if (scrollLoaderElementBottom < windowTop) {
629			window.__hsCatchUpRunning = true;
630			var scrollLoaderOriginalText = scrollLoaderElement.text();
631			scrollLoaderElement.text('Lädt alle Inhalte...');
632			scrollLoaderElement.addClass('is-loading');
633			setTimeout(function() {
634				try {
635					var loaded = loadNextContentRows(scrollLoaderElement, 1000);
636					if (!loaded) scrollLoaderElement.remove();
637					else {
638						scrollLoaderElement.text(scrollLoaderOriginalText);
639						scrollLoaderElement.removeClass('is-loading');
640					}
641				} catch (e) {
642					// ignore
643				} finally {
644					window.__hsCatchUpRunning = false;
645				}
646			}, 0);
647			return;
648		} else if (scrollLoaderElementBottom <= windowBottom && !window.__hsBfcacheRestored) {
649			loadNextContentRow(scrollLoaderElement);
650		}
651	} else {
652		if (!window.__hsBfcacheRestored) {
653			var loaded = loadNextContentRow(scrollLoaderElement);
654			if (!loaded) scrollLoaderElement.remove();
655		}
656	}
657}, 100, { leading: true, trailing: true}));
658
659$(window).scroll(_.throttle(function() {
660	if (isManualLoadMoreMode()) return;
661	var $rows = $('.content_row:not(.loaderhide)');
662	$rows.each(function() {
663		activateDeferredMedia($(this));
664	});
665}, 500, { leading: false, trailing: true }));
666
667window.__hsBfcacheRestored = false;
668window.__hsFirstScrollDone = false;
669
670$(window).resize(_.throttle(function() {
671	initManualLoadMoreMode();
672	callRowPaginationFull();
673}, 250, { leading: true, trailing: true}));
674
675function isTouchDevice() {
676	const touchy = (('ontouchstart' in window) ||
677	 (navigator.maxTouchPoints > 0) ||
678	 (navigator.msMaxTouchPoints > 0));
679	if (touchy) return true;
680	// Fallback for browsers (e.g. Brave) that spoof touch APIs via anti-fingerprinting
681	if (window.matchMedia && window.matchMedia('(pointer: coarse)').matches) return true;
682	if (window.matchMedia && window.matchMedia('(any-pointer: coarse)').matches) return true;
683	return false;
684}
685
686function openForm(url,bgc_click_enabled,separator) {
687	if(url=="") return '';
688	var backurl=encodeURIComponent(window.location.href);
689	$.ajax({
690	 url: url,	
691	 type:'POST',
692	 data:{'back':backurl},
693	 dataType: 'text',
694	 headers:{'Cache-control':'no-cache'},
695	 success: function (data, statusText, reqAr) {
696		if(separator==undefined) {
697			separator='||';
698		}
699		var out=data.split(separator);
700		if(out[0]=="ERROR") {
701			alert(out[1]);
702		} else if(data!="") {
703			if(out[1]=="JS") {
704				$('#popupContent').html(out[0]);
705				setupAccessibleDialog();
706				if(typeof(bgc_click_enabled)=="undefined" || bgc_click_enabled==1) {
707					$("#backgroundPopup").on('click keydown', function(e){  
708						if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
709						if (e.type === 'keydown') e.preventDefault();
710						disablePopup();
711					});
712				} else {
713					$("#backgroundPopup").off('click keydown');
714				}			
715				centerPopup();
716				loadPopup(out[2]);
717			} else {
718				$('#popupContent').html(out[0]);
719				setupAccessibleDialog();
720				if(typeof(bgc_click_enabled)=="undefined" || bgc_click_enabled==1) {
721					$("#backgroundPopup").on('click keydown', function(e){  
722						if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
723						if (e.type === 'keydown') e.preventDefault();
724						disablePopup();  
725					});
726				} else {
727					$("#backgroundPopup").off('click keydown');
728				}
729				centerPopup();
730				// $('#yourInputId').focus().val($('#yourInputId').val()).get(0).setSelectionRange($('#yourInputId').val().length, $('#yourInputId').val().length);
731
732				// loadPopup("$('#formBox input, #formBox select').first().focus();");
733				const callback = function() {
734					$(this).find('input:not([type="checkbox"]):not([type="hidden"]), select').first().focus();
735				};
736				loadPopup(callback);
737			}			
738		} else {
739			alert('Fehler!');
740		}
741	 },
742	 error: function (jqXHR, textStatus, errorThrown) {
743		alert('Fehler - '+textStatus+' '+errorThrown);
744	 }
745	});
746}
747
748// Setup accessible dialog attributes and behavior
749function setupAccessibleDialog() {
750	const popupContent = document.getElementById('popupContent');
751	const backgroundPopup = document.getElementById('backgroundPopup');
752	
753	if (!popupContent || !backgroundPopup) return;
754	
755	// Ensure sr-only CSS class exists
756	ensureSrOnlyCSS();
757	
758	// Store the element that had focus before the dialog opened
759	if (!window.lastFocusedElement) {
760		window.lastFocusedElement = document.activeElement;
761	}
762	
763	// Set up the dialog container
764	popupContent.setAttribute('role', 'dialog');
765	popupContent.setAttribute('aria-modal', 'true');
766	
767	// Try to find or create a title for the dialog
768	let dialogTitle = popupContent.querySelector('h1, h2, h3, h4, h5, h6, [role="heading"]');
769	if (!dialogTitle) {
770		// Create a hidden title if none exists
771		dialogTitle = document.createElement('h2');
772		dialogTitle.id = 'dialog-title';
773		dialogTitle.className = 'sr-only';
774		dialogTitle.textContent = 'Dialog';
775		popupContent.insertBefore(dialogTitle, popupContent.firstChild);
776	} else {
777		// Ensure existing title has an ID
778		if (!dialogTitle.id) {
779			dialogTitle.id = 'dialog-title';
780		}
781	}
782	
783	popupContent.setAttribute('aria-labelledby', dialogTitle.id);
784	
785	// Completely hide background from screen readers and accessibility tools
786	backgroundPopup.setAttribute('role', 'presentation');
787	backgroundPopup.setAttribute('aria-hidden', 'true');
788	backgroundPopup.setAttribute('tabindex', '-1');
789	backgroundPopup.setAttribute('aria-label', '');
790	backgroundPopup.setAttribute('aria-describedby', '');
791	
792	// Hide all child elements of background from screen readers
793	const backgroundChildren = backgroundPopup.querySelectorAll('*');
794	backgroundChildren.forEach(child => {
795		child.setAttribute('aria-hidden', 'true');
796		child.setAttribute('tabindex', '-1');
797	});
798	
799	// Apply contrast settings based on user preferences
800	applyContrastSettings();
801	
802	// Listen for contrast preference changes
803	if (window.matchMedia) {
804		const contrastQuery = window.matchMedia('(prefers-contrast: high)');
805		contrastQuery.addListener(applyContrastSettings);
806	}
807	
808	// Trap focus within the dialog
809	trapFocus(popupContent);
810	
811	// Handle Escape key to close dialog
812	$(document).off('keydown.dialog').on('keydown.dialog', function(e) {
813		if (e.key === 'Escape' && popupStatus === 1) {
814			e.preventDefault();
815			disablePopup();
816		}
817	});
818}
819
820// Ensure sr-only CSS class exists for screen reader only content
821function ensureSrOnlyCSS() {
822	if (!document.getElementById('sr-only-styles')) {
823		const style = document.createElement('style');
824		style.id = 'sr-only-styles';
825		style.textContent = `
826			.sr-only {
827				position: absolute !important;
828				width: 1px !important;
829				height: 1px !important;
830				padding: 0 !important;
831				margin: -1px !important;
832				overflow: hidden !important;
833				clip: rect(0, 0, 0, 0) !important;
834				white-space: nowrap !important;
835				border: 0 !important;
836			}
837		`;
838		document.head.appendChild(style);
839	}
840}
841
842// Focus trap for the dialog
843function trapFocus(element) {
844	const focusableElements = element.querySelectorAll(
845		'a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select, [tabindex]:not([tabindex="-1"])'
846	);
847	
848	const firstFocusableElement = focusableElements[0];
849	const lastFocusableElement = focusableElements[focusableElements.length - 1];
850	
851	// Focus the first element initially
852	if (firstFocusableElement) {
853		firstFocusableElement.focus();
854	}
855	
856	// Handle tab key navigation
857	$(element).off('keydown.trap').on('keydown.trap', function(e) {
858		if (e.key === 'Tab') {
859			if (e.shiftKey) {
860				// Shift + Tab
861				if (document.activeElement === firstFocusableElement) {
862					e.preventDefault();
863					lastFocusableElement.focus();
864				}
865			} else {
866				// Tab
867				if (document.activeElement === lastFocusableElement) {
868					e.preventDefault();
869					firstFocusableElement.focus();
870				}
871			}
872		}
873	});
874}
875
876//loading popup
877function loadPopup(callback,nomove=false){  
878   //loads popup only if it is disabled  
879	if(popupStatus==0){  
880		$("#backgroundPopup").css({  
881			"opacity": "0.4"  
882		});  
883		$("#backgroundPopup").fadeIn("fast");
884		if(callback){
885			$("#popupContent").fadeIn("fast",function(){
886				// Ensure accessibility is set up after content is loaded
887				setupAccessibleDialog();
888				
889				if(typeof callback === "string") {
890					const fstrings=callback.split(";;");
891					for(let i=0;i<fstrings.length;i++) {
892						console.log(fstrings[i]);
893						Function('return ' + fstrings[i])();
894					}
895				}
896				else if (typeof callback === 'function') {
897					// If callback is already a function, call it directly
898					callback.call(this);
899				}
900				else {
901					console.error('Callback is neither a string nor a function.');
902				}
903			});
904		} else {
905			$("#popupContent").fadeIn("fast", function() {
906				// Ensure accessibility is set up after content is loaded
907				setupAccessibleDialog();
908			});
909		}
910		if(!nomove && !isScrolledIntoView) {
911			$('html, body').animate({
912				scrollTop: $("#popupContent").offset().top
913			}, 50);
914		}
915		popupStatus = 1;  
916	} else if(callback!=undefined) {
917		// Ensure accessibility is set up for existing popup
918		setupAccessibleDialog();
919		
920		if(typeof callback === "string") {
921			const fstrings=callback.split(";;");
922			for(let i=0;i<fstrings.length;i++) {
923				console.log(fstrings[i]);
924				Function('return ' + fstrings[i])();
925			}
926		}
927		else if (typeof callback === 'function') {
928			// If callback is already a function, call it directly
929			callback();
930		}
931		else {
932			console.error('Callback is neither a string nor a function.');
933		}
934	}
935}  
936//disabling popup
937function disablePopup(){  
938	//disables popup only if it is enabled  
939	if(popupStatus==1){
940		// Clean up accessibility attributes
941		cleanupAccessibleDialog();
942		
943		// rest fade out etc
944		$("#backgroundPopup").fadeOut(90);  
945		$("#popupContent").fadeOut(90);  
946		$("#popupContent").html('');
947		$("#popupContent").attr('class','');
948		$("#popupContent").removeAttr('style');
949		$("#backgroundPopup, #popupContent").off('click keydown')
950		popupStatus=0;
951	}
952}
953
954// Clean up accessibility attributes when dialog closes
955function cleanupAccessibleDialog() {
956	const popupContent = document.getElementById('popupContent');
957	const backgroundPopup = document.getElementById('backgroundPopup');
958	
959	if (popupContent) {
960		popupContent.removeAttribute('role');
961		popupContent.removeAttribute('aria-modal');
962		popupContent.removeAttribute('aria-labelledby');
963		$(popupContent).off('keydown.trap');
964	}
965	
966	if (backgroundPopup) {
967		backgroundPopup.removeAttribute('role');
968		backgroundPopup.removeAttribute('aria-hidden');
969		backgroundPopup.removeAttribute('tabindex');
970		backgroundPopup.removeAttribute('aria-label');
971		backgroundPopup.removeAttribute('aria-describedby');
972		
973		// Restore background children accessibility
974		const backgroundChildren = backgroundPopup.querySelectorAll('*');
975		backgroundChildren.forEach(child => {
976			child.removeAttribute('aria-hidden');
977			child.removeAttribute('tabindex');
978		});
979	}
980	
981	// Remove global event listeners
982	$(document).off('keydown.dialog');
983	
984	// Restore focus to the element that had focus before the dialog opened
985	if (window.lastFocusedElement && window.lastFocusedElement.focus) {
986		window.lastFocusedElement.focus();
987		window.lastFocusedElement = null;
988	}
989}
990
991//centering popup  
992function centerPopup(minTop=100){  
993	//request data for centering  
994	var windowWidth = document.documentElement.clientWidth;  
995	var windowHeight = document.documentElement.clientHeight;  
996	var popupHeight = $("#popupContent").height();  
997	var popupWidth = $("#popupContent").width();  
998	var setTop= windowHeight/2-popupHeight/2+$(window).scrollTop();
999	if(setTop<minTop) {
1000		setTop=minTop;
1001	}
1002	//centering  
1003	$("#popupContent").css({  
1004		"position": "absolute",  
1005		"top": setTop,
1006		"left": windowWidth/2-popupWidth/2
1007	});  
1008	//only need force for IE6  
1009
1010	$("#backgroundPopup").css({  
1011		"height": windowHeight 
1012	});
1013}  
1014
1015function isScrolledIntoView(elem) {
1016    var docViewTop = $(window).scrollTop();
1017    var docViewBottom = docViewTop + $(window).height();
1018
1019    var elemTop = $(elem).offset().top;
1020    var elemBottom = elemTop + $(elem).height();
1021    return (elemTop >= docViewTop);
1022}
1023
1024function limitChars(textid, limit, infodiv) {
1025	var text = $('#'+textid).val(); 
1026	var textlength = text.length;
1027	if(textlength > limit) {
1028		$('#' + infodiv).html('Sie können nicht mehr wie '+limit+' Zeichen eingeben!');
1029		$('#'+textid).val(text.substr(0,limit));
1030		return false;
1031	} else {
1032		$('#' + infodiv).html('Sie haben noch '+ (limit - textlength) +' Zeichen übrig.');
1033		return true;
1034	}
1035}
1036
1037/** `.content_row_scroll` (#row…) → sibling `.steering_arrows` (no longer relies on id="…steerar"). */
1038function rowSteeringFromScrollCont(cont) {
1039	var $sc = $(cont);
1040	if (!$sc.length) {
1041		return $();
1042	}
1043	var $st = $sc.closest('.content_row').children('.steering_arrows').first();
1044	return $st.length ? $st : $();
1045}
1046
1047/**
1048 * Card width + flex gap for a horizontal product row (desktop gap 20px, phone 6px in CSS).
1049 * Uses computed `gap`, with offsetLeft fallback; last resort 20 to match legacy behaviour.
1050 */
1051function rowScrollCardStep(cont) {
1052	var $inner = $(cont + '>.content_row_scroll_inner');
1053	var innerEl = $inner[0];
1054	if (!innerEl) {
1055		return { cardW: 0, gap: 20, step: 0 };
1056	}
1057	var $first = $inner.children('.product_list_cont').first();
1058	var cardW = $first.length ? $first.outerWidth() : 0;
1059	var gapPx = 0;
1060	var style = window.getComputedStyle(innerEl);
1061	var rawGap = style.gap || style.columnGap || '';
1062	if (rawGap && rawGap !== 'normal') {
1063		var parts = String(rawGap).trim().split(/\s+/);
1064		for (var p = 0; p < parts.length; p++) {
1065			var n = parseFloat(parts[p]);
1066			if (Number.isFinite(n)) {
1067				gapPx = n;
1068				break;
1069			}
1070		}
1071	}
1072	if (!Number.isFinite(gapPx) || gapPx < 0) {
1073		gapPx = 0;
1074	}
1075	if (gapPx === 0) {
1076		var cards = $inner.children('.product_list_cont');
1077		if (cards.length >= 2) {
1078			var c0 = cards.get(0);
1079			var c1 = cards.get(1);
1080			gapPx = c1.offsetLeft - c0.offsetLeft - c0.offsetWidth;
1081		}
1082	}
1083	if (!Number.isFinite(gapPx) || gapPx < 0) {
1084		gapPx = 20;
1085	}
1086	var step = cardW + gapPx;
1087	return { cardW: cardW, gap: gapPx, step: step };
1088}
1089
1090function scrollRow(cont,dir,elcnt) {
1091	if(dir>0) {
1092		dir=1;		
1093	} else {
1094		dir=-1;
1095	}
1096
1097	let vis_width=$(cont).width();
1098
1099	var metrics = rowScrollCardStep(cont);
1100	var prod_cont_fullsize = metrics.step;
1101	if (!prod_cont_fullsize || prod_cont_fullsize <= 0) {
1102		return;
1103	}
1104	
1105	let grid_fix=$(cont).scrollLeft();
1106	let add_grid_move=grid_fix%prod_cont_fullsize;
1107	
1108	let anz_prod_cont=vis_width/prod_cont_fullsize;
1109	
1110	if(dir<0 && add_grid_move>0) {
1111		add_grid_move=prod_cont_fullsize-add_grid_move;
1112	} 
1113	let move_by=parseInt(anz_prod_cont)*prod_cont_fullsize-add_grid_move;
1114
1115	var pos=$(cont).scrollLeft()+(dir*move_by);
1116	$(cont).stop().animate({
1117	  scrollLeft: pos+"px"
1118	},1,"linear",function() {
1119		callRowPaginationSingle($(cont));
1120	});
1121}
1122
1123function callRowPaginationSingle(row_obj=null) {
1124	let current_row_obj=null;
1125	if(row_obj instanceof jQuery) {
1126		current_row_obj=row_obj;
1127		activateDeferredMedia(current_row_obj);
1128		current_row_obj.find(".steering_arrows > .scrollArrowData").each(callRowPagination);
1129	} else {
1130		current_row_obj=$(this);
1131		activateDeferredMedia(current_row_obj.parent());
1132		current_row_obj.parent().find(".steering_arrows > .scrollArrowData").each(callRowPagination);
1133	}
1134};
1135
1136function callRowPaginationFull() {
1137	activateDeferredMedia($('.content_row:not(.loaderhide)'));
1138	$('.content_row:not(.loaderhide)').find(".scrollArrowData").each(callRowPagination);
1139};
1140
1141function isElementNearVisible(el, scrollCont) {
1142	var rect = el.getBoundingClientRect();
1143	if (!scrollCont) {
1144		var winH = window.innerHeight || document.documentElement.clientHeight;
1145		var winW = window.innerWidth || document.documentElement.clientWidth;
1146		var buf = 200;
1147		return rect.top < winH + buf && rect.bottom > -buf && rect.left < winW + buf && rect.right > -buf;
1148	}
1149	var cRect = scrollCont.getBoundingClientRect();
1150	var buf = cRect.width;
1151	if (rect.bottom < cRect.top - 200 || rect.top > cRect.bottom + 200) {
1152		return false;
1153	}
1154	return rect.right >= cRect.left - buf && rect.left <= cRect.right + buf;
1155}
1156
1157function activateDeferredMedia(scope) {
1158	if (!scope || !scope.length) {
1159		return;
1160	}
1161	if (scope.length > 1) {
1162		scope.each(function () { activateDeferredMedia($(this)); });
1163		return;
1164	}
1165	var isMobile = isManualLoadMoreMode();
1166	var scrollCont = scope.find('.content_row_scroll')[0];
1167	var checkCont = isMobile ? scrollCont : null;
1168	var loadingAttr = isMobile ? 'eager' : 'lazy';
1169	scope.find('img[data-src]').each(function () {
1170		var $img = $(this);
1171		if ($img.data('hs-media-activated')) return;
1172		if (!isElementNearVisible(this, checkCont)) return;
1173		var val = $img.attr('data-src');
1174		if (!val || val === '') return;
1175		var picture = $img.closest('picture')[0];
1176		if (picture) {
1177			$(picture).find('source[data-srcset]').each(function () {
1178				var $s = $(this);
1179				var sv = $s.attr('data-srcset');
1180				if (sv && sv !== '') {
1181					$s.attr('srcset', sv);
1182					$s.removeAttr('data-srcset');
1183					$s.removeAttr('loading');
1184				}
1185			});
1186			var oldImg = $img[0];
1187			var newImg = document.createElement('img');
1188			for (var k = 0; k < oldImg.attributes.length; k++) {
1189				var attr = oldImg.attributes[k];
1190				if (attr.name !== 'data-src' && attr.name !== 'loading') {
1191					newImg.setAttribute(attr.name, attr.value);
1192				}
1193			}
1194			newImg.setAttribute('loading', loadingAttr);
1195			newImg.src = val;
1196			oldImg.parentNode.replaceChild(newImg, oldImg);
1197			$(newImg).data('hs-media-activated', true);
1198		} else {
1199			$img.attr('loading', loadingAttr);
1200			$img.attr('src', val);
1201			$img.removeAttr('data-src');
1202			$img.data('hs-media-activated', true);
1203		}
1204	});
1205}
1206
1207function callRowPagination() {
1208	let targ="";
1209	let elcnt=0;
1210	targ=$(this).data('target');
1211	elcnt=$(this).data('elcnt');
1212	let cont="#"+targ;
1213
1214	let vis_width=$(cont).width();
1215
1216	var metrics = rowScrollCardStep(cont);
1217	if (!metrics.step || metrics.step <= 0) {
1218		return;
1219	}
1220
1221	updateRowPagination(cont, metrics.step, vis_width, elcnt, metrics.gap);
1222}
1223
1224function cancelAbo(type='streaming') {
1225	var target_url="";
1226	var info_text='Möchtest Du Dein Abonnement wirklich kündigen und alle Abo-Vorteile verlieren';
1227	info_text='Möchtest Du dieses Abonnement kündigen?';
1228	if(type=='streaming') {		
1229		target_url='mein-konto/streaming-abo/cancel/';
1230	}
1231	if(type=='streaming_angebot') {		
1232		target_url='mein-konto/streaming-angebot/cancel/';
1233	}
1234	if(type=='dlh') {
1235		target_url='mein-konto/die-letzten-helden-abo/cancel/';
1236	}
1237	if(confirm(info_text)) {
1238		window.location.href=target_url;
1239	}
1240	return false;
1241}
1242
1243function updateRowPagination(cont, prod_cont_fullsize, vis_width, elcnt, gapTolerance) {
1244	if (gapTolerance === undefined || !Number.isFinite(gapTolerance)) {
1245		gapTolerance = 20;
1246	}
1247
1248	var anz_prod_left=Math.ceil($(cont).scrollLeft()/prod_cont_fullsize);	
1249	var left_area_prods_hidden=prod_cont_fullsize*anz_prod_left;
1250   var left_area_overlap=left_area_prods_hidden-$(cont).scrollLeft();
1251	var effective_visible_area=Math.ceil(vis_width)-left_area_overlap;
1252	var anz_prod_visible=parseInt((effective_visible_area + gapTolerance) / prod_cont_fullsize);
1253   var showLeftNr=anz_prod_left+1;
1254   var showRightNr=(anz_prod_left+anz_prod_visible);
1255   if(showLeftNr>elcnt) showLeftNr=elcnt;
1256	if(showRightNr>elcnt) showRightNr=elcnt;
1257
1258	var $steer = rowSteeringFromScrollCont(cont);
1259	var $steerLinks = $steer.children('a');
1260
1261	if($(cont).scrollLeft()==0) {
1262		$steerLinks.first().css('visibility','hidden');
1263	} else {
1264		$steerLinks.first().css('visibility','visible');
1265	}
1266	if($(cont)[0].scrollWidth<=Math.round($(cont).scrollLeft()+$(cont).width())) {
1267		$steerLinks.last().css('visibility','hidden');
1268	} else {
1269		$steerLinks.last().css('visibility','visible');
1270	}
1271
1272	if(anz_prod_visible>=elcnt) {
1273		$steer.css('visibility','hidden');
1274	} else {
1275		$steer.css('visibility','visible');
1276		var $pageNr = $steer.find('.scrollPageNr');
1277		if ($pageNr.length) {
1278			$pageNr.html(showLeftNr+" - "+showRightNr);
1279		} else {
1280			$(cont+"pagenr").html(showLeftNr+" - "+showRightNr);
1281		}
1282	}
1283
1284}
1285
1286function openTargetElement(element,override) {
1287	if(element!="") {
1288		var contentElementName=$(element).data('content'),
1289			 targetElementName=$(element).data('target'),
1290			 targetTitle=$(element).data('title');
1291	} else {
1292		var contentElementName=override.ce,
1293			 targetElementName=override.te,
1294			 targetTitle=override.tt;
1295	}
1296	var targetContent=$('#'+contentElementName).html(),
1297		 targetElement=$('#'+targetElementName);
1298	
1299	targetElement.children().addClass('hide').removeClass('unhide');
1300	$('#'+contentElementName).removeClass('hide').addClass('unhide');
1301	
1302	document.title=baseTitle.replace(' | '+mainTitle, '')+" - "+targetTitle+' | '+mainTitle;
1303	
1304	var state = {ce:contentElementName,te:targetElementName,tt:targetTitle},
1305		 title = targetTitle,
1306		 path = $(element).attr('href');
1307
1308	if(override!=undefined) {
1309		history.replaceState(state, title, path);
1310	} else {
1311		history.pushState(state, title, path);
1312	}
1313}
1314
1315function setProdMainImg(obj) {
1316	var img_cont=$('#product_full_main_img');
1317   var index=obj.data('index');	
1318	var src=obj.data('src');
1319	var alt=$('#product_full_media_img_'+index).attr('alt');
1320	img_cont.attr('src',src);
1321	img_cont.attr('alt',alt);
1322	$('.product_full_media_thumb').removeClass("product_full_media_thumb_sel");
1323	obj.addClass("product_full_media_thumb_sel");
1324}
1325
1326function initNewsMenu() {
1327	$('.news_menu_item').off('click').on('click', function () {
1328		$(this).toggle();
1329	});
1330}
1331
1332function showImage(obj) {
1333	let url=$(obj).data('ipop');
1334	let img_el=$('<img id="image_popup">');
1335	if(img_el) {
1336		img_el.remove();
1337	}
1338	img_el = $('<img id="image_popup">').off('load').on('load',function () {
1339		$("#backgroundPopup, #popupContent").off('click').on('click',function(){  
1340			disablePopup();
1341		});
1342		loadPopup(()=>{},true);
1343	});
1344	img_el.attr('src', url);
1345	$('#popupContent').addClass('image_popup_content');
1346	img_el.appendTo('#popupContent');
1347}
1348
1349
1350// SHOP
1351function addItem(prodid) {	
1352	if($('#prod'+prodid).length) {
1353		var amount=parseInt($('#prod'+prodid).val().match(/\d+/),10);
1354	} else {
1355	   var amount=1;
1356	}
1357	var backurl=encodeURIComponent(window.location.href);	
1358	new $.ajax({
1359	 url: 'warenkorb/'+prodid+'/',	
1360	 type:'POST',
1361	 data:{'back':backurl,'amount':amount},
1362	 headers:{'Cache-control':'no-cache'},
1363	 success: function (data, statusText, reqAr) {
1364		var out=data.split('||');
1365		if(out[0]=="ERROR") {
1366			alert(out[1]);
1367		} else if(data!="") {
1368			window.location.reload(false);
1369		} else {
1370			alert('Fehler!');
1371		}
1372	},
1373	error: function (jqXHR, textStatus, errorThrown) {
1374		alert('Fehler - '+textStatus+' '+errorThrown);
1375	}
1376	});
1377}
1378function remItem(prodid,check_message) {
1379	if(!confirm(check_message)) {
1380		return ;
1381	}
1382	var backurl=encodeURIComponent(window.location.href);	
1383	var wkCsrfToken=$('#WK input[name="wk_csrf_token"]').val();
1384	new $.ajax({
1385	 url: 'warenkorb/-'+prodid+'/',	
1386	 type:'POST',
1387	 data:{'back':backurl,'wk_csrf_token':wkCsrfToken},
1388	 headers:{'Cache-control':'no-cache'},
1389	 success: function (data, statusText, reqAr) {
1390		var out=data.split('||');
1391		if(out[0]=="ERROR") {
1392			alert(out[1]);
1393		} else if(data!="") {
1394			window.location.href='warenkorb/';
1395		} else {
1396			alert('Fehler!');
1397		}
1398	},
1399	error: function (jqXHR, textStatus, errorThrown) {
1400		alert('Fehler - '+textStatus+' '+errorThrown);
1401	}
1402	});
1403}
1404function remAbo(aboid,check_message) {
1405	if(!confirm(check_message)) {
1406		return ;
1407	}
1408	var backurl=encodeURIComponent(window.location.href);	
1409	new $.ajax({
1410	 url: 'abo-konfigurieren/-'+aboid+'/',	
1411	 type:'POST',
1412	 data:{'back':backurl},
1413	 headers:{'Cache-control':'no-cache'},
1414	 success: function (data, statusText, reqAr) {
1415		var out=data.split('||');
1416		if(out[0]=="ERROR") {
1417			alert(out[1]);
1418		} else if(data!="") {
1419			window.location.href='abo-konfigurieren/';
1420		} else {
1421			alert('Fehler!');
1422		}
1423	},
1424	error: function (jqXHR, textStatus, errorThrown) {
1425		alert('Fehler - '+textStatus+' '+errorThrown);
1426	}
1427	});
1428}
1429function refresh_wk() {
1430	$("#WK").submit();
1431}
1432function setLAtoInp() {
1433	var a=document.forms['lieferadresse'];
1434	if(a) {
1435		a.adresstyp[1].checked=true;
1436	}
1437}
1438function deleteWunschlisteItem(prod_id) {
1439   if(confirm('Möchten Sie dieses Produkt von Ihrer Liste entfernen?')) {
1440      window.location.href='mein-konto/wunschliste/rem-item/'+prod_id+'/';
1441   }
1442}
1443
1444function sendFetchData(url,data,returnUrl) {
1445	fetch(url, {
1446		method: 'POST',
1447		headers: {
1448			'Content-Type': 'application/json',
1449		},
1450		body: JSON.stringify(data),
1451	})
1452	.then(response => {
1453		if (!response.ok) {
1454			// Handle HTTP errors
1455			return response.json().then(errData => {
1456				throw new Error(`Error: ${errData.message}`);
1457			});
1458		}
1459		return response.json();
1460	})
1461	.then(result => {
1462		if(result.result=="1") {
1463			window.location.href=returnUrl;
1464		}
1465		else {
1466			if(result.message!='') {
1467				alert('Fehler: '+result.message);
1468			}
1469		}
1470	})
1471	.catch(error => {
1472		 console.error('Error:', error);
1473	});
1474}
1475function deleteUserlist(list_id) {
1476   if(confirm('Möchten Sie dieses Liste vollständig löschen?')) {
1477		const url = base + 'index.php?c=action&act=delete-userlist';
1478		const returnUrl = base + 'mein-konto/listen/';
1479
1480		const data = {
1481			list_id: list_id,
1482		};
1483		
1484		sendFetchData(url,data,returnUrl);
1485   }
1486}
1487function deleteUserlistItem(list_id,prod_id) {
1488   if(confirm('Möchten Sie dieses Hörspiel von Ihrer Liste entfernen?')) {
1489		const url = base + 'index.php?c=action&act=delete-prod-from-userlist';
1490		const returnUrl = base + 'mein-konto/listen/'+list_id+'/';
1491
1492		const data = {
1493			list_id: list_id,
1494			prod_id: prod_id,
1495		};
1496		
1497		sendFetchData(url,data,returnUrl);
1498   }
1499}
1500function sortUserList(list_id,sort_type,sort_order) {
1501	const url = base + 'index.php?c=action&act=sort-userlist';
1502	const returnUrl = base + 'mein-konto/listen/'+list_id+'/';
1503
1504	const data = {
1505		userList: {
1506			id: list_id,
1507			sort_type: sort_type,
1508			sort_order: sort_order,
1509		}
1510	};
1511	
1512	sendFetchData(url,data,returnUrl);
1513}
1514function overwritePersonalSorting(list_id) {
1515	if(confirm('Diese Listenreihenfolge als eigene Sortierung speichern?')) {
1516		const url = base + 'index.php?c=action&act=overwrite-personal-sorting-userlist';
1517		const returnUrl = base + 'mein-konto/listen/'+list_id+'/';
1518
1519		const data = {
1520			userList: {
1521				id: list_id,
1522			}
1523		};
1524		
1525		sendFetchData(url,data,returnUrl);
1526	}
1527}
1528
1529// BFSG Compliant High Contrast System - Reliable Implementation
1530function toggleHighContrast() {
1531    const isEnabled = document.body.classList.contains('high-contrast-mode');
1532    
1533    if (isEnabled) {
1534        // Disable high contrast
1535        disableHighContrast();
1536    } else {
1537        // Enable high contrast
1538        enableHighContrast();
1539    }
1540}
1541
1542function enableHighContrast() {
1543    // Add body class
1544    document.body.classList.add('high-contrast-mode');
1545    
1546    // Store preference
1547    localStorage.setItem('highContrastMode', 'true');
1548    
1549    // Update cookie for server-side detection
1550    document.cookie = 'highContrastMode=true; path=/; max-age=31536000; SameSite=Lax';
1551    
1552    // Ensure CSS is loaded
1553    loadHighContrastCSS();
1554       
1555    // Announce to screen readers
1556    announceToScreenReader('Hoher Kontrast aktiviert');
1557}
1558
1559function disableHighContrast() {
1560    // Remove body class
1561    document.body.classList.remove('high-contrast-mode');
1562    
1563    // Store preference
1564    localStorage.setItem('highContrastMode', 'false');
1565    
1566    // Update cookie for server-side detection
1567    document.cookie = 'highContrastMode=false; path=/; max-age=31536000; SameSite=Lax';
1568    
1569    // Remove high contrast CSS
1570    removeHighContrastCSS();
1571    
1572    // Announce to screen readers
1573    announceToScreenReader('Normaler Kontrast aktiviert');
1574}
1575
1576function loadHighContrastCSS() {
1577    let cssLink = document.getElementById('high-contrast-css');
1578    
1579    if (!cssLink) {
1580        // Create new CSS link if it doesn't exist
1581        cssLink = document.createElement('link');
1582        cssLink.id = 'high-contrast-css';
1583        cssLink.rel = 'stylesheet';
1584        cssLink.type = 'text/css';
1585        cssLink.href = 'css/styles-high-contrast.css?' + (typeof window.cacheBuster !== 'undefined' ? window.cacheBuster : Date.now());
1586        
1587        // Add error handling
1588        cssLink.onerror = function() {
1589            console.error('Failed to load high contrast CSS');
1590            announceToScreenReader('Fehler beim Laden des hohen Kontrasts');
1591        };
1592        
1593        cssLink.onload = function() {
1594            console.log('High contrast CSS loaded successfully');
1595        };
1596        
1597        document.head.appendChild(cssLink);
1598    } else {
1599        // CSS already exists, ensure it's not disabled
1600        cssLink.disabled = false;
1601    }
1602}
1603
1604function removeHighContrastCSS() {
1605    const cssLink = document.getElementById('high-contrast-css');
1606    if (cssLink) {
1607        // Don't remove the link, just disable it to avoid flash
1608        cssLink.disabled = true;
1609    }
1610}
1611
1612function announceToScreenReader(message) {
1613    let liveRegion = document.querySelector('#contrast-announcement');
1614    
1615    if (!liveRegion) {
1616        liveRegion = createLiveRegion();
1617    }
1618    
1619    // Clear any existing announcement
1620    liveRegion.textContent = '';
1621    
1622    // Add new announcement after a brief delay to ensure it's read
1623    setTimeout(() => {
1624        liveRegion.textContent = message;
1625        
1626        // Clear after announcement
1627        setTimeout(() => {
1628            liveRegion.textContent = '';
1629        }, 3000);
1630    }, 100);
1631}
1632
1633function createLiveRegion() {
1634    let liveRegion = document.querySelector('#contrast-announcement');
1635    
1636    if (!liveRegion) {
1637        liveRegion = document.createElement('div');
1638        liveRegion.id = 'contrast-announcement';
1639        liveRegion.className = 'sr-only';
1640        liveRegion.setAttribute('aria-live', 'polite');
1641        liveRegion.setAttribute('aria-atomic', 'true');
1642        liveRegion.setAttribute('role', 'status');
1643        document.body.appendChild(liveRegion);
1644    }
1645    
1646    return liveRegion;
1647}
1648
1649function initHighContrast() {
1650    // Create live region immediately
1651    createLiveRegion();
1652    
1653    // Check multiple sources for high contrast preference
1654    const savedMode = localStorage.getItem('highContrastMode');
1655    const urlParams = new URLSearchParams(window.location.search);
1656    const urlContrast = urlParams.get('contrast') === 'high';
1657    const bodyHasClass = document.body.classList.contains('high-contrast-mode');
1658    const cssExists = document.getElementById('high-contrast-css') !== null;
1659    
1660    // Determine current state from multiple sources
1661    const shouldEnable = urlContrast || savedMode === 'true' || bodyHasClass || cssExists;
1662    
1663    if (shouldEnable) {
1664        // Ensure everything is properly set up for high contrast
1665        document.body.classList.add('high-contrast-mode');
1666        localStorage.setItem('highContrastMode', 'true');
1667        document.cookie = 'highContrastMode=true; path=/; max-age=31536000; SameSite=Lax';
1668        loadHighContrastCSS();
1669    } else {
1670        // Ensure everything is properly set up for normal contrast
1671        document.body.classList.remove('high-contrast-mode');
1672        localStorage.setItem('highContrastMode', 'false');
1673        document.cookie = 'highContrastMode=false; path=/; max-age=31536000; SameSite=Lax';
1674        removeHighContrastCSS();
1675    }
1676}
1677
1678// Mobile menu functions (enhanced for accessibility)
1679function toggleMobileMenu(button) {
1680    const menu = document.querySelector('.mm_toggle');
1681    const isExpanded = button.getAttribute('aria-expanded') === 'true';
1682    
1683    if (menu) {
1684        menu.style.display = isExpanded ? 'none' : 'block';
1685        button.setAttribute('aria-expanded', !isExpanded);
1686        button.setAttribute('aria-label', isExpanded ? 'Mobiles Menu öffnen' : 'Mobiles Menu schließen');
1687    }
1688}
1689
1690function toggleCategoriesMenu(button) {
1691    const menu = document.querySelector('.top_categories_list');
1692    const isExpanded = button.getAttribute('aria-expanded') === 'true';
1693    
1694    if (menu) {
1695        menu.style.display = isExpanded ? 'none' : 'block';
1696        button.setAttribute('aria-expanded', !isExpanded);
1697    }
1698}
1699
1700function toggleUserMenu(button) {
1701    const menu = document.querySelector('.top_usermenu');
1702    const isExpanded = button.getAttribute('aria-expanded') === 'true';
1703    
1704    if (menu) {
1705        menu.style.display = isExpanded ? 'none' : 'block';
1706        button.setAttribute('aria-expanded', !isExpanded);
1707    }
1708}
1709
1710/** Narrow viewport: disclosure toggle for #top-primary-panel (BFSG: aria-expanded, Escape, resize). */
1711function initTopNavDisclosure() {
1712	const toggle = document.getElementById('top-nav-toggle');
1713	const panel = document.getElementById('top-primary-panel');
1714	if (!toggle || !panel) return;
1715
1716	const mq = window.matchMedia('(max-width: 768px)');
1717
1718	function isNarrow() {
1719		return mq.matches;
1720	}
1721
1722	function setOpen(open) {
1723		if (!isNarrow()) {
1724			panel.classList.remove('is-open');
1725			toggle.setAttribute('aria-expanded', 'false');
1726			toggle.setAttribute('aria-label', 'Hauptmenü öffnen');
1727			return;
1728		}
1729		panel.classList.toggle('is-open', !!open);
1730		toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
1731		toggle.setAttribute('aria-label', open ? 'Hauptmenü schließen' : 'Hauptmenü öffnen');
1732		if (!open) {
1733			document.querySelectorAll('.top_usermenu.dd_visible').forEach((el) => el.classList.remove('dd_visible'));
1734		}
1735	}
1736
1737	toggle.addEventListener('click', function (e) {
1738		if (!isNarrow()) return;
1739		e.preventDefault();
1740		setOpen(!panel.classList.contains('is-open'));
1741	});
1742
1743	document.addEventListener('keydown', function topNavEsc(e) {
1744		if (e.key !== 'Escape') return;
1745		if (!isNarrow() || !panel.classList.contains('is-open')) return;
1746		setOpen(false);
1747		toggle.focus();
1748	});
1749
1750	function onMqChange() {
1751		if (!mq.matches) {
1752			setOpen(false);
1753		} else {
1754			document.querySelectorAll('.top_usermenu.dd_visible').forEach((el) => el.classList.remove('dd_visible'));
1755		}
1756	}
1757	if (typeof mq.addEventListener === 'function') {
1758		mq.addEventListener('change', onMqChange);
1759	} else if (typeof mq.addListener === 'function') {
1760		mq.addListener(onMqChange);
1761	}
1762
1763	panel.addEventListener('click', function (e) {
1764		if (!isNarrow() || !panel.classList.contains('is-open')) return;
1765		const a = e.target.closest('a[href]');
1766		if (!a) return;
1767		const href = a.getAttribute('href') || '';
1768		if (href === '' || href === '#') return;
1769		setOpen(false);
1770	});
1771}
1772
1773// Initialize high contrast when DOM is ready
1774$(document).ready(function() {
1775	initManualLoadMoreMode();
1776	// Initialize high contrast system
1777	initHighContrast();
1778	initTopNavDisclosure();
1779
1780	// Fallback delegated handler in case ensureScrollLoaderButton missed the element
1781	$(document).on('click.loadMoreFallback keydown.loadMoreFallback', '#scrollLoader', function(e) {
1782		var $btn = $(this);
1783		if ($btn.data('hs-loader-init')) return;
1784		if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
1785		if (e.type === 'keydown') e.preventDefault();
1786		var batchSize = isManualLoadMoreMode() ? 4 : 1;
1787		var loaded = loadNextContentRows($btn, batchSize);
1788		if (!loaded) {
1789			$btn.remove();
1790		}
1791		e.stopImmediatePropagation();
1792	});
1793
1794	// Handle mobile menu accessibility
1795	$('.mobileMenuButton').off('click').on('click', function(e) {
1796		e.preventDefault();
1797		toggleMobileMenu(this);
1798	});
1799});
1800
1801// Check if user prefers high contrast
1802function prefersHighContrast() {
1803	return window.matchMedia && (
1804		window.matchMedia('(prefers-contrast: high)').matches ||
1805		window.matchMedia('(prefers-contrast: more)').matches ||
1806		document.body.classList.contains('high-contrast-mode')
1807	);
1808}
1809
1810// Apply appropriate contrast settings based on user preferences
1811function applyContrastSettings() {
1812	const backgroundPopup = document.getElementById('backgroundPopup');
1813	if (!backgroundPopup) return;
1814	
1815	if (prefersHighContrast()) {
1816		backgroundPopup.style.backgroundColor = 'rgba(0, 0, 0, 1)';
1817		backgroundPopup.style.border = '2px solid #ffffff';
1818		backgroundPopup.style.opacity = '1';
1819	} else {
1820		backgroundPopup.style.backgroundColor = 'rgba(0,0,0,0.4)';
1821		backgroundPopup.style.opacity = '1';
1822	}
1823}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.