'use strict';

SetDesign(); // vorab, damit die Seite nicht "flackert"

function InitDefault()
{
	InitMenu();
	InitCSS();
	InitDesign();
	PrettifyText();
	InitLinksPreview();
	InitFratzenbuch();
	InitArticleLinks();
	InitLinks();
	InitImageCarousel();
	InitImageGrid();
	InitImageViewer();
	InitTableOfContents();
	InitTranslateLink();
	InitAudioPlayer();
	InitStats();
	InitArticle();
}

function InitPreview()
{
	PrettifyText();
	InitFratzenbuch();
	InitLinks();
	InitImageCarousel();
	InitImageGrid();
	InitImageViewer();
	InitTableOfContents();
	InitTranslateLink();
	InitAudioPlayer();
	InitArticle();
}

function InitCSS()
{
	document.documentElement.style.setProperty('--ScrollbarWidth', (window.innerWidth - document.documentElement.clientWidth) + "px");
	document.documentElement.style.setProperty('--MenuHeight', (document.getElementsByTagName('nav')[0].offsetHeight) + "px");
}

// Design (hell/dunkel)

function InitDesign()
{
	SetDesign();
	window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function() { SetDesign(); });
}

function SwitchDesign(design)
{
	localStorage.setItem('Design', design);
	SetDesign(design);
	UpdateDesignControls(design);
}

function SetDesign(design)
{
	if (!design)
	{
		design = localStorage.getItem('Design');
		if (!design || !['auto', 'dark', 'light'].includes(design)) design = 'auto';
		UpdateDesignControls(design);
	}

	if (design === 'auto')
		design = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';

	if (design === 'dark')
		document.documentElement.classList.add('dark');
	else
		document.documentElement.classList.remove('dark');
}

function UpdateDesignControls(design)
{
	UpdateDesignIcon('icon-design-auto', design);
	UpdateDesignIcon('icon-design-light', design);
	UpdateDesignIcon('icon-design-dark', design);
}

function UpdateDesignIcon(idIcon, design)
{
	var elemIcon = document.getElementById(idIcon);
	if (!elemIcon) return;
	var icon = (idIcon === 'icon-design-' + design) ? ' <b>✔</b>' : '';
	elemIcon.innerHTML = icon;
}

// Menü

function InitMenu()
{
	var elemNav = document.getElementsByTagName('nav')[0];
	var elemLogo = elemNav.getElementsByClassName('logo')[0];
	var elemMenu = elemNav.getElementsByClassName('menu')[0];

	var elemMenuEmpty = document.createElement('div');
	elemMenuEmpty.classList.add('menu-empty');
	elemMenuEmpty.innerHTML = '&nbsp;';
	elemNav.appendChild(elemMenuEmpty);

	var elemMenuOpenButton = document.createElement('div');
	elemMenuOpenButton.classList.add('menu-open-button');
	elemMenuOpenButton.innerHTML = '&#x2630;';
	elemNav.appendChild(elemMenuOpenButton);

	var elemMenuCloseButton = document.createElement('div');
	elemMenuCloseButton.classList.add('menu-close-button');
	elemMenuCloseButton.innerHTML = '&#x2715;';
	elemNav.appendChild(elemMenuCloseButton);

	function OpenMenu()
	{
		msSubMenuOpened = Date.now();

		elemNav.classList.remove('closed');
		elemNav.classList.add('opened');
	}

	function CloseMenu()
	{
		elemNav.classList.remove('opened');
		elemNav.classList.add('closed');

		CloseSubMenus(elemMenu);
	}

	function OpenSubMenu(elemSubMenu, elemItem)
	{
		msSubMenuOpened = Date.now();

		if (elemSubMenu.parentElement === elemMenu)
			CloseSubMenus(elemMenu);

		elemSubMenu.classList.remove('closed');
		elemSubMenu.classList.add('opened');

		if (elemNav.classList.contains('opened'))
		{
			elemSubMenu.style.top = '0';
			elemSubMenu.style.left = '0';
		}
		else
		{
			var itemRect = elemItem.getBoundingClientRect();
			elemSubMenu.style.top = (itemRect.top + itemRect.height) + 'px';
			elemSubMenu.style.left = '0';
			var width = elemSubMenu.clientWidth + elemSubMenu.offsetLeft;
			var left = Math.floor(itemRect.left);
			var right = left + width;
			var docWidth  = document.documentElement.clientWidth;
			if (right >= docWidth) left -= (right - docWidth + 1);
			if (left < 0) left = 0;
			elemSubMenu.style.left = left + 'px';
		}
	}

	function CloseSubMenu(elemSubMenu)
	{
		CloseSubMenus(elemSubMenu);
		msSubMenuOpened = Date.now();

		elemSubMenu.classList.remove('opened');
		elemSubMenu.classList.add('closed');
	}

	function CloseSubMenus(elemMenu)
	{
		var elemsSubMenu = elemMenu.getElementsByTagName('div');
		for (var i = 0; i < elemsSubMenu.length; i++)
			CloseSubMenu(elemsSubMenu[i]);
	}

	function OnClickSubMenu(evt)
	{
		var elemItem = evt.target;
		var elemSubMenu = elemItem.nextElementSibling;
		if (elemSubMenu && elemSubMenu.tagName.toLowerCase() === 'div')
		{
			if (elemSubMenu.classList.contains('closed'))
				OpenSubMenu(elemSubMenu, elemItem);
			else
				CloseSubMenu(elemSubMenu);
		}
	}

	function ResetMenu()
	{
		elemNav.classList.remove('closed');
		elemNav.classList.remove('opened');

		if (elemMenu.offsetWidth + elemLogo.offsetWidth >= elemNav.offsetWidth)
			elemNav.classList.add('closed');
	}

	function SetupMenu(elemMenu, bSubmenu)
	{
		var loc = MakeComparableURL(window.location.href);
		var elemsChild = elemMenu.children;
		for (var i = 0; i < elemsChild.length; i++)
		{
			var elemChild = elemsChild[i];
			if (elemChild.tagName.toLowerCase() === 'div')
			{
				CloseSubMenu(elemChild);
				SetupMenu(elemChild, true);
			}
			else if (elemChild.tagName.toLowerCase() === 'a')
			{
				if (elemChild.getAttribute('href'))
				{
					if (loc === MakeComparableURL(elemChild.href))
						elemChild.classList.add('active');
					else
						elemChild.classList.remove('active');
				}
				else
				{
					elemChild.addEventListener('click', OnClickSubMenu);
				}
			}
		}
		function MakeComparableURL(url)
		{
			var urlParts = url.split('?');
			var path = urlParts[0].replace(/\.php$/, '');
			if (path.slice(-1) === '/') path += 'index';
			urlParts[0] = path;
			url = urlParts.join('?');

			if (urlParts.length > 1)
			{
				var search = urlParts[1];
				var name = search.split('=')[0];
				if (name !== 'cats' && name !== 'tags' && name !== 'month' && name !== 'search')
					url = urlParts[0];
			}
			return url;
		}
	}

	SetupMenu(elemMenu);

	var prevWidth = elemNav.offsetWidth;
	window.addEventListener('resize', function()
	{
		CloseSubMenus(elemMenu);
		if (prevWidth != elemNav.offsetWidth)
		{
			ResetMenu(); 
			prevWidth = elemNav.offsetWidth;
		}
	});

	var msSubMenuOpened = 0;
	window.addEventListener('click', function(evt)
	{
		if (msSubMenuOpened > 0 && Date.now() - msSubMenuOpened < 100)
			return;

		CloseSubMenus(elemMenu);
		if (elemNav.classList.contains('opened'))
		{
			elemNav.classList.remove('opened');
			elemNav.classList.add('closed');
		}
	});

	elemMenuOpenButton.addEventListener('click', OpenMenu);
	elemMenuCloseButton.addEventListener('click', CloseMenu);

	ResetMenu();

	elemMenu.style.visibility = 'visible';
}

function InitArticleLinks()
{
	var elemsArticle = document.getElementsByTagName('article');

	for (var i = 0; i < elemsArticle.length; i++)
	{
		var elemArticle = elemsArticle[i];
		if (elemArticle.classList.contains('front')) continue;

		var elemsHeading = elemArticle.getElementsByTagName('h1');
		var elemHeading = elemsHeading[0];
		var elemsLink = elemHeading.getElementsByTagName('a');

		for (var k = 0; k < elemsLink.length; k++)
		{
			var elemLink = elemsLink[k];
			var url = elemLink.getAttribute('href');

			elemArticle.style.cursor = 'pointer';
			elemArticle.setAttribute('onclick', 'window.location="' + url + '";');
			elemArticle.classList.add('hover');
			break;
		}
	}
}

function InitLinks()
{
	var thisServer = window.location.hostname;

	var elemArticles = document.getElementsByTagName('article');
	for (var iArticle = 0; iArticle < elemArticles.length; iArticle++)
	{
		var elemArticle = elemArticles[iArticle];
		var elemsLink = elemArticle.getElementsByTagName('a');
		for (var iLink = 0; iLink < elemsLink.length; iLink++)
		{
			var elemLink = elemsLink[iLink];
			var href = elemLink.href;

			// "Translate this page" link
			var sourceLang = elemLink.getAttribute('x-data-translate-from');
			if (sourceLang !== null)
			{
				var url = GetGoogleTranslateUrl(sourceLang, href);
				if (url) elemLink.href = href = url;
			}

			// Bible verse
			var translation = (href.match(/^https:\/\/www.bibleserver.com\/([A-Z]*)\//) || ['', ''])[1];

			if (translation === 'LUT') translation = 'Lutherbibel 2017';
			else if (translation === 'ELB') translation = 'Elberfelder Bibel 2006';
			else if (translation === 'SLT') translation = 'Schlachter 2000';
			else if (translation === 'ZB') translation = 'Zürcher Bibel';
			else if (translation === 'MENG') translation = 'Menge Bibel';
			else if (translation === 'EU') translation = 'Einheitsübersetzung 2016';
			else if (translation === 'HFA') translation = 'Hoffnung für alle';
			else if (translation === 'GNB') translation = 'Gute Nachricht Bibel 2018';
			else if (translation === 'NLB') translation = 'Neues Leben. Die Bibel';
			else if (translation === 'NeÜ') translation = 'Neue evangelistische Übersetzung';
			else if (translation === 'NGÜ') translation = 'Neue Genfer Übersetzung';

			// Compose the link title
			var title = '';

			var linkText = elemLink.title || elemLink.innerText;
			if (translation !== '')
				title = '🕮\u{FE0E} ' + linkText + '\n文\u{FE0E} Übersetzung: ' + translation;
			else if (linkText)
				title = '➤\u{FE0E} ' + linkText;

			if (title)
			{
				var server = (href.match(/^(?:https|http):\/\/([^\/]*)\//) || ['', ''])[1];
				if (server !== '' && server !== thisServer)
					title += '\n🌎\u{FE0E} ' + server;
				else
					title += '\n✔︎\u{FE0E} ' + thisServer;
				elemLink.title = title;
			}
		}
	}
}

function InitLinksPreview()
{
	var params = new URLSearchParams(window.location.search);
	if (params.get('preview') === null) return;
	var thisServer = window.location.hostname;
	var elemsLink = document.getElementsByTagName('a');
	for (var iLink = 0; iLink < elemsLink.length; iLink++)
	{
		var elemLink = elemsLink[iLink];
		var href = elemLink.href;
		if (href === '' || href.indexOf('#') !== -1) continue;
		var server = (href.match(/^https:\/\/([^\/]*)\//) || ['', ''])[1];
		if (server !== '' && server !== thisServer) continue;
		href += (href.indexOf('?') === -1) ? '?' : '&';
		href += 'preview';
		elemLink.href = href;
	}
}

function InitImageCarousel()
{
	function GetItemInView(elemImageCarousel)
	{
		var rectImageCarousel = elemImageCarousel.getBoundingClientRect();
		var centerImageCarousel = (rectImageCarousel.right + rectImageCarousel.left) / 2;

		var elemsItem = elemImageCarousel.children;
		for (var i = 0; i < elemsItem.length; i++)
		{
			var elemItem = elemsItem[i];
			var rectItem = elemItem.getBoundingClientRect();

			if (elemItem.id)
			{
				var centerItem = (rectItem.right + rectItem.left) / 2;
				if (centerItem >= rectImageCarousel.left)
				{
					return elemItem.id; 
				}
			}
		}
		return undefined;
	}

	function ScrollToItem(elemImageCarousel, elemPageInfo, skip)
	{
		var elemItem = document.getElementById(GetItemInView(elemImageCarousel));
		var itemIDs = ItemIds[elemImageCarousel.id];
		var indexItem = itemIDs.indexOf(elemItem.id) + skip;
		if (indexItem < 0) indexItem = itemIDs.length - 1;
		else if (indexItem >= itemIDs.length) indexItem = 0;
		elemItem = document.getElementById(itemIDs[indexItem]);
		var itemPos = elemItem.offsetLeft - elemImageCarousel.offsetLeft;
		elemImageCarousel.scrollTo({ top: 0, left: itemPos, behavior: "smooth" });
	}

	function OnScroll(elemImageCarousel, elemPageInfo)
	{
		var itemIDs = ItemIds[elemImageCarousel.id];
		var elemItem = document.getElementById(GetItemInView(elemImageCarousel));
		var indexItem = itemIDs.indexOf(elemItem.id);
		if (indexItem !== -1) elemPageInfo.innerHTML = (indexItem + 1) + ' / ' + itemIDs.length;
	}

	var ItemIds = {};

	// Initialisierung
	var elemsImageCarousel = document.getElementsByClassName('image-carousel');
	for (var iImageCarousel = 0; iImageCarousel < elemsImageCarousel.length; iImageCarousel++)
	{
		var elemImageCarousel = elemsImageCarousel[iImageCarousel];
		var idImageCarousel = 'image-carousel-' + iImageCarousel;
		elemImageCarousel.id = idImageCarousel;

		ItemIds[idImageCarousel] = [];

		var elemsItem = elemImageCarousel.children;
		for (var iItem = 0; iItem < elemsItem.length; iItem++)
		{
			var elemItem = elemsItem[iItem];
			elemItem.id = idImageCarousel + '-image-' + (iItem + 1);
			ItemIds[idImageCarousel].push(elemItem.id);
		}

		var elemPageInfo = document.createElement('div');

		var elemButtonLeft = document.createElement('button');
		elemButtonLeft.innerHTML = '&#xEA38;';
		elemButtonLeft.addEventListener('click', ScrollToItem.bind(this, elemImageCarousel, elemPageInfo, -1));

		var elemButtonRight = document.createElement('button');
		elemButtonRight.innerHTML = '&#xEA34;';
		elemButtonRight.addEventListener('click', ScrollToItem.bind(this, elemImageCarousel, elemPageInfo, 1));

		var elemPanel = document.createElement('div'); // Flex-Container
		elemPanel.appendChild(elemButtonLeft);
		elemPanel.appendChild(elemPageInfo);
		elemPanel.appendChild(elemButtonRight);

		elemImageCarousel.parentNode.insertBefore(elemPanel, elemImageCarousel.nextSibling);

		elemPageInfo.innerHTML = '1 / ' + ItemIds[idImageCarousel].length;
		elemImageCarousel.addEventListener('scroll', OnScroll.bind(this, elemImageCarousel, elemPageInfo));
	}
}

// Bildbetrachter (ImageViewer)
// - Es werden alle Bilder auf der Seite berücksichtigt, die innerhalb einer FIGURE mit der Klasse "viewer" enthalten sind (<figure class="viewer"><img>...),
//   oder die innerhalb einer FIGURE innerhalb eines DIV mit der Klasse "viewer" enthalten sind (<div class="viewer"><figure><img>...)
// - Wenn zusätzlich zum src-Attribut des Bildes ein "data-src"-Attribut angegeben ist, wird das dort angegebene Bild angezeigt (z.B. ein Bild in höherer Auflösung).
// - Eine innerhalb von <figcaption> angegebene Bildbeschreibung wird im Bildbetrachter unterhalb des Bildes angezeigt, sofern vorhanden.
// - Ein im alt-Attribut zum Bild angegebener Text wird gegenüber <figcaption> bevorzugt. Innerhalb des alt-Textes dürfen keine HTML-eigenen Zeichen wie <>" verwendet werden. Links und einige Textformatierungen können in BB-Code angegeben werden, z.B.: [url=bild.jpg]Bild[/url] [i]kursiv[/i] [b]fett[/b]
// - Bilder können durch Angabe eines name-Attributes gruppiert werden (z.B. <figure class="viewer" name="gruppe1">). So können mehrere "Alben" auf einer Seite angelegt werden.
// - Bilder innerhalb von FIGUREs innerhalb eines DIV mit der Klasse "viewer" werden automatisch gruppiert.
// - Bei Klick auf ein Bild öffnet sich der Bildbetrachter und zeigt das angeklickte Bild an.
// - Mittels Klick auf die Links/Rechts-Buttons, Drücken der Links/Rechts-Pfeiltasten oder Links/Rechts-Wischen wird das vorherige/nächste Bild innerhalb der Gruppe (des "Albums") angezeigt.
// - Leertaste oder Klick auf das Bild oder daneben blenden die Beschriftung aus/ein und dimmt die Buttons, so dass das Bild (fast) ohne störende Überlagerungen betrachtet werden kann.
// - Links oben befinden sich 3 Buttons mit folgenden Funktionen: 1.) Bild-Link in neuem Tab öffnen, 2.) Bild einpassen/vergrößern, 3.) Fullscreen-Modus. Die Einstellungen 2) + 3.) werden gespeichert.
// - Klick auf den X-Button oben rechts oder Drücken der Esc-Taste schließt den Bildbetrachter.

function InitImageViewer()
{
	var elemViewer = null;
	var elemImage, swipeX, elemDivCaption, elemButtonPrev, elemButtonNext, elemButtonClose, elemButtonNewTab, elemLinkNewTab, elemButtonFitImage, elemButtonFullScreen;

	var elemsArticle = document.getElementsByTagName('article');
	if (!elemsArticle) return;
	var elemArticle = elemsArticle[0];

	var name;
	var images = {}; // hash of arrays with image data per group (name)
	var imageCount = {}; // hash of integers with number of images per group (name)
	var imageIndex = 0;
	var totalImageCount = 0;

	// collect image data of all images inside a 'viewer' element
	var elems = elemArticle.getElementsByClassName('viewer');
	for (var i = 0; i < elems.length; i++)
	{
		var elem = elems[i];
		var elemName = elem.getAttribute('name');
		if (elem.tagName.toLowerCase() === 'figure')
			AddFigure(elem, elemName);
		else if (elem.tagName.toLowerCase() === 'div')
		{
			var elemsFigure = elem.getElementsByTagName('figure');
			for (var f = 0; f < elemsFigure.length; f++)
				AddFigure(elemsFigure[f], elemName ? elemName : 'group-div-' + (i + 1)); // if no name is given for the DIV, then create one
		}
	}

	function AddFigure(elemFigure, groupName)
	{
		name = groupName;
		var figcaption = '';
		var elemsCaption = elemFigure.getElementsByTagName('figcaption');
		if (elemsCaption.length > 0) figcaption = elemsCaption[0].innerHTML;

		var elemsImage = elemFigure.getElementsByTagName('img');
		for (var j = 0; j < elemsImage.length; j++)
		{
			var elemImage = elemsImage[j];
			var src = elemImage.getAttribute('data-src');
			if (!src || src === '') src = elemImage.getAttribute('src');
			if (!src || src === '') continue;

			var caption = elemImage.getAttribute('alt');
			if (!caption) caption = '';
			if (caption !== '')
			{
				caption = caption.replace(/\[url=([^\]]*)\]([^\[]*)\[\/url\]/g, '<a href="$1" target="_blank">$2</a>');
				caption = caption.replaceAll('[', '<');
				caption = caption.replaceAll(']', '>');
			}
			if (caption === '' && figcaption) caption = figcaption;

			if (images[name] === undefined) images[name] = [];
			if (imageCount[name] === undefined) imageCount[name] = 0;

			images[name].push({ 'src': src, 'caption': caption });

			// surround the image with a new div to display an overlay icon
			var elemDivImage = document.createElement('div');
			elemDivImage.classList.add('viewer-img-container');
			elemImage.replaceWith(elemDivImage);
			elemDivImage.appendChild(elemImage);

			elemDivImage.addEventListener('click', Viewer.bind(this, { 'name': name, 'imageIndex': imageCount[name] }, null), false);
			elemDivImage.style.cursor = 'zoom-in';

			imageCount[name]++;
			totalImageCount++;
		}
	}

	if (totalImageCount > 0)
		SetupViewer();

	function SetupViewer()
	{
		// create viewer
		elemViewer = document.createElement('div');
		elemViewer.classList.add('view');
		elemViewer.classList.add('click-target');
		elemViewer.addEventListener('click', Toggle, false);

		elemImage = document.createElement('img');
		elemImage.classList.add('click-target');
		elemViewer.appendChild(elemImage);

		elemDivCaption = document.createElement('div');
		elemDivCaption.classList.add('caption');
		elemViewer.appendChild(elemDivCaption);

		elemButtonPrev = document.createElement('button');
		elemButtonPrev.innerHTML = '&#xEA38;';
		elemButtonPrev.classList.add('prev');
		elemButtonPrev.addEventListener('click', function() { Prev(1); }, false);
		elemViewer.appendChild(elemButtonPrev);

		elemButtonNext = document.createElement('button');
		elemButtonNext.innerHTML = '&#xEA34;';
		elemButtonNext.classList.add('next');
		elemButtonNext.addEventListener('click', function() { Next(1); }, false);
		elemViewer.appendChild(elemButtonNext);

		elemButtonClose = document.createElement('button');
		elemButtonClose.innerHTML = '&#xEA0F;';
		elemButtonClose.classList.add('close');
		elemButtonClose.addEventListener('click', Close, false);
		elemViewer.appendChild(elemButtonClose);

		elemButtonNewTab = document.createElement('button');
		elemButtonNewTab.innerHTML = '&#xEA7E;';
		elemButtonNewTab.classList.add('newtab');
		elemLinkNewTab = document.createElement('a');
		elemLinkNewTab.setAttribute('target', '_blank');
		elemLinkNewTab.appendChild(elemButtonNewTab);
		elemViewer.appendChild(elemLinkNewTab);

		elemButtonFitImage = document.createElement('button');
		elemButtonFitImage.innerHTML = '&#xE987;';
		elemButtonFitImage.classList.add('fitimg');
		elemButtonFitImage.addEventListener('click', FitImage, false);
		elemViewer.appendChild(elemButtonFitImage);

		elemButtonFullScreen = document.createElement('button');
		elemButtonFullScreen.innerHTML = '&#xE989;';
		elemButtonFullScreen.classList.add('fullscreen');
		elemButtonFullScreen.addEventListener('click', FullScreen, false);
		elemViewer.appendChild(elemButtonFullScreen);

		document.body.appendChild(elemViewer);

		FitImage(null);

		// keyboard shortcuts
		document.documentElement.addEventListener('keydown', function(event)
		{
			if (!elemViewer.classList.contains('open'))
				return;

			if (event.code === 'ArrowLeft' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Prev(1); }
			else if (event.code === 'ArrowRight' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Next(1); }
			else if (event.code === 'PageUp' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Prev(10); }
			else if (event.code === 'PageDown' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Next(10); }
			else if (event.code === 'Home' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Prev(1000000); }
			else if (event.code === 'End' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Next(1000000); }
			else if (event.code === 'Space' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Toggle(); }
			else if (event.code === 'Escape' && !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
			{ event.preventDefault(); Close(); }
		});

		// swipe actions
		var touchStartX, touchEndX, touchStartY, touchEndY;
		elemViewer.addEventListener('touchstart', function(evt)
		{
			touchStartX = evt.changedTouches[0].screenX;
			touchStartY = evt.changedTouches[0].screenY;
		});
		elemViewer.addEventListener('touchend', function(evt)
		{
			touchEndX = evt.changedTouches[0].screenX;
			touchEndY = evt.changedTouches[0].screenY;
			if (imageCount[name] > 1 && Math.abs(touchEndX - touchStartX) * 0.577 > Math.abs(touchEndY - touchStartY)) // horizontal swipe angle > 30°
			{
				swipeX = touchEndX - touchStartX;
				if (touchEndX < touchStartX - 20) Next(1);
				else if (touchEndX > touchStartX + 20) Prev(1);
				else swipeX = 0, elemImage.style.left = '0';
			}
			else elemImage.style.left = '0';
		});
		elemViewer.addEventListener('touchmove', function(evt)
		{
			var touchMoveX = evt.touches[0].screenX;
			var touchMoveY = evt.touches[0].screenY;
			elemImage.style.left = '' + (touchMoveX - touchStartX) + 'px';
		});
		elemViewer.addEventListener('touchcancel', function(evt)
		{
			elemImage.style.left = '0';
		});
	}

	function Viewer(imageData, callback)
	{
		if (imageData) // Bild wurde angeklickt
		{
			Open();
			name = imageData.name;
			imageIndex = imageData.imageIndex;
			Toggle(null, true);
			elemButtonPrev.style.visibility = elemButtonNext.style.visibility = (imageCount[name] > 1 ? 'visible' : 'hidden');
		}

		swipeX = 0;
		elemImage.addEventListener('load', callback ? callback : function() { elemImage.style.visibility = 'visible'; }, { once: true });
		elemImage.src = images[name][imageIndex].src;
		elemLinkNewTab.setAttribute('href', elemImage.src);

		var caption = images[name][imageIndex].caption;
		var hasCaption = (caption !== '');
		if (hasCaption) caption += ' (';
		caption += (imageIndex + 1) + '/' + imageCount[name];
		if (hasCaption) caption += ')';
		elemDivCaption.innerHTML = caption;
	}

	function Prev(amount)
	{
		var i = imageIndex;
		i -= amount;
		if (i < 0)
			i = (amount === 1 ? imageCount[name] -1 : 0);
		if (i !== imageIndex)
		{
			imageIndex = i;
			MoveImage(0, 1, function()
			{
					Viewer(null, function() { MoveImage(-1, 0); });
			});
		}
	}

	function Next(amount)
	{
		var i = imageIndex;
		i += amount;
		if (i >= imageCount[name])
			i = (amount === 1 ?  0 : imageCount[name] - 1);
		if (i !== imageIndex)
		{
			imageIndex = i;
			MoveImage(0, -1, function()
			{
					Viewer(null, function() { MoveImage(1, 0); });
			});
		}
	}

	function Toggle(event, visible)
	{
		if (event && !event.target.classList.contains('click-target'))
			return;
		
		if (visible === undefined)
			visible = (elemDivCaption.style.opacity === '0');

		elemDivCaption.style.opacity = (visible ? '1.0' : '0');
		var opacity = (visible ? '1.0' : '0.2');
		elemButtonPrev.style.opacity = opacity;
		elemButtonNext.style.opacity = opacity;
		elemButtonClose.style.opacity = opacity;
		elemButtonNewTab.style.opacity = opacity;
		elemButtonFitImage.style.opacity = opacity;
		elemButtonFullScreen.style.opacity = opacity;
	}

	function FitImage(evt)
	{
		var fit = (localStorage.getItem('ImageViewer_FitImage') === 'true' ? true : false);
		if (evt) fit = !fit;
		localStorage.setItem('ImageViewer_FitImage', fit ? 'true' : 'false');
		if (fit)
		{
			elemImage.classList.add('fit');
			elemButtonFitImage.innerHTML = '&#xE988;';
		}
		else
		{
			elemImage.classList.remove('fit');
			elemButtonFitImage.innerHTML = '&#xE987;';
		}
	}

	function FullScreen(evt)
	{
		var fullscreen = (localStorage.getItem('ImageViewer_FullScreen') === 'true' ? true : false);
		if (evt) fullscreen = !fullscreen;
		localStorage.setItem('ImageViewer_FullScreen', fullscreen ? 'true' : 'false');
		if (fullscreen)
		{
			OpenFullscreen(elemViewer);
			elemButtonFullScreen.innerHTML = '&#xE98A;';
		}
		else
		{
			CloseFullscreen();
			elemButtonFullScreen.innerHTML = '&#xE989;';
		}
	}

	function Open()
	{
		elemViewer.classList.add('open');
		document.documentElement.classList.add('noscroll');
		FullScreen(null);
	}

	function Close()
	{
		elemViewer.classList.remove('open');
		elemImage.style.visibility = 'hidden';
		document.documentElement.classList.remove('noscroll');
		CloseFullscreen();
	}

	function OpenFullscreen(elem)
	{
		if (elem.requestFullscreen)
			elem.requestFullscreen();
		else if (elem.webkitRequestFullscreen) /* Safari */
			elem.webkitRequestFullscreen();
		else if (elem.msRequestFullscreen) /* IE11 */
			elem.msRequestFullscreen();
	}

	function CloseFullscreen()
	{
		if (document.exitFullscreen)
			document.exitFullscreen().catch(function(){});
		else if (document.webkitExitFullscreen) /* Safari */
			document.webkitExitFullscreen();
		else if (document.msExitFullscreen) /* IE11 */
			document.msExitFullscreen();
	}

	function MoveImage(from, to, callback)
	{
		var stepCount = 10;
		var stepTime = 10; // milliseconds

		var docWidth  = document.documentElement.clientWidth;
		from *= docWidth; from += swipeX; to *= docWidth;
		var stepWidth = docWidth / stepCount;

		if (to < from) MoveImageLeft(from, to, callback);
		else if (to > from) MoveImageRight(from, to, callback);

		function MoveImageLeft(from, to, callback)
		{
			if (from < to) from = to;
			elemImage.style.left = '' + from + 'px';
			if (to < from)
			{
				from -= stepWidth;
				setTimeout(function() { MoveImageLeft(from, to, callback); }, stepTime);
			}
			else if (callback) (callback)();
		}

		function MoveImageRight(from, to, callback)
		{
			if (from > to) from = to;
			elemImage.style.left = '' + from + 'px';
			if (to > from)
			{
				from += stepWidth;
				setTimeout(function() { MoveImageRight(from, to, callback); }, stepTime);
			}
			else if (callback) (callback)();
		}
	}
}

// Bildraster (ImageGrid)
// Ordnet die enthaltenen Bilder automatisch in Form eines Rasters/Gitters an.
// Die Anzahl der Spalten hängt von der verfügbaren Breite ab.
// Die Bilder (Vorschaubilder/Thumbnails) sind quadratisch und haben eine Mindestgröße von 200px.
// Für Bilder, die sich innerhalb einer FIGURE befinden, wird der Bildbetrachter (ImageViewer) aktiviert.
// Eine Bildbeschreibung in der FIGCAPTION wird nicht angezeigt, jedoch vom Bildbetrachter (ImageViewer) benutzt
// und außerdem als TITLE gesetzt, so dass er beim Darüberhalten des Mauszeigers als Tooltip erscheint.
// Anwendung:
//	<div class="image-grid cover viewer">
//		<figure><img src="..."><figcaption>...</figcaption></figure>
//		...
//	</div>
// Die optionale Klasse "cover" bewirkt, dass die Bildquadrate vollständig ausgefüllt (und dazu ggf. beschnitten) werden.
// Ohne die Klasse "cover" werden die Bilder vollständig angezeigt, aber ggf. verkleinert und mit "Füll-Hintergrund".

function InitImageGrid()
{
	var elemsArticle = document.getElementsByTagName('article');
	if (!elemsArticle) return;
	var elemArticle = elemsArticle[0];

	var elemsImageGrid = elemArticle.getElementsByClassName('image-grid');
	for (var i = 0; i < elemsImageGrid.length; i++)
	{
		var elemImageGrid = elemsImageGrid[i];

		var elemsFigure = elemImageGrid.getElementsByTagName('figure');
		for (var f = 0; f < elemsFigure.length; f++)
		{
			var elemFigure = elemsFigure[f];
			var elemsFigCaption = elemFigure.getElementsByTagName('figcaption');
			if (elemsFigCaption)
				elemFigure.setAttribute('title', elemsFigCaption[0].innerText);
		}
	}
}

// Automatisches Inhaltsverzeichnis für Artikel erstellen
// - Erstellt anhand der Überschriften (H1...H6) innerhalb eines Artikels ein Inhaltsverzeichnis.
// - Es werden nur Überschriften berücksichtigt, die eine ID haben. Die ID wird als Sprungziel verwendet.
// - Das erzeugte Inhaltsverzeichnis wird in Elemente mit der Klasse "toc" eingefügt.
// - Es werden nur Elemente in das Inhaltsverzeichnis eingefügt, deren Attribute "lang" (falls vorhanden) denselben Wert haben.
// - Wenn das Element mit der Klasse "toc" ein Attribut "data-list-tag" enthält, wird dieses anstelle von "ol" verwendet.
// - Per CSS kann das Inhaltsverzeichnis über den Selektor ".toc ol" gestyled werden.
function InitTableOfContents()
{
	var elemsArticle = document.getElementsByTagName('article');
	for (var a = 0; a < elemsArticle.length; a++)
	{
		var elemArticle = elemsArticle[a];
		var elemsTOC = elemArticle.getElementsByClassName('toc');
//		var html = '';
		for (var d = 0; d < elemsTOC.length; d++)
		{
			var elemTOC = elemsTOC[d];
			var langTOC = elemTOC.getAttribute('lang');
//			if (html === '') html = CreateTOC(elemArticle, langTOC);
//			elemTOC.innerHTML += html;
			elemTOC.innerHTML += CreateTOC(elemArticle, langTOC);
		}
	}

	function CreateTOC(elemArticle, langTOC)
	{
		var elems = elemArticle.getElementsByTagName('*');
		var levelStart = -1
		var level = levelStart;
		var html = '';
		var ids = [];
		for (var i = 0; i < elems.length; i++)
		{
			var elem = elems[i];
			var tagName = elem.tagName;
			var matches = tagName.match(/^H([1-6])$/);
			if (matches)
			{
				if (!elem.id) continue;
				var langElem = elem.getAttribute('lang');
				if (langTOC && langElem && langTOC !== langElem) continue;
				var txtDuplicateId = '';
				if (ids.includes(elem.id))
					txtDuplicateId = '<span class="error"> &ndash; DUPLICATE ID: ' + elem.id + '</span>';
				else
					ids.push(elem.id);

				var levelNew = matches[1];
				if (levelStart === -1)
				{
					levelStart = levelNew - 1;
					level = levelStart;
				}

				if (level < levelNew)
				{
					while (level < levelNew)
					{
						html += '<ol><li>';
						level++;
					}
					html += '<a href="#' + elem.id + '">' + elem.innerText + '</a>' + txtDuplicateId;
				}
				else
				{
					while (level > levelNew)
					{
						html += '</li></ol>';
						level--;
					}
					html += '</li><li><a href="#' + elem.id + '">' + elem.innerText + '</a>' + txtDuplicateId;
				}
			}
		}

		while (level > levelStart)
		{
			html += '</li></ol>';
			level--;
		}

		return html;
	}
}

function InitTranslateLink()
{
	if (window.location.hostname.includes('.translate.goog')) return;

	var elemsTranslate = document.getElementsByClassName('translate');
	for (var i = 0; i < elemsTranslate.length; i++)
	{
		var elemTranslate = elemsTranslate[i];
		var sourceLang = elemTranslate.getAttribute('x-data-translate-from');
		if (sourceLang !== null)
		{
			var url = GetGoogleTranslateUrl(sourceLang);
			if (url)
			{
				var linkText = elemTranslate.innerHTML;
				if (!linkText) linkText = '<span style="position: relative; top: -0.1em; font-size: initial;">文</span><span style="position: relative; top: 0.1em; left: -0.1em; font-size: initial;">A</span> <?php echo GetString("TranslateThisPage");?>';
				elemTranslate.innerHTML = '<a href="' + url + '" class="button-link">' + linkText + '</a>';
				elemTranslate.classList.remove('hidden');
			}
		}
	}
}

function GetGoogleTranslateUrl(sourceLang, url)
{
	if (!sourceLang) sourceLang = document.documentElement.getAttribute('lang');

	var userLang = navigator.language.split(/[-_]/)[0];
	if (sourceLang === userLang) return null; // no translation needed

	// url = 'http://dev.gottsucher.de/translate/test#anchor'
	if (url)
	{
		var [prot, uri]  = url.split('//'); // -> prot = 'http:', uri = 'dev.gottsucher.de/translate/test#anchor'
		var [host, path] = uri.split(/\/(.*)/); // -> host = 'dev.gottsucher.de', path = 'translate/test#anchor'
		var [path, hash] = path.split('#'); // -> path = 'translate/test', hash = 'anchor'
		path = '/' + path; // -> path = '/translate/test'
		hash = hash ? '#' + hash : ''; // -> hash = '#anchor'
	}
	else
	{
		var prot = window.location.protocol; // -> prot = 'http:'
		var host = window.location.hostname; // -> host = 'dev.gottsucher.de'
		var path = window.location.pathname; // -> path = '/translate/test'
		var hash = window.location.hash; // -> hash = '#anchor'
	}

	var subdomain = host.replaceAll('.', '-'); // -> subdomain = 'dev-gottsucher-de'

	var params = [];
	if (prot === 'http:') params.push('_x_tr_sch=http');
	params.push('_x_tr_sl=' + sourceLang);
	params.push('_x_tr_tl=' + userLang);
	params.push('_x_tr_hl=' + userLang);
	params.push('_x_tr_pto=wapp');

	var url = 'https://' + subdomain + '.translate.goog' + path + '?' + params.join('&') + hash;

	return url;
}

// Artikeltext verschönern
function PrettifyText()
{
	if (typeof g_noPrettify !== 'undefined' && g_noPrettify === true) return;
	var elemsArticle = document.getElementsByTagName('article');
	for (var a = 0; a < elemsArticle.length; a++)
	{
		var elemArticle = elemsArticle[a];
		var html = elemArticle.innerHTML.split(''); // HTML-Array mit den Zeichen des Artikeltextes inklusive aller HTML-Tags
		var text = []; // Text-Array mit den Zeichen des Artikeltextes ohne die HTML-Tags (nur der reine Text)
		var text2html = []; // Zuordnungs-Array für die Indizes der Zeichen im Text-Array zu denen im HTML-Array

		// 1. Durchgang: Den Artikeltext von den HTML-Tags befreien und in das Text-Array kopieren; dabei das Zuordnungs-Array erstellen
		var bTag = false; // innerhalb eines HTML-Tags
		var bIgnore = false; // innerhalb eines zu ignorierenden Abschnittes des HTML-Textes
		var tag = ''; // aktuell gelesenes HTML-Tag
		var n = html.length;
		for (var i = 0; i < n; i++)
		{
			var c = html[i];
			if (c === '<') // Beginn des HTML-Tags
			{
				bTag = true;
				tag = '';
			}
			else if (c === '>') // Ende des HTML-Tags
			{
				bTag = false;
				// JavaScript-Abschnitt ignorieren:
				if (tag === 'script')
				{
					bIgnore = true;
				}
				else if (tag === '/script')
				{
					bIgnore = false;
				}
				// Besondere HTML-Tags, die für den 2. Durchgang berücksichtigt werden müssen:
				else if (tag === 'br')
				{
					text.push("\n");
					text2html.push(-1);
				}
			}
			else if (bTag) // Innerhalb des HTML-Tags
			{
				tag += c;
			}
			else if (!bIgnore) // Außerhalb von HTML-Tags und zu ignorierender Abschnitte
			{
				text.push(c);
				text2html.push(i);
			}
		}

		// 2. Durchgang: Das Text-Array durchlaufen, die zu ersetzenden Zeichen bestimmen und ins HTML-Array zurückschreiben
		var quotes = 0;
		var charsBeforeDash = "\r\n\t ";
		var charsAfterDash = "\r\n\t ,;";
		var n = text.length;
		for (var i = 0; i < n; i++)
		{
			var cBefore = (i > 0) ? text[i - 1] : '';
			var cCurrent = text[i];
			var cAfter = (i < n - 1) ? text[i + 1] : '';
			var c = cCurrent;

			// Gerade Anführungszeichen durch "typographische" (öffnende und schließende) Anführungszeichen ersetzen.
			// Es wird vorausgesetzt, dass auf ein öffnendes immer auch ein schließendes Anführungszeichen folgt.
			// Verschachtelungen von Texten in Anführungszeichen sind mit diesem einfachen Algorithmus nicht möglich. Aus
			// "Machen Sie Gottes Wort zu Ihrer "Gedankennahrung"!" wird daher
			// „Machen Sie Gottes Wort zu Ihrer “Gedankennahrung„!“ und nicht
			// „Machen Sie Gottes Wort zu Ihrer „Gedankennahrung“!“ (wie es eigentlich gemeint ist).
			if (c === '"')
			{
				c = (quotes % 2) ? '“' : '„';
				quotes++;
			}
			// Hochkommata durch Apostrophe ersetzen
			else if (c === "'")
			{
				c = '’';
			}
			// Bindestriche durch Gedankenstriche ersetzen
			else if (c === "-")
			{
				// html = html.replace(/([ >])-([ <])/g, '$1 – $2');
				if ((cBefore == '' || charsBeforeDash.indexOf(cBefore) != -1) && (cAfter  == '' || charsAfterDash.indexOf(cAfter) != -1))
				{
					c = '–';
				}
			}

			if (c !== cCurrent)
				html[text2html[i]] = c;
		}

		// Das verschönerte HTML ins DOM schreiben
		elemArticle.innerHTML = html.join('');
	}
}

//
// Audio-Player initialisieren
//

function InitAudioPlayer()
{
	var elemsPlayer = document.getElementsByTagName('audio');

	for (var i = 0; i < elemsPlayer.length; i++)
	{
		var elemPlayer = elemsPlayer[i];

		elemPlayer.addEventListener('play', function() {
			PlayerStop(this);
		}, false);

		elemPlayer.addEventListener('playing', function() {
			this.classList.add("playing");
		}, false);

		elemPlayer.addEventListener('pause', function() {
			this.classList.remove("playing");
		}, false);

		elemPlayer.addEventListener('ended', function() {
			this.classList.remove("playing");
			PlayerNext(this);
		}, false);
	}
}

function PlayerStop(elemExcept)
{
	var elemsPlayer = document.getElementsByTagName('audio');

	for (var i = 0; i < elemsPlayer.length; i++)
	{
		var elemPlayer = elemsPlayer[i];
		if(elemExcept === undefined || elemExcept !== elemPlayer)
		{
			elemPlayer.pause();
			if (elemPlayer.currentTime > 0) // Workaround für MSIE11
				elemPlayer.currentTime = 0;
		}
	}
}

function PlayerNext(elemCurrent)
{
//	var elemsPlayer = document.getElementsByTagName('audio');
	var elemsPlayer = document.querySelectorAll('figure.playlist audio');

	for (var i = 0; i < elemsPlayer.length; i++)
	{
		var elemPlayer = elemsPlayer[i];
		if(elemCurrent === elemPlayer)
		{
			var elemPlayerNext = elemsPlayer[i + 1];
			if (elemPlayerNext !== undefined)
			{
				elemPlayerNext.play();
				return;
			}
		}
	}
}

function InitStats()
{
	if (localStorage.getItem('InitStats') === 'skip') return;
	var xhr = new XMLHttpRequest();
	xhr.open('POST', 'lib/cgi-bin/stats.pl', true);
	xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	xhr.send
	(
		'target=' + encodeURIComponent(window.location) +
		'&referrer=' + encodeURIComponent(document.referrer) +
		'&language=' + encodeURIComponent(navigator.language) +
		'&winsize=' + encodeURIComponent(window.innerWidth + 'x' + window.innerHeight) +
		'&userAgent=' + encodeURIComponent(navigator.userAgent) +
		'&errorCode=' + '<?php echo get_cats() === "error" ? get_tags() : ""; ?>'
	);
}

function FromFratzenbuch()
{
	return window.location.href.includes('fbclid') || document.referrer.includes('facebook.com');
}

function InitFratzenbuch()
{
	if (FromFratzenbuch())
	{
		var elemsLink  = document.getElementsByTagName('A');
		for (var i = 0; i < elemsLink.length; i++)
		{
			var elemLink = elemsLink[i];
			var href = elemLink.getAttribute('href');
			if (href && (href.substr(0,4) !== 'http' || href.includes('/' + window.location.hostname)))
			{
				href += href.includes('?') ? '&fbclid' : '?fbclid';
				elemLink.setAttribute('href', href);
			}
		}
	}
}

// Wenn der Artikel JavaScript enthält, das Event-Handler hinzufügt, dann darf dieses erst nach PrettifyText() aufgerufen werden.
// Hierzu im Artikel die Funktion OnLoadLocal() definieren und die Initialisierungen dort vornehmen.

function InitArticle()
{
	if (typeof OnLoadLocal === "function")
		OnLoadLocal();
}
