PageSourceSearch

https://www.hochkoenig.at/js/mw-core/mw-core-teaser.js

js hochkoenig.at collected 2026-10-02 09:56:20 UTC 81,459 bytes, 3,227 lines download raw bytes

1/* eslint-disable */
2import mwBookmark from "/js/mw-core/mw-bookmark.js";
3import "/js/mw-core/mw-core-button.js";
4import "/js/mw-core/mw-core-actionsheet.js";
5import mwCore from "/js/mw-core/mw-core.js";
6import mwUtility from "/js/mw-core/mw-utility.js";
7
8class mwCoreTeaser extends mwCore {
9	// livecycle methods
10
11	beforeFirstRender() {
12		let template;
13
14		// render data
15
16		try {
17			template = this.querySelector(`template[is="component-data"]`);
18
19			if (template) {
20				this._data = JSON.parse(template.content.textContent);
21
22				// add mqmedia rule to data
23				// relevant for bookmark
24
25				if (this.mqmedia) {
26					this._data.mqmedia = this.mqmedia;
27				}
28
29				// remove template from DOM
30
31				template.remove();
32			}
33		} catch (error) {
34			console.error(error);
35			return;
36		}
37	}
38
39	updated() {
40		// private properties
41
42		this._dom = {};
43
44		// cache DOM
45
46		this.cacheDOM();
47
48		// register eventListener
49
50		this.registerEventListener();
51	}
52
53	static get observedAttributes() {
54		// variants: default, inline, compact
55
56		return [
57			"linkelement",
58			"button",
59			"buttonattr",
60			"bookmark",
61			"background",
62			"variant",
63			"saleschannelid",
64			"mqmedia",
65			"maxlines",
66			"maxlineslineheight",
67			"fallbackpicture",
68			"as",
69			"priceaddition",
70			"priceformat"
71		];
72	}
73
74	// css
75
76	styles() {
77		return [
78			`<link href="/css/styleguide.css" rel="stylesheet">`,
79			`
80				* {
81					user-select: none;
82				}
83
84				${
85					this.maxlines && this.maxlineslineheight
86						? `
87					:host {
88						--max-lines: ${this.maxlines};
89						--lh: ${this.maxlineslineheight};
90					}
91				`
92						: ``
93				}
94				*:focus-visible {
95					outline-offset: -2px !important;
96				}
97				.root {
98					height: 100%;
99				}
100				p {
101					margin: 0;
102				}
103				a {
104					color: var(--mw-core-color-body-text-dark);
105					text-decoration: none;
106				}
107				a.button:focus-visible {
108					outline: none !important;
109				}
110				img {
111					display: block;
112					height: 100%;
113					object-fit: cover;
114					width: 100%;
115				}
116				.root > a.href {
117					height: 100%;
118					width: 100%;
119				}
120				.teaser {
121					display: flex;
122					flex-direction: column;
123					height: 100%;
124					width: 100%;
125				}
126				:host([linkelement]) .teaser {
127					cursor: pointer;
128				}
129				.main {
130					border-radius: 0;
131					display: flex;
132					flex: 1;
133					flex-direction: column;
134					padding: 24px 16px;
135				}
136				:host([background]) .main {
137					background: var(--mw-core-color-background-bright);
138				}
139				:host([variant="compact"]) .main {
140					padding: 12px;
141					text-align: center;
142				}
143				:host([noimage][tag]) .main {
144					padding-top: 50px;
145				}
146				.content {
147					margin: 0 0 10px;
148				}
149				:host([variant="compact"]) .content {
150					margin-bottom: 0;
151				}
152				:host([objecttype="provider"]) .content {
153					display: flex;
154					flex-wrap: wrap;
155					margin: 0;
156				}
157				.bottom {
158					align-items: flex-end;
159					display: flex;
160					justify-content: flex-end;
161					margin-top: auto;
162				}
163				:host([variant="compact"]) .bottom {
164					justify-content: center;
165				}
166				.bottom.has-info {
167					justify-content: space-between;
168				}
169				.tag {
170					background: var(--mw-core-color-action-primary);
171					color: var(--mw-core-color-body-text-dark);
172					font-size: 16px;
173					left: 20px;
174					line-height: 1;
175					padding: 5px 8px;
176					position: absolute;
177					text-transform: unset;
178					text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.24);
179					top: 20px;
180					visibility: visible;
181					z-index: 1;
182				}
183				.tag.no-tag {
184					display: none;
185				}
186				:host([variant="inline"]) .tag {
187					bottom: 20px;
188					left: auto;
189					right: 20px;
190					top: auto;
191				}
192				:host([variant="inline"]) .double-tag .tag.state {
193					top: 54px;
194				}
195				.tag.level1 {
196					background: var(--mw-core-color-label-moderate);
197				}
198				.tag.level2 {
199					background: var(--mw-core-color-state-error);
200					color: #FFFFFF;
201				}
202				.tag.level3 {
203					background: var(--mw-core-color-label-difficult);
204					color: #FFFFFF;
205				}
206				.tag.state {
207					left: auto;
208					right: 20px;
209					top: 20px;
210				}
211				.tag.state.state-cancelled,
212				.tag.state.state-postponed {
213					background: var(--mw-core-color-state-error);
214					color: #FFFFFF;
215				}
216				.tag.state.state-scheduled,
217				.tag.state.state-rescheduled {
218					background: var(--mw-core-color-state-success);
219				}
220				.image {
221					position: relative;
222					width: 100%;
223					z-index: -1;
224				}
225				.image a.href {
226					display: block;
227				}
228				.text,
229				.teasertext {
230					hyphens: auto;
231					word-break: break-word;
232				}
233				:host([objecttype="provider"]) .teasertext,
234				:host([objecttype="infrastructure"]) .teasertext,
235				:host([objecttype="tour"]) .teasertext {
236					display: none;
237				}
238				.teasertext ul {
239					margin: 8px 0;
240					padding: 0 0 0 16px;
241				}
242				.place {
243					margin-top: 16px;
244				}
245				.opening-days {
246					margin: 14px 0;
247				}
248				.opening-days .current {
249					align-items: center;
250					background: none;
251					border: none;
252					border-bottom: 2px solid transparent;
253					cursor: pointer;
254					display: inline-flex;
255					padding: 5px 0;
256				}
257				.opening-days .current:not(.no-further-openingdays):hover {
258					border-bottom: 2px solid;
259				}
260				.opening-days .current .state-good {
261					color: var(--mw-core-color-label-good);
262				}
263				.opening-days .current .state-medium {
264					color: var(--mw-core-color-label-medium);
265				}
266				.opening-days .current .state-bad {
267					color: var(--mw-core-color-label-bad);
268				}
269				.opening-days .current mw-core-icon {
270					display: block;
271					line-height: 1;
272					margin: 0 0 0 4px;
273				}
274				.opening-days.show-further .current mw-core-icon {
275					transform: rotate(180deg);
276				}
277				.opening-days .further {
278					display: none;
279				}
280				.opening-days.show-further .further {
281					display: block;
282					margin: 8px 0 0 0;
283				}
284				.opening-days .day {
285					display: flex;
286					padding: 2px 0;
287				}
288				.opening-days .day .d {
289					display: flex;
290					flex: 2;
291				}
292				.opening-days .day .t {
293					display: flex;
294					flex: 3;
295				}
296				.date {
297					color: var(--mw-core-color-brand1);
298					margin-bottom: 4px;
299				}
300				.price {
301					color: var(--mw-core-color-body-text-dark);
302					margin: 12px 0 0 0;
303				}
304				.center {
305					align-items: flex-end;
306					display: flex;
307					flex: 0 0 100%;
308					margin: 16px 0;
309				}
310				.availability:not(.no-availability) {
311					color: var(--mw-core-color-state-success);
312					margin-top: 8px;
313				}
314				.marketinggroups {
315					display: inline-flex;
316					flex-wrap: wrap;
317					gap: 16px;
318					width: 100%;
319				}
320				.marketinggroups .logo img {
321					display: block;
322					height: 100%;
323					max-height: 50px;
324					max-width: 100px;
325					object-fit: contain;
326					width: 100%;
327				}
328				.holidayThemes {
329					display: inline-flex;
330					flex-wrap: wrap;
331					gap: 16px;
332					width: 100%;
333				}
334				.holidayThemes .logo img {
335					display: block;
336					height: 100%;
337					max-height: 50px;
338					max-width: 100px;
339					object-fit: contain;
340					width: 100%;
341				}
342				.rating {
343					align-items: flex-end;
344					display: flex;
345					flex: 0 0 120px;
346					flex-direction: column;
347					padding: 6px 0 0 0;
348				}
349				:host([variant="compact"]) .price {
350					margin-top: 0;
351				}
352				a.href {
353					color: var(--mw-core-color-body-text-dark);
354					display: flex;
355					text-decoration: none;
356				}
357				:host([variant="compact"]) a.href {
358					justify-content: center;
359				}
360				.title {
361					margin: 0 0 12px;
362				}
363				:host([variant="compact"]) .title {
364					margin-bottom: 4px;
365				}
366				:host([objecttype="infrastructure"]) .title {
367					margin-bottom: 4px;
368				}
369				:host([objecttype="tour"]) .title {
370					margin-bottom: 4px;
371				}
372				:host([objecttype="provider"]) .title {
373					margin-bottom: 4px;
374				}
375				:host([variant="inline"]) .title {
376					color: var(--mw-core-color-body-text-bright);
377					margin-bottom: 0;
378				}
379				:host([objecttype="tour"]) span.state {
380					color: var(--mw-core-color-state-success);
381				}
382				:host([objecttype="tour"]) span.state-closed,
383				:host([objecttype="tour"]) span.state-disturbance,
384				:host([objecttype="tour"]) span.state-underconstruction,
385				:host([objecttype="tour"]) span.state-blocked {
386					color: var(--mw-core-color-state-error);
387				}
388				:host([objecttype="tour"]) span.state-inpreparation,
389				:host([objecttype="tour"]) span.state-partiallyopen {
390					color: var(--mw-core-color-label-moderate);
391				}
392				:host([objecttype="infrastructure"]) .tag.state {
393					background: var(--mw-core-color-state-success);
394				}
395				:host([objecttype="infrastructure"]) .tag.state-closed,
396				:host([objecttype="infrastructure"]) .tag.state-disturbance,
397				:host([objecttype="infrastructure"]) .tag.state-underconstruction {
398					background: var(--mw-core-color-state-error);
399					color: #FFFFFF;
400				}
401				.left {
402					margin: 0 10px 0 0;
403				}
404				:host([objecttype="tour"]) .left {
405					align-items: center;
406					display: flex;
407				}
408				.left .location {
409					margin: 0 0 2px;
410				}
411				.starttime {
412					background: var(--mw-core-color-brand1);
413					color: #FFFFFF;
414					display: table;
415					padding: 3px 6px;
416				}
417				.right {
418					align-items: center;
419					display: flex;
420				}
421				.phone {
422					align-items: center;
423					display: flex;
424				}
425				.phone mw-core-icon {
426					margin: 0 6px 0 0;
427				}
428				.tour-data {
429					align-items: center;
430					display: flex;
431					margin: 0 16px 0 0;
432				}
433				.tour-data:last-child {
434					margin-right: 0;
435				}
436				.tour-data.uphill {
437					display: none;
438				}
439				.tour-data mw-core-icon {
440					color: var(--mw-core-color-brand1);
441					margin-right: 6px;
442				}
443				.provider {
444					flex: 1;
445				}
446				.rating-value-wrapper {
447					margin-bottom: 4px;
448				}
449				.rating-value {
450					background: var(--mw-core-color-brand1);
451					color: #FFFFFF;
452					display: inline-flex;
453					padding: 2px 8px;
454				}
455				.provider-price {
456					background: none;
457					border: none;
458					cursor: pointer;
459					padding: 0;
460					position: relative;
461				}
462				.provider-price .price-value {
463					color: var(--mw-core-color-brand1);
464					display: flex;
465				}
466				.provider-price .price-value .price {
467					color: var(--mw-core-color-brand1);
468					display: flex;
469					margin: 0;
470				}
471				.provider-price .price-value .price-wrapper {
472					align-items: center;
473					display: flex;
474				}
475				.provider-price .price-value .price-wrapper .previous-price {
476					font-size: 18px;
477					margin-right: 8px;
478					text-decoration: line-through;
479					opacity: 0.5;
480				}
481				.provider-price .price-info {
482					display: flex;
483				}
484				.provider-price .free-cancellation {
485					background: var(--mw-core-color-state-success);
486					border-radius: 4px;
487					color: #FFFFFF;
488					display: table;
489					margin: 4px 0 0 0;
490					padding: 4px 8px;
491				}´
492				.provider-price .preloader {
493					position: absolute;
494					top: 50%;
495				}
496				.bookmark {
497					background: none;
498					border: none;
499					color: var(--mw-core-color-brand1);
500					cursor: pointer;
501					line-height: 1;
502					padding: 0;
503					position: relative;
504				}
505				.bookmark:hover {
506					color: var(--mw-core-color-background-dark);
507				}
508				.bookmark + .button,
509				.bookmark + a.href {
510					margin: 0 0 0 18px;
511				}
512
513				/*** Inline ***/
514
515				:host([variant="inline"]) {
516					min-height: 110px;
517				}
518				:host([variant="inline"]) .image {
519					height: 100%;
520				}
521				:host([variant="inline"]) .main {
522					background: var(--mw-core-color-overlay-gradient-dark-top);
523					left: 0;
524					min-height: 110px;
525					padding: 18px 16px;
526					position: absolute;
527					right: 0;
528					top: 0;
529				}
530				:host([variant="inline"]) .content {
531					align-items: center;
532					display: flex;
533					justify-content: space-between;
534					margin: 0;
535				}
536				:host([variant="inline"]) a.href {
537					color: var(--mw-core-color-body-text-bright);
538				}
539				:host([variant="inline"]) .bookmark {
540					line-height: 1;
541					margin: 0 0 0 10px;
542				}
543				:host([variant="inline"]) .bookmark {
544					color: var(--mw-core-color-action-primary);
545				}
546				:host([variant="inline"]) .bookmark:hover {
547					color: var(--mw-core-color-action-primary-variant);
548				}
549
550				@media all and (min-width: 420px) {
551
552					.tour-data.uphill {
553						display: flex;
554					}
555
556				}
557
558				@media all and (min-width: 500px) {
559
560					:host([as="singleteaser"]) .teaser {
561						flex-direction: row;
562					}
563					:host([as="singleteaser"]) .image {
564						flex: 1;
565					}
566					:host([as="singleteaser"]) .main {
567						flex: 2;
568					}
569					:host([as="singleteaser"][objecttype="provider"]) .content {
570						flex: 1;
571					}
572					:host([as="singleteaser"][objecttype="provider"]) .teasertext,
573					:host([as="singleteaser"][objecttype="infrastructure"]) .teasertext,
574					:host([as="singleteaser"][objecttype="tour"]) .teasertext {
575						display: block;
576						margin: 24px 0 0 0;
577					}
578
579				}
580
581				@media all and (min-width: 769px) {
582
583					.main {
584						padding: 32px 24px;
585					}
586					.content {
587						margin: 0 0 20px;
588					}
589					.price {
590						margin: 18px 0 0;
591					}
592					.title {
593						margin: 0 0 18px;
594					}
595					.left p.location {
596						margin: 0 0 4px;
597					}
598
599					/*** Inline ***/
600
601					:host([variant="inline"]) .main {
602						padding: 25px;
603					}
604
605				}
606
607			`
608		];
609	}
610
611	// render template
612
613	render() {
614		// check data
615
616		if (!this._data) {
617			console.error("no valid data");
618			return "";
619		}
620
621		if (!this.variant) {
622			this.variant = "default";
623		}
624
625		let objectType = "web";
626
627		if (this._data.objectType) {
628			objectType = this._data.objectType;
629		}
630
631		this.objectType = objectType;
632
633		// detect objectType
634
635		switch (objectType) {
636			case "web":
637			case "event":
638				return this.layoutDefault();
639
640			case "infrastructure":
641				return this.layoutInfrastructure();
642
643			case "tour":
644				return this.layoutTour();
645
646			case "service":
647				return this.layoutService();
648
649			case "provider":
650			case "package":
651				return this.layoutProvider();
652
653			case "joboffer":
654				return this.layoutJobOffer();
655
656			default:
657				return this.layoutDefault();
658		}
659	}
660
661	// getter and setter
662
663	get linkelement() {
664		return this._getAttribute("linkelement");
665	}
666
667	set linkelement(val) {
668		this._setAttribute("linkelement", val);
669	}
670
671	get button() {
672		return this._getAttribute("button");
673	}
674
675	set button(val) {
676		this._setAttribute("button", val);
677	}
678
679	get buttonattr() {
680		return this._getAttribute("buttonattr");
681	}
682
683	set buttonattr(val) {
684		this._setAttribute("buttonattr", val);
685	}
686
687	get bookmark() {
688		return this._getAttribute("bookmark");
689	}
690
691	set bookmark(val) {
692		this._setAttribute("bookmark", val);
693	}
694
695	set background(val) {
696		this._setAttribute("background", val);
697	}
698
699	get background() {
700		return this._getAttribute("background");
701	}
702
703	get variant() {
704		return this._getAttribute("variant");
705	}
706
707	set variant(val) {
708		this._setAttribute("variant", val);
709	}
710
711	get saleschannelid() {
712		return this._getAttribute("salesChannelID");
713	}
714
715	set saleschannelid(val) {
716		this._setAttribute("salesChannelID", val);
717	}
718
719	get mqmedia() {
720		return this._getAttribute("mqmedia");
721	}
722
723	set mqmedia(val) {
724		this._setAttribute("mqmedia", val);
725	}
726
727	get maxlines() {
728		return this._getAttribute("maxlines");
729	}
730
731	set maxlines(val) {
732		this._setAttribute("maxlines", val);
733	}
734
735	get maxlineslineheight() {
736		return this._getAttribute("maxlineslineheight");
737	}
738
739	set maxlineslineheight(val) {
740		this._setAttribute("maxlineslineheight", val);
741	}
742
743	get fallbackpicture() {
744		return this._getAttribute("fallbackpicture");
745	}
746
747	set fallbackpicture(val) {
748		this._setAttribute("fallbackpicture", val);
749	}
750
751	get objectType() {
752		return this._getAttribute("objectType");
753	}
754
755	set objectType(val) {
756		this._setAttribute("objectType", val);
757	}
758
759	get as() {
760		return this._getAttribute("as");
761	}
762
763	set as(val) {
764		this._setAttribute("as", val);
765	}
766
767	get priceaddition() {
768		return this._getAttribute("priceaddition");
769	}
770
771	set priceaddition(val) {
772		this._setAttribute("priceaddition", val);
773	}
774
775	get priceformat() {
776		return this._getAttribute("priceformat");
777	}
778
779	set priceformat(val) {
780		this._setAttribute("priceformat", val);
781	}
782
783	get preloadprices() {
784		return this._getAttribute("preloadprices");
785	}
786
787	set preloadprices(val) {
788		this._setAttribute("preloadprices", val);
789	}
790
791	set asyncprices(teaser) {
792		// try to insert prices
793		// ! important note: preloadprices should be set before
794
795		if (teaser.price && teaser.price.from >= 0) {
796			if (!this._dom.providerPriceValue) {
797				console.error("providerPriceValue missing");
798				return undefined;
799			}
800
801			let previousPrice = ``;
802			this._data = teaser;
803
804			if (this._dom.providerPrice) {
805				this._dom.providerPrice.setAttribute("tabindex", "0");
806			}
807
808			if (teaser.price.isSpecialPrice) {
809				let prefix = this._CONSTANTS.PRICEFROM;
810				let currency = "€";
811				let discount = this._data.price.specialPriceDiscount;
812
813				let formatedPrice = mwUtility.formatNumber(this._data.price.from + discount);
814
815				previousPrice = `${prefix} ${formatedPrice} ${currency}`;
816			}
817
818			if (teaser.price.isSpecialPrice) {
819				this._dom.providerPriceValue.innerHTML += `<span class="previous-price">${this.priceValue()}</span>`;
820			}
821
822			this._dom.providerPriceValue.innerHTML = `${
823				teaser.price.isSpecialPrice
824					? `<span class="price-wrapper"><span class="previous-price">${previousPrice}</span>`
825					: ``
826			}<span class="price">${this.priceValue()}<span class="price-info"><mw-core-icon class="cancellation" name="I" size="s"></mw-core-icon></span></span></span>`;
827
828			if (this._data.price.addition) {
829				this._dom.providerPriceLabel.innerHTML = this.priceLabel();
830			} else {
831				this._dom.providerPriceLabel.innerHTML = ``;
832			}
833		} else {
834			this._dom.providerPriceValue.innerHTML = ``;
835
836			if (this._dom.providerPrice) {
837				this._dom.providerPrice.setAttribute("tabindex", "-1");
838			}
839		}
840
841		if (this._data.price.specialPriceName) {
842			this._dom.tag.innerHTML = this._data.price.specialPriceName;
843			this._dom.tag.classList.remove("no-tag");
844		} else {
845			this._dom.tag.classList.add("no-tag");
846		}
847
848		if (this._dom.availability) {
849			if (this._data.availability && this._data.availability <= 3) {
850				this._dom.availability.innerHTML = `${this._CONSTANTS.COUNTAVAILABILITY.replace(
851					"%%COUNT%%",
852					this._data.availability
853				)}`;
854				this._dom.availability.classList.remove("no-availability");
855			} else {
856				this._dom.availability.innerHTML = ``;
857				this._dom.availability.classList.add("no-availability");
858			}
859		}
860
861		if (this._data && this._data.cancellationDetails && this._data.cancellationDetails.free) {
862			if (this._dom.providerPriceLabel) {
863				this._dom.providerPriceLabel.classList.add("free-cancellation");
864				this._dom.providerPriceLabel.innerHTML = this._CONSTANTS.FREECANCELLATION;
865			}
866		}
867	}
868
869	get id() {
870		if (this._data.id) {
871			return this._data.id;
872		}
873
874		return undefined;
875	}
876
877	// custom methods
878
879	cacheDOM() {
880		// cache DOM elements for faster access
881
882		this._dom = {
883			teaser: this.shadowRoot.querySelector(".teaser"),
884			button: this.shadowRoot.querySelector(".button"),
885			buttonComponent: this.shadowRoot.querySelector(".button mw-core-button"),
886			tag: this.shadowRoot.querySelector(".tag"),
887			availability: this.shadowRoot.querySelector(".availability"),
888			image: this.shadowRoot.querySelector(".image"),
889			date: this.shadowRoot.querySelector(".date"),
890			price: this.shadowRoot.querySelector(".price"),
891			href: this.shadowRoot.querySelector("a.href"),
892			title: this.shadowRoot.querySelector(".title"),
893			text: this.shadowRoot.querySelector(".text"),
894			addition: this.shadowRoot.querySelector(".addition"),
895			location: this.shadowRoot.querySelector(".location"),
896			bookmark: this.shadowRoot.querySelector(".bookmark"),
897			bookmarkIcon: this.shadowRoot.querySelector(".bookmark mw-core-icon"),
898			openingDays: this.shadowRoot.querySelector(".opening-days"),
899			providerPrice: this.shadowRoot.querySelector(".provider-price"),
900			providerPriceValue: this.shadowRoot.querySelector(".price-value"),
901			providerPriceLabel: this.shadowRoot.querySelector(".price-label")
902		};
903	}
904
905	registerEventListener() {
906		// bookmark
907
908		if (this._dom.bookmark) {
909			this._dom.bookmark.addEventListener("click", (e) => {
910				e.preventDefault();
911
912				// add to bookmark
913
914				if (mwBookmark.add(this._data)) {
915					// change icon
916
917					this._dom.bookmarkIcon.name = "heartselected";
918					this._dom.bookmark.setAttribute("aria-pressed", true);
919				} else {
920					// remove from bookmark
921
922					mwBookmark.delete(this._data);
923
924					// change icon
925
926					this._dom.bookmarkIcon.name = "heart";
927					this._dom.bookmark.setAttribute("aria-pressed", false);
928				}
929			});
930		}
931
932		// toggle opening days wrapper
933
934		if (this._dom.openingDays) {
935			let current = this._dom.openingDays.querySelector(".current");
936
937			if (current) {
938				current.addEventListener("click", (e) => {
939					e.preventDefault();
940
941					const isExpanded = current.getAttribute("aria-expanded") === "true";
942					const newState = !isExpanded;
943
944					this._dom.openingDays.classList.toggle("show-further", newState);
945					current.setAttribute("aria-expanded", newState);
946				});
947			}
948		}
949
950		// show price info
951
952		if (this._dom.providerPrice) {
953			this._dom.providerPrice.setAttribute("tabindex", "-1");
954
955			this._dom.providerPrice.addEventListener("click", (e) => {
956				if (mwUtility.cookie("mw-core-bookingsearch") === "true") {
957					e.preventDefault();
958					this.showPriceInfo();
959				}
960			});
961		}
962	}
963
964	parseConstants() {
965		// parse constants
966		// from component-constants template
967
968		try {
969			let template = this.querySelector(`template[is="component-constants"]`);
970
971			if (template) {
972				this._CONSTANTS = JSON.parse(template.content.textContent);
973
974				// remove template from DOM
975
976				template.remove();
977			}
978		} catch (error) {
979			console.error(error);
980			return;
981		}
982	}
983
984	layoutDefault() {
985		// layout default (web, events)
986
987		this.parseConstants();
988
989		// pagename
990
991		let pagename = this._data.pageName === "//.html" && this._data.externalLink ? this._data.externalLink : this._data.pageName ? this._data.pageName : `#`;
992
993		if (this._data.linkAnchorTag !== "") {
994			pagename += `#${this._data.linkAnchorTag}`;
995		}
996
997		// target
998
999		let target = this._data.target ? `target="${this._data.target}"` : ``;
1000
1001		// rel attribute ("noopener noreferrer" if external url)
1002
1003		let rel = this._data.external === true ? `rel="noopener noreferrer"` : ``;
1004
1005		// teaserimage
1006
1007		let picture = ``;
1008
1009		// date
1010
1011		let date = ``;
1012		let dateString = ``;
1013		let startTime = ``;
1014
1015		if (this._data.date) {
1016			if (this._data.date.next && !this._data.recurring) {
1017				let dateNextObj = new Date(this._data.date.next);
1018
1019				if (mwUtility.checkValidDate(dateNextObj)) {
1020					let dateNextStr = mwUtility.formatDateToString(dateNextObj);
1021					date = dateNextStr;
1022				}
1023			} else {
1024				// datefrom
1025
1026				let dateFromObj = new Date(this._data.date.from);
1027
1028				if (mwUtility.checkValidDate(dateFromObj)) {
1029					let dateFromStr = mwUtility.formatDateToString(dateFromObj);
1030					date = dateFromStr;
1031				}
1032
1033				// dateto
1034
1035				let dateToObj = new Date(this._data.date.to);
1036
1037				if (mwUtility.checkValidDate(dateToObj)) {
1038					let dateToStr = mwUtility.formatDateToString(dateToObj);
1039					date !== "" ? (date += " - " + dateToStr) : (date = dateToStr);
1040				}
1041			}
1042
1043			dateString = date;
1044
1045			if (date) {
1046				date = `<p class="date ${
1047					this.variant === "compact" ? `normal-text-bold` : `large-text-bold`
1048				}">${date}</p>`;
1049			}
1050
1051			// starttime
1052
1053			if (this._data.date.timeFrom && this._data.date.timeFrom !== "00:00") {
1054				startTime = this._data.date.timeFrom;
1055			}
1056		}
1057
1058		if (startTime && startTime !== "00:00") {
1059			startTime = `<p class="starttime small-text-bold effect-border-radius">${startTime}</p>`;
1060		}
1061
1062		// read rule
1063		// if this.mqmedia is null
1064		// try read mqmedia from data
1065		// for example relevant in bookmark
1066
1067		let mqMediaRule;
1068
1069		if (this.mqmedia) {
1070			mqMediaRule = this.mqmedia;
1071		} else if (this._data.mqmedia) {
1072			mqMediaRule = this._data.mqmedia;
1073		}
1074
1075		if (this._data.media && this._data.media.length > 0 && this._data.media[0].filename) {
1076			picture = mwUtility.htmlMediaPicture(
1077				this._data.media[0],
1078				mqMediaRule,
1079				this._data?.media[0]?.alt || this._data?.title,
1080				{ lang: this._data.media[0]?.lang}
1081			);
1082		} else {
1083			if (this.fallbackpicture) {
1084				picture = `<picture><img src="${this.fallbackpicture}" alt="Fallback Image"></picture>`;
1085			}
1086		}
1087
1088		// link picture?
1089
1090		if (!this.linkelement && picture) {
1091			picture = `<a class="href" href="${
1092				this.objectType === "event" ? pagename + "#" + dateString : pagename
1093			}" ${target} ${rel}>${picture}</a>`;
1094		}
1095
1096		// teasertext
1097
1098		let description = ``;
1099
1100		if (this._data.description) {
1101			if (this.variant === "compact") {
1102				description = `<div class="text small-text">${this._data.description}</div>`;
1103			} else {
1104				description = `<div class="text normal-text">${this._data.description}</div>`;
1105			}
1106		}
1107
1108		// teasertitle
1109
1110		let teasertitle = this._data.title
1111			? `<p class="title ${
1112					this.variant === "compact" ? `normal-text-bold` : `large-text-bold`
1113			  }">${this._data.title}</p>`
1114			: ``;
1115
1116		// link teasertitle?
1117
1118		if (!this.linkelement && teasertitle) {
1119			teasertitle = `<a class="href" href="${
1120				this.objectType === "event" ? pagename + "#" + dateString : pagename
1121			}" ${target} ${rel}>${teasertitle}</a>`;
1122		}
1123
1124		// price
1125
1126		let price = ``;
1127
1128		if (this.objectType === "event") {
1129			price = ``;
1130		} else {
1131			if (!this.priceformat) {
1132				this.priceformat = "long";
1133			}
1134
1135			if (this._data.price) {
1136				let priceShort = this._data.price.split(",")[0] + ",-";
1137				let priceLong = this._data.price;
1138
1139				if (this.priceformat === "short") {
1140					price = `<p class="price ${
1141						this.variant === "compact" ? `small-text-bold` : `large-text-bold`
1142					}">${priceShort} € ${
1143						this.priceaddition
1144							? `<span class="small-text-bold">${this.priceaddition}</span>`
1145							: ``
1146					}</p>`;
1147				} else {
1148					price = `<p class="price ${
1149						this.variant === "compact" ? `small-text-bold` : `large-text-bold`
1150					}">${priceLong} € ${
1151						this.priceaddition
1152							? `<span class="small-text-bold">${this.priceaddition}</span>`
1153							: ``
1154					}</p>`;
1155				}
1156			}
1157		}
1158
1159		// location
1160
1161		let location =
1162			this._data.location && this.variant !== "compact"
1163				? `<p class="location small-text-bold">${this._data.location}</p>`
1164				: ``;
1165
1166		// addition
1167
1168		let addition =
1169			this._data.addition && this.variant !== "compact"
1170				? `<p class="addition caption-text">${this._data.addition}</p>`
1171				: ``;
1172
1173		// bookmark
1174
1175		let bookmark =
1176			this.bookmark && this.variant !== "compact"
1177				? `<button aria-label="${document.documentElement.lang === "de" ? "merken" : "save"}" class="bookmark" type="button"><mw-core-icon size="m" name="${this.checkBookmark(
1178						"heart",
1179						"heartselected"
1180				  )}" aria-pressed="false"></mw-core-icon></button>`
1181				: ``;
1182
1183		// tag + recurring tag (events)
1184
1185		let tag = this._data.tag || this._data.recurring
1186			? `<p class="tag caption-text-bold effect-border-radius">${this._data.tag || this._CONSTANTS.RECURRING}</p>`
1187			: ``;
1188
1189		// status (events)
1190
1191		let status = ``
1192
1193		if (this.objectType === "event" && this._data.statusType) {
1194
1195			let label = ``;
1196			let state = ``;
1197
1198			switch (this._data.statusType) {
1199				case "cancelled":
1200					label = this._CONSTANTS.STATECANCELLED;
1201					state = "cancelled"
1202					break;
1203
1204				case "scheduled":
1205					label = this._CONSTANTS.STATESCHEDULED;
1206					state = "scheduled"
1207					break;
1208
1209				case "rescheduled":
1210					label = this._CONSTANTS.STATERESCHEDULED;
1211					state = "rescheduled"
1212					break;
1213
1214				case "postponed":
1215					label = this._CONSTANTS.STATEPOSTPONED;
1216					state = "postponed"
1217					break;
1218
1219			}
1220
1221			status = `<p class="tag state state-${state} caption-text-bold effect-border-radius">${label}</p>` 
1222		}
1223
1224		// button
1225
1226		let button = ``;
1227
1228		if (this.button !== true && this.button !== null) {
1229			if (this.variant === "compact") {
1230				button = `
1231					<p class="button">
1232						<mw-core-button nofocus tertiary ${this.buttonattr} nolabel icon="3" small iconsize="s"></mw-core-button>
1233					</p>`;
1234			} else {
1235				button = `
1236					<p class="button">
1237						<mw-core-button nofocus primary ${this.buttonattr} label="${this.button}" labelclass="primary-link"></mw-core-button>
1238					</p>
1239				`;
1240			}
1241		}
1242
1243		// link button?
1244
1245		if (!this.linkelement && button) {
1246			button = `<a class="href" href="${
1247				this.objectType === "event" ? pagename + "#" + dateString : pagename
1248			}" ${target} ${rel}>${button}</a>`;
1249		}
1250
1251		let html = ``;
1252
1253		if (this.variant === "inline") {
1254			html = `
1255				${
1256					this.linkelement
1257						? `<a class="href" href="${
1258								this.objectType === "event" ? pagename + "#" + dateString : pagename
1259						  }" ${target} ${rel}>`
1260						: ``
1261				}
1262					<div class="teaser">
1263						${tag}
1264						${status}
1265						<div class="image">
1266							${picture}
1267							<span class="teaser-frame" aria-hidden="true"></span>
1268						</div>
1269						<div class="main">
1270							<div class="content">
1271								${teasertitle}
1272								${bookmark}
1273							</div>
1274						</div>
1275					</div>
1276				${this.linkelement ? `</a>` : ``}
1277			`;
1278		} else {
1279			html = `
1280				${
1281					this.linkelement
1282						? `<a class="href" href="${
1283								this.objectType === "event" ? pagename + "#" + dateString : pagename
1284						  }" ${target} ${rel}>`
1285						: ``
1286				}
1287					<div class="teaser ${tag && status ? `double-tag` : ``}">
1288						${tag}
1289						${status}
1290						<div class="image">
1291							${picture}
1292							<span class="teaser-frame" aria-hidden="true"></span>
1293						</div>
1294						<div class="main">
1295							<div class="content">
1296								${date}
1297								${teasertitle}
1298								${description}
1299								${price}
1300							</div>
1301							<div class="bottom ${
1302								this._data.location || this._data.addition || startTime ? `has-info` : ``
1303							}">
1304								${
1305									location || addition || startTime
1306										? `<div class="left">${location + addition + startTime}</div>`
1307										: ""
1308								}
1309								${bookmark || button ? ` <div class="right">${bookmark + button}</div>` : ""}
1310							</div>
1311						</div>
1312					</div>
1313				${this.linkelement ? `</a>` : ``}
1314			`;
1315		}
1316
1317		return html;
1318	}
1319
1320	layoutInfrastructure() {
1321		// layout infrastructure
1322
1323		// parse constants
1324
1325		this.parseConstants();
1326
1327		if (!this._CONSTANTS) {
1328			console.error(`CONSTANTS missing in ${this.tagName.toLowerCase()}`);
1329			return;
1330		}
1331
1332		// pagename
1333
1334		let pagename = this._data.pageName ? this._data.pageName : `#`;
1335
1336		// target
1337
1338		let target = this._data.target ? `target="${this._data.target}"` : ``;
1339
1340		// teaserimage
1341
1342		let picture = ``;
1343
1344		// read rule
1345		// if this.mqmedia is null
1346		// try read mqmedia from data
1347		// for example relevant in bookmark
1348
1349		let mqMediaRule;
1350
1351		if (this.mqmedia) {
1352			mqMediaRule = this.mqmedia;
1353		} else if (this._data.mqmedia) {
1354			mqMediaRule = this._data.mqmedia;
1355		}
1356
1357		if (this._data.media && this._data.media.length > 0) {
1358			picture = mwUtility.htmlMediaPicture(
1359				this._data.media[0],
1360				mqMediaRule,
1361				this._data?.media[0]?.alt || this._data?.title
1362			);
1363		} else {
1364			if (this.fallbackpicture) {
1365				picture = `<picture><img src="${this.fallbackpicture}" alt="Fallback Image"></picture>`;
1366			}
1367		}
1368
1369		// link picture?
1370
1371		if (!this.linkelement && picture) {
1372			picture = `<a class="href" href="${
1373				this.objectType === "event" ? pagename + "#" + dateString : pagename
1374			}" ${target}>${picture}</a>`;
1375		}
1376
1377		// title
1378
1379		let title = this._data.title
1380			? `<p class="title ${
1381					this.variant === "compact" ? `normal-text-bold` : `large-text-bold`
1382			  }">${this._data.title}</p>`
1383			: ``;
1384
1385		// link title?
1386
1387		if (!this.linkelement && title) {
1388			title = `<a class="href" href="${
1389				this.objectType === "event" ? pagename + "#" + dateString : pagename
1390			}" ${target}>${title}</a>`;
1391		}
1392
1393		// teasertext
1394
1395		let description = ``;
1396
1397		if (this._data.description) {
1398			if (this.variant === "compact") {
1399				description = `<div class="teasertext small-text">${this._data.description}</div>`;
1400			} else {
1401				description = `<div class="teasertext normal-text">${this._data.description}</div>`;
1402			}
1403		}
1404
1405		// address
1406
1407		let address = ``;
1408
1409		if (this._data.address) {
1410			let street = this._data.address.street ? `${this._data.address.street}` : ``;
1411			let zipCode = this._data.address.zipCode ? `${this._data.address.zipCode}` : ``;
1412			let town = this._data.address.town ? `${this._data.address.town}` : ``;
1413
1414			address = `
1415				<div class="text small-text">
1416					${street + (street ? `,&nbsp` : ``) + zipCode + (zipCode ? `&nbsp` : ``) + town}
1417				</div>
1418			`;
1419		}
1420
1421		// phone
1422
1423		let phone = ``;
1424
1425		if (this._data.address) {
1426			if (this._data.address.phone || this._data.address.mobile) {
1427				let phonelink = this._data.address.phone
1428					? this._data.address.phone
1429					: this._data.address.mobile;
1430
1431				phone = `
1432					<a class="phone caption-text" href="tel:${phonelink
1433						.replace(/\s/g, "")
1434						.replace(/[\(\)]/g, "")}">
1435						<mw-core-icon size="m" name="phone"></mw-core-icon>
1436						${this._data.address.phone ? this._data.address.phone : this._data.address.mobile}
1437					</a>
1438				`;
1439			}
1440		}
1441
1442		// opening days
1443
1444		let openingDays = ``;
1445
1446		if (this._data.openingTimes && this._data.openingTimes.length > 0) {
1447			let currentOpening = this.checkCurrentOpening();
1448
1449			// check if next opening date <= 7 days
1450
1451			if (this.checkNextOpeningDaySmallerThen(this._data.openingTimes, 6)) {
1452				openingDays = `
1453					<div class="opening-days">
1454						<button class="current" type="button" aria-expanded="false">
1455							${
1456								currentOpening
1457									? `${currentOpening} <mw-core-icon size="s" name="5"></mw-core-icon>`
1458									: ``
1459							}
1460						</button>
1461						<div class="further">${this.listNextOpeningDays()}</div>
1462					</div>
1463				`;
1464			} else {
1465				openingDays = `
1466					<div class="opening-days">
1467						<div class="current no-further-openingdays">
1468							${currentOpening ? `${currentOpening}` : ``}
1469						</div>
1470					</div>
1471				`;
1472			}
1473		}
1474
1475		// status
1476
1477		let status = ``;
1478
1479		if (this.objectType === "infrastructure" && this._data.statusType) {
1480
1481			let label = ``;
1482			let state = ``;
1483
1484			switch (this._data.statusType) {
1485				case "open":
1486					label = this._CONSTANTS.OPEN;
1487					state = "open"
1488					break;
1489
1490				case "closed":
1491					label = this._CONSTANTS.CLOSED;
1492					state = "closed"
1493					break;
1494
1495				case "underconstruction":
1496					label = this._CONSTANTS.UNDERCONSTRUCTION;
1497					state = "underconstruction"
1498					break;
1499
1500				case "disturbance":
1501					label = this._CONSTANTS.DISTURBANCE;
1502					state = "disturbance"
1503					break;
1504					
1505			}
1506
1507			status = `<p class="tag state state-${state} caption-text-bold effect-border-radius">${label}</p>` 
1508		}
1509
1510		// holiday themes
1511
1512		let holidayThemes = ``;
1513
1514		if (this._data.holidayThemes) {
1515			holidayThemes += `<div class="holidayThemes">`;
1516
1517			this._data.holidayThemes.forEach((item) => {
1518				if (item?.media?.[0]?.filename) {
1519					holidayThemes += `<div class="logo" title="${item?.title}"><img src="${item?.media?.[0]?.filename}" alt="${item?.title}" aria-label="${item?.title}" /></div>`;
1520				}
1521			})
1522
1523			holidayThemes += `</div>`;
1524		}
1525
1526		// bookmark
1527
1528		let bookmark =
1529			this.bookmark && this.variant !== "compact"
1530				? `<button aria-label="${document.documentElement.lang === "de" ? "merken" : "save"}" class="bookmark" type="button"><mw-core-icon size="m" name="${this.checkBookmark(
1531						"heart",
1532						"heartselected"
1533				  )}" aria-pressed="false"></mw-core-icon></button>`
1534				: ``;
1535
1536		// button
1537
1538		let button = ``;
1539
1540		if (this.button !== true && this.button !== null) {
1541			if (this.variant === "compact") {
1542				button = `
1543					<p class="button">
1544						<mw-core-button nofocus tertiary nolabel icon="3" small iconsize="s"></mw-core-button>
1545					</p>`;
1546			} else {
1547				button = `
1548					<p class="button">
1549						<mw-core-button nofocus primary label="${this.button}" labelclass="primary-link"></mw-core-button>
1550					</p>
1551				`;
1552			}
1553		}
1554
1555		let html = ``;
1556
1557		if (this.variant === "inline") {
1558			html = `
1559				<div class="teaser">
1560					<a class="href" href="${
1561						this.objectType === "event" ? pagename + "#" + dateString : pagename
1562					}" ${target}>
1563						<div class="image">
1564							${picture}
1565							<span class="teaser-frame" aria-hidden="true"></span>
1566						</div>
1567					</a>
1568					<div class="main">
1569						<div class="content">
1570							<a class="href" href="${
1571								this.objectType === "event" ? pagename + "#" + dateString : pagename
1572							}" ${target}>
1573								${title}
1574							</a>
1575							${bookmark}
1576						</div>
1577					</div>
1578				</div>
1579			`;
1580		} else {
1581			html = `
1582					<div class="teaser">
1583						<a class="href" href="${
1584							this.objectType === "event" ? pagename + "#" + dateString : pagename
1585						}" ${target}>
1586							<div class="image">
1587								${picture}
1588								<span class="teaser-frame" aria-hidden="true"></span>
1589							</div>
1590						</a>
1591						<div class="main">
1592							<div class="content">
1593								<a class="href" href="${
1594									this.objectType === "event" ? pagename + "#" + dateString : pagename
1595								}" ${target}>
1596									${title}
1597								</a>
1598								${address}
1599								${description}
1600								${status ? status : openingDays}
1601							</div>
1602							<div class="bottom ${phone ? `has-info` : ``}">
1603								<div class="left">${phone}</div>
1604								${bookmark || button ? ` <div class="right">${bookmark + button}</div>` : ""}
1605							</div>
1606						</div>
1607					</div>
1608			`;
1609		}
1610
1611		return html;
1612	}
1613
1614	layoutService() {
1615		// layout service
1616
1617		// parse constants
1618
1619		this.parseConstants();
1620
1621		if (!this._CONSTANTS) {
1622			console.error(`CONSTANTS missing in ${this.tagName.toLowerCase()}`);
1623			return;
1624		}
1625
1626		// pagename
1627
1628		let pagename = this._data.pageName ? this._data.pageName : `#`;
1629
1630		// target
1631
1632		let target = this._data.target ? `target="${this._data.target}"` : ``;
1633
1634		// teaserimage
1635
1636		let picture = ``;
1637
1638		// read rule
1639		// if this.mqmedia is null
1640		// try read mqmedia from data
1641		// for example relevant in bookmark
1642
1643		let mqMediaRule;
1644
1645		if (this.mqmedia) {
1646			mqMediaRule = this.mqmedia;
1647		} else if (this._data.mqmedia) {
1648			mqMediaRule = this._data.mqmedia;
1649		}
1650
1651		if (this._data.mediaList && this._data.mediaList.length > 0) {
1652			picture = mwUtility.htmlMediaPicture(
1653				this._data.mediaList[0],
1654				mqMediaRule,
1655				this._data?.mediaList[0]?.alt || this._data?.title,
1656				{ lang: this._data.mediaList[0]?.lang}
1657			);
1658		} else {
1659			if (this.fallbackpicture) {
1660				picture = `<picture><img src="${this.fallbackpicture}" alt="Fallback Image"></picture>`;
1661			}
1662		}
1663
1664		// link picture?
1665
1666		if (!this.linkelement && picture) {
1667			picture = `<a class="href" href="${pagename}" ${target}>${picture}</a>`;
1668		}
1669
1670		// title
1671
1672		let title = this._data.title
1673			? `<p class="title ${
1674					this.variant === "compact" ? `normal-text-bold` : `large-text-bold`
1675			  }">${this._data.title}</p>`
1676			: ``;
1677
1678		// link title?
1679
1680		if (!this.linkelement && title) {
1681			title = `<a class="href" href="${pagename}" ${target}>${title}</a>`;
1682		}
1683
1684		// price
1685
1686		let price = ``;
1687
1688		if (!this.priceformat) {
1689			this.priceformat = "long";
1690		}
1691
1692		if (this._data.price && !isNaN(this._data.price.from)) {
1693			let priceValue = this.priceValue();
1694			
1695			price = `<p class="price ${this.variant === "compact" ? `small-text-bold` : `large-text-bold`}">${priceValue}</p>`;
1696		}
1697
1698		// teasertext
1699
1700		let description = ``;
1701
1702		if (this._data.description) {
1703			if (this.variant === "compact") {
1704				description = `<div class="teasertext small-text">${this._data.description}</div>`;
1705			} else {
1706				description = `<div class="teasertext normal-text">${this._data.description}</div>`;
1707			}
1708		}
1709
1710		// bookmark
1711
1712		let bookmark =
1713			this.bookmark && this.variant !== "compact"
1714				? `<button aria-label="${document.documentElement.lang === "de" ? "merken" : "save"}" class="bookmark" type="button"><mw-core-icon size="m" name="${this.checkBookmark(
1715						"heart",
1716						"heartselected"
1717				  )}" aria-pressed="false"></mw-core-icon></button>`
1718				: ``;
1719
1720		// button
1721
1722		let button = ``;
1723
1724		if (this.button !== true && this.button !== null) {
1725			if (this.variant === "compact") {
1726				button = `
1727					<p class="button">
1728						<mw-core-button nofocus tertiary nolabel icon="3" small iconsize="s"></mw-core-button>
1729					</p>`;
1730			} else {
1731				button = `
1732					<p class="button">
1733						<mw-core-button nofocus primary label="${this.button}" labelclass="primary-link"></mw-core-button>
1734					</p>
1735				`;
1736			}
1737		}
1738
1739		let html = ``;
1740
1741		if (this.variant === "inline") {
1742			html = `
1743				<div class="teaser">
1744					<a class="href" href="${pagename}" ${target}>
1745						<div class="image">
1746							${picture}
1747							<span class="teaser-frame" aria-hidden="true"></span>
1748						</div>
1749					</a>
1750					<div class="main">
1751						<div class="content">
1752							<a class="href" href="${pagename}" ${target}>
1753								${title}
1754							</a>
1755							${bookmark}
1756						</div>
1757					</div>
1758				</div>
1759			`;
1760		} else {
1761			html = `
1762					<div class="teaser">
1763						<a class="href" href="${pagename}" ${target}>
1764							<div class="image">
1765								${picture}
1766								<span class="teaser-frame" aria-hidden="true"></span>
1767							</div>
1768						</a>
1769						<div class="main">
1770							<div class="content">
1771								<a class="href" href="${pagename}" ${target}>
1772									${title}
1773								</a>
1774								${description}
1775								${price}
1776							</div>
1777							<div class="bottom">
1778								${bookmark || button ? ` <div class="right">${bookmark + button}</div>` : ""}
1779							</div>
1780						</div>
1781					</div>
1782			`;
1783		}
1784
1785		return html;
1786	}
1787
1788	layoutJobOffer() {
1789		// layout default (job offer)
1790
1791		// parse constants
1792
1793		this.parseConstants();
1794
1795		if (!this._CONSTANTS) {
1796			console.error(`CONSTANTS missing in ${this.tagName.toLowerCase()}`);
1797			return;
1798		}
1799
1800		// pagename
1801
1802		let pagename = this._data.pagename ? this._data.pagename : `#`;
1803
1804		// target
1805
1806		let target = this._data.target ? `target="${this._data.target}"` : ``;
1807
1808		// rel attribute ("noopener noreferrer" if external url)
1809
1810		let rel = this._data.external === true ? `rel="noopener noreferrer"` : ``;
1811
1812		// teaserimage
1813
1814		let picture = ``;
1815
1816		// read rule
1817		// if this.mqmedia is null
1818		// try read mqmedia from data
1819		// for example relevant in bookmark
1820
1821		let mqMediaRule;
1822
1823		if (this.mqmedia) {
1824			mqMediaRule = this.mqmedia;
1825		} else if (this._data.mqmedia) {
1826			mqMediaRule = this._data.mqmedia;
1827		}
1828
1829		if (this._data.media && this._data.media.length > 0 && this._data.media[0].filename) {
1830			picture = mwUtility.htmlMediaPicture(
1831				this._data.media[0],
1832				mqMediaRule,
1833				this._data.media[0]?.alt || this._data?.title,
1834				{ lang: this._data.media[0]?.lang}
1835			);
1836		} else {
1837			if (this.fallbackpicture) {
1838				picture = `<picture><img src="${this.fallbackpicture}" alt="Fallback Image"></picture>`;
1839			}
1840		}
1841
1842		// teasertext
1843
1844		let description = ``;
1845
1846		if (this._data.description) {
1847			if (this.variant === "compact") {
1848				description = `<div class="text small-text">${this._data.description}</div>`;
1849			} else {
1850				description = `<div class="text normal-text">${this._data.description}</div>`;
1851			}
1852		}
1853
1854		// teasertitle
1855
1856		let teasertitle = this._data.title
1857			? `<p class="title ${
1858					this.variant === "compact" ? `normal-text-bold` : `large-text-bold`
1859			  }">${this._data.title}</p>`
1860			: ``;
1861
1862		// place
1863
1864		let place =
1865			this._data.placeTitle && this.variant !== "compact"
1866				? `<p class="place normal-text-bold">${this._data.placeTitle}</p>`
1867				: ``;
1868
1869		// town
1870
1871		let town =
1872			this._data.townTitle && this.variant !== "compact"
1873				? `<p class="town normal-text-bold">${this._data.townTitle}</p>`
1874				: ``;
1875
1876		// salary
1877
1878		let salary =
1879			this._data.salary && this.variant !== "compact"
1880				? `
1881						<p class="salary large-text-bold">${this._data.salaryText}</p>
1882					`
1883				: ``;
1884
1885		// occupationType
1886
1887		let occupationType =
1888			this._data.occupationType && this.variant !== "compact"
1889				? `
1890						<p class="occupation-type small-text-bold">
1891							${this._data.occupationType === "fulltime" ? this._CONSTANTS.LABELFULLTIME : ``}
1892							${this._data.occupationType === "parttime" ? this._CONSTANTS.LABELPARTTIME : ``}
1893							${
1894								this._data.occupationType === "fulltimeorparttime"
1895									? this._CONSTANTS.LABELFULLTIMEORPARTTIME
1896									: ``
1897							}
1898						</p>`
1899				: ``;
1900
1901			// occupationType
1902
1903		let occupationPeriod =
1904			this._data.occupationPeriod && this.variant !== "compact"
1905				? `
1906						<p class="occupation-period small-text-bold">
1907							${this._data.occupationPeriod === "seasonal" ? this._CONSTANTS.LABELSEASONAL : ``}
1908							${this._data.occupationPeriod === "limited" ? this._CONSTANTS.LABELLIMITED : ``}
1909							${
1910								this._data.occupationPeriod === "yearround"
1911									? this._CONSTANTS.LABELYEARROUND
1912									: ``
1913							}
1914						</p>`
1915				: ``;
1916
1917		// bookmark
1918
1919		let bookmark =
1920			this.bookmark && this.variant !== "compact"
1921				? `<button aria-label="${document.documentElement.lang === "de" ? "merken" : "save"}" class="bookmark" type="button"><mw-core-icon size="m" name="${this.checkBookmark(
1922						"heart",
1923						"heartselected"
1924				  )}" aria-pressed="false"></mw-core-icon></button>`
1925				: ``;
1926
1927		// button
1928
1929		let button = ``;
1930
1931		if (this.button !== true && this.button !== null) {
1932			if (this.variant === "compact") {
1933				button = `
1934					<p class="button">
1935						<mw-core-button nofocus tertiary ${this.buttonattr} nolabel icon="3" small iconsize="s"></mw-core-button>
1936					</p>`;
1937			} else {
1938				button = `
1939					<p class="button">
1940						<mw-core-button nofocus primary ${this.buttonattr} label="${this.button}" labelclass="primary-link"></mw-core-button>
1941					</p>
1942				`;
1943			}
1944		}
1945
1946		let html = ``;
1947
1948		if (this.variant === "inline") {
1949			html = `
1950				${
1951					this.linkelement
1952						? `<a class="href" href="${
1953								this.objectType === "event" ? pagename + "#" + dateString : pagename
1954						  }" ${target} ${rel}>`
1955						: ``
1956				}
1957					<div class="teaser">
1958						<div class="image">
1959							${picture}
1960							<span class="teaser-frame" aria-hidden="true"></span>
1961						</div>
1962						<div class="main">
1963							<div class="content">
1964								${teasertitle}
1965								${bookmark}
1966							</div>
1967						</div>
1968					</div>
1969				${this.linkelement ? `</a>` : ``}
1970			`;
1971		} else {
1972			html = `
1973				 <a href="${pagename}" ${target} ${rel}>
1974					<div class="teaser">
1975						<div class="image">${picture}</div>
1976						<div class="main">
1977							<div class="content">
1978								${teasertitle}
1979								${description}
1980								${place || town ? `${place + town}` : ""}
1981							</div>
1982							<div class="bottom ${this._data.placeTitle || this._data.townTitle ? `has-info` : ``}">
1983								${salary || occupationType || occupationPeriod ? `<div class="left">${salary + occupationType + occupationPeriod}</div>` : ""}
1984								${bookmark || button ? ` <div class="right">${bookmark + button}</div>` : ""}
1985							</div>
1986						</div>
1987					</div>
1988				</a>
1989			`;
1990		}
1991
1992		return html;
1993	}
1994
1995	layoutTour() {
1996		// layout tour
1997
1998		// parse constants
1999
2000		this.parseConstants();
2001
2002		if (!this._CONSTANTS) {
2003			console.error(`CONSTANTS missing in ${this.tagName.toLowerCase()}`);
2004			return;
2005		}
2006
2007		// pagename
2008
2009		let pagename = this._data.pageName ? this._data.pageName : `#`;
2010
2011		// target
2012
2013		let target = this._data.target ? `target="${this._data.target}"` : ``;
2014
2015		// teaserimage
2016
2017		let picture = ``;
2018
2019		// read rule
2020		// if this.mqmedia is null
2021		// try read mqmedia from data
2022		// for example relevant in bookmark
2023
2024		let mqMediaRule;
2025
2026		if (this.mqmedia) {
2027			mqMediaRule = this.mqmedia;
2028		} else if (this._data.mqmedia) {
2029			mqMediaRule = this._data.mqmedia;
2030		}
2031
2032		if (this._data.media && this._data.media.length > 0) {
2033			picture = mwUtility.htmlMediaPicture(
2034				this._data.media[0],
2035				mqMediaRule,
2036				this._data.media[0]?.alt || this._data?.title,
2037				{ lang: this._data.media[0]?.lang}
2038			);
2039		} else {
2040			if (this.fallbackpicture) {
2041				picture = `<picture><img src="${this.fallbackpicture}" alt="Fallback Image"></picture>`;
2042			}
2043		}
2044
2045		// link picture?
2046
2047		if (!this.linkelement && picture) {
2048			picture = `<a class="href" href="${
2049				this.objectType === "event" ? pagename + "#" + dateString : pagename
2050			}" ${target}>${picture}</a>`;
2051		}
2052
2053		// title
2054
2055		let title = this._data.title
2056			? `<p class="title ${
2057					this.variant === "compact" ? `normal-text-bold` : `large-text-bold`
2058			  }">${this._data.title}</p>`
2059			: ``;
2060
2061		// link title?
2062
2063		if (!this.linkelement && title) {
2064			title = `<a class="href" href="${
2065				this.objectType === "event" ? pagename + "#" + dateString : pagename
2066			}" ${target}>${title}</a>`;
2067		}
2068
2069		// opening state
2070
2071		let state = ``;
2072
2073		if (this._data.statusType) {
2074			let label = ``;
2075
2076			switch (this._data.statusType) {
2077				case "open":
2078					label = this._CONSTANTS.OPEN;
2079					state = "open";
2080					break;
2081
2082				case "closed":
2083					label = this._CONSTANTS.CLOSED;
2084					state = "closed";
2085					break;
2086
2087				case "blocked":
2088					label = this._CONSTANTS.BLOCKED;
2089					state = "blocked";
2090					break;
2091
2092				case "partiallyopen":
2093					label = this._CONSTANTS.PARTIALLYOPEN;
2094					state = "partiallyopen";
2095					break;
2096
2097				case "inpreparation":
2098					label = this._CONSTANTS.INPREPARATION;
2099					state = "inpreparation";
2100					break;
2101
2102				case "underconstruction":
2103					label = this._CONSTANTS.UNDERCONSTRUCTION;
2104					state = "underconstruction";
2105					break;
2106
2107				case "disturbance":
2108					label = this._CONSTANTS.DISTURBANCE;
2109					state = "disturbance";
2110					break;
2111			}
2112
2113			if (label !== "") {
2114				state = `<span class="state state-${state} small-text-bold">${label}</span>`;
2115			}
2116		}
2117
2118		// teasertext
2119
2120		let description = ``;
2121
2122		if (this._data.description) {
2123			if (this.variant === "compact") {
2124				description = `<div class="teasertext small-text">${this._data.description}</div>`;
2125			} else {
2126				description = `<div class="teasertext normal-text">${this._data.description}</div>`;
2127			}
2128		}
2129
2130		// tag
2131
2132		let tag = ``;
2133
2134		if (this._data.difficultyLevel) {
2135			let level = ``;
2136			let levelNum = 0;
2137
2138			switch (this._data.difficultyLevel) {
2139				case "easy":
2140					level = this._CONSTANTS.EASY;
2141					levelNum = 1;
2142					break;
2143
2144				case "medium":
2145					level = this._CONSTANTS.MODERATE;
2146					levelNum = 2;
2147					break;
2148
2149				case "hard":
2150					level = this._CONSTANTS.DIFFICULT;
2151					levelNum = 3;
2152					break;
2153			}
2154
2155			if (level !== "") {
2156				tag = `<p class="tag level${levelNum} caption-text-bold effect-border-radius">${level}</p>`;
2157			}
2158		}
2159
2160		// categories
2161
2162		let categories = this._data.categories
2163			? `<p class="categories small-text">${this._data.categories.join(", ")}</p>`
2164			: ``;
2165
2166		// duration
2167
2168		let tmpDurationValue = parseInt(this._data.duration);
2169		let tmpDurationString = ``;
2170
2171		if (tmpDurationValue > 0) {
2172			let hours = Math.floor(tmpDurationValue / 60);
2173			let minutes = tmpDurationValue % 60;
2174			tmpDurationString = `${mwUtility.leadingZero(hours, 2)}:${mwUtility.leadingZero(
2175				minutes,
2176				2
2177			)} ${this._CONSTANTS.PREFIXH}`;
2178		}
2179
2180		let duration = tmpDurationString
2181			? `<p class="duration tour-data small-text"><mw-core-icon size="m" name="t"></mw-core-icon>${tmpDurationString}</p>`
2182			: ``;
2183
2184		// distance
2185
2186		let tmpDistanceValue = parseInt(this._data.distance);
2187		let tmpDistanceString = ``;
2188
2189		if (tmpDistanceValue >= 1000) {
2190			tmpDistanceString = tmpDistanceValue / 1000;
2191			tmpDistanceString = `${tmpDistanceString.toFixed(2).toString().replace(".", ",")} ${
2192				this._CONSTANTS.PREFIXKM
2193			}`;
2194		} else if (tmpDistanceValue > 0) {
2195			tmpDistanceString = `${tmpDistanceValue.toString().replace(".", ",")} ${
2196				this._CONSTANTS.PREFIXM
2197			}`;
2198		}
2199
2200		let distance = tmpDistanceString
2201			? `<p class="distance tour-data small-text">
2202					<mw-core-icon size="m" name="l"></mw-core-icon>
2203					${tmpDistanceString}
2204				</p>`
2205			: ``;
2206
2207		// uphill
2208
2209		let tmpUphillValue = parseInt(this._data.uphill);
2210		let tmpUphillString = ``;
2211
2212		if (tmpUphillValue > 0) {
2213			tmpUphillString = `${Math.ceil(tmpUphillValue).toLocaleString()} ${
2214				this._CONSTANTS.PREFIXM
2215			}`;
2216		}
2217
2218		let uphill = tmpUphillString
2219			? `<p class="uphill tour-data small-text"><mw-core-icon size="m" name="*"></mw-core-icon>${tmpUphillString}</p>`
2220			: ``;
2221
2222		// bookmark
2223
2224		let bookmark =
2225			this.bookmark && this.variant !== "compact"
2226				? `<button aria-label="${document.documentElement.lang === "de" ? "merken" : "save"}" class="bookmark" type="button"><mw-core-icon size="m" name="${this.checkBookmark(
2227						"heart",
2228						"heartselected"
2229				  )}" aria-pressed="false"></mw-core-icon></button>`
2230				: ``;
2231
2232		// button
2233
2234		let button = ``;
2235
2236		if (this.button !== true && this.button !== null) {
2237			if (this.variant === "compact") {
2238				button = `
2239					<p class="button">
2240						<mw-core-button nofocus tertiary nolabel icon="3" small iconsize="s"></mw-core-button>
2241					</p>`;
2242			} else {
2243				button = `
2244					<p class="button">
2245						<mw-core-button nofocus primary label="${this.button}" labelclass="primary-link"></mw-core-button>
2246					</p>
2247				`;
2248			}
2249		}
2250
2251		let html = ``;
2252
2253		if (this.variant === "inline") {
2254			html = `
2255				${
2256					this.linkelement
2257						? `<a class="href" href="${
2258								this.objectType === "event" ? pagename + "#" + dateString : pagename
2259						  }" ${target}>`
2260						: ``
2261				}
2262					<div class="teaser">
2263						${tag}
2264						<div class="image">
2265							${picture}
2266							<span class="teaser-frame" aria-hidden="true"></span>
2267						</div>
2268						<div class="main">
2269							<div class="content">
2270								${title}
2271								${bookmark}
2272							</div>
2273						</div>
2274					</div>
2275				${this.linkelement ? `</a>` : ``}
2276			`;
2277		} else {
2278			html = `
2279				${
2280					this.linkelement
2281						? `<a class="href" href="${
2282								this.objectType === "event" ? pagename + "#" + dateString : pagename
2283						  }" ${target}>`
2284						: ``
2285				}
2286					<div class="teaser">
2287						${tag}
2288						<div class="image">
2289							${picture}
2290							<span class="teaser-frame" aria-hidden="true"></span>
2291						</div>
2292						<div class="main">
2293							<div class="content">
2294								${title}
2295								${categories}
2296								${state}
2297								${description}
2298							</div>
2299							<div class="bottom ${duration || distance || uphill ? `has-info` : ``}">
2300								<div class="left">${duration + distance + uphill}</div>
2301								${bookmark || button ? ` <div class="right">${bookmark + button}</div>` : ""}
2302							</div>
2303						</div>
2304					</div>
2305				${this.linkelement ? `</a>` : ``}
2306			`;
2307		}
2308
2309		return html;
2310	}
2311
2312	layoutProvider() {
2313		// layout provider
2314
2315		// parse constants
2316
2317		this.parseConstants();
2318
2319		// pagename
2320
2321		let pagename = ``
2322
2323		if (this?.objectType === "package") {
2324			if (this._data?.type !== "provider") {
2325				if (micadoWeb?.lang === "de") {
2326					pagename = `/de/hotel-unterkunft/spezial-angebote.html#/spezialangebote/SBG/${this._data.interfaceIdentifier}`;
2327				} else {
2328					pagename = `/en/hotel-accomodation/special-offers.html#/specialoffers/SBG/${this._data.interfaceIdentifier}`;
2329				}
2330			} else {
2331				pagename = this._data.pageName ? this._data.pageName : `#`;
2332			}
2333		} else {
2334			pagename = this._data.pageName ? this._data.pageName : `#`;
2335
2336			if (this.saleschannelid && this.saleschannelid != 4242) {
2337				pagename += `?salesChannelID=${this.saleschannelid}`
2338			}
2339		}
2340
2341		// target
2342
2343		let target = this._data.target ? `target="${this._data.target}"` : ``;
2344
2345		// teaserimage
2346
2347		let picture = ``;
2348
2349		// read rule
2350		// if this.mqmedia is null
2351		// try read mqmedia from data
2352		// for example relevant in bookmark
2353
2354		let mqMediaRule;
2355
2356		if (this.mqmedia) {
2357			mqMediaRule = this.mqmedia;
2358		} else if (this._data.mqmedia) {
2359			mqMediaRule = this._data.mqmedia;
2360		}
2361
2362		if (this._data.media && this._data.media.length > 0) {
2363			picture = mwUtility.htmlMediaPicture(
2364				this._data.media[0],
2365				mqMediaRule,
2366				this._data.media[0]?.alt || this._data?.title,
2367				{ lang: this._data.media[0]?.lang}
2368			);
2369		} else {
2370			if (this.fallbackpicture) {
2371				picture = `<picture><img src="${this.fallbackpicture}" alt="Fallback Image"></picture>`;
2372			}
2373		}
2374
2375		// link picture?
2376
2377		if (!this.linkelement && picture) {
2378			picture = `<a class="href" href="${
2379				this.objectType === "event" ? pagename + "#" + dateString : pagename
2380			}" ${target}>${picture}</a>`;
2381		}
2382
2383		// title
2384
2385		let title = this._data.title
2386			? `<p class="title ${
2387					this.variant === "compact" ? `normal-text-bold` : `large-text-bold`
2388			  }">${this._data.title}</p>`
2389			: ``;
2390
2391		// link title?
2392
2393		if (!this.linkelement && title) {
2394			title = `<a class="href" href="${
2395				this.objectType === "event" ? pagename + "#" + dateString : pagename
2396			}" ${target}>${title}</a>`;
2397		}
2398
2399		// teasertext
2400
2401		let description = ``;
2402
2403		if (this._data.description) {
2404			if (this.variant === "compact") {
2405				description = `<div class="teasertext small-text">${this._data.description}</div>`;
2406			} else {
2407				description = `<div class="teasertext normal-text">${this._data.description}</div>`;
2408			}
2409		}
2410
2411		// marketinggroups
2412
2413		let marketinggroups = ``;
2414
2415		if (this._data.marketinggroups) {
2416			marketinggroups += `<div class="marketinggroups">`;
2417
2418			this._data.marketinggroups.forEach((item) => {
2419				marketinggroups += `<div class="logo" title="${item?.title}"><img src="${item?.media?.[0]?.filename}" alt="${item?.title}" aria-label="${item?.title}" /></div>`;
2420			});
2421
2422			marketinggroups += `</div>`;
2423		}
2424
2425		// classification & town
2426
2427		let classificationAndTownArr = [];
2428		let classificationAndTown = ``;
2429
2430		if (this._data && Array.isArray(this._data.categories)) {
2431			classificationAndTownArr.push(this._data.categories.join(", "));
2432		}
2433
2434		if (this._data && this._data.classificationTitle) {
2435			classificationAndTownArr.push(this._data.classificationTitle);
2436		}
2437
2438		if (this._data && this._data.address && this._data.address.town) {
2439			classificationAndTownArr.push(this._data.address.town);
2440		}
2441
2442		if (classificationAndTownArr.length > 0) {
2443			classificationAndTown = `<div class="text small-text">${classificationAndTownArr.join(
2444				", "
2445			)}</div>`;
2446		}
2447
2448		// bookmark
2449
2450		let bookmark =
2451			this.bookmark && this.variant !== "compact"
2452				? `<button aria-label="${document.documentElement.lang === "de" ? "merken" : "save"}" class="bookmark" type="button"><mw-core-icon size="m" name="${this.checkBookmark(
2453						"heart",
2454						"heartselected"
2455				  )}" aria-pressed="false"></mw-core-icon></button>`
2456				: ``;
2457
2458		// button
2459
2460		let button = ``;
2461
2462		if (this.button !== true && this.button !== null) {
2463			if (this.variant === "compact") {
2464				button = `
2465					<p class="button">
2466						<mw-core-button nofocus tertiary nolabel icon="3" small iconsize="s"></mw-core-button>
2467					</p>`;
2468			} else {
2469				button = `
2470					<p class="button">
2471						<mw-core-button nofocus primary label="${this.button}" labelclass="primary-link"></mw-core-button>
2472					</p>
2473				`;
2474			}
2475		}
2476
2477		// link button?
2478
2479		if (!this.linkelement && button) {
2480			button = `<a class="href" href="${
2481				this.objectType === "event" ? pagename + "#" + dateString : pagename
2482			}" ${target}>${button}</a>`;
2483		}
2484
2485		// rating
2486
2487		const calculateAverageByTotalValue = (average, total) => {
2488			let value = (total * average) / 100;
2489
2490			return Math.round(value * 10) / 10;
2491		};
2492
2493		let rating = ``;
2494		let tag = ``;
2495
2496		if (
2497			this._data.ratingAverage &&
2498			parseInt(this._data.ratingAverage) > 0 &&
2499			parseInt(this._data.ratingCount) > 0
2500		) {
2501			rating = `
2502				<div class="rating">
2503					<p class="rating-value-wrapper">
2504						<span class="rating-value effect-border-radius large-text-bold">
2505							${calculateAverageByTotalValue(this._data.ratingAverage, 5)}
2506						</span> /5</p>
2507					<p class="rating-summ small-text">
2508						${
2509							this._data.ratingCount === 1
2510								? `
2511							${this._data.ratingCount}&nbsp${this._CONSTANTS.RATING}`
2512								: `
2513							${this._data.ratingCount}&nbsp${this._CONSTANTS.RATINGS}`
2514						}
2515					</p>
2516				</div>
2517			`;
2518		}
2519
2520		// tag
2521
2522		if (this._data && this._data.price && this._data.price.isBestPrice) {
2523			tag = this._CONSTANTS.BESTPRICE;
2524		}
2525
2526		// price
2527
2528		let price = ``;
2529
2530		if (this.preloadprices) {
2531			if (!this._data.price) {
2532				this._data.price = {
2533					from: -1,
2534					addition: "perperson"
2535				};
2536			}
2537		}
2538
2539		if (this._data.price && !isNaN(this._data.price.from)) {
2540			let label = this.priceLabel();
2541			let priceValue = this.priceValue();
2542
2543			const brandColor = getComputedStyle(document.documentElement).getPropertyValue(
2544				"--mw-core-color-brand1"
2545			);
2546
2547			const preloader = `
2548				<span class="preloader">
2549					<?xml version="1.0" encoding="utf-8"?>
2550					<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="24px" height="24px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
2551						<circle cx="50" cy="50" fill="none" stroke="${brandColor}" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138">
2552							<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"></animateTransform>
2553						</circle>
2554					</svg>
2555				</span>
2556			`;
2557
2558			price = `
2559				<button class="provider-price" type="button" aria-haspopup="dialog" aria-controls="modal-cancellation-details" title="${document.documentElement.lang === "de" ? "Weitere Preisinformationen anzeigen" : "Further price details"}">
2560					<p class="price-value large-text-bold">${
2561						this._data.price.from > -1 ? `${priceValue}` : `${preloader}`
2562					}</p>
2563					<p class="price-label small-text">
2564						${this._data.price.from > -1 ? `${label}` : ``}
2565					</p>
2566				</button>
2567			`;
2568		}
2569
2570		let html = ``;
2571
2572		if (this.variant === "inline") {
2573			html = `
2574				${
2575					this.linkelement
2576						? `<a class="href" href="${
2577								this.objectType === "event" ? pagename + "#" + dateString : pagename
2578						  }" ${target}>`
2579						: ``
2580				}
2581					<div class="teaser">
2582						<div class="image">
2583							${picture}
2584							<span class="teaser-frame" aria-hidden="true"></span>
2585						</div>
2586						<div class="main">
2587							<div class="content">
2588								${title}
2589								${bookmark}
2590							</div>
2591						</div>
2592					</div>
2593				${this.linkelement ? `</a>` : ``}
2594			`;
2595		} else {
2596			html = `
2597				${
2598					this.linkelement
2599						? `<a class="href" href="${
2600								this.objectType === "event" ? pagename + "#" + dateString : pagename
2601						  }" ${target}>`
2602						: ``
2603				}
2604					<div class="teaser">
2605						<p class="tag caption-text-bold effect-border-radius${!tag ? ` no-tag` : ``}">${tag}</p>
2606						<div class="image">
2607							${picture}
2608							<span class="teaser-frame" aria-hidden="true"></span>
2609						</div>
2610						<div class="main">
2611							<div class="content">
2612								<div class="provider">
2613									${title}
2614									${classificationAndTown}
2615									${description}
2616								</div>
2617								${rating}
2618								<div class="center">
2619									${marketinggroups}
2620								</div>
2621							</div>
2622							<div class="bottom ${price ? `has-info` : ``}">
2623								<div class="left">
2624									${price}
2625									<p class="availability no-availability normal-text-bold"></p>
2626								</div>
2627								${bookmark || button ? ` <div class="right">${bookmark + button}</div>` : ""}
2628							</div>
2629						</div>
2630					</div>
2631				${this.linkelement ? `</a>` : ``}
2632			`;
2633		}
2634
2635		return html;
2636	}
2637
2638	listNextOpeningDays() {
2639		// list next openingdays
2640
2641		let htmlop = ``;
2642
2643		let days = [];
2644
2645		for (let ot of this._data.openingTimes) {
2646			let from = new Date(ot.from);
2647			let to = new Date(ot.to);
2648			let id = from.getDate() + from.getMonth() + from.getFullYear();
2649
2650			// find pos in array
2651
2652			let result = days
2653				.map(function (e) {
2654					if (e[0]) {
2655						return e[0].id;
2656					} else {
2657						return -1;
2658					}
2659				})
2660				.indexOf(id);
2661
2662			// append new array or to index
2663
2664			if (result >= 0) {
2665				days[result].push({
2666					id: id,
2667					from: from,
2668					to: to
2669				});
2670			} else {
2671				days.push([
2672					{
2673						id: id,
2674						from: from,
2675						to: to
2676					}
2677				]);
2678			}
2679		}
2680
2681		for (let day of days) {
2682			let htmlDay = ``;
2683
2684			if (day) {
2685				// writes single day line
2686
2687				htmlDay += `<div class="day small-text">`;
2688
2689				if (day.length === 1) {
2690					let state = `${document.documentElement.lang === "en" ? mwUtility.formatDateToEnglishTime(day[0].from) : mwUtility.formatDateToTime(
2691						day[0].from
2692					)} - ${document.documentElement.lang === "en" ? mwUtility.formatDateToEnglishTime(day[0].to) : mwUtility.formatDateToTime(day[0].to)}`;
2693
2694					// check opened 24 hours
2695
2696					if (Math.abs(day[0].to - day[0].from) / 36e5 >= 24) {
2697						state = this._CONSTANTS.OPENINGSTATE6;
2698					}
2699
2700					// single opening time per day
2701
2702					htmlDay += `<span class="d">${this._CONSTANTS.DAYS[day[0].from.getDay()]}</span>
2703						<span class="t">${state}</span>`;
2704				} else if (day.length > 1) {
2705					// multiple opening times per day
2706
2707					let htmlTimes = ``;
2708					let counter = 0;
2709
2710					for (let times of day) {
2711						if (counter > 0) {
2712							htmlTimes += `, `;
2713						}
2714
2715						htmlTimes += `${document.documentElement.lang === "en" ? mwUtility.formatDateToEnglishTime(times.from) : mwUtility.formatDateToTime(
2716							times.from
2717						)} - ${document.documentElement.lang === "en" ? mwUtility.formatDateToEnglishTime(times.to) : mwUtility.formatDateToTime(times.to)}`;
2718
2719						counter++;
2720					}
2721
2722					htmlDay += `<span class="d">${this._CONSTANTS.DAYS[day[0].from.getDay()]}</span>
2723						<span class="t">
2724							${htmlTimes}
2725						</span>
2726					`;
2727				}
2728
2729				htmlDay += `</div>`;
2730			}
2731
2732			htmlop += htmlDay;
2733		}
2734
2735		return htmlop;
2736	}
2737
2738	checkCurrentOpening() {
2739		// find current date
2740
2741		let currentOrNextOpeningDate = this.findCurrentOpening();
2742
2743		// detect current opening state
2744
2745		if (currentOrNextOpeningDate) {
2746			let openingTimeFrom = new Date(currentOrNextOpeningDate.from);
2747			let openingTimeTo = new Date(currentOrNextOpeningDate.to);
2748
2749			if (!openingTimeFrom.getTime() || !openingTimeTo.getTime()) {
2750				return "";
2751			}
2752
2753			switch (this.detectOpeningState(openingTimeFrom, openingTimeTo)) {
2754				case 0:
2755					return "";
2756
2757				case 1:
2758					// closing in max 1 hour
2759
2760					return `
2761						<p class="current-title small-text" data-state-1>
2762							<span class="state-medium small-text-bold">
2763								${this._CONSTANTS.OPENINGSTATE1}
2764							</span> - ${document.documentElement.lang === "en" ? mwUtility.formatDateToEnglishTime(openingTimeTo) : mwUtility.formatDateToTime(openingTimeTo)} ${this._CONSTANTS.HOURS}
2765						</p>
2766					`;
2767
2768				case 2:
2769					// opening at (same day)
2770
2771					return `
2772						<p class="current-title small-text" data-state-2>
2773							<span class="state-medium small-text-bold">
2774								${this._CONSTANTS.OPENINGSTATE2}
2775							</span> ${document.documentElement.lang === "en" ? mwUtility.formatDateToEnglishTime(openingTimeFrom) : mwUtility.formatDateToTime(openingTimeFrom)} ${this._CONSTANTS.HOURS}
2776						</p>
2777					`;
2778
2779				case 3:
2780					// opened
2781
2782					return `
2783						<p class="current-title small-text" data-state-3>
2784							<span class="state-good small-text-bold">
2785								${this._CONSTANTS.OPENINGSTATE3}
2786							</span>${document.documentElement.lang === "en" ? mwUtility.formatDateToEnglishTime(openingTimeTo) : mwUtility.formatDateToTime(openingTimeTo)} ${this._CONSTANTS.HOURS}
2787						</p>
2788					`;
2789
2790				case 4:
2791					// closed - opens at
2792
2793					return `
2794						<p class="current-title small-text">
2795							<span class="state-bad small-text-bold" data-state-4>
2796								${this._CONSTANTS.OPENINGSTATE4}
2797							</span> ${mwUtility.formatDateToString(openingTimeFrom)} ${document.documentElement.lang === "en" ? mwUtility.formatDateToEnglishTime(openingTimeFrom) : mwUtility.formatDateToTime(
2798						openingTimeFrom
2799					)} ${this._CONSTANTS.HOURS}
2800						</p>
2801					`;
2802
2803				case 5:
2804					// opens tomorrow
2805
2806					return `
2807						<p class="current-title small-text" data-state-5>
2808							<span class="state-bad small-text-bold">
2809								${this._CONSTANTS.OPENINGSTATE5}
2810							</span> ${document.documentElement.lang === "en" ? mwUtility.formatDateToEnglishTime(openingTimeFrom) : mwUtility.formatDateToTime(openingTimeFrom)} ${this._CONSTANTS.HOURS}
2811						</p>
2812					`;
2813
2814				case 6:
2815					// opened 24 hours
2816
2817					return `
2818						<p class="current-title small-text" data-state-6>
2819							<span class="state-good small-text-bold">
2820								${this._CONSTANTS.OPENINGSTATE6}
2821							</span>
2822						</p>
2823					`;
2824			}
2825		}
2826
2827		return ``;
2828	}
2829
2830	findCurrentOpening() {
2831		// find current openingtime
2832
2833		let currentDate = new Date();
2834		let strike = ``;
2835
2836		for (let item of this._data.openingTimes) {
2837			let tmpStart = new Date(item.from);
2838			let tmpEnd = new Date(item.to);
2839
2840			if (currentDate > tmpStart && currentDate < tmpEnd) {
2841				strike = item;
2842				break;
2843			}
2844		}
2845
2846		// check if first openingTime is in future
2847
2848		if (strike === "") {
2849			for (let item of this._data.openingTimes) {
2850				let currentTime = new Date();
2851				let dateStart = new Date(item.from);
2852
2853				if (currentTime < dateStart) {
2854					strike = item;
2855					break;
2856				}
2857			}
2858		}
2859
2860		return strike;
2861	}
2862
2863	detectOpeningState(openingTimeFrom, openingTimeTo) {
2864		let currentTime = new Date();
2865
2866		if (!openingTimeFrom.getTime() || !openingTimeTo.getTime()) {
2867			return 0;
2868		}
2869
2870		// check closing in max 1 hour
2871
2872		if (currentTime <= openingTimeTo && Math.abs(openingTimeTo - currentTime) / 36e5 < 1) {
2873			return 1;
2874		}
2875
2876		// check opening at (same day)
2877
2878		if (
2879			openingTimeFrom > currentTime &&
2880			Math.abs(openingTimeFrom - currentTime) / 36e5 < 24 &&
2881			currentTime.getDate() === openingTimeFrom.getDate()
2882		) {
2883			return 2;
2884		}
2885
2886		// check opened 24 hours
2887
2888		if (
2889			currentTime.getDate() === openingTimeFrom.getDate() &&
2890			currentTime.getMonth() === openingTimeFrom.getMonth() &&
2891			currentTime.getFullYear() === openingTimeFrom.getFullYear() &&
2892			Math.abs(openingTimeFrom - openingTimeTo) / 36e5 === 24
2893		) {
2894			return 6;
2895		}
2896
2897		// check opened
2898
2899		if (
2900			currentTime.getTime() >= openingTimeFrom.getTime() &&
2901			currentTime.getTime() <= openingTimeTo.getTime()
2902		) {
2903			return 3;
2904		}
2905
2906		// check opens tomorrow
2907
2908		let tomorrow = new Date();
2909		tomorrow.setDate(tomorrow.getDate() + 1);
2910		tomorrow.setHours(0, 0, 0, 0);
2911
2912		if (Math.abs(tomorrow - openingTimeFrom) / 36e5 < 24) {
2913			return 5;
2914		}
2915
2916		// return closed - opens at
2917
2918		return 4;
2919	}
2920
2921	checkNextOpeningDaySmallerThen(openingTimes, days) {
2922		let today = new Date();
2923
2924		if (openingTimes.length > 0) {
2925			let firstFromDate = new Date(openingTimes[0].from);
2926
2927			let diffTime = Math.abs(firstFromDate - today);
2928			let diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
2929
2930			if (diffDays > days) {
2931				return false;
2932			}
2933		}
2934
2935		return true;
2936	}
2937
2938	checkBookmark(iconInactive, iconActive) {
2939		// check if bookmark active/inactive
2940
2941		if (mwBookmark.entryIsInStorage(this._data)) {
2942			// is bookmarked
2943
2944			return iconActive;
2945		} else {
2946			// is not bookmarked
2947
2948			return iconInactive;
2949		}
2950	}
2951
2952	priceLabel() {
2953		let label = "";
2954
2955		if (this._data.price.addition) {
2956			if (this.objectType === "provider") {
2957				// provider
2958
2959				if (this._data.price.addition && this._data.price.productType) {
2960					if (
2961						this._data.price.addition === "perunit" &&
2962						this._data.price.productType === "apartment"
2963					) {
2964						if (this._data.price.nights && this._data.price.nights > 1) {
2965							label = `${this._CONSTANTS.PER} ${this._CONSTANTS.UNIT}/${this._data.price.nights} ${this._CONSTANTS.NIGHTS}`;
2966						} else {
2967							label = this._CONSTANTS.PRICEPERUNIT;
2968						}
2969					} else if (
2970						this._data.price.addition === "perunit" &&
2971						this._data.price.productType === "hotelroom"
2972					) {
2973						if (this._data.price.nights && this._data.price.nights > 1) {
2974							label = `${this._CONSTANTS.PER} ${this._CONSTANTS.ROOM}/${this._data.price.nights} ${this._CONSTANTS.NIGHTS}`;
2975						} else {
2976							label = this._CONSTANTS.PRICEPERROOM;
2977						}
2978					} else if (this._data.price.addition === "total") {
2979						if (this._data.price.nights && this._data.price.nights > 1) {
2980							label = `${this._CONSTANTS.PRICETOTAL}/${this._data.price.nights} ${this._CONSTANTS.NIGHTS}`;
2981						} else {
2982							label = this._CONSTANTS.PRICETOTAL;
2983						}
2984					} else {
2985						label = this._CONSTANTS.PRICEPERPERSON;
2986					}
2987				}
2988			} else {
2989				// package
2990
2991				switch (this._data.price.addition) {
2992					case "perunit":
2993						if (this._data.price.nights) {
2994							if (this._data.price.nights === "1") {
2995								label = `${this._CONSTANTS.PRICEFOR} ${this._data.price.nights} ${this._CONSTANTS.PRICEFORNIGHTUNIT}`;
2996							} else {
2997								label = `${this._CONSTANTS.PRICEFOR} ${this._data.price.nights} ${this._CONSTANTS.PRICEFORNIGHTSUNIT}`;
2998							}
2999						} else {
3000							label = `${this._CONSTANTS.PRICEPERUNIT}`;
3001						}
3002
3003						break;
3004
3005					case "perperson":
3006						if (this._data.price.nights) {
3007							if (this._data.price.nights === "1") {
3008								label = `${this._CONSTANTS.PRICEFOR} ${this._data.price.nights} ${this._CONSTANTS.PRICEFORNIGHTPERSON}`;
3009							} else {
3010								label = `${this._CONSTANTS.PRICEFOR} ${this._data.price.nights} ${this._CONSTANTS.PRICEFORNIGHTSPERSON}`;
3011							}
3012						} else {
3013							label = `${this._CONSTANTS.PRICEPERPERSON}`;
3014						}
3015
3016						break;
3017				}
3018			}
3019		}
3020
3021		return label;
3022	}
3023
3024	priceValue() {
3025		let prefix = this._CONSTANTS.PRICEFROM;
3026		let currency = "€";
3027		let price = this._data.price.from;
3028
3029		let formatedPrice = mwUtility.formatNumber(price);
3030
3031		return `${prefix} ${formatedPrice} ${currency}`;
3032	}
3033
3034	showPriceInfo() {
3035		let addition = [];
3036		let adults = ``;
3037		let children = ``;
3038		let nights = ``;
3039
3040		if (typeof this._data.price.adults === `number` && this._data.price.adults === 1) {
3041			adults = `${this._data.price.adults} ${this._CONSTANTS.ADULT}`;
3042		} else if (typeof this._data.price.adults === `number` && this._data.price.adults > 1) {
3043			adults = `${this._data.price.adults} ${this._CONSTANTS.ADULTS}`;
3044		}
3045
3046		if (
3047			Array.isArray(this._data.price.childrenAges) &&
3048			this._data.price.childrenAges.length === 1
3049		) {
3050			children = `${this._data.price.childrenAges.length} ${this._CONSTANTS.CHILD}`;
3051		} else if (
3052			Array.isArray(this._data.price.childrenAges) &&
3053			this._data.price.childrenAges.length > 1
3054		) {
3055			children = `${this._data.price.childrenAges.length} ${this._CONSTANTS.CHILDREN}`;
3056		}
3057
3058		if (this._data.price.dateFrom && this._data.price.dateTo) {
3059			const dateFrom = new Date(this._data.price.dateFrom);
3060			const dateTo = new Date(this._data.price.dateTo);
3061
3062			if (mwUtility.checkValidDate(dateFrom) && mwUtility.checkValidDate(dateTo)) {
3063				const dateDiff = (dateTo.getTime() - dateFrom.getTime()) / (1000 * 3600 * 24);
3064				nights =
3065					dateDiff > 1
3066						? dateDiff + " " + this._CONSTANTS.NIGHTS
3067						: dateDiff + " " + this._CONSTANTS.NIGHT;
3068			}
3069		}
3070
3071		if (this._data.price.mealType) {
3072			addition.push(this._CONSTANTS.MEALTYPE[this._data.price.mealType.toUpperCase()]);
3073		}
3074
3075		if (adults) {
3076			addition.push(adults);
3077		}
3078
3079		if (children) {
3080			addition.push(children);
3081		}
3082
3083		if (nights) {
3084			addition.push(nights);
3085		}
3086
3087		const finalCleaning =
3088			this._data.price && this._data.price.finalCleaning
3089				? `
3090			<p class="sheet-price-info normal-text">
3091				${this._CONSTANTS.INCLUSIVE} ${mwUtility.formatNumber(this._data.price.finalCleaning)} € ${
3092						this._CONSTANTS.FINALCLEANING
3093				  }
3094			</p>
3095		`
3096				: ``;
3097
3098		const content = `
3099			<mw-core-actionsheet ok="OK">
3100				${finalCleaning}
3101				${this.parseLocalTax()}
3102				<div class="cancellation">${this.parseCancellation()}</div>
3103				<style>
3104					.cancellation {
3105						margin: 16px 0 16px 0;
3106					}
3107					p.sheet-price-info {
3108						margin: 0;
3109					}
3110				</style>
3111				<template is="component-style">
3112					<style>
3113						.actionsheet {
3114							padding: 4px 4px 0;
3115						}
3116						.inner {
3117							padding: 0 16px 90px;
3118						}
3119
3120						@media all and (min-width: 500px) {
3121
3122							.actionsheet {
3123								padding: 16px 16px 0;
3124							}
3125							.content {
3126								height: auto;
3127								max-width: 500px;
3128							}
3129							.inner {
3130								height: auto;
3131								padding: 0 32px 90px;
3132							}
3133							
3134						}
3135					</style>
3136				</template>
3137			</mw-core-actionsheet>
3138		`;
3139
3140		// append actionsheet
3141
3142		document.body.insertAdjacentHTML(`beforeend`, content);
3143	}
3144
3145	parseLocalTax() {
3146		let html = ``;
3147
3148		if (this._data.price && this._data.price.visitorTax && this._data.price.visitorTax?.inclusive) {
3149
3150			html += `<p class="sheet-price-info local-tax normal-text">
3151					${
3152						this._data.price.visitorTax?.inclusive === false
3153							? this._CONSTANTS.EXCLUSIVE
3154							: this._CONSTANTS.INCLUSIVE
3155					}  ${this._CONSTANTS.TOURISMTAX} ${this._CONSTANTS.PRICEOF} ${mwUtility.formatNumber(
3156				this._data.price.visitorTax?.price
3157			)} € ${
3158				this._data.price.visitorTax?.addition === "perpersonnight"
3159					? this._CONSTANTS.PRICEPERPERSON
3160					: ""
3161			}
3162				</p>`;
3163		}
3164
3165		// --- Aufgrund von Kundenwunsch, am 25.01.2024 - Ortstaxe fix hinterlegt --- //
3166
3167		/*html += `<p class="sheet-price-info local-tax normal-text">
3168			${this._CONSTANTS.EXCLUSIVE} ${this._CONSTANTS.LOCALTAX} ${mwUtility.formatNumber(2)} € ${this._CONSTANTS.PRICEPERPERSON}
3169		</p>`;*/
3170
3171		/* if (this._data.price && this._data.price.visitorTax && !this._data.price.visitorTax?.inclusive) {
3172
3173			html += `<p class="sheet-price-info local-tax normal-text">
3174				<br>
3175				${document.documentElement.lang === "de" ? "Ortstaxe und Mobilitätsabgabe sind nicht im Preis enthalten und werden zusätzlich verrechnet: 3,20 € pro Person und Nacht." : "Local tax and mobility fee are not included in the price and will be charged additionally: €3.20 per person per night."}
3176			</p>`
3177
3178		} */
3179
3180		if (this._data?.price?.visitorTax?.description) {
3181
3182			html += `<p class="sheet-price-info local-tax normal-text">
3183				${this._data?.price?.visitorTax?.description}
3184			</p>`;
3185
3186		}
3187
3188		return html;
3189	}
3190
3191	parseCancellation() {
3192		if (
3193			this._data &&
3194			this._data.cancellationDetails &&
3195			Array.isArray(this._data.cancellationDetails.items)
3196		) {
3197			const items = this._data.cancellationDetails.items.map((item) => {
3198				let date = ``;
3199				if (item.dateFrom) {
3200					date = mwUtility.formatDateToString(new Date(item.dateFrom));
3201				}
3202				return `<p class="sheet-price-info normal-text">
3203						${this._CONSTANTS.CANCELLATIONTO} ${date}
3204						<span class="normal-text-bold">${item.percentage}% - ${mwUtility.formatNumber(
3205					item.price
3206				)} €</span></p>`;
3207			});
3208
3209			if (this._data.cancellationDetails.free && this._data.cancellationDetails.freeUntil) {
3210				const freeUntilDate = mwUtility.formatDateToString(
3211					new Date(this._data.cancellationDetails.freeUntil)
3212				);
3213				const freeUntilText = `
3214					<p class="sheet-price-info normal-text-bold">
3215						${this._CONSTANTS.CANCELLATIONFREEUNTIL} ${freeUntilDate}
3216					</p>
3217				`;
3218				items.unshift(freeUntilText);
3219			}
3220			return items.join("");
3221		} else {
3222			return ``;
3223		}
3224	}
3225}
3226
3227customElements.define("mw-core-teaser", mwCoreTeaser);

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.