1!(function ( $, me, w, d ) { 2 "use strict"; 3 if ( !$ || !me ) { 4 return; 5 } 6 7 if ( d.location.href.indexOf( '.vioma.de' ) > -1 ) { 8 w.me = me; 9 } 10 11 // Overwrite Modernizr so no time is wasted on some old legacy checks. 12 window.Modernizr = { 13 touchevents: ('ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0), 14 intersectionobserver: ('IntersectionObserver' in window || 'IntersectionObserverEntry' in window || 'intersectionRatio' in window.IntersectionObserverEntry.prototype), 15 } 16 17 18 me.custom_events = function () { 19 // customer functions here 20 } 21 22 me.init(); 23 24 me.tmp = { 25 // tmp is for storing temporal stuff. 26 widgetYear: {} 27 }; 28 29 me.vanilla = { 30 site: d.querySelector("#site"), 31 header: d.querySelector("header"), 32 main: d.querySelector("main"), 33 widget: d.querySelector(".widget-form-book"), 34 footer: d.querySelector("footer") 35 } 36 37 me.opt.bgChangeElements = me.vanilla.main.querySelectorAll(":scope > .grp-bg-dark-smooth, article[data-attr-hintergrund='dark-smooth']"), 38 me.opt.roundImageTextBoxSVGs = me.vanilla.main.querySelectorAll("figure.image-text-box-figure > svg"); 39 40 me.opt.mails = me.vanilla.site.querySelectorAll("a[data-mail]"); 41 me.vanilla.layout = me.vanilla.site.querySelector(":scope > #layout-vanilla"); 42 43 me.opt.touch = window.Modernizr.touchevents; 44 45 me.vanilla.site.classList.remove("no-js"), 46 me.vanilla.site.classList.add("js-active"); 47 48 me.opt.globalToggleMenu = me.vanilla.site.querySelector(":scope > #global-toggle-menu"), 49 me.opt.globalToggleWidget = me.vanilla.site.querySelector(":scope > #global-toggle-widget"); 50 51 me.opt.scroll_breakpoint = () => { 52 if (me.vanilla.header.querySelector("[data-smallheader]")) { 53 return (me.opt.scroll >= me.opt.main_top * 0.02); 54 }; 55 return (me.opt.scroll >= me.opt.main_top * 0.075); 56 }; 57 58 me.scroll_event = function () { 59 me.opt.site.toggleClass( 'scrolled', me.opt.scroll_breakpoint() ); 60 if ((me.opt.already_scrolled == false) && (me.opt.scroll_breakpoint() == true)) { 61 me.opt.site.addClass("first-scroll"); 62 me.opt.noscript_afterload.each(function() { 63 var i = $(this) 64 me.opt.head.append(i.text()); 65 i.remove(); 66 }); 67 me.opt.already_scrolled = true; 68 } 69 }; 70 71 me.opt.scrollBarVisibility = () => { 72 const upScrollDelayPixels = 100; 73 74 if (me.tmp.windowOffsetY > window.scrollY) { 75 me.tmp.scrolledTopDirection = me.tmp.scrolledTopDirection + (me.tmp.windowOffsetY - window.scrollY); 76 } 77 else { 78 me.tmp.scrolledTopDirection = 0; 79 } 80 81 if (me.tmp.scrolledTopDirection > upScrollDelayPixels) { 82 me.vanilla.site.classList.add("scrolled-upwards"); 83 } 84 else { 85 me.vanilla.site.classList.remove("scrolled-upwards"); 86 } 87 88 // Now reset offset variable 89 me.tmp.windowOffsetY = window.scrollY; 90 } 91 92 // CSS :has() Fallback Support 93 if (!CSS.supports("selector(html:has(body))")) { 94 me.vanilla.site.classList.add("no-css-has"), 95 me.vanilla.site.classList.remove("supports-css-has"); 96 97 // .media-display-small only exists in main with group layout = Spalten! 98 // Add fallback class so stuff can be applied on article and media 99 me.vanilla.main.querySelectorAll(".media-display-small").forEach(md => { 100 md.closest(".grp-layout-spalten").classList.add("has-fallback-article-md"); 101 }); 102 103 me.opt.customSmallGallery = me.vanilla.header.querySelector(".custom-gallery-main[data-smallheader='true']"); 104 105 if (me.opt.customSmallGallery) { 106 107 me.vanilla.header.querySelector(".custom-gallery").classList.add("has-fallback-smallheader-true"); 108 } 109 110 let smallHeader = me.vanilla.header.querySelector("[data-smallheader='absolute']"); 111 112 if (w.innerWidth <= 639 && smallHeader && smallHeader.previousElementSibling && smallHeader.previousElementSibling.classList.contains("familotel-label")) { 113 smallHeader.previousElementSibling.style.setProperty("top", "75px"); 114 } 115 } 116 117 me.f = { 118 // f = functions 119 120 visibilityClassToggler: (el, className, once, options) => { 121 // el give single element with querySelector or multiple via querySelectorAll or text str
121ing, like ".mosaic-item" 122 // className the name of the visibility class, usually .visible, 123 // once mode. 124 // mode true => class will always be added and removed again, and added and removed, ... 125 // mode false/not given 126 // options some options like rootMargin 127 128 // There is a "hidden" support for pseudo media queries. Give this to el: 129 // el._ioMinWidth = 640 ==> Applies only if screen width is greater or exact this value, <= 640 130 // el._ioMaxWidth = 639 ==> Applies only if screen width is greater or exact this value, >= 639 131 132 if (!el) return false; 133 className = className || "visible"; 134 135 options = options || { 136 rootMargin: "-20% 0% -20% 0%" 137 }; 138 139 if (me.opt.userPrefers.reducedMotion) { 140 el.classList.add(className); 141 return; 142 } 143 144 let video = el.querySelector("video"); 145 146 let classTogglerIO = new IntersectionObserver((entries) => { 147 entries.forEach(entry => { 148 let target = entry.target; 149 150 if (entry.isIntersecting && 151 ((!target._ioMinWidth || target._ioMinWidth && w.innerWidth >= target._ioMinWidth ) && 152 (!target._ioMaxWidth || target._ioMaxWidth && w.innerWidth <= target._ioMaxWidth )) 153 ) { 154 target.classList.add(className); 155 156 if (video && video.dataset.allowautoplay == "true") { 157 video.play(); 158 159 if (!video.controls) { 160 video.addEventListener("ended", () => { 161 video.play(); 162 }); 163 } 164 } 165 166 if (once) classTogglerIO.unobserve(target); 167 168 // Special for background color switch 169 if (className == 'darken-page') {
170 me.opt.roundImageTextBoxSVGs.forEach(svg => { 171 svg.setAttribute("fill", "#222c36"); 172 }); 173 } 174 175 176 177 } 178 else { 179 entry.target.classList.remove(className); 180 181 if (video && video.dataset.allowautoplay == "true") { 182 video.pause(); 183 } 184 185 // Special for background color switch 186 if (className == 'darken-page') { 187 me.opt.roundImageTextBoxSVGs.forEach(svg => { 188 svg.setAttribute("fill", "#4c5b69"); 189 }); 190 } 191 } 192 }); 193 }, options); 194 195 if (typeof el == "string") { 196 d.querySelectorAll(el).forEach(elem => { 197 classTogglerIO.observe(elem); 198 }); 199 } 200 else if (el.nodeName) { 201 classTogglerIO.observe(el); 202 } 203 else if (el[0] && el[0].nodeName) { 204 el.forEach(elem => { 205 classTogglerIO.observe(elem); 206 }); 207 } 208 }, 209 210 resizeObserver: () => { 211 if (!me.vanilla.layout) { 212 return false; 213 } 214 var newMqEvent = new CustomEvent("mq-resize"); 215 const layoutResizeObserver = new ResizeObserver(entries => { 216 entries.forEach(entry => { 217 me.vanilla.layout._width = entry.contentRect.width; 218 me.vanilla.layout._portrait = window.innerHeight > window.innerWidth; 219 window.dispatchEvent(newMqEvent); 220 }); 221 }); 222 layoutResizeObserver.observe(me.vanilla.layout); 223 }, 224 225 packageCardsFilter: (container) => { 226 container = typeof container == "object" ? container : d.querySelector(container); 227 if (!container) return; 228 229 container._filters = { 230 category: "", 231 month: "" 232 }, 233 container._filterButtons = container.querySelectorAll(":scope > .condeon-cst-list-filterlist button"), 234 container._cards = container.querySelectorAll(":scope > .condeon-cst-list-items > .small-itemslider-item"); 235 236 237 // Main filter function for both indicator and months list 238 container._filterTheList = (e) => { 239 const btn = e.target.closest("button"); 240 const filterType = btn.dataset.id ? "category" : "month"; 241 242 if (filterType == "category") { 243 container._filters.category = btn.dataset.id && btn.dataset.id == "all" ? "" : btn.dataset.id || container._filters.category; 244 } 245 else { 246 container._filters.month = btn.dataset.month && btn.dataset.month == "all" ? "" : btn.dataset.month || container._filters.month; 247 } 248 249 // Toggle cards themselfes 250 container._cards.forEach(card => { 251 card._i = 0; 252 Object.values(container._filters).forEach(filterValue => { 253 if (!filterValue || filterValue == "" || filterValue == "all") { 254 card._i++; 255 } 256 else if ( 257 (card.dataset.indicators && card.dataset.indicators.includes(filterValue)) || 258 (card.dataset.months && card.dataset.months.includes(filterValue)) 259 ) { 260 card._i++; 261 } 262 }); 263 if (card._i == 2) { 264 card.classList.remove("filtered-out"); 265 } 266 else { 267 card.classList.add("filtered-out") 268 } 269 }); 270 271 // If monthly filter, close dorpdown 272 if (filterType == "month") { 273 btn.closest(".condeon-cst-list-filterlist").querySelector(".condeon-cst-list-monthtoggle").checked = false; 274 275 let dropdownLabel = btn.closest(".pseudo-dropdown").querySelector("label"); 276 if (!dropdownLabel.style.minWidth) { 277 dropdownLabel.style.minWidth = dropdownLabel.clientWidth + "px"; 278 } 279 280 dropdownLabel.textContent = btn.textContent; 281 } 282 283 // If done, disable all filters which would return a zero results list 284 285 container._filterButtons.forEach(button => { 286 287 if ((button.dataset.id && button.dataset.id == "all") || 288 (button.dataset.month && button.dataset.month == "all")) 289 return; 290 291 if (button.dataset.id && button.dataset.id > 0) { 292 var hasVisibleCard = container.querySelector(".small-itemslider-item[data-indicators*='" + button.dataset.id + "']:not(.filtered-out)"); 293 button.disabled = !hasVisibleCard; 294 } 295 296 else if (button.dataset.month && button.dataset.month > 0) { 297 console.log(); 298 var cat = container._filters.category || ""; 299 var stillEnabled = !!container._filters.category ? 300 container.querySelector(".small-itemslider-item[data-indicators*='" + container._filters.category + "'][data-months*='" + button.dataset.month + "']") : 301 container.querySelector(".small-itemslider-item[data-months*='" + button.dataset.month + "']"); 302 button.disabled = !stillEnabled; 303 } 304 }); 305 306 /* container._filterButtons.forEach(button => { 307 308 if ((button.dataset.id && button.dataset.id == "all") || (button.dataset.month && button.dataset.month == "all")) return; 309 310 else if (button.dataset.month && button.dataset.month > 0) { 311 var hasVisibleCard = container.querySelector(".small-itemslider-item[data-months*='" + button.dataset.month + "']:not(.filtered-out)"); 312 button.disabled = !hasVisibleCard; 313 } 314 else if (button.dataset.id && button.dataset.id > 0) { 315 var hasVisibleCard = container.querySelector(".small-itemslider-item[data-indicators*='" + button.dataset.id + "']:not(.filtered-out)"); 316 button.disabled = !hasVisibleCard; 317 } 318 }); */ 319 } 320 321 // Apply click event on filter buttons 322 container._filterButtons.forEach(btn => { 323 btn.addEventListener("click", container._filterTheList, {passive: true}); 324 }); 325 326 // Done here with filters, now apply visible class adder for the cards as well 327 container.querySelectorAll("div.small-itemslider-item").forEach(item => { 328 me.f.visibilityClassToggler(item, false, false, { 329 rootMargin: "-5% 0% -5% 0%" 330 }); 331 }) 332 }, 333 334 packageCardsRegular: (container) => { 335 container = typeof container == "object" ? container : d.querySelector(container); 336 337 if (!container) return; 338 339 container.querySelectorAll("div.small-itemslider-item").forEach(item => { 340 me.f.visibilityClassToggler(item, false, true, { 341 rootMargin: "-20% 0% -20% 0%" 342 }); 343 }) 344 } 345 } 346 347 me.f.mailificator = (e) => { 348 349 let mailIO = new IntersectionObserver(entries => {
350 entries.forEach(entry => { 351 if (entry.isIntersecting) { 352 entry.target.textContent = entry.target._address; 353 mailIO.unobserve(entry.target); 354 } 355 }) 356 }) 357 358 me.opt.mails.forEach(m => { 359 m._address = m.dataset.mail + atob("QGhvdGVsLWtpbmRsLmF0"); 360 361 mailIO.observe(m); 362 363 m.addEventListener("click", (e) => { 364 e.preventDefault(); 365 w.location.href = (atob("bWFpbHRvOg") + m._address); 366 }); 367 }) 368 } 369 370 me.f.mobileNumberficator = () => { 371 if (me.opt.touch) { 372 me.opt.mobileNumbers = me.vanilla.site.querySelectorAll("a[data-mobile]"); 373 374 me.opt.mobileNumbers.forEach(link => { 375 link.addEventListener("click", (e) => { 376 e.preventDefault(); 377 w.location.href = link.dataset.mobile; 378 }); 379 }) 380 } 381 } 382 383 me.f.footerSlider = (el) => { 384 el = el.id ? el : me.vanilla.footer.querySelector("#" + el); 385 386 if (!el) return; 387 388 el._prevButton = el.querySelector("button.slide-arrow-left"), 389 el._nextButton = el.querySelector("button.slide-arrow-right"), 390 el._itemPos = 0, 391 el._slideItems = el.querySelectorAll(".footer-logoslider-slide"); 392 393 if (el._slideItems.length > 1) { 394 395 let slideFunction = (pos) => { 396 397 if (!el._initialized) { 398 var track = el.querySelector(".footer-logoslider-main"); 399 if (track) { 400 track.scrollLeft = 0, 401 track.style.overflow = "hidden"; 402 } 403 el._initialized = true; 404 } 405 406 // preload images as soon as user clicks an arrow 407 if (!el._slideActive) { 408 el.querySelectorAll("img").forEach(img => { 409 img.loading = "eager"; 410 }) 411 el._slideActive = true; 412 } 413 414 let newPos = el._itemPos + pos, 415 skip = el.clientWidth >= 805 ? 4 : el.clientWidth >= 600 ? 3 : el.clientWidth >= 395 ? 2 : 1; 416 417 if (newPos < 0 ) return; // Nicht nach links wenn Position Start wäre 418 if (newPos > el._slideItems.length - skip) return; 419 420 421 el._slideItems.forEach(item => { 422 item.style.transform = "translateX(calc(-" + (100 * newPos) + "% - " + (15 * newPos) + "px))"; 423 }); 424 425 el._itemPos = newPos; 426 } 427 428 el._nextButton.addEventListener("click", () => { 429 slideFunction(1); 430 }, {passive: true}); 431 432 el._prevButton.addEventListener("click", () => { 433 slideFunction(-1); 434 }, {passive: true}); 435 } 436 else { 437 el._arrowPrev.remove(), 438 el._arrowNext.remove(); 439 } 440 } 441 442 me.f.mediaDisplayGrid = (el) => { 443 el = el && el.id ? el : d.getElementById(el); 444 445 if (!el) return; 446 447 let video = el.querySelector("video, img"); 448 449 if (me.opt.touch) { 450 me.f.visibilityClassToggler(el, 'wide-media-mobile', false, { 451 rootMargin: "-50% 0% -50% 0%" 452 }); 453 } 454 455 !me.opt.touch && video && video.addEventListener("click", () => { 456 el.classList.toggle("wide-media"); 457 if (video.nodeName == "VIDEO") video.play(); 458 el._hoverActive = !el._hoverActive; 459 }, {passive: true}); 460 461 !me.opt.touch && video && el.addEventListener("mouseenter", () => { 462 if (video.nodeName == "VIDEO" && !el._videoPlayed && !el.classList.contains("wide-media")) { 463 video.play(); 464 } 465 el._hoverActive = true; 466 setTimeout(() => { 467 if (el._hoverActive) { 468 el.classList.add("wide-media") 469 } 470 }, 1500); 471 }, {passive: true}); 472 473 !me.opt.touch && video && el.addEventListener("mouseleave", () => { 474 el._hoverActive = false; 475 }); 476 477 video && video.nodeName == "VIDEO" && video.addEventListener("ended", () => { 478 el._videoPlayed = true; 479 el.classList.remove("wide-media"); 480 }, {passive: true}); 481 } 482 483 me.f.masonrier = (container, itemClass) => { 484 container = container.id ? container : d.getElementById(container); 485 if (!container) return; 486 487 container._items = container.querySelectorAll("." + itemClass); 488 if (!container._items || container._items.length <= 1) return; 489
490 if (!CSS.supports("display: grid-lanes")) { 491 var size = 0, 492 timeout = 0, 493 setTimer = null; 494 495 function masonryFunction() { 496 if (window.innerWidth >= 640) { 497 container._rowHeight = parseInt(window.getComputedStyle(container).getPropertyValue('grid-auto-rows')), 498 container._rowGap = parseInt(window.getComputedStyle(container).getPropertyValue('grid-row-gap')); 499 500 for (let x = 0; x < container._items.length; x++){ 501 resizeGridItem(container._items[x], container._rowHeight, container._rowGap); 502 } 503 504 for (let x = 0; x < container._items.length; x++) { 505 container._items[x].style.gridRowEnd = "span " + container._items[x]._rowSpan; 506 } 507 } 508 } 509 510 function resizeGridItem(item) { 511 item._rowSpan = Math.ceil((item.querySelector('.masonry-inner').getBoundingClientRect().height + container._rowGap)/(container._rowHeight + container._rowGap)); 512 } 513 514 w.addEventListener("mq-resize", () => { 515 if (timeout == 0) { 516 container.classList.add("js-masonry-fallback"); 517 masonryFunction(), 518 timeout = 100, 519 size = me.vanilla.layout._width; 520 return; 521 } 522 else { 523 size = me.vanilla.layout._width; 524 525 // Timeout: To stop browser from recalculating every single event, 526 // but instead do it in a reasonably fashion. 527 clearTimeout(setTimer); 528 setTimer = setTimeout(() => { 529 masonryFunction(); 530 }, timeout); 531 } 532 533 }, {passive: true}); 534 } 535 536 // Special, nothing to do with masonry in itself. Align items correctly in topic grid gallery 537 538 if (container.classList.contains('topic-grid')) { 539 540 container._items.forEach(item => { 541 me.f.visibilityClassToggler(item, false, true); 542 }); 543 544 if (w.innerWidth >= 640 && container._items.length > 4) { 545 546 w.addEventListener("mq-resize", () => { 547 container._items.forEach(item => { 548 if (item.classList.contains("topic-grid-item-margin") && item.offsetLeft > (container.clientWidth / 2)) { 549 item.style.marginInline = "0 auto"; 550 } 551 else if (item.classList.contains("topic-grid-item-margin")) { 552 item.style.marginInline = "auto 0"; 553 } 554 }); 555 }, {passive: true}); 556 } 557 } 558 } 559 560 me.f.onepagerRoomlist = (container) => { 561 562 container = container.id ? container : me.vanilla.main.querySelector("." + container); 563 if (!container) return; 564 565 // Quicklinks will be handeled as a filter via JS, instead of scrolling to an area. 566 567 container._quicklinkBox = container.querySelector(":scope > .onepager-quicklinks"); 568 569 if (container._quicklinkBox) { 570 container.append(container._quicklinkBox.cloneNode(true)); 571 } 572 573 container._quicklinks = container.querySelectorAll(":scope > .onepager-quicklinks > a"), 574 container._cards = container.querySelectorAll(":scope > .onepager-list > *"); 575 576 container._quicklinks.forEach((link, pos) => { 577 link.addEventListener("click", (e) => { 578 e.preventDefault(); 579 if (!link.closest(".onepager-quicklinks").nextElementSibling) { 580 container.closest("section") && container.closest("section").previousElementSibling && container.closest("section").previousElementSibling.scrollIntoView(); 581 } 582 583 container.classList.remove("filtered"); 584 setTimeout(() => { 585 if (link.classList.contains('onepager-quicklink-all')) { 586 // Klick auf "Alle" Filter 587 container._cards.forEach(item => { 588 item.classList.remove("removed-by-filter"); 589 }); 590 } 591 else { 592 container._showItems = false; 593 container._cards.forEach(item => { 594 // Item in dem Kontext ist ein Onepager Item Zimmer oder die Headline der Zimmergruppe 595 if (item.nodeName === "H2" && !container._showItems) { 596 item.style.display = "none"; 597 if (item.id === link.hash.substring(1, link.hash.length)) { 598 container._showItems = true; 599 } 600 } 601 602 else if (item.nodeName === "H2") { 603 // Nächste Headline, also nach der erfolgreichen Filterung, reset. 604 container._showItems = false;
605 } 606 607 if (container._showItems) { 608 item.classList.remove("removed-by-filter"); 609 } 610 else { 611 item.classList.add("removed-by-filter"); 612 } 613 }); 614 615 container._hiding = false; 616 } 617 container.classList.add("filtered"); 618 }, 10); 619 }); 620 }); 621 622 container.querySelectorAll(".onepager-item-room-meta").forEach(meta => { 623 var newSubContainer = d.createElement("div"); 624 newSubContainer.classList.add("onepager-custom-meta"); 625 626 meta.parentElement.insertAdjacentElement("afterbegin", newSubContainer); 627 628 newSubContainer.append(meta); 629 630 var clone = meta.cloneNode(true); 631 newSubContainer.append(clone); 632 633 meta.querySelectorAll("li:nth-child(n + 3)").forEach(li => { 634 li.remove(); 635 }); 636 637 clone.querySelectorAll("li:nth-child(-n + 2)").forEach(li => { 638 li.remove(); 639 }); 640 }); 641 } 642 643 me.f.widgetCloser = () => { 644 if (!me.opt.globalToggleWidget) return; 645 646 const widgetCloseLabel = d.createElement("label"), 647 widgetCloseText = me.opt.lang == 'de' ? "schlieÃen" : "close", 648 widgetHandleCTA = me.vanilla.header.querySelector("label.widget-handle > .cta"); 649 650 widgetCloseLabel.htmlFor = "global-toggle-widget", 651 widgetCloseLabel.id = "js-widgetcloser"; 652 widgetHandleCTA._contents = widgetHandleCTA.innerHTML; 653 654 const widgetCloserAppender = () => { 655 if (me.opt.globalToggleWidget.checked) { 656 me.vanilla.header.append(widgetCloseLabel); 657 widgetHandleCTA.textContent = widgetCloseText; 658 } 659 else { 660 widgetCloseLabel.remove(); 661 widgetHandleCTA.textContent = ""; 662 widgetHandleCTA.insertAdjacentHTML('beforeend', widgetHandleCTA._contents); 663 } 664 } 665 666 [me.opt.globalToggleMenu, me.opt.globalToggleWidget].forEach(toggle => { 667 toggle.addEventListener("change", widgetCloserAppender, {passive: true}); 668 }); 669 } 670 671 me.f.downloader = (btn) => { 672 673 if (btn.disabled) return; 674 675 if (btn.dataset.type == 'all') { 676 var downloadText =
677 me.opt.lang == "de" ? "Wirklich alle " + btn.dataset.files + " Bilder mit einer GesamtgröÃe von " + btn.dataset.size + " MB herunterladen?" : 678 "Do you really want to download all " + btn.dataset.files + " files? That will be " + btn.dataset.size + " MB."; 679 680 if (window.confirm(downloadText)) { 681 download_files(JSON.parse(btn.dataset.deliver)); 682 btn.disabled = true; 683 } 684 } 685 else { 686 window.location.href = btn.dataset.deliver; 687 btn.disabled = true; 688 } 689 } 690 691 // Widget Flatpickr 692 693 me.customWidgetFp = (clientUrl, hotelId, objPrefix, objId, dateFormat) => { 694 695 me.opt.globalToggleWidget && me.opt.globalToggleWidget.addEventListener("change", () => { 696 me.customWidgetFpAction(clientUrl, hotelId, objPrefix, objId, dateFormat); 697 }, {passive: true, once: true}); 698 } 699 700 me.customWidgetFpAction = function (clientUrl, hotelId, objPrefix, objId, dateFormat) { 701 702 var calendarInput = { 703 from: d.getElementById( objPrefix + '_date_from' ), 704 to: d.getElementById( objPrefix + '_date_to' ) 705 } 706 707 var dateFromInit = calendarInput.from && calendarInput.from.dataset.date ? new Date(calendarInput.from.dataset.date) : new Date((new Date()).valueOf() + 24*60*60*1000), 708 dateToInit = calendarInput.to && calendarInput.to.dataset.date ? new Date(calendarInput.to.dataset.date) : new Date((new Date()).valueOf() + 2*24*60*60*1000); 709 710 // Set date value per default 711 if (calendarInput.from && calendarInput.from.dataset.date) { 712 calendarInput.from.value = me.date_convert(dateFromInit, "Date", 'iso'); 713 } 714 if (calendarInput.to && calendarInput.to.dataset.date) { 715 calendarInput.to.value = me.date_convert(dateToInit, "Date", 'iso'); 716 } 717 718 me.opt.avail = {}, 719 me.opt.widgetSelectDates = {}; 720 function getAvail(dateFrom, inst) { 721 dateFrom = dateFrom || new Date(); 722 var requestFrom = me.date_convert(new Date(dateFrom.getFullYear(), dateFrom.getMonth(), 1), 'Date', 'ymd', 0, true); 723 var requestTo = me.date_convert(new Date(dateFrom.getFullYear(), dateFrom.getMonth() + 2, 1), 'Date', 'ymd'); 724 if (me.opt.dev) { 725 console.info('Request: ' + requestFrom + ' -> ' + requestTo); 726 } 727 $.ajax({ 728 dataType: 'json', 729 url: clientUrl + 'vsc.php', 730 data: { 731 return_json: true, 732 return_html: false, 733 step: 'calendar_from', 734 date_from: requestFrom, 735 date_to: requestTo, 736 cart_language: me.opt.lang, 737 c: { 738 id_hotel: hotelId 739 } 740 }, 741 success: function (d) { 742 if (d.cart) { 743 $.map(d.cart.calendar.date_from.days, function (el, i) { 744 me.opt.avail[i] = el; 745 }); 746 if (inst) { 747 inst.redraw(); 748 } 749 } 750 } 751 }); 752 } 753 754 function updateDate(inst, date) { 755 date = date || inst.latestSelectedDateObj; 756 if(!date){ return; } 757 var newDateDate = date.getDate(); 758 if (newDateDate < 10) { 759 newDateDate = "0" + newDateDate; 760 } 761 $(inst.element).closest('.widget-date').find('.widget-date-day').text(newDateDate); 762 $(inst.element).closest('.widget-date').find('.widget-date-month').text(inst.l10n.months.shorthand[date.getMonth()]); 763 764 // If year of date_from and date_to is not the same, write both years 765 if (me.opt.widgetSelectDates.dateFrom && me.opt.widgetSelectDates.dateTo && (me.opt.widgetSelectDates.dateFrom.split("-")[0] !== me.opt.widgetSelectDates.dateTo.split("-")[0])) { 766 $(inst.element).closest('.widget-form-book').find('.widget-date-year').text(me.opt.widgetSelectDates.dateFrom.split("-")[0] + " - " + me.opt.widgetSelectDates.dateTo.split("-")[0]); 767 } 768 // Else if a year is present: 769 else if (me.opt.widgetSelectDates.dateFrom) { 770 $(inst.element).closest('.widget-form-book').find('.widget-date-year').text(me.opt.widgetSelectDates.dateFrom.split("-")[0]); 771 } 772 } 773 774 var fpConfig = { 775 altInput: true, 776 altFormat: dateFormat, 777 locale: me.opt.lang, 778 disableMobile: true, 779 static: true, 780 minDate: 'today', 781 onClose: function (selDates, dateStr, inst) { 782 inst.altInput.blur(); 783 } 784 }; 785 786 if ( objId == 'date_from' ) { 787 $.extend( fpConfig, { 788 onReady: function ( selDates, dateStr, inst ) { 789 updateDate(inst, dateFromInit); 790 inst.set( 'disable', [ 791 function ( currentDay ) { 792 var availDay = me.opt.avail[ me.date_convert( currentDay, 'Date', 'ymd' ) ]; 793 if ( availDay ) { 794 return !( availDay.type == 2 || availDay.type == 3 ); 795 } 796 return true; 797 } 798 ]); 799 getAvail( new Date(), inst ); 800 }, 801 onOpen: function ( selDates, dateStr, inst ) { 802 inst.fpDateTo = d.getElementById( objPrefix + '_date_to' )._flatpickr; 803 }, 804 onDayCreate: function ( dObj, dStr, inst, dayElem ) { 805 var span; 806 var dateIso = me.date_convert( dayElem.dateObj, 'Date', 'ymd' ); 807 if ( !me.opt.avail || !me.opt.avail[dateIso] ) { 808 return; 809 } 810 if ( me.opt.avail[dateIso].type == 2 ) { 811 span = d.createElement( 'span' ); 812 span.className = 'departure-only'; 813 dayElem.appendChild( span ) 814 } 815 }, 816 onMonthChange: function ( selDates, dateStr, inst ) { 817 if ( !me.opt.avail[ me.date_convert( new Date( inst.currentYear, inst.currentMonth + 1, 15 ), 'Date', 'ymd' ) ] ) { 818 getAvail( new Date( inst.currentYear, inst.currentMonth, 1 ), inst ); 819 } 820 }, 821 onYearChange: function ( selDates, dateStr, inst ) { 822 if ( !me.opt.avail[ me.date_convert( new Date( inst.currentYear, inst.currentMonth + 1, 15 ), 'Date', 'ymd' ) ] ) { 823 getAvail( new Date( inst.currentYear, inst.currentMonth, 1 ), inst ); 824 } 825 }, 826 onChange: function ( selDates, dateStr, inst ) { 827 if ( inst.latestSelectedDateObj ) { 828 me.opt.widgetSelectDates.dateFrom = dateStr; 829 updateDate(inst); 830 inst.fpDateTo.clear(); 831 inst.fpDateTo.set( 'minDate', me.date_convert( inst.latestSelectedDateObj, 'Date', 'Date', 1 ) ); 832 inst.fpDateTo.set( 'maxDate', me.date_convert( inst.latestSelectedDateObj, 'Date', 'Date', 28 ) ); 833 inst.fpDateTo.redraw(); 834 inst.fpDateTo.open(); 835 $( 'input.rdfrom1', $( inst.element ).closest( 'form' ) ).val( me.date_convert( inst.latestSelectedDateObj, 'Date', 'dmy' ) ); 836 $('.widget-date-from').addClass('changed'); 837 } 838 } 839 }); 840 } 841 else if (objId === 'date_to') { 842 $.extend(fpConfig, { 843 onReady: function (selDates, dateStr, inst) { 844 updateDate(inst, dateToInit); 845 inst.set('disable', [ 846 function (currentDay) { 847 var availDay = me.opt.avail[me.date_convert(currentDay, 'Date', 'ymd')]; 848 if (availDay) { 849 return availDay.type === -1; 850 } 851 return true; 852 } 853 ]); 854 inst.fpDateFrom = d.getElementById(objPrefix + '_date_from')._flatpickr; 855 }, 856 onDayCreate: function (dObj, dStr, inst, dayElem) { 857 if (!inst.fpDateFrom || !inst.fpDateFrom.latestSelectedDateObj) { 858 return; 859 } 860 var $dayElem = $(dayElem); 861 var fromDate = me.date_convert(inst.fpDateFrom.latestSelectedDateObj, 'Date', 'ymd'); 862 var toDate = inst.latestSelectedDateObj ? me.date_convert(inst.latestSelectedDateObj, 'Date', 'ymd') : false;
863 var currentDate = me.date_convert(dayElem.dateObj, 'Date', 'ymd'); 864 $dayElem.toggleClass('arrival-day', currentDate === fromDate) 865 .toggleClass('following-day', currentDate > fromDate) 866 .toggleClass('between-day', currentDate > fromDate && currentDate < toDate) 867 .filter('.following-day:not(.disabled)') 868 .hover( 869 function () { 870 $dayElem.prevAll('.following-day').addBack().addClass('following-day-hover'); 871 }, 872 function () { 873 $dayElem.prevAll('.following-day').addBack().removeClass('following-day-hover'); 874 } 875 ); 876 }, 877 onChange: function (selDates, dateStr, inst) { 878 if (inst.latestSelectedDateObj) { 879 me.opt.widgetSelectDates.dateTo = dateStr; 880 updateDate(inst); 881 $('input.rdto1', $(inst.element).closest('form')).val(me.date_convert(inst.latestSelectedDateObj, 'Date', 'dmy')); 882 $('.widget-date-to').addClass('changed'); 883 }; 884 } 885 }); 886 } 887 $('#' + objPrefix + '_' + objId).flatpickr(fpConfig); 888 }; 889 890 me.loadingClasses = function() { 891 if (me.opt.touch) { 892 me.opt.html.removeClass('no-touchevents').addClass('touchevents'); 893 } 894 if (me.opt.intersectionobserver) { 895 me.opt.html.addClass('intersectionobserver'); 896 } 897 } 898 899 me.globalToggles = me.vanilla.site.querySelectorAll(":scope > .global-toggle"); 900 901 me.globalToggles.forEach(gt => { 902 gt.addEventListener("change", () => { 903 var isChecked = gt.checked; 904 me.globalToggles.forEach(gt2 => { 905 gt2.checked = false; 906 }); 907 gt.checked = isChecked; 908 }, {passive: true}); 909 }); 910 911 me.opt.bgChangeElements.forEach(section => { 912 me.f.visibilityClassToggler(section, "darken-page", false, {rootMargin: "-50% -0% -50% -0%"}); 913 }); 914 915 me.vanilla.main.querySelectorAll(":scope > .grp-single-87[data-attr-layout^='bild-text-box']").forEach(imagetextbox => { 916 me.f.visibilityClassToggler(imagetextbox); 917 }); 918 919 me.vanilla.main.querySelectorAll("video:not(.nst21-video-autoplay)").forEach(video => { 920 921 if (video.dataset.poster) { 922 video.poster = video.dataset.poster; 923 } 924 else if (navigator.userAgent.includes("Safari")) { 925 video.currentTime = 0.001; 926 video.preload = "metadata"; 927 video.querySelectorAll("source").forEach(source => { 928 if (source.type.includes("video")) { 929 source.src = source .src + "#t=0.001"; 930 } 931 }); 932 933 var clone = video.cloneNode(true); 934 935 video.insertAdjacentHTML("afterend", clone.outerHTML); 936 video.remove(); 937 } 938 else { 939 video.style.backgroundColor = "#000000"; 940 } 941 }); 942 943 me.onloadFunctions = () => { 944 // Functions that can be delayed or should occur _after_ load event of the whole page 945 946 me.vanilla.site.classList.remove("no-animations"), 947 me.vanilla.site.classList.add("animations-allowed"); 948 949 me.f.resizeObserver(); 950 me.f.mailificator(); 951 me.f.mobileNumberficator(); 952 me.f.widgetCloser(); 953 } 954 955 nst2021.f.smallSliderSlick = (el) => { 956 // Entgegen des Namens wurde doch kein Slick Slider verwendet. Custom JS. 957 el = el.id ? el : me.vanilla.main.querySelector("#" + el); 958 959 if (!el) return console.log("No small slider gallery found. You need to give it an unique id"); 960 961 // Setup: 962 el._itemCount = el.childNodes.length, 963 el._itemPos = 0, 964 el._arrowPrev = el.closest(".small-itemslider-mobilescroll").nextElementSibling.querySelector(".slide-arrow-left"), 965 el._arrowNext = el.closest(".small-itemslider-mobilescroll").nextElementSibling.querySelector(".slide-arrow-right"); 966 967 if (el._itemCount > 1) { 968 969 let slideFunction = (pos) => { 970 971 // preload images as soon as user clicks an arrow 972 if (!el._slideActive) { 973 el.querySelectorAll("img").forEach(img => { 974 img.loading = "eager"; 975 }) 976 el._slideActive = true; 977 } 978 979 let newPos = el._itemPos + pos, 980 skip = window.innerWidth > 960 ? 2 : 1; 981 982 if (newPos < 0 ) return; // Nicht nach links wenn Position Start wäre 983 if (newPos > el._itemCount - skip) return; 984 985 el.childNodes.forEach(item => { 986 item.style.transform = "translateX(calc(-" + (100 * newPos) + "% - " + (40 * newPos) + "px))"; 987 }); 988 989 el._itemPos = newPos; 990 991 // disable buttons if first or last item 992 if (el._itemPos == 0) { 993 el._arrowPrev.setAttribute("disabled", "disabled"); 994 } 995 else { 996 el._arrowPrev.removeAttribute("disabled", "disabled"); 997 }
998 if (el._itemPos + skip == el._itemCount) { 999 el._arrowNext.setAttribute("disabled", "disabled"); 1000 } 1001 else { 1002 el._arrowNext.removeAttribute("disabled", "disabled"); 1003 } 1004 } 1005 1006 el._arrowNext.addEventListener("click", () => { 1007 slideFunction(1); 1008 }, {passive: true}); 1009 1010 el._arrowPrev.addEventListener("click", () => { 1011 slideFunction(-1); 1012 }, {passive: true}); 1013 1014 if (!w.matchMedia('(prefers-reduced-motion: reduce)').matches) { 1015 el.childNodes.forEach(slide => { 1016 me.f.visibilityClassToggler(slide, false, true, { 1017 rootMargin: "-20% 0% -20% 0%" 1018 }); 1019 }); 1020 } 1021 } 1022 else { 1023 el._arrowPrev.remove(), 1024 el._arrowNext.remove(); 1025 } 1026 } 1027 1028 me.domReadyFunctions = () => { 1029 1030 let trackingConsentMinimize = d.body.querySelector(":scope > #condeon-consentmanager > button.tracking-consent-minimize"); 1031 1032 if (trackingConsentMinimize) { 1033 trackingConsentMinimize.ariaLabel = me.opt.lang == "de" ? "Datenschutzeinstellungen minimieren" : "Minimize privacy references"; 1034 } 1035 1036 if (!me.opt.touch) { 1037 me.opt.mainMenuHandles = me.vanilla.header.querySelectorAll("label.main-menu-handle"); 1038 1039 me.opt.mainMenuHandleMouseChecker = (el) => { 1040 el._toggle = el.target.closest(".main-menu-item").querySelector(".main-menu-toggle"); 1041 1042 if (el._toggle) { 1043 el._toggle.checked = true; 1044 } 1045 } 1046 1047 me.opt.mainMenuHandles.forEach(handle => { 1048 handle.addEventListener("mouseenter", me.opt.mainMenuHandleMouseChecker, {passive: true}); 1049 }) 1050 } 1051 } 1052 1053 me.trackingEnabled = () => { 1054 setTimeout(() => { 1055 // NOTE: The value 67 in the next line is for Google Analytics 4 tracking code without signals, 70 with signals 1056 if (document.cookie.match(/(?:^| )tracking_consent=c(?:[0-9]+,)*(67)(?:,|;|$)/) || document.cookie.match(/(?:^| )tracking_consent=c(?:[0-9]+,)*(70)(?:,|;|$)/)) { 1057 let newCetScript = document.createElement("script"); 1058 newCetScript.src = "/(cms)/module/static/default/content/libs/js/analytics-click-events/cet.min.js"; 1059 d.body.append(newCetScript); 1060 } 1061 }, 10); 1062 } 1063 1064 me.track = { 1065 1066 quicklinks: { 1067 name: "Header Quicklinks Navigation", 1068 el: $(".nav-quicklinks a", me.opt.header) 1069 }, 1070 logo: { 1071 name: "Header Logo", 1072 subtype: "logo", 1073 el: $(".logo a", me.opt.header) 1074 }, 1075 familotellogoheader: { 1076 name: "Familotel Logo im Header", 1077 el: $("a.familotel-label", me.opt.header) 1078 }, 1079 headergalCta: { 1080 name: "Header Galerie CTA", 1081 el: $(".custom-gallery-grid-1 a", me.opt.header), 1082 }, 1083 poi: { 1084 name: "Header POI Link", 1085 el: $('a.nst21-poi', me.opt.header) 1086 }, 1087 topicGridItem: { 1088 name: "Normale Galerie, Klick auf Bild", 1089 el: $("a.topic-grid-item", me.opt.main) 1090 }, 1091 imageTextBoxCTA: { 1092 name: "Bild Text Box CTA", 1093 el: $(".image-text-box a[class*='cta']", me.opt.main) 1094 }, 1095 cstCardDetailLink: { 1096 name: "CST Card Detail Link", 1097 el: $('.grp-mem-57 a.small-itemslider-link', me.opt.main), 1098 labeladd: 'h4', 1099 labelin: '.small-itemslider-item', 1100 labelAsCategory: true 1101 }, 1102 bildergalerieSlider: { 1103 name: "Bildergalerie Slider", 1104 el: $('.grp-mem-54 a.small-itemslider-link', me.opt.main) 1105 }, 1106 pseudoMaillink: { 1107 el: $('a[data-mail]', me.opt.site), 1108 name: "Email link" 1109 }, 1110 pseudoTellink: { 1111 el: $('a[data-mobile]', me.opt.site), 1112 name: "Phone link" 1113 }, 1114 roomFilter: { 1115 name: "Onepager Zimmer Filter/Quicklink", 1116 el: $('a.onepager-quicklink', me.opt.main) 1117 }, 1118 roomBook: { 1119 name: "Onepager Zimmer buchen und anfragen", 1120 el: $('.onepager-list form.cst-bookform', me.opt.main), 1121 labeladd: '.onepager-item-headline', 1122 labelin: '.onepager-item', 1123 labelAsCategory: true 1124 }, 1125 cstDetailAction: { 1126 name: "CST Detail Action Button", 1127 el: $('.gallery-actions-buttons form, .cst-detail-actions form', me.opt.site), 1128 subtype: $('.cst-detail-actions').length >= 2 ? 'packagevariant' : '', 1129 labeladd: '.gallery-headlines h1', 1130 labelin: '.site' 1131 }, 1132 }; 1133 1134 1135 w.addEventListener("DOMContentLoaded", me.domReadyFunctions, {passive: true}); 1136 w.addEventListener("load", me.onloadFunctions, {passive: true}); 1137 w.addEventListener("scroll", me.opt.scrollBarVisibility, {passive: true}); 1138 d.addEventListener('CondeonTrackingEnabled', me.trackingEnabledFunctions, {passive: true}); 1139 1140 1141 1142 me.opt.globalToggleMenu.addEventListener("change", () => { 1143 // Give Safari some time to calculate scrollbar width 1144 me.opt.globalToggleMenu.checked = false;
1145 d.body.style.setProperty("--scrollbar-width", w.innerWidth - d.body.clientWidth + "px"); 1146 me.opt.globalToggleMenu.checked = true; 1147 1148 }, {passive: true, once: true}); 1149 1150 // check if headings have duplicate IDs and/or anchors have duplicate IDs or same IDs 1151 // NOTE Duplicate ID checker only for headings and anchors 1152 me.handleDuplicateHeadingIds = function() { 1153 1154 const headings = document.querySelectorAll("h1,h2,h3,h4,h5,h6"); 1155 const anchors = document.querySelectorAll("[data-attr-sprungmarke]"); 1156 const idCounts = {}; 1157 1158 // Handle ID duplicates on anchors/headings 1159 const handleDuplicateId = (element) => { 1160 1161 if (!element.id || element.id.trim() === "") { 1162 element.removeAttribute("id"); 1163 return; 1164 } 1165 1166 idCounts[element.id] = (idCounts[element.id] || 0) + 1; 1167 1168 if (idCounts[element.id] > 1) { 1169 console.log(`Element with ID "${element.id}" has been processed, count: ${idCounts[element.id]}`); 1170 } 1171 1172 if (idCounts[element.id] > 1) { 1173 element.id = `${element.id}_${idCounts[element.id] - 1}`; 1174 } 1175 }; 1176 1177 anchors.forEach(handleDuplicateId);
1178 headings.forEach(handleDuplicateId); 1179 1180 // Update/test anchors to match headings 1181 anchors.forEach(anchor => { 1182 if (!anchor.id) return; 1183 1184 headings.forEach(heading => { 1185 1186 if (!heading.id) return; 1187 if (heading.id === anchor.id) { 1188 idCounts[heading.id]++; 1189 heading.id = `${heading.id}_${idCounts[heading.id]}`; 1190 } 1191 }); 1192 }); 1193 }; 1194 1195 const navMenuCheckbox = document.getElementById("global-toggle-menu"); 1196 const navContainer = document.querySelector(".nav-level-0"); 1197 1198 // Uncheck menu checkbox when Escape key is pressed 1199 if (navMenuCheckbox) { 1200 document.addEventListener("keydown", function (e) { 1201 if (e.key === "Escape" || e.code === 27) { 1202 1203 // If the nav menu is open, close it 1204 if (navMenuCheckbox.checked) { 1205 navMenuCheckbox.checked = false; 1206 } 1207 } 1208 }); 1209 } 1210 1211 // Remove hrefs from flex-prev and flex-next links 1212 window.addEventListener('load', function() { 1213 document.querySelectorAll('a.flex-prev, a.flex-next, a.slick-prev, a.slick-next').forEach(link => { 1214 if (!link.getAttribute('href') || link.getAttribute('href') === '' || link.getAttribute('href') === '#') { 1215 link.removeAttribute('href'); 1216 } 1217 }); 1218 }); 1219 1220 1221 me.handleDuplicateHeadingIds(); 1222 1223})( window.jQuery, window.nst2021, window, document );
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.