Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
//<nowiki> /*******************************************************************************  * ListingInfo v1.6  * Date: 2025-01-11  * This script is called as a gadget  * Presents a dialog showing characteristics of a single listing  * Original author: Roland Unger  * Support of desktop and mobile views  * Documentation: https://de.wikivoyage.org/wiki/Wikivoyage:ListingInfo.js  ******************************************************************************/ /* eslint-disable mediawiki/class-doc */  ( function ( $, mw ) { 	'use strict';  	var listingPopup = function() {  		/******************* Internationalization *****************************/  		const version = '2025-01-11';  		// strings depending on user language 		const userStrings = { 			de: { 				// headers 				booking:  'Buchung, Vergleich und Bewertung', 				contact:  'Kontakt', 				credit:   'Kreditkarten', 				features: 'Ausstattung', 				figure:   'Bild', 				hours:    'Zeiten', 				map:      'Lagekarte',  				// contacts 				email:    'E-Mail', 				fax:      'Fax', 				mobile:   'Mobil', 				phone:    'Tel.', 				skype:    'Skype', 				tollfree: 'Tel. gebührenfrei', 				web:      'Internet',  				// actionButtons 				buttonText:      'info', 				buttonTooltip:   'Öffnet ein Popup-Fenster mit den wichtigsten vCard-Daten und teilweise mit Buchungsmöglichkeiten an',  				bookingTooltip:  'Buchungslinks anzeigen', 				notCompleteHint: 'Die nachfolgende Liste erhebt keinen Anspruch auf Vollständigkeit.', 				closeTooltip:    'Dialogfenster schließen', 				contactTooltip:  'Kontakt anzeigen', 				featuresTooltip: 'Ausstattung anzeigen', 				figureTooltip:   'Abbildung anzeigen', 				mapTooltip:      'Karte anzeigen', 				taxiTooltip:     'Bringen Sie mich zu',  				rssTooltip:      'RSS-Feed der Einrichtung', 				urlTooltip:      'Website der Einrichtung', 				extraMaps:       '→ Weitere Karten', 				extraMapsTitle:  'Spezialseite mit weiteren Kartenlinks' 			}, 			en: { 				// headers 				booking:  'Booking, comparison, and evaluation', 				contact:  'Contact', 				credit:   'Credit cards', 				features: 'Features', 				figure:   'Image', 				hours:    'Hours', 				map:      'Position map',  				// contacts 				email:    'Email', 				fax:      'Fax', 				mobile:   'Mobile', 				phone:    'Phone', 				skype:    'Skype', 				tollfree: 'Tollfree', 				web:      'Internet',  				// actionButtons 				buttonText:      'info', 				buttonTooltip:   'Opens a pop-up window with the most important listing data and partly with booking opportunities',  				bookingTooltip:  'Shows booking links', 				notCompleteHint: 'The following list does not claim to be complete.', 				closeTooltip:    'Closes the dialog', 				contactTooltip:  'Shows contacts', 				featuresTooltip: 'Shows features', 				figureTooltip:   'Shows an image', 				mapTooltip:      'Shows a position map', 				taxiTooltip:     'Please take me to',  				rssTooltip:      'RSS feed of this institution', 				urlTooltip:      'Website of this institution', 				extraMaps:       '→ Extra maps', 				extraMapsTitle:  'Special page with additional map sources' 			}, 			es: { 				// headers 				booking:  'Reserva, comparación y valoración', 				contact:  'Contacto', 				credit:   'Tarjetas de crédito', 				features: 'Características', 				figure:   'Imagen', 				hours:    'Horas', 				map:      'Mapa de posiciones',  				// contacts 				email:    'Correo electrónico', 				fax:      'Fax', 				mobile:   'Móvil', 				phone:    'Teléfono', 				skype:    'Skype', 				tollfree: 'Número gratuito', 				web:      'Internet',  				// actionButtons 				buttonText:      'info', 				buttonTooltip:   'Abre una ventana emergente con los datos más importantes del listado y en parte con oportunidades de reserva.',  				bookingTooltip:  'Mostrar enlaces de reserva', 				notCompleteHint: 'La siguiente lista no pretende ser completa.', 				closeTooltip:    'Cerrar ventana de diálogo', 				contactTooltip:  'Mostrar contacto', 				featuresTooltip: 'Mostrar aracterísticas', 				figureTooltip:   'Mostrar imagen', 				mapTooltip:      'Mostrar apa de posiciones', 				taxiTooltip:     'Please take me to',  				rssTooltip:      'Canal RSS de esta institución', 				urlTooltip:      'Sitio web de esta institución', 				extraMaps:       '→ Mapas adicionales', 				extraMapsTitle:  'Página especial con fuentes de mapas adicionales' 			} 		};  		const translations = { 			de: { takeRequest: 'Bitte bringen Sie mich zu…', 				name: 'Name', comment: 'Kommentar', address: 'Anschrift', directions: 'Wegbeschreibung' }, 			en: { takeRequest: 'Please take me to…', 				name: 'Name', comment: 'Comment', address: 'Address', directions: 'Directions' }, 			af: { takeRequest: 'Neem my asseblief na …', 				name: 'Naam', comment: 'Opmerking', address: 'Adres', directions: 'Hoe om daar te kom' }, // native speaker 			ar: { takeRequest: 'من فضلك ، أريد الذهاب إلى…', 				name: 'الاسم', comment: 'التعليق', address: 'العنوان', directions: 'الموقع والاتجاه' }, // native speaker 			az: { takeRequest: 'Xahiş edirəm məni … aparın', 				name: 'Ad', comment: 'Şərh', address: 'Ünvan', directions: 'Yer və necə getməli' }, // native speaker 			be: { takeRequest: 'Калі ласка, вазьміце мяне да …' }, // Weißrussisch 			bg: { takeRequest: 'Моля, вземете ме до …' , 				name: 'Име', comment: 'Коментар', address: 'Адрес', directions: 'Местоположение и пристигане' }, // native speaker 			bn: { takeRequest: 'আমাকে নিয়ে যান …', 				name: 'নাম', comment: 'মন্তব্য', address: 'ঠিকানা', directions: 'দিকনির্দেশ' }, 			bs: { takeRequest: 'Molim te, vodi me do …', 				name: 'Ime', comment: 'Komentar', address: 'Adresa', directions: 'Mjesto i odredište' }, // native speaker 			ca: { takeRequest: 'Porta’m a …' }, 			cs: { takeRequest: 'Vezměte mě prosím na toto místo:', 				name: 'Jméno', comment: 'Komentář', address: 'Adresa', directions: 'Cíl a cesta' }, // native speaker 			da: { takeRequest: 'Jeg vil gerne til …', 				name: 'Navn', comment: 'Kommentar', address: 'Adresse', directions: 'Kørselsvejledning' }, // native speaker 			el: { takeRequest: 'Παρακαλώ να με πάτε στο(ν)/στη(ν) …', 				name: 'Όνομα', comment: 'Σχόλιο', address: 'Διεύθυνση', directions: 'Τοποθεσία και άφιξη' }, // native speaker 			es: { takeRequest: 'Por favor, lléveme a…', 				name: 'Nombre', comment: 'Comentario', address: 'Dirección', directions: 'Indicaciones' }, // native speaker 			et: { takeRequest: 'Palun viige mind …', 				name: 'Nimi', comment: 'Kommentaar', address: 'Aadress', directions: 'Asukoht ja saabumine' }, // native speaker 			fa: { takeRequest: 'لطفا من را ببر به…', 				name: 'نام', comment: 'یادداشت', address: 'نشانی', directions: 'مسیرها' }, // native speaker 			fi: { takeRequest: 'Vie minut …', 				name: 'Nimi', comment: 'Kommentti', address: 'Osoite', directions: 'Ohjeet' }, // native speaker 			fr: { takeRequest: 'Pouvez-vous m’emmenez à/au…', 				name: 'Nom', comment: 'Commentaire', address: 'Adresse', directions: 'Direction' }, // native speaker 			ga: { takeRequest: 'Tabhair dom go …' }, // Irisch 			gd: { takeRequest: 'Thoir dhomh gu …' }, // Schottisch-gälisch 			gu: { takeRequest: 'કીરપા કરકે મૈના લે જો …'    , // Gujarati provided by an Indian guy 				name: 'નાબ', comment: 'પાસ', address: 'પતા', directions: 'ઢીશા' }, 			he: { takeRequest: 'בבקשה תיקח אותי ל…', 				name: 'שם', comment: 'תגובה', address: 'כתובת', directions: 'הוראות' }, 			hi: { takeRequest: 'कृपया मुझे वहाँ ले जाएं…' , 				name: 'नाम', comment: 'पास', address: 'पता', directions: 'दीशा' }, //Hindi provided by an Indian guy 			hr: { takeRequest: 'Molim Vas, odvedi me …' }, 			hu: { takeRequest: 'Kérem, vigyen a/az …-hoz/hez/höz', 				name: 'Név', comment: 'Megjegyzés', address: 'Cím', directions: 'Odajutás' }, // native speaker 			hy: { takeRequest: 'Խնդրում եմ ինձ տանել …', 				name: 'Անուն', comment: 'Բացատրություն', address: 'Հասցե', directions: 'Վայր եւ ցուցումներ' }, // Armenisch, native speaker 			id: { takeRequest: 'Tolong hantarkan saya ke …', 				name: 'Nama', comment: 'Komen', address: 'Alamat', directions: 'Arah' }, // Indonesian: same like malay (statement of a Sabahan native Malay speaker) 			is: { takeRequest: 'Vinsamlegast taktu mig til …' }, 			it: { takeRequest: 'Per favore, mi porti a…', 				name: 'Nome', comment: 'Commento', address: 'Indirizzo', directions: 'Posizione e arrivo' }, // Italian native speaker 			ja: { takeRequest: '… までお願いします。', 				name: '場所', comment: 'コメント', address: '住所', directions: 'アクセス' }, // Japanese native speaker 			ka: { takeRequest: 'გთხოვთ მიმიყვანოთ…', 				name: 'დასახელება', comment: 'კომენტარი', address: 'მისამართი', directions: 'დანიშნულების ადგილი' }, // Georgisch, native speaker 			kk: { takeRequest: 'Маған …' }, // Kasachisch 			km: { takeRequest: 'សូមជូនខ្ញុំទៅ …', 				name: 'ឈ្មោះ', comment: 'នែនាំ', address: 'អាសយដ្ឋាន', directions: 'ទិសដៅ' }, // Khmer: by native speaker from Phnom Penh 			ko: { takeRequest: '… 로 가주세요', 				name: '이름', comment: '호텔 안내', address: '주소', directions: '찾아가는 길' }, // Korean: verified by a native speaker from Seoul 			ky: { takeRequest: 'Суранам, мени алып…' }, // Kirgisisch 			lb: { takeRequest: 'Huelt mech op …' }, 			lt: { takeRequest: 'Prašau, paimk mane …' }, 			lv: { takeRequest: 'Lūdzu, aizvediet mani uz …', 				name: 'Nosaukums', comment: 'Komentars', address: 'Adrese', directions: 'Virzieni' }, // Lettisch -- native speaker 			mk: { takeRequest: 'Ве молам, земи ме …' }, // Mazedonisch 			mn: { takeRequest: 'Намайг аваарай …' }, 			ms: { takeRequest: 'Tolong hantarkan saya ke …', 				name: 'Nama', comment: 'Komen', address: 'Alamat', directions: 'Arah' }, // Malay: verified by a Sabahan native speaker 			mt: { takeRequest: 'Jekk jogħġbok ħudni …' }, 			my: { takeRequest: 'ငါ့ကိုအယူကို ကျေးဇူးပြု. ...' }, // Birmanisch 			nan: { takeRequest: '請帶我去 …', 				name: '姓名', comment: '評語', address: '住址', directions: '抵達方式' }, // Taiwanesisch: verified by a Taiwanese native speaker 			nb: { takeRequest: 'Kan du kjøre meg til …', 				name: 'Navn', comment: 'Kommentar', address: 'Adresse', directions: 'Sted og tid' }, // Bokmål, native speaker 			ne: { takeRequest: 'कृपया मुझे वहाँ ले जाएं…' , 				name: 'नाम', comment: 'पास', address: 'पता', directions: 'दीशा' }, // Nepalese provided by an Indian guy 			nl: { takeRequest: 'Breng me alstublieft naar…', 				name: 'Naam', comment: 'Commentaar', address: 'Adres', directions: 'Route' }, // native speaker 			no: { takeRequest: 'Kan du kjøre meg til …', 				name: 'Navn', comment: 'Kommentar', address: 'Adresse', directions: 'Sted og tid' }, // Bokmål, native speaker 			pl: { takeRequest: 'Proszę mnie zabrać do…', 				name: 'Nazwa', comment: 'Komentarz', address: 'Adres', directions: 'Wskazówki dojuzdu' }, // native speaker 			pt: { takeRequest: 'Por favor, leve-me para …', 				name: 'Nome', comment: 'Comentário', address: 'Endereço', directions: 'Direções' }, // native speaker 			pu: { takeRequest: 'ਕਿਰਪਾ ਕਰਕੇ ਮੈਨੂ ਲੈ ਚਲੌं…' , 				name: 'ਨਾਮ', comment: 'ਢੇ ਕੋਲ', address: 'ਪਤਾ', directions: 'ਵਲ' }, //Punjabie provided by an Indian guy 			ro: { takeRequest: 'Te rog să mă dai la …', 				name: 'Nume', comment: 'Comentariu', address: 'Adresă', directions: 'Indicații' }, 			ru: { takeRequest: 'Пожалуйста, отвезите меня в…', 				name: 'Название', comment: 'Комментарий', address: 'Адрес', directions: 'Пояснения' }, // Russian native speaker 			sk: { takeRequest: 'Prosím, môžete ma vziať na miesto …', 				name: 'Menom', comment: 'Komentár', address: 'Adresa', directions: 'v oblasti' }, // native spaker 			sl: { takeRequest: 'Prosim, vzemite me …' }, 			sq: { takeRequest: 'Ju lutem më dërgoni në …', 				name: 'Emri', comment: 'Komenti', address: 'Adresa', directions: 'Vendndodhja dhe Mbërritja' }, // Albanisch, native speaker 			sr: { takeRequest: 'Молим те, води ме до …', 				name: 'Име', comment: 'Коментар', address: 'Адреса', directions: 'Место и одредиште' }, // native speaker 			sv: { takeRequest: 'Snälla ta mig till …', 				name: 'Namn', comment: 'Kommentar', address: 'Adress', directions: 'Vägbeskrivning' }, // native speaker 			tg: { takeRequest: 'Лутфан маро ба …' }, 			th: { takeRequest: 'กรุณาพาฉันไปที่ …' , 				name: 'ชื่อ', comment: 'แนะนำ', address: 'ที่อยู่', directions: 'สถานที่ตั้ง' }, //Thai: by native speakerfrom Chiang Mai, North Thailand 			tl: { takeRequest: 'Pakiusap dalhin mo ako sa …', 				name: 'Pangalan', comment: 'Komento', address: 'Address', directions: 'Paano pumunta doon' }, // Tagalog (Filipino) provided by a native speaker from anywhere in Luzon 			tr: { takeRequest: 'Lütfen beni … götür', 				name: 'Ad', comment: 'Özellik', address: 'Adres', directions: 'Yer ve varış noktası' }, // native speaker 			uk: { takeRequest: 'Будь ласка, відвезіть мене до …', 				name: 'Назва', comment: 'Коментар', address: 'Адреса', directions: 'Як дістатись' }, // native speaker 			uz: { takeRequest: 'Iltimos, meni olib boring …' }, 			vi: { takeRequest: 'Xin hãy đưa tôi đến …', 				name: 'Tên', comment: 'Chú thích', address: 'Địa chỉ', directions: 'Chỉ đường' }, 			yue: { takeRequest: '请带我去……', 				name: '名字', comment: '评论', address: '地址', directions: '方向' }, // Cantonese: from a native speaker from city of Guangzhou 			zh: { takeRequest: '您好,请您带我去……', 				name: '名称', comment: '评价与备注', address: '地址', directions: '如何到达酒店' }, // Mandarin: from a native speaker from city of Hefei 		};  		const sites = [ 			{ data: 'data-agoda-com', site: 'Agoda.com', title: 'Hotel auf Agoda.com', formatter: 'https://www.agoda.com/de-de/$1.html', grClass: 'group1' }, 			{ data: 'data-booking-com', site: 'Booking.com', title: 'Hotel auf Booking.com', formatter: 'https://www.booking.com/hotel/$1.de.html', grClass: 'group1' }, 			{ data: 'data-expedia-com', site: 'Expedia.com', title: 'Hotel auf Expedia.com', formatter: 'https://www.expedia.com/$1.Hotel-Information', grClass: 'group1' }, 			{ data: 'data-expedia-com', site: 'Expedia.de', title: 'Hotel auf Expedia.de', formatter: 'https://www.expedia.de/$1.Hotel-Beschreibung', grClass: 'group1' }, 			{ data: 'data-historic-hotels-america', site: 'HistoricHotels.org', title: 'Hotel auf HistoricHotels.org', formatter: 'https://www.historichotels.org/hotels-resorts/$1', grClass: 'group1' }, 			{ data: 'data-historic-hotels-europe', site: 'HistoricHotelsOfEurope.com', title: 'Hotel auf HistoricHotelsOfEurope.com', formatter: 'https://www.historichotelsofeurope.com/property-details.html/$1', grClass: 'group1' }, 			{ data: 'data-historic-hotels-worldwide', site: 'HistoricHotelsWorldwide.com', title: 'Hotel auf HistoricHotelsWorldwide.com', formatter: 'http://www.historichotelsworldwide.com/hotels-resorts/$1', grClass: 'group1' }, 			{ data: 'data-hotels-com', site: 'Hotels.com', title: 'Hotel auf Hotels.com', formatter: 'https://de.hotels.com/$1/', grClass: 'group1' }, 			{ data: 'data-hostelworld-com', site: 'Hostelworld.com', title: 'Hostel auf Hostelworld.com', formatter: 'https://www.hostelworld.com/hosteldetails.php/_/_/$1', grClass: 'group1' }, 			{ data: 'data-kayak-com', site: 'Kayak.com', title: 'Hotel auf Kayak.com', formatter: 'https://www.kayak.de/hotels/-h$1-details/', grClass: 'group1' }, 			{ data: 'data-leading-hotels', site: 'LHW.com', title: 'Hotel auf Leading Hotels of the World', formatter: 'https://www.lhw.com/hotel/$1', grClass: 'group1' }, 			{ data: 'data-preferred-hotels', site: 'PreferredHotels.com', title: 'Hotel auf PreferredHotels.com', formatter: 'https://preferredhotels.com/destinations/$1', grClass: 'group1' }, 			{ data: 'data-recreation-gov', site: 'Recreation.gov facility', title: 'Einrichtung auf Recreation.gov', formatter: 'https://www.recreation.gov/recreationalAreaDetails.do?facilityId=$1', grClass: 'group1' }, 			{ data: 'data-relais-chateaux', site: 'RelaisChateaux.com', title: 'Einrichtung auf RelaisChateaux.com', formatter: 'https://www.relaischateaux.com/us/wd/$1', grClass: 'group1' }, 			{ data: 'data-skyscanner-com', site: 'Skyscanner.com', title: 'Metasuche auf Skyscanner.com', formatter: 'https://www.skyscanner.de/hotels/_/_/_/ht-$1', grClass: 'group1' }, 			{ data: 'data-trip-com', site: 'Trip.com', title: 'Einrichtung auf Trip.com', formatter: 'https://www.trip.com/hotels/_-hotel-detail-$1', grClass: 'group1' }, 			{ data: 'data-tripadvisor-com', site: 'Tripadvisor.com', title: 'Einrichtung auf Tripadvisor.com', formatter: 'https://www.tripadvisor.com/$1', grClass: 'group1' },  			{ data: 'data-alpenverein-de', site: 'Alpenverein.de', title: 'Schutzhütte auf Alpenverein.de', formatter: 'https://www.alpenverein.de/DAV-Services/Huettensuche/wd/$1', grClass: 'group1' }, 			{ data: 'data-alpenverein-at', site: 'Alpenverein.at', title: 'Schutzhütte auf Alpenverein.at', formatter: 'https://www.alpenverein.at/huetten/index.php?huette_nr=$1', grClass: 'group1' }, 			{ data: 'data-pzs-si', site: 'PZS.si', title: 'Schutzhütte auf im Verzeichnis des Alpenvereins Sloweniens', formatter: 'https://en.pzs.si/koce.php?pid=$1', grClass: 'group1' }, 			{ data: 'data-sac-cas-ch', site: 'SAC-CAS.ch', title: 'Schutzhütte und Gipfel auf im Verzeichnis des Schweizer Alpen-Clubs', formatter: 'https://beta.sac-cas.ch/de/huetten-und-touren/tourenportal/$1/', grClass: 'group1' },  			{ data: 'data-station-number', site: 'Abfahrtstafel Deutsche Bahn', title: 'Abfahrtstafel der Deutschen Bahn', formatter: 'https://reiseauskunft.bahn.de/bin/bhftafel.exe/dn?rt=1&input=$1&boardType=dep&time=actual&productsFilter=1111111111&start=yes', grClass: 'group1' }, //de -> en	{ data: 'data-station-number', site: 'Abfahrtstafel Deutsche Bahn', title: 'Abfahrtstafel der Deutschen Bahn', formatter: 'https://reiseauskunft.bahn.de/bin/bhftafel.exe/en?rt=1&input=$1&boardType=dep&time=actual&productsFilter=1111111111&start=yes', grClass: 'group1' }, 			{ data: 'data-station-number', site: 'Auskunft Deutsche Bahn', title: 'Auskunft und Buchung der Deutschen Bahn', formatter: 'https://www.bahn.de/buchung/start?intern=1&so=$1', grClass: 'group1' },  			{ data: 'data-osm-relation-id', site: 'OpenStreetMap.org (Relation)', title: 'Einrichtung auf OpenStreetMap', formatter: 'https://www.openstreetmap.org/relation/$1', grClass: 'group2' }, 			{ data: 'data-osm-way-id', site: 'OpenStreetMap.org (Umriss)', title: 'Einrichtung auf OpenStreetMap', formatter: 'https://www.openstreetmap.org/way/$1', grClass: 'group2' }, 			{ data: 'data-osm-node-id', site: 'OpenStreetMap.org (Knoten)', title: 'Einrichtung auf OpenStreetMap', formatter: 'https://www.openstreetmap.org/node/$1', grClass: 'group2' }, 			{ data: 'data-foursquare-id', site: 'Foursquare.com', title: 'Einrichtung auf Foursquare.com', formatter: 'https://www.foursquare.com/v/$1', grClass: 'group2' }, 			{ data: 'data-google-maps-cid', site: 'Maps.google.com', title: 'Einrichtung auf Google Maps', formatter: 'https://maps.google.com/?cid=$1', grClass: 'group2' } 		];  		// technical constants 		const contactKeys = [ 'phone', 'mobile', 'tollfree', 'fax', 'email', 'skype' ], 			fallbackLang = 'en', 			allowedNamespaces = [ 				0, // Main 				2, // User 				4  // Project 			];  		// separators for translate function 		const separators = { 			header:  '<br />', 			section: ' / ' 		};  		const selectors = { 			background:       '#voy-info-background', 			kartographerLink: '.mw-kartographer-maplink', 			listing:          '.vcard', 			infoDialog:       '#voy-listing-info', 			metadata:         'span.listing-metadata-items' 		};  		const classes = { 			address:     'listing-address', 			alt:         'listing-alt', 			checkin:     'listing-checkin', 			checkout:    'listing-checkout', 			comment:     'listing-comment', 			commons:     'listing-sister-commons', 			credit:      'listing-credit', 			directions:  'listing-directions', 			email:       'listing-email', 			fax:         'listing-fax', 			features:    'listing-subtype', 			hours:       'listing-hours', 			icon:        'listing-icon', 			mobile:      'listing-mobile', 			name:        'listing-name', 			phone:       'listing-landline', 			tollfree:    'listing-tollfree', 			skype:       'listing-skype', 			socialMedia: 'listing-social-media',  			prefix:      'voy-info-',  			booking:     'voy-info-booking', 			button:      'voy-info-button', 			buttonImage: 'voy-info-button-img', 			buttonPane:  'voy-info-button-pane', 			container:   'voy-info-container', 			image:       'voy-info-image', 			infoPane:    'voy-info-pane', 			isMobile:    'voy-info-mobile', 			map:         'voy-info-map', 			mapCation:   'voy-info-map-caption', 			extraMaps:   'voy-info-extra-maps', 			placesList:  'voy-info-places-list',  			background:  'ui-widget-overlay' 		};  		const data = { 			addressLocal: 'data-address-local', 			color:     'data-color', 			directionsLocal: 'data-directions-local', 			image:     'data-image', 			lat:       'data-lat', 			lon:       'data-lon', 			lang:      'data-lang', 			name:      'data-name', 			nameLocal: 'data-name-local', 			rss:       'data-rss', 			type:      'data-group', // other wikis: 'data-type' 			url:       'data-url', 			zoom:      'data-zoom' 		};  		const makiIcons = { 			area:      'land-use', 			buy:       'shop', 			'do':      'swimming', 			drink:     'bar', 			eat:       'restaurant', 			error:     'cross', 			go:        'suitcase', 			health:    'hospital', 			nature:    'park', 			other:     'star-stroked', 			religion:  'circle-stroked', 			see:       'town-hall', 			sleep:     'lodging', 			populated: 'town', 			view:      'camera', 		};  		// internal use 		const pageLang = mw.config.get( 'wgPageContentLanguage' ), 			userLang = mw.config.get( 'wgUserLanguage' ), 			isMobile = ( /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test( navigator.userAgent.toLowerCase() ) ); //			isMobile = true;  		// dialog move 		const position = { 			mouse: {}, 			dialog: {} 		};  		// map support 		const mapParams = { 			map: null, 			url: `https://${pageLang}.wikivoyage.org/w/index.php?title=Special%3AMapsources&params=` 		};  		/********************* String management ******************************/  		var messages = {};  		// copying translation strings to messages depending on chain languages 		function addMessages( strings, chain ) { 			for ( var i = chain.length - 1; i >= 0; i-- ) { 				if ( strings.hasOwnProperty( chain[ i ] ) ) { 					$.extend( messages, strings[ chain[ i ] ] ); 				} 			} 		}  		// copying translation strings to messages 		function setupMessages() { 			const chain = userLang == pageLang ? [ pageLang, fallbackLang ] : 				[ userLang, pageLang, fallbackLang ]; 			addMessages( userStrings, chain ); 		}  		/************************** Dialog ************************************/  		// Opening the dialog 		function open() { 			close();  			const width = 400, height = 300, 				id = selectors.infoDialog.substring( 1 ); 			var left = ( document.body.scrollWidth - width ) / 2 + $( document ).scrollLeft(); 			left = left < 0 ? 0 : left; 			var top = ( window.innerHeight - height ) / 2 + $( document ).scrollTop(); 			top = top < 0 ? 0 : top;  			const infoDialog = $( '<div/>', { 				id: id, 				'class': 'mw-parser-output' + ( isMobile ? ( ' ' + classes.isMobile ) : '' ), 				role: 'dialog', 				tabindex: '-1', 				'aria-modal': 'true', 				'aria-labelledby': id, 				'data-version': version, 				css: { width: width, height: height, display: 'flex', 					left: left, top: top } 				}) 				.keydown( handleKeyCodes ); // Handle TAB and ESC keycodes 			$( 'body' ) 				.append( $( '<div/>', { 					id: selectors.background.substring( 1 ), 					'class': classes.background 				}) ) 				.append( infoDialog ) 				.click( handleOutsideClick );  			return infoDialog; 		}  		// Key code event handler for TAB and ESC keys 		function handleKeyCodes( event ) { 			switch( event.keyCode ) { 				case 9: // TAB 					const tabbables = $( event.delegateTarget ).find( ':visible' ).filter( 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]' ), 						first = tabbables.filter( ':first' ), 						last = tabbables.filter( ':last' ); 					if ( event.target === last[ 0 ] && !event.shiftKey ) { 						event.preventDefault(); 						first.focus(); 					} else if ( event.target === first[ 0 ] && event.shiftKey ) { 						event.preventDefault(); 						last.focus(); 					}     	    		break; 	        	case 27: // ESC 					event.preventDefault();         			close(); 			} 		}  		// Click event handler if clicked outside the dialog 		function handleOutsideClick( event ) { 			// Real ouside click? 			if ( !$( event.target ).closest( selectors.infoDialog ).length ) { 				event.preventDefault();         		close(); 			} else { 				const infoDialog = $( selectors.infoDialog ), 					disabledButton = $( 'button:disabled', infoDialog ); 				if ( disabledButton.length ) { 					disabledButton.next().focus(); 				} else { 					$( 'button', infoDialog ).first().focus(); 				} 			} 		}  		// Closing the dialog 		function close() { 			$( 'body' ).off( 'click', handleOutsideClick ); 			$( selectors.infoDialog ).remove(); 			$( selectors.background ).remove(); 		}  		// Focus the button of the following info pane 		function focusNextButton() { 			$( selectors.infoDialog + ' button:disabled' ).next().focus(); 		}  		// Creating header with the opportunity to use it as a move tool 		function makeHeader( text, move ) { 			const header = $( '<h2/>', { 				css: { flex: 'none' } 			}).html( text ); 		 			if ( move ) { 				header.css( 'cursor', 'move' ) 				.mouseup( function( e ) { 					$( this ).off( 'mousemove', dialogMove ).css( 'cursor', 'move' ); 					focusNextButton(); 				}) 				.mouseout( function( e ) { 					$( this ).off( 'mousemove', dialogMove ).css( 'cursor', 'move' ); 					focusNextButton(); 				}) 				.mousedown( function( e ) { 					var dialog = $( selectors.infoDialog ); 					$( this ).on( 'mousemove', dialogMove ).css( 'cursor', 'grabbing' ); 					position.mouse.X = e.clientX; 					position.mouse.Y = e.clientY; 					position.dialog.X = parseInt( dialog.css( 'left' ) ); 					position.dialog.Y = parseInt( dialog.css( 'top' ) ); 				}); 			} 			return header; 		}  		// Event handler for mouse move 		function dialogMove( e ) { 			$( selectors.infoDialog ) 				.css( { left: position.dialog.X - position.mouse.X + e.clientX, 					top: position.dialog.Y - position.mouse.Y + e.clientY } ); 		} 	 		/***************** Info panes and selection ***************************/  		// Making buttons with event handlers 		// Image name is used as an id, too 		// cancel = true means it is the cancel button 		function makeButton( buttonPane, img, tooltip, cancel ) { 			const button = $( '<button/>', { 				'class': classes.button, 				title: messages[ tooltip ], 				id: img, 				css: { clear: 'right', float: 'right' } 			}) 			.append( $( '<div/>', { 				'class': classes.buttonImage 			}) );  			if ( cancel ) { 				button.click( function() { 					close(); 				}); 			} else { 				button.click( function( e ) { 					changeInfoPane( e, buttonPane ); 				}); 			} 			button.appendTo( buttonPane ); 		} 	 		// Make an info pane distinguishable by id 		function makeInfoPane( id ) { 			return $( '<div/>', { 				id: classes.prefix + id, 				'class': classes.infoPane, 				css: { display: 'none' } 			}); 		}  		// Select an info pane by id and disable its button 		function selectInfoPane( buttonPane, id ) { 			const buttons = buttonPane.children(); 			if ( buttons.length < 2 ) { 				$( '.voy-info-pane' ).show(); 				return; 			} 			if ( !id ) { 				id = buttons.first().attr( 'id' ); 			} 			buttons.each( function() { 				$( this ).prop( 'disabled', $( this ).attr( 'id' ) === id ); 			}); 		 			buttonPane.siblings().each( function() { 				if ( $( this ).attr( 'id' ) === classes.prefix + id ) { 					$( this ).css( { display: 'flex', 'flex-direction': 'column' } ) 						.trigger( 'voy:show' ); 				} else { 					$( this ).css( { display: 'none' } ); 				} 			});  			buttons.filter( ':disabled' ).next().focus(); 		}  		// Event handler for info-pane change triggered by clicking on its 		// belonging buttons or button child images 		function changeInfoPane( e, buttonPane ) { 			const button = $( e.target ).closest( 'button' ), 				id = button.length ? button.attr( 'id' ) : ''; 			if ( id ) { 				selectInfoPane( buttonPane, id ); 			} 		}  		/********************** Helper function *******************************/  		// Specifying language of a text and wrap it with right-to-left mark 		// if necessary 		function langSpan( text, lang ) { 			if ( !text || text === '' ) { 				return ''; 			} 			const r2l = { 				ar: 1, 				dv: 1, 				fa: 1, 				he: 1, 				ms: 1, 				ur: 1, 			}; 			const dir = lang in r2l ? ' dir="rtl"' : '', 				t =`<span lang="${lang}"${dir}>${text}</span>`; 			return lang in r2l ? `&rlm;${t}&lrm;` : t; 		} 	 		// Getting HTML text from a wrapper tag specified by aClass. 		// context is the dialog itself 		function getHTML( aClass, context ) { 			const span = $( '.' + aClass, context ); 			return span.length ? span.first().html() : ''; 		} 	 		function getOuterHTML( aClass, context ) { 			const span = $( '.' + aClass, context ); 			return span.length ? span.first().prop( 'outerHTML' ) : ''; 		} 	 		// Making a header with wikilang and country lang identifiers 		function translate( lang, wikiLang, id, separator ) { 			var s = translations[ wikiLang ][ id ]; 			if ( wikiLang !== lang ) { 				var t = ''; 				if ( lang && lang in translations && id in translations[ lang ] ) { 					t = langSpan( translations[ lang ][ id ], lang ); 				} else if ( wikiLang !== 'en' ) { 					t = translations.en[ id ]; 				} 				s += t ? separator + t : ''; 			} 			return s; 		}  		// replace spaces and entities 		function replaceEntities( s ) { 			return $( '<span />' ).html( s.replace( /\s/g, '_' ) ).text(); 		}  		/**************** Making and filling dialog ***************************/  		// Creating the dialog 		// Event handler called by listingPopup.init 		function dialog( element ) { 			const listing = element.closest( selectors.listing ), 				listingName = $( '.' + classes.name, listing ).first(), 				place = {}, 				link = $( 'a', listingName ).first();  			const dialog = open(), 				buttonPane = $( '<div/>', { 				id: classes.buttonPane 			});  			place.lang = listing.attr( data.lang ); 			const at = place.lang.indexOf( '-' ); 			if ( at > -1 ) { 				place.lang = place.lang.substring( 0, at ); 			}  			place.name = listing.attr( data.name ); 			if ( !place.name ) { 				place.name = link.length ? link.text() : listingName.text(); 			} 			if ( link.length ) { 				place.nameHTML = `<a href="${link.attr( 'href' )}" title="${place.name}" target="_blank">${place.name}</a>`; 			}  			const s = listing.attr( data.nameLocal ); 			place.nameLocal = s ? langSpan( s, place.lang ) : getHTML( classes.alt, listing ); 			place.nameAll = place.nameHTML || place.name; 			if ( place.nameLocal ) { 				place.nameAll += '<br />' + place.nameLocal; 			}  			// adding pages 			for ( var i = 0; i < pages.length; i++ ) { 				pages[ i ]( dialog, buttonPane, listing, place ); 			}  			// combining elements 			if ( $( 'button', buttonPane ).length < 2 ) { 				buttonPane.empty(); 			} 			makeButton( buttonPane, 'cancelImg', 'closeTooltip', true ); 			dialog.append( buttonPane ); 			selectInfoPane( buttonPane ); 		}  		const pages = [];  		/****** "Bring me to …" page ******/  		function bringMeToPage( dialog, buttonPane, listing, place ) {             function placeInfo( container, key, keyLocal, wikiLang ) {                 const global = getHTML( classes[ key ], listing );                 var s = keyLocal ? listing.attr( data[ keyLocal ] ) : null;                 if ( global && s && global.toLowerCase() == s.toLowerCase() ) {                 	s = null;                 }                 const local = s ? langSpan( s, place.lang ) : '';                 if ( global || local ) {                     s = translate( place.lang, wikiLang, key, separators.section );                     var t = local;                     if ( global ) {                         t += local ? '<br />' + global : global;                     }                     container.append( `<dl><dt>${s}</dt><dd>${t}</dd></dl>` );                 }             }             	const buttonId = 'taxiImg'; 			var wikiLang = pageLang; 			if ( userLang && translations[ userLang ] ) { 				wikiLang = userLang; 			}              var s = translate( place.lang, wikiLang, 'takeRequest', separators.header ); 			const container = $( '<div/>', { 				'class': classes.container 			}); 			const infoPane = makeInfoPane( buttonId ) 				.append( makeHeader( s, true ) ) 				.append( container );  			s = translate( place.lang, wikiLang, 'name', separators.section ); 			var t = place.nameLocal; 			t += place.nameLocal ? '<br />' + place.name : place.name; 			container.append( `<dl><dt>${s}</dt><dd>${t}</dd></dl>` );  			placeInfo( container, 'comment', null, wikiLang ); 			placeInfo( container, 'address', 'addressLocal', wikiLang ); 			placeInfo( container, 'directions', 'directionsLocal', wikiLang );  			dialog.append( infoPane ); 			makeButton( buttonPane, buttonId, 'taxiTooltip', false ); 		} 		pages.push( bringMeToPage );  		/****** Image page ******/  		function imagePage( dialog, buttonPane, listing, place ) { 			const buttonId = 'figureImg'; 			var image = replaceEntities( listing.attr( data.image ) || '' ); 			if ( image ) { 				var s = place.nameAll;  				image = 'https://commons.wikimedia.org/wiki/Special:FilePath/' + 					mw.html.escape( image ) + '?width=700'; 				image = `<img src="${image}" title="${place.name}" />`; 				var infoPane = makeInfoPane( buttonId ) 					.append( `<div class="${classes.image}">${image}</div>` ) 					.append( `<p><span class="voy-info-name">${s}</span> ` + 						`${getOuterHTML( classes.commons, listing )}</p>` ); 				// map support 				mapParams.thumb = image;  				dialog.append( infoPane ); 				makeButton( buttonPane, buttonId, 'figureTooltip', false );			 			} 		} 		pages.push( imagePage );  		/****** Map page ******/  		// Creating a Kartographer map 		function createMap() { 			// see also: https://www.mediawiki.org/wiki/Help:Extension:Kartographer/Developer_guide 			mw.loader.using( [ 'ext.kartographer.box' ], function () { 				const kartoBox = mw.loader.require( 'ext.kartographer.box' );  				mapParams.map = kartoBox.map( { 					container: $( '#' + classes.map )[ 0 ], 					center: [ mapParams.lat, mapParams.lon ], 					captionText: mapParams.title, 					zoom: 15, 					allowFullScreen: true, 					alwaysInteractive: true, 					isFullScreen: false, 					featureType: 'mapframe' 				} );  				const mapData = [ { 					'type': 'Feature', 					properties: { 						'marker-color': mapParams.color, 						'marker-size': 'medium', 						'marker-symbol': makiIcons[ mapParams.type ] || '', 						title: mapParams.title, 						description: mapParams.thumb 					}, 					geometry: { 						'type': 'Point', 						coordinates: [ mapParams.lon, mapParams.lat ] 					} 				} ]; 				const layerOptions = { name: 'Position' }; 				mapParams.map.addGeoJSONLayer( mapData, layerOptions ); 			} ); 		}  		function extraMaps() { 			const scales = [ 500000000, 250000000, 150000000, 70000000, 35000000, 				15000000, 10000000, 4000000, 2000000, 1000000, 500000, 250000, 				150000, 70000, 35000, 15000, 8000, 4000, 2000, 1000 ], 				scale = scales[ mapParams.zoom ] || 17; 			var lat = parseFloat( mapParams.lat ), 				lon = parseFloat( mapParams.lon ), 				url = mapParams.url + 					Math.abs( lat ) + ( lat < 0 ? '_S_' : '_N_' ) + 					Math.abs( lon ) + ( lon < 0 ? '_W' : '_E' ) + 					`_scale%3A${scale}&locname=${encodeURI( mapParams.title.replace( / /g, '+' ) )}`; 			return `<div class="${classes.extraMaps}" title="${messages.extraMapsTitle}"><a href="${url}" target="_blank">${messages.extraMaps}</a></div>`; 		}  		function mapPage( dialog, buttonPane, listing, place ) { 			mapParams.map = null;  			const link = $( selectors.kartographerLink, listing ).first(); 			if ( link.length ) { 				const buttonId = 'mapImg'; 				var s = place.nameAll;  				mapParams.title = place.name; 				mapParams.type = listing.attr( data.type ); 				mapParams.lat = link.attr( data.lat ); 				mapParams.lon = link.attr( data.lon ); 				mapParams.color = listing.attr( data.color ); 				mapParams.zoom = listing.attr( data.zoom ); 				if ( !mapParams.zoom ) { 					mapParams.zoom = 17; 				}  				const infoPane = makeInfoPane( buttonId ) 					.append( $( '<div/>', { 						id: classes.map 						}) ) 					.append( `<p class="${classes.mapCation}">${s}</p>` ) 					.append( extraMaps() ) 					.on( 'voy:show', function( event ) { 						// map is created later if dialog is visible 						if ( !mapParams.map ) { 							createMap(); 						} 					});  				dialog.append( infoPane ); 				makeButton( buttonPane, buttonId, 'mapTooltip', false ); 			} 		} 		pages.push( mapPage );  		/****** Contact page ******/  		function contactPage( dialog, buttonPane, listing, place ) { 			function makeImgLink( linkType, link, title ) { 				return `<span class="${classes.icon} listing-${linkType}" title="${title}">` + 					`<a class="external text" href="${link}" rel="nofollow" target="_blank">` + 					`<span style="color-adjust:exact;-webkit-print-color-adjust:exact;print-color-adjust:exact">${linkType}</span>` + 					'</a></span> '; 			}  			const buttonId = 'contactImg'; 			const container = $( '<div/>', { 				'class': classes.container 			}); 			var c, i, s; 			for ( i = 0; i < contactKeys.length; i++ ) { 				c = contactKeys[ i ]; 				s = getHTML( classes[ c ], listing ); 				if ( s ) { 					container.append( `<p><span class="voy-info-section">${messages[ c ]}:</span> ${s}</p>` ); 				} 			}  			s = ''; 			const url = listing.attr( data.url ); 			s = url ? makeImgLink( 'url', url, messages.urlTooltip ) : '';  			const rss = listing.attr( data.rss ); 			s += rss ? makeImgLink( 'rss', rss, messages.rssTooltip ) : '';  			const socialMedia = $( '.' + classes.socialMedia, listing ); 			if ( socialMedia.length ) { 				socialMedia.each( function() { 					var icon = $( this ).prop( 'outerHTML' ) 						.replace( '<a ', '<a target="_blank" ' ); 					s += icon; 				}); 			} 			if ( s ) { 				container.append( `<p><span class="voy-info-section">${messages.web}:</span> ${s}</p>` ); 			}  			if ( $( 'p', container ).length ) { 				const infoPane = makeInfoPane( buttonId ) 					.append( makeHeader( messages.contact, true ) ) 					.append( container ); 				dialog.append( infoPane ); 				makeButton( buttonPane, buttonId, 'contactTooltip', false ); 			} 		} 		pages.push( contactPage );  		/****** Features page ******/ 		 		function featuresPage( dialog, buttonPane, listing, place ) { 			const buttonId = 'featuresImg'; 			const infoPane = makeInfoPane( buttonId ) 				.css( { 'overflow-y': 'auto' } ); 			var move = true;  			var features = getHTML( classes.features, listing ); 			if ( features ) { 				infoPane.append( makeHeader( messages.features, move ) ); 				move = false; 				infoPane.append( `<p>${features}</p>` ); 			}  			var credit = getHTML( classes.credit, listing ); 			if ( credit ) { 				infoPane.append( makeHeader( messages.credit, move ) ); 				move = false; 				infoPane.append( `<p>${credit}</p>` ); 			}  			const params = { 				hours: getHTML( classes.hours, listing ), 				checkin: getHTML( classes.checkin, listing ), 				checkout: getHTML( classes.checkout, listing ) 			}; 			if ( params.hours + params.checkin + params.checkout ) { 				infoPane.append( makeHeader( messages.hours, move ) ); 				var key, value; 				for ( key in params ) { 					value = params[ key ]; 					if ( value ) { 						infoPane.append( `<p>${value}</p>` ); 					} 				} 			}  			if ( $( 'h2', infoPane ).length ) { 				dialog.append( infoPane ); 				makeButton( buttonPane, buttonId, 'featuresTooltip', false ); 			} 		} 		pages.push( featuresPage );  		/****** Booking, comparison, and rating page ******/  		function bookingPage( dialog, buttonPane, listing, place ) { 			var count = 0, i, li, s, site; 			const ul = $( '<ul/>', { 				'class': classes.booking 			});  			for ( i = 0; i < sites.length; i++ ) { 				site = sites[ i ]; 				s = listing.attr( site.data ); 				if ( s ) { 					s = site.formatter.replace( '$1', s ); 					li = $( '<li/>', { 						'class': selectors.infoDialog.substring( 1 ) + '-' + site.grClass, 						}) 						.append( $( '<a/>', { 							'class': 'external text', 							target: '_blank', 							href: s, 							title: site.title, 							text: site.site 						}) ); 					ul.append( li ); 					count += 1; 				} 			} 	 			if ( count ) { 				const buttonId = 'bookingImg'; 				const container = $( '<div/>', { 					'class': classes.container 				}); 				const infoPane = makeInfoPane( buttonId ) 					.append( makeHeader( messages.booking, true ) ) 					.append( container ); 				container.append( `<p>${messages.notCompleteHint}</p>`  );  				container.append( `<ul class="${classes.placesList}"><li>${place.nameHTML || place.name}</li></ul>` ) 					.append( ul );  				dialog.append( infoPane ); 				makeButton( buttonPane, buttonId, 'bookingTooltip', false ); 			} 		} 		pages.push( bookingPage );  		/*********************** Initialization *******************************/  		// Check if namespace and action is allowed 		function checkIfAllowed() { 			const namespace = mw.config.get( 'wgNamespaceNumber' ); 			return allowedNamespaces.includes( namespace ); 		}  		// Adding "info" buttons and event handlers after vCard text 		function init() { 			if ( !checkIfAllowed() ) { 				return; 			} 			setupMessages();  			var popupButton = $( '<button/>', { 					title: messages.buttonTooltip, 					text: messages.buttonText 				} ) 				.click( function( e ) { 					e.stopImmediatePropagation(); 					dialog( $( this ) ); 				}); 			popupButton = $( '<span/>', { 					'class': 'listing-metadata-item listing-info-button voy-timeless-no-emoji noprint' 				}) 				.append( popupButton );  			$( selectors.metadata ).append( popupButton ); 		}  		return { init: init }; 	} (); 	 	$( listingPopup.init );  } ( jQuery, mediaWiki ) );  //</nowiki>