1/*! Picturefill - v2.1.0 - 2014-07-25 2* http://scottjehl.github.io/picturefill 3* Copyright (c) 2014 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT */ 4/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license */ 5 6window.matchMedia || (window.matchMedia = function () { 7 "use strict"; 8 9 // For browsers that support matchMedium api such as IE 9 and webkit 10 var styleMedia = (window.styleMedia || window.media); 11 12 // For those that don't support matchMedium 13 if (!styleMedia) { 14 var style = document.createElement('style'), 15 script = document.getElementsByTagName('script')[0], 16 info = null; 17 18 style.type = 'text/css'; 19 style.id = 'matchmediajs-test'; 20 21 script.parentNode.insertBefore(style, script); 22 23 // 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers 24 info = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle; 25 26 styleMedia = { 27 matchMedium: function (media) { 28 var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }'; 29 30 // 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers 31 if (style.styleSheet) { 32 style.styleSheet.cssText = text; 33 } else { 34 style.textContent = text; 35 } 36 37 // Test if media query is true or false 38 return info.width === '1px'; 39 } 40 }; 41 } 42 43 return function (media) { 44 return { 45 matches: styleMedia.matchMedium(media || 'all'), 46 media: media || 'all' 47 }; 48 }; 49}()); 50/*! Picturefill - Responsive Images that work today. 51* Author: Scott Jehl, Filament Group, 2012 ( new proposal implemented by Shawn Jansepar ) 52* License: MIT/GPLv2 53* Spec: http://picture.responsiveimages.org/ 54*/ 55(function (w, doc) { 56 // Enable strict mode 57 "use strict"; 58 59 // If picture is supported, well, that's awesome. Let's get outta here... 60 if (w.HTMLPictureElement) { 61 w.picturefill = function () { }; 62 return; 63 } 64 65 // HTML shim|v it for old IE (IE9 will still need the HTML video tag workaround) 66 doc.createElement("picture"); 67 68 // local object for method references and testing exposure 69 var pf = {}; 70 71 // namespace 72 pf.ns = "picturefill"; 73 74 // srcset support test 75 pf.srcsetSupported = "srcset" in doc.createElement("img"); 76 pf.sizesSupported = w.HTMLImageElement.sizes; 77 78 // just a string trim workaround 79 pf.trim = function (str) { 80 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ""); 81 }; 82 83 // just a string endsWith workaround 84 pf.endsWith = function (str, suffix) { 85 return str.endsWith ? str.endsWith(suffix) : str.indexOf(suffix, str.length - suffix.length) !== -1; 86 }; 87 88 /** 89 * Shortcut method for matchMedia ( for easy overriding in tests ) 90 */ 91 pf.matchesMedia = function (media) { 92 return w.matchMedia && w.matchMedia(media).matches; 93 }; 94 95 /** 96 * Shortcut method for `devicePixelRatio` ( for easy overriding in tests ) 97 */ 98 pf.getDpr = function () { 99 return (w.devicePixelRatio || 1); 100 }; 101 102 /** 103 * Get width in css pixel value from a "length" value 104 * http://dev.w3.org/csswg/css-values-3/#length-value 105 */ 106 pf.getWidthFromLength = function (length) { 107 // If no length was specified, or it is 0 or negative, default to `100vw` (per the spec). 108 length = length && (parseFloat(length) > 0 || length.indexOf("calc(") > -1) ? length : "100vw"; 109 110 /** 111 * If length is specified in `vw` units, use `%` instead since the div we�re measuring 112 * is injected at the top of the document. 113 * 114 * TODO: maybe we should put this behind a feature test for `vw`? 115 */ 116 length = length.replace("vw", "%"); 117 118 // Create a cached element for getting length value widths 119 if (!pf.lengthEl) { 120 pf.lengthEl = doc.createElement("div"); 121 doc.documentElement.insertBefore(pf.lengthEl, doc.documentElement.firstChild); 122 } 123 124 // Positioning styles help prevent padding/margin/width on `html` from throwing calculations off. 125 pf.lengthEl.style.cssText = "position: absolute; left: 0; width: " + length + ";"; 126 127 if (pf.lengthEl.offsetWidth <= 0) { 128 // Something has gone wrong. `calc()` is in use and unsupported, most likely. Default to `100vw` (`100%`, for broader support.): 129 pf.lengthEl.style.cssText = "width: 100%;"; 130 } 131 132 return pf.lengthEl.offsetWidth; 133 }; 134 135 // container of supported mime types that one might need to qualify before using 136 pf.types = {}; 137 138 // Add support for standard mime types. 139 pf.types["image/jpeg"] = true; 140 pf.types["image/gif"] = true; 141 pf.types["image/png"] = true; 142 143 // test svg support 144 pf.types["image/svg+xml"] = doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1"); 145 146 // test webp support, only when the markup calls for it 147 pf.types["image/webp"] = function () { 148 // based on Modernizr's lossless img-webp test 149 // note: asynchronous 150 var img = new w.Image(), 151 type = "image/webp"; 152 153 img.onerror = function () { 154 pf.types[type] = false; 155 picturefill(); 156 }; 157 img.onload = function () { 158 pf.types[type] = img.width === 1; 159 picturefill(); 160 }; 161 img.src = "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA="; 162 }; 163 164 /** 165 * Takes a source element and checks if its type attribute is present and if so, supported 166 * Note: for type tests that require a async logic, 167 * you can define them as a function that'll run only if that type needs to be tested. Just make the test function call picturefill again when it is complete. 168 * see the async webp test above for example 169 */
170 pf.verifyTypeSupport = function (source) { 171 var type = source.getAttribute("type"); 172 // if type attribute exists, return test result, otherwise return true 173 if (type === null || type === "") { 174 return true; 175 } else { 176 // if the type test is a function, run it and return "pending" status. The function will rerun picturefill on pending elements once finished. 177 if (typeof (pf.types[type]) === "function") { 178 pf.types[type](); 179 return "pending"; 180 } else { 181 return pf.types[type]; 182 } 183 } 184 }; 185 186 /** 187 * Parses an individual `size` and returns the length, and optional media query 188 */ 189 pf.parseSize = function (sourceSizeStr) { 190 var match = /(\([^)]+\))?\s*(.+)/g.exec(sourceSizeStr); 191 return { 192 media: match && match[1], 193 length: match && match[2] 194 }; 195 }; 196 197 /** 198 * Takes a string of sizes and returns the width in pixels as a number 199 */ 200 pf.findWidthFromSourceSize = function (sourceSizeListStr) { 201 // Split up source size list, ie ( max-width: 30em ) 100%, ( max-width: 50em ) 50%, 33% 202 // or (min-width:30em) calc(30% - 15px) 203 var sourceSizeList = pf.trim(sourceSizeListStr).split(/\s*,\s*/), 204 winningLength; 205 206 for (var i = 0, len = sourceSizeList.length; i < len; i++) { 207 // Match <media-condition>? length, ie ( min-width: 50em ) 100% 208 var sourceSize = sourceSizeList[i], 209 // Split "( min-width: 50em ) 100%" into separate strings 210 parsedSize = pf.parseSize(sourceSize), 211 length = parsedSize.length, 212 media = parsedSize.media; 213 214 if (!length) { 215 continue; 216 } 217 if (!media || pf.matchesMedia(media)) { 218 // if there is no media query or it matches, choose this as our winning length 219 // and end algorithm 220 winningLength = length; 221 break; 222 } 223 } 224 225 // pass the length to a method that can properly determine length 226 // in pixels based on these formats: http://dev.w3.org/csswg/css-values-3/#length-value 227 return pf.getWidthFromLength(winningLength); 228 }; 229 230 pf.parseSrcset = function (srcset) { 231 /** 232 * A lot of this was pulled from Boris Smus� parser for the now-defunct WHATWG `srcset` 233 * https://github.com/borismus/srcset-polyfill/blob/master/js/srcset-info.js 234 * 235 * 1. Let input (`srcset`) be the value passed to this algorithm. 236 * 2. Let position be a pointer into input, initially pointing at the start of the string. 237 * 3. Let raw candidates be an initially empty ordered list of URLs with associated 238 * unparsed descriptors. The order of entries in the list is the order in which entries 239 * are added to the list. 240 */ 241 var candidates = []; 242 243 while (srcset !== "") { 244 srcset = srcset.replace(/^\s+/g, ""); 245 246 // 5. Collect a sequence of characters that are not space characters, and let that be url. 247 var pos = srcset.search(/\s/g), 248 url, descriptor = null; 249 250 if (pos !== -1) { 251 url = srcset.slice(0, pos); 252 253 var last = url[url.length - 1]; 254 255 // 6. If url ends with a U+002C COMMA character (,), remove that character from url 256 // and let descriptors be the empty string. Otherwise, follow these substeps 257 // 6.1. If url is empty, then jump to the step labeled descriptor parser. 258 259 if (last === "," || url === "") { 260 url = url.replace(/,+$/, ""); 261 descriptor = ""; 262 } 263 srcset = srcset.slice(pos + 1); 264 265 // 6.2. Collect a sequence of characters that are not U+002C COMMA characters (,), and 266 // let that be descriptors. 267 if (descriptor === null) { 268 var descpos = srcset.indexOf(","); 269 if (descpos !== -1) { 270 descriptor = srcset.slice(0, descpos); 271 srcset = srcset.slice(descpos + 1); 272 } else { 273 descriptor = srcset; 274 srcset = ""; 275 } 276 } 277 } else { 278 url = srcset; 279 srcset = ""; 280 } 281 282 // 7. Add url to raw candidates, associated with descriptors. 283 if (url || descriptor) { 284 candidates.push({ 285 url: url, 286 descriptor: descriptor 287 }); 288 } 289 } 290 return candidates; 291 }; 292 293 pf.parseDescriptor = function (descriptor, sizesattr) { 294 // 11. Descriptor parser: Let candidates be an initially empty source set. The order of entries in the list 295 // is the order in which entries are added to the list. 296 var sizes = sizesattr || "100vw", 297 sizeDescriptor = descriptor && descriptor.replace(/(^\s+|\s+$)/g, ""), 298 widthInCssPixels = pf.findWidthFromSourceSize(sizes), 299 resCandidate; 300 301 if (sizeDescriptor) { 302 var splitDescriptor = sizeDescriptor.split(" "); 303 304 for (var i = splitDescriptor.length + 1; i >= 0; i--) { 305 if (splitDescriptor[i] !== undefined) { 306 var curr = splitDescriptor[i], 307 lastchar = curr && curr.slice(curr.length - 1); 308 309 if ((lastchar === "h" || lastchar === "w") && !pf.sizesSupported) { 310 resCandidate = parseFloat((parseInt(curr, 10) / widthInCssPixels)); 311 } else if (lastchar === "x") { 312 var res = curr && parseFloat(curr, 10); 313 resCandidate = res && !isNaN(res) ? res : 1; 314 } 315 } 316 } 317 } 318 return resCandidate || 1; 319 }; 320 321 /** 322 * Takes a srcset in the form of url/ 323 * ex. "images/pic-medium.png 1x, images/pic-medium-2x.png 2x" or 324 * "images/pic-medium.png 400w, images/pic-medium-2x.png 800w" or 325 * "images/pic-small.png" 326 * Get an array of image candidates in the form of 327 * {url: "/foo/bar.png", resolution: 1} 328 * where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value 329 * If sizes is specified, resolution is calculated 330 */ 331 pf.getCandidatesFromSourceSet = function (srcset, sizes) { 332 var candidates = pf.parseSrcset(srcset), 333 formattedCandidates = []; 334 335 for (var i = 0, len = candidates.length; i < len; i++) { 336 var candidate = candidates[i]; 337 338 formattedCandidates.push({ 339 url: candidate.url, 340 resolution: pf.parseDescriptor(candidate.descriptor, sizes) 341 }); 342 } 343 return formattedCandidates; 344 }; 345 346 /* 347 * if it's an img element and it has a srcset property, 348 * we need to remove the attribute so we can manipulate src 349 * (the property's existence infers native srcset support, and a srcset-supporting browser will prioritize srcset's value over our winning picture candidate) 350 * this moves srcset's value to memory for later use and removes the attr 351 */ 352 pf.dodgeSrcset = function (img) { 353 if (img.srcset) { 354 img[pf.ns].srcset = img.srcset; 355 img.removeAttribute("srcset"); 356 } 357 }; 358 359 /* 360 * Accept a source or img element and process its srcset and sizes attrs 361 */ 362 pf.processSourceSet = function (el) { 363 var srcset = el.getAttribute("srcset"), 364 sizes = el.getAttribute("sizes"), 365 candidates = []; 366 367 // if it's an img element, use the cached srcset property (defined or not)
368 if (el.nodeName.toUpperCase() === "IMG" && el[pf.ns] && el[pf.ns].srcset) { 369 srcset = el[pf.ns].srcset; 370 } 371 372 if (srcset) { 373 candidates = pf.getCandidatesFromSourceSet(srcset, sizes); 374 } 375 return candidates; 376 }; 377 378 pf.applyBestCandidate = function (candidates, picImg) { 379 var candidate, 380 length, 381 bestCandidate; 382 383 candidates.sort(pf.ascendingSort); 384 385 length = candidates.length; 386 bestCandidate = candidates[length - 1]; 387 388 for (var i = 0; i < length; i++) { 389 candidate = candidates[i]; 390 if (candidate.resolution >= pf.getDpr()) { 391 bestCandidate = candidate; 392 break; 393 } 394 } 395 396 if (bestCandidate && !pf.endsWith(picImg.src, bestCandidate.url)) { 397 picImg.src = bestCandidate.url; 398 // currentSrc attribute and property to match 399 // http://picture.responsiveimages.org/#the-img-element 400 picImg.currentSrc = picImg.src; 401 } 402 }; 403 404 pf.ascendingSort = function (a, b) { 405 return a.resolution - b.resolution; 406 }; 407 408 /* 409 * In IE9, <source> elements get removed if they aren't children of 410 * video elements. Thus, we conditionally wrap source elements 411 * using <!--[if IE 9]><video style="display: none;"><![endif]--> 412 * and must account for that here by moving those source elements 413 * back into the picture element. 414 */ 415 pf.removeVideoShim = function (picture) { 416 var videos = picture.getElementsByTagName("video"); 417 if (videos.length) { 418 var video = videos[0], 419 vsources = video.getElementsByTagName("source"); 420 while (vsources.length) { 421 picture.insertBefore(vsources[0], video); 422 } 423 // Remove the video element once we're finished removing its children 424 video.parentNode.removeChild(video); 425 } 426 }; 427 428 /* 429 * Find all `img` elements, and add them to the candidate list if they have 430 * a `picture` parent, a `sizes` attribute in basic `srcset` supporting browsers, 431 * a `srcset` attribute at all, and they haven�t been evaluated already. 432 */ 433 pf.getAllElements = function () { 434 var elems = [], 435 imgs = doc.getElementsByTagName("img"); 436 437 for (var h = 0, len = imgs.length; h < len; h++) { 438 var currImg = imgs[h]; 439 440 if (currImg.parentNode.nodeName.toUpperCase() === "PICTURE" || 441 (currImg.getAttribute("srcset") !== null) || currImg[pf.ns] && currImg[pf.ns].srcset !== null) { 442 elems.push(currImg); 443 } 444 } 445 return elems; 446 }; 447 448 pf.getMatch = function (img, picture) { 449 var sources = picture.childNodes, 450 match; 451 452 // Go through each child, and if they have media queries, evaluate them 453 for (var j = 0, slen = sources.length; j < slen; j++) { 454 var source = sources[j]; 455 456 // ignore non-element nodes 457 if (source.nodeType !== 1) { 458 continue; 459 } 460 461 // Hitting the `img` element that started everything stops the search for `sources`. 462 // If no previous `source` matches, the `img` itself is evaluated later. 463 if (source === img) { 464 return match; 465 } 466 467 // ignore non-`source` nodes 468 if (source.nodeName.toUpperCase() !== "SOURCE") { 469 continue; 470 } 471 // if it's a source element that has the `src` property set, throw a warning in the console 472 if (source.getAttribute("src") !== null && typeof console !== undefined) { 473 console.warn("The `src` attribute is invalid on `picture` `source` element; instead, use `srcset`."); 474 } 475 476 var media = source.getAttribute("media"); 477 478 // if source does not have a srcset attribute, skip 479 if (!source.getAttribute("srcset")) { 480 continue; 481 } 482 483 // if there's no media specified, OR w.matchMedia is supported
484 if ((!media || pf.matchesMedia(media))) { 485 var typeSupported = pf.verifyTypeSupport(source); 486 487 if (typeSupported === true) { 488 match = source; 489 break; 490 } else if (typeSupported === "pending") { 491 return false; 492 } 493 } 494 } 495 496 return match; 497 }; 498 499 function picturefill(opt) { 500 var elements, 501 element, 502 parent, 503 firstMatch, 504 candidates, 505 506 options = opt || {}; 507 elements = options.elements || pf.getAllElements(); 508 509 // Loop through all elements 510 for (var i = 0, plen = elements.length; i < plen; i++) { 511 element = elements[i]; 512 parent = element.parentNode; 513 firstMatch = undefined; 514 candidates = undefined; 515 516 // expando for caching data on the img 517 if (!element[pf.ns]) { 518 element[pf.ns] = {}; 519 } 520 521 // if the element has already been evaluated, skip it 522 // unless `options.force` is set to true ( this, for example, 523 // is set to true when running `picturefill` on `resize` ). 524 if (!options.reevaluate && element[pf.ns].evaluated) { 525 continue; 526 } 527 528 // if `img` is in a `picture` element 529 if (parent.nodeName.toUpperCase() === "PICTURE") { 530 531 // IE9 video workaround 532 pf.removeVideoShim(parent); 533 534 // return the first match which might undefined 535 // returns false if there is a pending source 536 // TODO the return type here is brutal, cleanup 537 firstMatch = pf.getMatch(element, parent); 538 539 // if any sources are pending in this picture due to async type test(s) 540 // remove the evaluated attr and skip for now ( the pending test will 541 // rerun picturefill on this element when complete) 542 if (firstMatch === false) { 543 continue; 544 } 545 } else { 546 firstMatch = undefined; 547 } 548 549 // Cache and remove `srcset` if present and we�re going to be doing `picture`/`srcset`/`sizes` polyfilling to it. 550 if (parent.nodeName.toUpperCase() === "PICTURE" || 551 (element.srcset && !pf.srcsetSupported) || 552 (!pf.sizesSupported && (element.srcset && element.srcset.indexOf("w") > -1))) { 553 pf.dodgeSrcset(element); 554 } 555 556 if (firstMatch) { 557 candidates = pf.processSourceSet(firstMatch); 558 pf.applyBestCandidate(candidates, element); 559 } else { 560 // No sources matched, so we�re down to processing the inner `img` as a source. 561 candidates = pf.processSourceSet(element); 562 563 if (element.srcset === undefined || element[pf.ns].srcset) { 564 // Either `srcset` is completely unsupported, or we need to polyfill `sizes` functionality. 565 pf.applyBestCandidate(candidates, element); 566 } // Else, resolution-only `srcset` is supported natively. 567 } 568 569 // set evaluated to true to avoid unnecessary reparsing 570 element[pf.ns].evaluated = true; 571 } 572 } 573 574 /** 575 * Sets up picture polyfill by polling the document and running 576 * the polyfill every 250ms until the document is ready. 577 * Also attaches picturefill on resize 578 */ 579 function runPicturefill() { 580 picturefill(); 581 var intervalId = setInterval(function () { 582 // When the document has finished loading, stop checking for new images 583 // https://github.com/ded/domready/blob/master/ready.js#L15 584 picturefill(); 585 if (/^loaded|^i|^c/.test(doc.readyState)) { 586 clearInterval(intervalId); 587 return; 588 } 589 }, 250); 590 if (w.addEventListener) { 591 var resizeThrottle; 592 w.addEventListener("resize", function () { 593 if (!w._picturefillWorking) { 594 w._picturefillWorking = true; 595 w.clearTimeout(resizeThrottle); 596 resizeThrottle = w.setTimeout(function () { 597 picturefill({ reevaluate: true });
598 w._picturefillWorking = false; 599 }, 60); 600 } 601 }, false); 602 } 603 } 604 605 runPicturefill(); 606 607 /* expose methods for testing */ 608 picturefill._ = pf; 609 610 /* expose picturefill */ 611 if (typeof module === "object" && typeof module.exports === "object") { 612 // CommonJS, just export 613 module.exports = picturefill; 614 } else if (typeof define === "function" && define.amd) { 615 // AMD support 616 define(function () { return picturefill; }); 617 } else if (typeof w === "object") { 618 // If no AMD and we are in the browser, attach to window 619 w.picturefill = picturefill; 620 } 621 622})(this, this.document); 623 624/*! lazysizes - v1.1.3 - Licensed MIT */ 625!function (a, b) { var c = b(a, a.document); a.lazySizes = c, "object" == typeof module && module.exports ? module.exports = c : "function" == typeof define && define.amd && define(c) }(window, function (a, b) { "use strict"; if (b.getElementsByClassName) { var c, d = b.documentElement, e = a.addEventListener, f = a.setTimeout, g = a.requestAnimationFrame || f, h = /^picture$/i, i = ["load", "error", "lazyincluded", "_lazyloaded"], j = function (a, b) { var c = new RegExp("(\\s|^)" + b + "(\\s|$)"); return a.className.match(c) && c }, k = function (a, b) { j(a, b) || (a.className += " " + b) }, l = function (a, b) { var c; (c = j(a, b)) && (a.className = a.className.replace(c, " ")) }, m = function (a, b, c) { var d = c ? "addEventListener" : "removeEventListener";
625 c && m(a, b), i.forEach(function (c) { a[d](c, b) }) }, n = function (a, c, d, e, f) { var g = b.createEvent("CustomEvent"); return g.initCustomEvent(c, !e, !f, d || {}), g.details = g.detail, a.dispatchEvent(g), g }, o = function (b, d) { var e; a.HTMLPictureElement || ((e = a.picturefill || a.respimage || c.pf) ? e({ reevaluate: !0, elements: [b] }) : d && d.src && (b.src = d.src)) }, p = function (a, b) { return getComputedStyle(a, null)[b] }, q = function (a, b, d) { for (d = d || a.offsetWidth; d < c.minSize && b && !a._lazysizesWidth;) d = b.offsetWidth, b = b.parentNode; return d }, r = function (b) { var d, e = 0, h = a.Date, i = function () { d = !1, e = h.now(), b() }, j = function () { f(i) }, k = function () { g(j) }; return function () { if (!d) { var a = c.throttle - (h.now() - e); d = !0, 9 > a && (a = 9), f(k, a) } } }, s = function () { var i, q, s, u, v, w, x, y, z, A, B, C, D, E = /^img$/i, F = /^iframe$/i, G = "onscroll" in a && !/glebot/.test(navigator.userAgent), H = 0, I = 0, J = 0, K = 1, L = function (a) { J--, a && a.target && m(a.target, L), (!a || 0 > J || !a.target) && (J = 0) }, M = function (a, b) { var c, d = a, e = "hidden" != p(a, "visibility"); for (y -= b, B += b, z -= b, A += b; e && (d = d.offsetParent) ;) e = (p(d, "opacity") || 1) > 0, e && "visible" != p(d, "overflow") && (c = d.getBoundingClientRect(), e = A > c.left && z < c.right && B > c.top - 1 && y < c.bottom + 1); return e }, N = function () { var a, b, d, e, f, g, h, j, k; if ((v = c.loadMode) && 8 > J && (a = i.length)) { for (b = 0, K++, D > I && 1 > J && K > 3 && v > 2 ? (I = D, K = 0) : I = I != C && v > 1 && K > 2 && 6 > J ? C : H; a > b; b++) i[b] && !i[b]._lazyRace && (G ? ((j = i[b].getAttribute("data-expand")) && (g = 1 * j) || (g = I), k !== g && (w = innerWidth + g, x = innerHeight + g, h = -1 * g, k = g), d = i[b].getBoundingClientRect(), (B = d.bottom) >= h && (y = d.top) <= x && (A = d.right) >= h && (z = d.left) <= w && (B || A || z || y) && (s && 3 > J && !j && (3 > v || 4 > K) || M(i[b], g)) ? (S(i[b], d.width), f = !0) : !f && s && !e && 3 > J && 4 > K && v > 2 && (q[0] || c.preloadAfterLoad) && (q[0] || !j && (B || A || z || y || "auto" != i[b].getAttribute(c.sizesAttr))) && (e = q[0] || i[b])) : S(i[b])); e && !f && S(e) } }, O = r(N), P = function (a) { k(a.target, c.loadedClass), l(a.target, c.loadingClass), m(a.target, P) }
625, Q = function (a, b) { try { a.contentWindow.location.replace(b) } catch (c) { a.setAttribute("src", b) } }, R = function () { var a, b = [], c = function () { for (; b.length;) b.shift()(); a = !1 }; return function (d) { b.push(d), a || (a = !0, g(c)) } }(), S = function (a, b) { var d, e, g, i, p, q, r, v, w, x, y, z = E.test(a.nodeName), A = a.getAttribute(c.sizesAttr) || a.getAttribute("sizes"), B = "auto" == A; (!B && s || !z || !a.src && !a.srcset || a.complete || j(a, c.errorClass)) && (a._lazyRace = !0, J++, R(function () { if (a._lazyRace && delete a._lazyRace, l(a, c.lazyClass), !(w = n(a, "lazybeforeunveil")).defaultPrevented) { if (A && (B ? (t.updateElem(a, !0, b), k(a, c.autosizesClass)) : a.setAttribute("sizes", A)), q = a.getAttribute(c.srcsetAttr), p = a.getAttribute(c.srcAttr), z && (r = a.parentNode, v = r && h.test(r.nodeName || "")), x = w.detail.firesLoad || "src" in a && (q || p || v), w = { target: a }, x && (m(a, L, !0), clearTimeout(u), u = f(L, 2500), k(a, c.loadingClass), m(a, P, !0)), v) for (d = r.getElementsByTagName("source"), e = 0, g = d.length; g > e; e++) (y = c.customMedia[d[e].getAttribute("data-media") || d[e].getAttribute("media")]) && d[e].setAttribute("media", y), i = d[e].getAttribute(c.srcsetAttr), i && d[e].setAttribute("srcset", i); q ? a.setAttribute("srcset", q) : p && (F.test(a.nodeName) ? Q(a, p) : a.setAttribute("src", p)), (q || v) && o(a, { src: p }) } (!x || a.complete) && (x ? L(w) : J--, P(w)) })) }, T = function () { var a, b = function () { c.loadMode = 3, O() }; s = !0, K += 8, c.loadMode = 3, e("scroll", function () { 3 == c.loadMode && (c.loadMode = 2), clearTimeout(a), a = f(b, 99) }, !0) }; return { _: function () { i = b.getElementsByClassName(c.lazyClass), q = b.getElementsByClassName(c.lazyClass + " " + c.preloadClass), C = c.expand, D = Math.round(C * c.expFactor), e("scroll", O, !0), e("resize", O, !0), a.MutationObserver ? new MutationObserver(O).observe(d, { childList: !0, subtree: !0, attributes: !0 }) : (d.addEventListener("DOMNodeInserted", O, !0), d.addEventListener("DOMAttrModified", O, !0), setInterval(O, 999)), e("hashchange", O, !0), ["focus", "mouseover", "click", "load", "transitionend", "animationend", "webkitAnimationEnd"].forEach(function (a) { b.addEventListener(a, O, !0) }), (s = /d$|^c/.test(b.readyState)) ? T() : (e("load", T), b.addEventListener("DOMContentLoaded", O)), O() }, checkElems: O, unveil: S } }(), t = function () { var a, d = function (a, b, c) { var d, e, f, g, i = a.parentNode; if (i && (c = q(a, i, c), g = n(a, "lazybeforesizes", { width: c, dataAttr: !!b }), !g.defaultPrevented && (c = g.detail.width, c && c !== a._lazysizesWidth))) { if (a._lazysizesWidth = c, c += "px", a.setAttribute("sizes", c), h.test(i.nodeName || "")) for (d = i.getElementsByTagName("source"), e = 0, f = d.length; f > e; e++) d[e].setAttribute("sizes", c); g.detail.dataAttr || o(a, g.detail) } }, f = function () { var b, c = a.length; if (c) for (b = 0; c > b; b++) d(a[b]) }, g = r(f); return { _: function () { a = b.getElementsByClassName(c.autosizesClass), e("resize", g) }, checkElems: g, updateElem: d } }(), u = function () { u.i || (u.i = !0, t._(), s._()) }; return function () { var b, d = { lazyClass: "lazyload", loadedClass: "lazyloaded", loadingClass: "lazyloading", preloadClass: "lazypreload", errorClass: "lazyerror", autosizesClass: "lazyautosizes", srcAttr: "data-src", srcsetAttr: "data-srcset", sizesAttr: "data-sizes", minSize: 40, customMedia: {}, init: !0, expFactor: 2, expand: 359, loadMode: 2, throttle: 125 }; c = a.lazySizesConfig || a.lazysizesConfig || {}; for (b in d) b in c || (c[b] = d[b]); a.lazySizesConfig = c, f(function () { c.init && u() }) }(), { cfg: c, autoSizer: t, loader: s, init: u, uP: o, aC: k, rC: l, hC: j, fire: n, gW: q } } }); 626/*! jCarousel - v0.3.9 - 2018-07-30 627* http://sorgalla.com/jcarousel/ 628* Copyright (c) 2006-2018 Jan Sorgalla; Licensed MIT */ 629 630!function (t) { "use strict"; var i = t.jCarousel = {}; i.version = "0.3.9"; var s = /^([+\-]=)?(.+)$/; i.parseTarget = function (t) { var i = !1, e = "object" != typeof t ? s.exec(t) : null; return e ? (t = parseInt(e[2], 10) || 0, e[1] && (i = !0, "-=" === e[1] && (t *= -1))) : "object" != typeof t && (t = parseInt(t, 10) || 0), { target: t, relative: i } }, i.detectCarousel = function (t) { for (var i; t.length > 0;
vendor: 10,372 bytes, line 630
630) { if ((i = t.filter("[data-jcarousel]")).length > 0) return i; if ((i = t.find("[data-jcarousel]")).length > 0) return i; t = t.parent() } return null }, i.base = function (s) { return { version: i.version, _options: {}, _element: null, _carousel: null, _init: t.noop, _create: t.noop, _destroy: t.noop, _reload: t.noop, create: function () { return this._element.attr("data-" + s.toLowerCase(), !0).data(s, this), !1 === this._trigger("create") ? this : (this._create(), this._trigger("createend"), this) }, destroy: function () { return !1 === this._trigger("destroy") ? this : (this._destroy(), this._trigger("destroyend"), this._element.removeData(s).removeAttr("data-" + s.toLowerCase()), this) }, reload: function (t) { return !1 === this._trigger("reload") ? this : (t && this.options(t), this._reload(), this._trigger("reloadend"), this) }, element: function () { return this._element }, options: function (i, s) { if (0 === arguments.length) return t.extend({}, this._options); if ("string" == typeof i) { if (void 0 === s) return void 0 === this._options[i] ? null : this._options[i]; this._options[i] = s } else this._options = t.extend({}, this._options, i); return this }, carousel: function () { return this._carousel || (this._carousel = i.detectCarousel(this.options("carousel") || this._element), this._carousel || t.error('Could not detect carousel for plugin "' + s + '"')), this._carousel }, _trigger: function (i, e, r) { var n, o = !1; return r = [this].concat(r || []), (e || this._element).each(function () { n = t.Event((s + ":" + i).toLowerCase()), t(this).trigger(n, r), n.isDefaultPrevented() && (o = !0) }), !o } } }, i.plugin = function (s, e) { var r = t[s] = function (i, s) { this._element = t(i), this.options(s), this._init(), this.create() }; return r.fn = r.prototype = t.extend({}, i.base(s), e), t.fn[s] = function (i) { var e = Array.prototype.slice.call(arguments, 1), n = this; return "string" == typeof i ? this.each(function () { var r = t(this).data(s); if (!r) return t.error("Cannot call methods on " + s + ' prior to initialization; attempted to call method "' + i + '"'); if (!t.isFunction(r[i]) || "_" === i.charAt(0)) return t.error('No such method "' + i + '" for ' + s + " instance"); var o = r[i].apply(r, e); return o !== r && void 0 !== o ? (n = o, !1) : void 0 }) : this.each(function () { var e = t(this).data(s); e instanceof r ? e.reload(i) : new r(this, i) }), n }, r } }(jQuery), function (t, i) { "use strict"; var s = t(i), e = function (t) { return parseFloat(t) || 0 }; t.jCarousel.plugin("jcarousel", { animating: !1, tail: 0, inTail: !1, resizeState: null, resizeTimer: null, lt: null, vertical: !1, rtl: !1, circular: !1, underflow: !1, relative: !1, _options: { list: function () { return this.element().children().eq(0) }, items: function () { return this.list().children() }, animation: 400, transitions: !1, wrap: null, vertical: null, rtl: null, center: !1 }, _list: null, _items: null, _target: t(), _first: t(), _last: t(), _visible: t(), _fullyvisible: t(), _init: function () { var t = this; return t.resizeState = s.width() + "x" + s.height(), this.onWindowResize = function () { t.resizeTimer && clearTimeout(t.resizeTimer), t.resizeTimer = setTimeout(function () { var i = s.width() + "x" + s.height(); i !== t.resizeState && (t.resizeState = i, t.reload()) }, 100) }, this }, _create: function () { this._reload(), s.on("resize.jcarousel", this.onWindowResize) }, _destroy: function () { s.off("resize.jcarousel", this.onWindowResize) }, _reload: function () { this.vertical = this.options("vertical"), null == this.vertical && (this.vertical = e(this.list().height()) > e(this.list().width())), this.rtl = this.options("rtl"), null == this.rtl && (this.rtl = function (i) { if ("rtl" === ("" + i.attr("dir")).toLowerCase()) return !0; var s = !1; return i.parents("[dir]").each(function () { if (/rtl/i.test(t(this).attr("dir"))) return s = !0, !1 }), s }(this._element)), this.lt = this.vertical ? "top" : "left", this.relative = "relative" === this.list().css("position"), this._list = null, this._items = null; var i = this.index(this._target) >= 0 ? this._target : this.closest(); this.circular = "circular" === this.options("wrap"), this.underflow = !1; var s = { left: 0, top: 0 }; return i.length > 0 && (this._prepare(i), this.list().find("[data-jcarousel-clone]").remove(), this._items = null, this.underflow = this._fullyvisible.length >= this.items().length, this.circular = this.circular && !this.underflow, s[this.lt] = this._position(i) + "px"), this.move(s), this }, list: function () { if (null === this._list) { var i = this.options("list"); this._list = t.isFunction(i) ? i.call(this) : this._element.find(i) } return this._list }, items: function () { if (null === this._items) { var i = this.options("items"); this._items = (t.isFunction(i) ? i.call(this) : this.list().find(i)).not("[data-jcarousel-clone]") } return this._items }, index: function (t) { return this.items().index(t) }, closest: function () { var i, s = this, r = this.list().position()[this.lt], n = t(), o = !1, l = this.vertical ? "bottom" : this.rtl && !this.relative ? "left" : "right"; return this.rtl && this.relative && !this.vertical && (r += e(this.list().width()) - this.clipping()), this.items().each(function () { if (n = t(this), o) return !1; var a = s.dimension(n); if ((r += a) >= 0) { if (i = a - e(n.css("margin-" + l)), !(Math.abs(r) - a + i / 2 <= 0)) return !1; o = !0 } }), n }, target: function () { return this._target }, first: function () { return this._first }, last: function () { return this._last }, visible: function () { return this._visible }, fullyvisible: function () { return this._fullyvisible }, hasNext: function () { if (!1 === this._trigger("hasnext")) return !0; var t = this.options("wrap"), i = this.items().length - 1, s = this.options("center") ? this._target : this._last; return !!(i >= 0 && !this.underflow && (t && "first" !== t || this.index(s) < i || this.tail && !this.inTail)) }, hasPrev: function () { if (!1 === this._trigger("hasprev")) return !0; var t = this.options("wrap"); return !!(this.items().length > 0 && !this.underflow && (t && "last" !== t || this.index(this._first) > 0 || this.tail && this.inTail)) }, clipping: function () { return e(this._element["inner" + (this.vertical ? "Height" : "Width")]()) }, dimension: function (t) { return e(t["outer" + (this.vertical ? "Height" : "Width")](!0)) }, scroll: function (i, s, e) { if (this.animating) return this; if (!1 === this._trigger("scroll", null, [i, s])) return this; t.isFunction(s) && (e = s, s = !0); var r = t.jCarousel.parseTarget(i); if (r.relative) { var n, o, l, a, h, u, c, f, d = this.items().length - 1, _ = Math.abs(r.target), p = this.options("wrap"); if (r.target > 0) { var g = this.index(this._last); if (g >= d && this.tail) this.inTail ? "both" === p || "last" === p ? this._scroll(0, s, e) : t.isFunction(e) && e.call(this, !1) : this._scrollTail(s, e); else if (n = this.index(this._target), this.underflow && n === d && ("circular" === p || "both" === p || "last" === p) || !this.underflow && g === d && ("both" === p || "last" === p)) this._scroll(0, s, e); else if (l = n + _, this.circular && l > d) { for (f = d, h = this.items().get(-1); f++ < l;)h = this.items().eq(0), (u = this._visible.index(h) >= 0) && h.after(h.clone(!0).attr("data-jcarousel-clone", !0)), this.list().append(h), u || ((c = {})[this.lt] = this.dimension(h), this.moveBy(c)), this._items = null; this._scroll(h, s, e) } else this._scroll(Math.min(l, d), s, e) } else if (this.inTail) this._scroll(Math.max(this.index(this._first) - _ + 1, 0), s, e); else if (o = this.index(this._first), n = this.index(this._target), l = (a = this.underflow ? n : o) - _, a <= 0 && (this.underflow && "circular" === p || "both" === p || "first" === p)) this._scroll(d, s, e); else if (this.circular && l < 0) { for (f = l, h = this.items().get(0); f++ < 0;) { h = this.items().eq(-1), (u = this._visible.index(h) >= 0) && h.after(h.clone(!0).attr("data-jcarousel-clone", !0)), this.list().prepend(h), this._items = null; var m = this.dimension(h); (c = {})[this.lt] = -m, this.moveBy(c) } this._scroll(h, s, e) } else this._scroll(Math.max(l, 0), s, e) } else this._scroll(r.target, s, e); return this._trigger("scrollend"), this }, moveBy: function (t, i) { var s = this.list().position(), r = 1, n = 0; return this.rtl && !this.vertical && (r = -1, this.relative && (n = e(this.list().width()) - this.clipping())), t.left && (t.left = e(s.left) + n + e(t.left) * r + "px"), t.top && (t.top = e(s.top) + n + e(t.top) * r + "px"), this.move(t, i) }, move: function (i, s) { s = s || {}; var e = this.options("transitions"), r = !!e, n = !!e.transforms, o = !!e.transforms3d, l = s.duration || 0, a = this.list(); if (!r && l > 0) a.animate(i, s); else { var h = s.complete || t.noop, u = {}; if (r) { var c = { transitionDuration: a.css("transitionDuration"), transitionTimingFunction: a.css("transitionTimingFunction"), transitionProperty: a.css("transitionProperty") }, f = h; h = function () { t(this).css(c), f.call(this) }, u = { transitionDuration: (l > 0 ? l / 1e3 : 0) + "s", transitionTimingFunction: e.easing || s.easing, transitionProperty: l > 0 ? n || o ? "all" : i.left ? "left" : "top" : "none", transform: "none" } } o ? u.transform = "translate3d(" + (i.left || 0) + "," + (i.top || 0) + ",0)" : n ? u.transform = "translate(" + (i.left || 0) + "," + (i.top || 0) + ")" : t.extend(u, i), r && l > 0 && a.one("transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd", h), a.css(u), l <= 0 && a.each(function () { h.call(this) }) } }, _scroll: function (i, s, r) { if (this.animating) return t.isFunction(r) && r.call(this, !1), this; if ("object" != typeof i ? i = this.items().eq(i) : void 0 === i.jquery && (i = t(i)), 0 === i.length) return t.isFunction(r) && r.call(this, !1), this; this.inTail = !1, this._prepare(i); var n = this._position(i); if (n === e(this.list().position()[this.lt])) return t.isFunction(r) && r.call(this, !1), this; var o = {}; return o[this.lt] = n + "px", this._animate(o, s, r), this }, _scrollTail: function (i, s) { if (this.animating || !this.tail) return t.isFunction(s) && s.call(this, !1), this; var r = this.list().position()[this.lt]; this.rtl && this.relative && !this.vertical && (r += e(this.list().width())
vendor: 6,180 bytes, line 630
630- this.clipping()), this.rtl && !this.vertical ? r += this.tail : r -= this.tail, this.inTail = !0; var n = {}; return n[this.lt] = r + "px", this._update({ target: this._target.next(), fullyvisible: this._fullyvisible.slice(1).add(this._visible.last()) }), this._animate(n, i, s), this }, _animate: function (i, s, e) { if (e = e || t.noop, !1 === this._trigger("animate")) return e.call(this, !1), this; this.animating = !0; var r = this.options("animation"), n = t.proxy(function () { this.animating = !1; var t = this.list().find("[data-jcarousel-clone]"); t.length > 0 && (t.remove(), this._reload()), this._trigger("animateend"), e.call(this, !0) }, this), o = "object" == typeof r ? t.extend({}, r) : { duration: r }, l = o.complete || t.noop; return !1 === s ? o.duration = 0 : void 0 !== t.fx.speeds[o.duration] && (o.duration = t.fx.speeds[o.duration]), o.complete = function () { n(), l.call(this) }, this.move(i, o), this }, _prepare: function (i) { var s, r, n, o = this.index(i), l = o, a = this.dimension(i), h = this.clipping(), u = this.vertical ? "bottom" : this.rtl ? "left" : "right", c = this.options("center"), f = { target: i, first: i, last: i, visible: i, fullyvisible: a <= h ? i : t() }; if (c && (a /= 2, h /= 2), a < h) for (; ;) { if (0 === (s = this.items().eq(++l)).length) { if (!this.circular) break; if (s = this.items().eq(0), i.get(0) === s.get(0)) break; if ((r = this._visible.index(s) >= 0) && s.after(s.clone(!0).attr("data-jcarousel-clone", !0)), this.list().append(s), !r) { var d = {}; d[this.lt] = this.dimension(s), this.moveBy(d) } this._items = null } if (0 === (n = this.dimension(s))) break; if (a += n, f.last = s, f.visible = f.visible.add(s), a - e(s.css("margin-" + u)) <= h && (f.fullyvisible = f.fullyvisible.add(s)), a >= h) break } if (!this.circular && !c && a < h) for (l = o; !(--l < 0 || 0 === (s = this.items().eq(l)).length || 0 === (n = this.dimension(s)) || (a += n, f.first = s, f.visible = f.visible.add(s), a - e(s.css("margin-" + u)) <= h && (f.fullyvisible = f.fullyvisible.add(s)), a >= h));); return this._update(f), this.tail = 0, c || "circular" === this.options("wrap") || "custom" === this.options("wrap") || this.index(f.last) !== this.items().length - 1 || (a -= e(f.last.css("margin-" + u))) > h && (this.tail = a - h), this }, _position: function (t) { var i = this._first, s = e(i.position()[this.lt]), r = this.options("center"), n = r ? this.clipping() / 2 - this.dimension(i) / 2 : 0; return this.rtl && !this.vertical ? (this.relative ? s -= e(this.list().width()) - this.dimension(i) : s -= this.clipping() - this.dimension(i), s += n) : s -= n, !r && (this.index(t) > this.index(i) || this.inTail) && this.tail ? (s = this.rtl && !this.vertical ? s - this.tail : s + this.tail, this.inTail = !0) : this.inTail = !1, -s }, _update: function (i) { var s, e = this, r = { target: this._target, first: this._first, last: this._last, visible: this._visible, fullyvisible: this._fullyvisible }, n = this.index(i.first || r.first) < this.index(r.first), o = function (s) { var o = [], l = []; i[s].each(function () { r[s].index(this) < 0 && o.push(this) }), r[s].each(function () { i[s].index(this) < 0 && l.push(this) }), n ? o = o.reverse() : l = l.reverse(), e._trigger(s + "in", t(o)), e._trigger(s + "out", t(l)), e["_" + s] = i[s] }; for (s in i) o(s); return this } }) }(jQuery, window), function (t) { "use strict"; t.jcarousel.fn.scrollIntoView = function (i, s, e) { var r, n = t.jCarousel.parseTarget(i), o = this.index(this._fullyvisible.first()), l = this.index(this._fullyvisible.last()); if ((r = n.relative ? n.target < 0 ? Math.max(0, o + n.target) : l + n.target : "object" != typeof n.target ? n.target : this.index(n.target)) < o) return this.scroll(r, s, e); if (r >= o && r <= l) return t.isFunction(e) && e.call(this, !1), this; for (var a, h = this.items(), u = this.clipping(), c = this.vertical ? "bottom" : this.rtl ? "left" : "right", f = 0; 0 !== (a = h.eq(r)).length;) { if ((f += this.dimension(a)) >= u) { f - (parseFloat(a.css("margin-" + c)) || 0) !== u && r++; break } if (r <= 0) break; r-- } return this.scroll(r, s, e) } }(jQuery), function (t) { "use strict"; t.jCarousel.plugin("jcarouselControl", { _options: { target: "+=1", event: "click", method: "scroll" }, _active: null, _init: function () { this.onDestroy = t.proxy(function () { this._destroy(), this.carousel().one("jcarousel:createend", t.proxy(this._create, this)) }, this), this.onReload = t.proxy(this._reload, this), this.onEvent = t.proxy(function (i) { i.preventDefault(); var s = this.options("method"); t.isFunction(s) ? s.call(this) : this.carousel().jcarousel(this.options("method"), this.options("target")) }, this) }, _create: function () { this.carousel().one("jcarousel:destroy", this.onDestroy).on("jcarousel:reloadend jcarousel:scrollend", this.onReload), this._element.on(this.options("event") + ".jcarouselcontrol", this.onEvent), this._reload() }, _destroy: function () { this._element.off(".jcarouselcontrol", this.onEvent), this.carousel().off("jcarousel:destroy", this.onDestroy).off("jcarousel:reloadend jcarousel:scrollend", this.onReload) }, _reload: function () { var i, s = t.jCarousel.parseTarget(this.options("target")), e = this.carousel(); if (s.relative) i = e.jcarousel(s.target > 0 ? "hasNext" : "hasPrev"); else { var r = "object" != typeof s.target ? e.jcarousel("items").eq(s.target) : s.target; i = e.jcarousel("target").index(r) >= 0 } return this._active !== i && (this._trigger(i ? "active" : "inactive"), this._active = i), this } }) }(jQuery), function (t) { "use strict"; t.jCarousel.plugin("jcarouselPagination", { _options: { perPage: null, item: function (t) { return '<a href="#' + t + '">' + t + "</a>" }, event: "click", method: "scroll" }, _carouselItems: null, _pages: {}, _items: {}, _currentPage: null, _init: function () { this.onDestroy = t.proxy(function () { this._destroy(), this.carousel().one("jcarousel:createend", t.proxy(this._create, this)) }, this), this.onReload = t.proxy(this._reload, this), this.onScroll = t.proxy(this._update, this) }, _create: function () { this.carousel().one("jcarousel:destroy", this.onDestroy).on("jcarou
630sel:reloadend", this.onReload).on("jcarousel:scrollend", this.onScroll), this._reload() }, _destroy: function () { this._clear(), this.carousel().off("jcarousel:destroy", this.onDestroy).off("jcarousel:reloadend", this.onReload).off("jcarousel:scrollend", this.onScroll), this._carouselItems = null }, _reload: function () { var i = this.options("perPage"); if (this._pages = {}, this._items = {}, t.isFunction(i) && (i = i.call(this)), null == i) this._pages = this._calculatePages(); else for (var s, e = parseInt(i, 10) || 0, r = this._getCarouselItems(), n = 1, o = 0; 0 !== (s = r.eq(o++)).length;)this._pages[n] ? this._pages[n] = this._pages[n].add(s) : this._pages[n] = s, o % e == 0 && n++; this._clear(); var l = this, a = this.carousel().data("jcarousel"), h = this._element, u = this.options("item"), c = this._getCarouselItems().length; t.each(this._pages, function (i, s) { var e = l._items[i] = t(u.call(l, i, s)); e.on(l.options("event") + ".jcarouselpagination", t.proxy(function () { var t = s.eq(0); if (a.circular) { var e = a.index(a.target()), r = a.index(t); parseFloat(i) > parseFloat(l._currentPage) ? r < e && (t = "+=" + (c - e + r)) : r > e && (t = "-=" + (e + (c - r))) } a[this.options("method")](t) }, l)), h.append(e) }), this._update() }, _update: function () { var i, s = this.carousel().jcarousel("target"); t.each(this._pages, function (t, e) { if (e.each(function () { if (s.is(this)) return i = t, !1 }), i) return !1 }), this._currentPage !== i && (this._trigger("inactive", this._items[this._currentPage]), this._trigger("active", this._items[i])), this._currentPage = i }, items: function () { return this._items }, reloadCarouselItems: function () { return this._carouselItems = null, this }, _clear: function () { this._element.empty(), this._currentPage = null }, _calculatePages: function () { for (var t, i, s = this.carousel().data("jcarousel"), e = this._getCarouselItems(), r = s.clipping(), n = 0, o = 0, l = 1, a = {}; 0 !== (t = e.eq(o++)).length;)n + (i = s.dimension(t)) > r && (l++ , n = 0), n += i, a[l] ? a[l] = a[l].add(t) : a[l] = t; return a }, _getCarouselItems: function () { return this._carouselItems || (this._carouselItems = this.carousel().jcarousel("items")), this._carouselItems } }) }(jQuery), function (t, i) { "use strict"; var s, e; t.each({ hidden: "visibilitychange", mozHidden: "mozvisibilitychange", msHidden: "msvisibilitychange", webkitHidden: "webkitvisibilitychange" }, function (t, r) { if (void 0 !== i[t]) return s = t, e = r, !1 }), t.jCarousel.plugin("jcarouselAutoscroll", { _options: { target: "+=1", interval: 3e3, autostart: !0, method: "scroll" }, _timer: null, _started: !1, _init: function () { this.onDestroy = t.proxy(function () { this._destroy(), this.carousel().one("jcarousel:createend", t.proxy(this._create, this)) }, this), this.onAnimateEnd = t.proxy(this._start, this), this.onVisibilityChange = t.proxy(function () { i[s] ? this._stop() : this._start() }, this) }, _create: function () { this.carousel().one("jcarousel:destroy", this.onDestroy), t(i).on(e, this.onVisibilityChange), this.options("autostart") && this.start() }, _destroy: function () { this._stop(), this.carousel().off("jcarousel:destroy", this.onDestroy), t(i).off(e, this.onVisibilityChange) }, _start: function () { if (this._stop(), this._started) return this.carousel().one("jcarousel:animateend", this.onAnimateEnd), this._timer = setTimeout(t.proxy(function () { this.carousel().jcarousel(this.options("method"), this.options("target")) }, this), this.options("interval")), this }, _stop: function () { return this._timer && (this._timer = clearTimeout(this._timer)), this.carousel().off("jcarousel:animateend", this.onAnimateEnd), this }, start: function () { return this._started = !0, this._start(), this }, stop: function () { return this._started = !1, this._stop(), this } }) }(jQuery, document); 631/** ========================================================== 632 633* jquery lightGallery.js v1.1.4 634* http://sachinchoolur.github.io/lightGallery/ 635* Released under the MIT License - http://opensource.org/licenses/mit-license.html ---- FREE ---- 636 637=========================================================/**/ 638; 639(function ($) { 640 "use strict"; 641 $.fn.lightGallery = function (options) { 642 var defaults = {
643 mode: 'slide', 644 useCSS: true, 645 cssEasing: 'ease', //'cubic-bezier(0.25, 0, 0.25, 1)',// 646 easing: 'linear', //'for jquery animation',// 647 speed: 600, 648 addClass: '', 649 650 closable: true, 651 loop: false, 652 auto: false, 653 pause: 4000, 654 escKey: true, 655 controls: true, 656 hideControlOnEnd: false, 657 658 preload: 1, //number of preload slides. will exicute only after the current slide is fully loaded. ex:// you clicked on 4th image and if preload = 1 then 3rd slide and 5th slide will be loaded in the background after the 4th slide is fully loaded.. if preload is 2 then 2nd 3rd 5th 6th slides will be preloaded.. ... ... 659 showAfterLoad: true, 660 selector: null, 661 index: false, 662 663 lang: { 664 allPhotos: 'All photos' 665 }, 666 counter: false, 667 668 exThumbImage: false, 669 thumbnail: true, 670 showThumbByDefault: false, 671 animateThumb: true, 672 currentPagerPosition: 'middle', 673 thumbWidth: 100, 674 thumbMargin: 5, 675 676 677 mobileSrc: false, 678 mobileSrcMaxWidth: 640, 679 swipeThreshold: 50, 680 enableTouch: true, 681 enableDrag: true, 682 683 vimeoColor: 'CCCCCC', 684 videoAutoplay: true, 685 videoMaxWidth: '855px', 686 687 dynamic: false, 688 dynamicEl: [], 689 //callbacks 690 691 onOpen: function (plugin) { }, 692 onSlideBefore: function (plugin) { }, 693 onSlideAfter: function (plugin) { }, 694 onSlideNext: function (plugin) { }, 695 onSlidePrev: function (plugin) { }, 696 onBeforeClose: function (plugin) { }, 697 onCloseAfter: function (plugin) { } 698 }, 699 el = $(this), 700 plugin = this, 701 $children = null, 702 index = 0, 703 isActive = false, 704 lightGalleryOn = false, 705 isTouch = document.createTouch !== undefined || ('ontouchstart' in window) || ('onmsgesturechange' in window) || navigator.msMaxTouchPoints, 706 $gallery, $galleryCont, $slider, $slide, $prev, $next, prevIndex, $thumb_cont, $thumb, windowWidth, interval, usingThumb = false, 707 aTiming = false, 708 aSpeed = false; 709 var settings = $.extend(true, {}, defaults, options); 710 var lightGallery = { 711 init: function () { 712 el.each(function () { 713 var $this = $(this); 714 if (settings.dynamic) { 715 $children = settings.dynamicEl; 716 index = 0; 717 prevIndex = index; 718 setUp.init(index); 719 } else { 720 if (settings.selector !== null) { 721 $children = $(settings.selector); 722 } else { 723 $children = $this.children(); 724 } 725 $children.on('click', function (e) { 726 if (settings.selector !== null) { 727 $children = $(settings.selector); 728 } else { 729 $children = $this.children(); 730 } 731 e.preventDefault(); 732 e.stopPropagation(); 733 index = $children.index(this); 734 prevIndex = index; 735 setUp.init(index); 736 }); 737 } 738 }); 739 } 740 }; 741 var setUp = { 742 init: function () { 743 isActive = true; 744 this.structure(); 745 this.getWidth(); 746 this.closeSlide(); 747 this.autoStart(); 748 this.counter(); 749 this.slideTo(); 750 this.buildThumbnail(); 751 this.keyPress(); 752 if (settings.index) { 753 this.slide(settings.index); 754 this.animateThumb(settings.index); 755 } else { 756 this.slide(index); 757 this.animateThumb(index); 758 } 759 if (settings.enableDrag) { 760 this.touch(); 761 }; 762 if (settings.enableTouch) { 763 this.enableTouch(); 764 } 765 766 setTimeout(function () { 767 $gallery.addClass('opacity'); 768 }, 50); 769 }, 770 structure: function () { 771 $('body').append('<div id="lightGallery-outer" class="' + settings.addClass + '"><div id="lightGallery-Gallery"><div id="lightGallery-slider"></div><a id="lightGallery-close" class="close"></a></div></div>').addClass('lightGallery'); 772 $galleryCont = $('#lightGallery-outer'); 773 $gallery = $('#lightGallery-Gallery'); 774 if (settings.showAfterLoad === true) { 775 $gallery.addClass('showAfterLoad'); 776 } 777 778 $slider = $gallery.find('#lightGallery-slider'); 779 var slideList = ''; 780 if (settings.dynamic) { 781 for (var i = 0; i < settings.dynamicEl.length; i++) { 782 slideList += '<div class="lightGallery-slide"></div>'; 783 } 784 } else { 785 $children.each(function () { 786 slideList += '<div class="lightGallery-slide"></div>'; 787 }); 788 } 789 $slider.append(slideList); 790 $slide = $gallery.find('.lightGallery-slide'); 791 }, 792 closeSlide: function () { 793 var $this = this; 794 if (settings.closable) { 795 $('#lightGallery-outer') 796 .on('click', function (event) { 797 if ($(event.target).is('.lightGallery-slide')) { 798 plugin.destroy(false); 799 } 800 }); 801 } 802 $('#lightGallery-close').bind('click touchend', function () { 803 plugin.destroy(false); 804 }); 805 }, 806 getWidth: function () { 807 var resizeWindow = function () { 808 windowWidth = $(window).width(); 809 }; 810 $(window).bind('resize.lightGallery', resizeWindow()); 811 }, 812 doCss: function () { 813 var support = function () { 814 var transition = ['transition', 'MozTransition', 'WebkitTransition', 'OTransition', 'msTransition', 'KhtmlTransition']; 815 var root = document.documentElement; 816 for (var i = 0; i < transition.length; i++) { 817 if (transition[i] in root.style) { 818 return true; 819 } 820 } 821 }; 822 if (settings.useCSS && support()) { 823 return true; 824 } 825 return false; 826 }, 827 enableTouch: function () { 828 var $this = this; 829 if (isTouch) { 830 var startCoords = {}, 831 endCoords = {}; 832 $('body').on('touchstart.lightGallery', function (e) { 833 endCoords = e.originalEvent.targetTouches[0]; 834 startCoords.pageX = e.originalEvent.targetTouches[0].pageX; 835 startCoords.pageY = e.originalEvent.targetTouches[0].pageY; 836 }); 837 $('body').on('touchmove.lightGallery', function (e) { 838 var orig = e.originalEvent; 839 endCoords = orig.targetTouches[0]; 840 e.preventDefault(); 841 }); 842 $('body').on('touchend.lightGallery', function (e) {
843 var distance = endCoords.pageX - startCoords.pageX, 844 swipeThreshold = settings.swipeThreshold; 845 if (distance >= swipeThreshold) { 846 $this.prevSlide(); 847 clearInterval(interval); 848 } else if (distance <= -swipeThreshold) { 849 $this.nextSlide(); 850 clearInterval(interval); 851 } 852 }); 853 } 854 }, 855 touch: function () { 856 var xStart, xEnd; 857 var $this = this; 858 $('.lightGallery').bind('mousedown', function (e) { 859 e.stopPropagation(); 860 e.preventDefault(); 861 xStart = e.pageX; 862 }); 863 $('.lightGallery').bind('mouseup', function (e) { 864 e.stopPropagation(); 865 e.preventDefault(); 866 xEnd = e.pageX; 867 if (xEnd - xStart > 20) { 868 $this.prevSlide(); 869 } else if (xStart - xEnd > 20) { 870 $this.nextSlide(); 871 } 872 }); 873 }, 874 isVideo: function (src, index) { 875 var youtube = src.match(/\/\/(?:www\.)?youtu(?:\.be|be\.com)\/(?:watch\?v=|embed\/)?([a-z0-9_\-]+)/i); 876 var vimeo = src.match(/\/\/(?:www\.)?vimeo.com\/([0-9a-z\-_]+)/i); 877 var iframe = false; 878 if (settings.dynamic) { 879 if (settings.dynamicEl[index].iframe == 'true') { 880 iframe = true; 881 } 882 } else { 883 if ($children.eq(index).attr('data-iframe') == 'true') { 884 iframe = true; 885 } 886 } 887 if (youtube || vimeo || iframe) { 888 return true; 889 } 890 }, 891 loadVideo: function (src, _id) { 892 var youtube = src.match(/\/\/(?:www\.)?youtu(?:\.be|be\.com)\/(?:watch\?v=|embed\/)?([a-z0-9_\-]+)/i); 893 var vimeo = src.match(/\/\/(?:www\.)?vimeo.com\/([0-9a-z\-_]+)/i); 894 var video = ''; 895 var a = ''; 896 if (youtube) { 897 if (settings.videoAutoplay === true && lightGalleryOn === false) { 898 a = '?autoplay=1&rel=0&wmode=opaque'; 899 } else { 900 a = '?wmode=opaque'; 901 } 902 video = '<iframe class="object" width="560" height="315" src="//www.youtube.com/embed/' + youtube[1] + a + '" frameborder="0" allowfullscreen></iframe>'; 903 } else if (vimeo) { 904 if (settings.videoAutoplay === true && lightGalleryOn === false) { 905 a = 'autoplay=1&'; 906 } else { 907 a = ''; 908 } 909 video = '<iframe class="object" id="video' + _id + '" width="560" height="315" src="//player.vimeo.com/video/' + vimeo[1] + '?' + a + 'byline=0&portrait=0&color=' + settings.vimeoColor + '" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>'; 910 } else { 911 video = '<iframe class="object" frameborder="0" src="' + src + '" allowfullscreen="true"></iframe>'; 912 } 913 return '<div class="video_cont" style="max-width:' + settings.videoMaxWidth + ' !important;"><div class="video">' + video + '</div></div>'; 914 }, 915 addHtml: function (index) { 916 var dataSubHtml = null; 917 if (settings.dynamic) { 918 dataSubHtml = settings.dynamicEl[index]['sub-html']; 919 } else { 920 dataSubHtml = $children.eq(index).attr('data-sub-html'); 921 } 922 if (typeof dataSubHtml !== 'undefined' && dataSubHtml !== null) { 923 var fL = dataSubHtml.substring(0, 1); 924 if (fL == '.' || fL == '#') { 925 dataSubHtml = $(dataSubHtml).html(); 926 } else { 927 dataSubHtml = dataSubHtml; 928 } 929 $slide.eq(index).append(dataSubHtml); 930 } 931 }, 932 preload: function (index) { 933 var newIndex = index; 934 for (var k = 0; k <= settings.preload; k++) { 935 if (k >= $children.length - index) { 936 break; 937 } 938 this.loadContent(newIndex + k, true); 939 } 940 for (var h = 0; h <= settings.preload; h++) { 941 if (newIndex - h < 0) { 942 break; 943 } 944 this.loadContent(newIndex - h, true); 945 } 946 }, 947 loadObj: function (r, index) { 948 var $this = this; 949 $slide.eq(index).find('.object').on('load error', function () { 950 $slide.eq(index).addClass('complete'); 951 }); 952 if (r === false) { 953 if (!$slide.eq(index).hasClass('complete')) { 954 $slide.eq(index).find('.object').on('load error', function () { 955 $this.preload(index); 956 }); 957 } else { 958 $this.preload(index); 959 } 960 } 961 }, 962 loadContent: function (index, rec) { 963 var $this = this; 964 var i, j, l = $children.length - index; 965 var src; 966 967 if (settings.preload > $children.length) { 968 settings.preload = $children.length; 969 } 970 if (settings.mobileSrc === true && windowWidth <= settings.mobileSrcMaxWidth) { 971 if (settings.dynamic) { 972 src = settings.dynamicEl[index].mobileSrc; 973 } else { 974 src = $children.eq(index).attr('data-responsive-src'); 975 } 976 } else { 977 if (settings.dynamic) { 978 src = settings.dynamicEl[index].src; 979 } else { 980 src = $children.eq(index).attr('data-src'); 981 } 982 } 983 var time = 0; 984 if (rec === true) { 985 time = settings.speed + 400; 986 } 987 988 989 990 991 if (typeof src !== 'undefined' && src !== '') { 992 if (!$this.isVideo(src, index)) { 993 setTimeout(function () { 994 if (!$slide.eq(index).hasClass('loaded')) { 995 $slide.eq(index).prepend('<img class="object" src="' + src + '" />'); 996 $this.addHtml(index); 997 $slide.eq(index).addClass('loaded'); 998 } 999 $this.loadObj(rec, index); 1000 }, time); 1001 } else { 1002 setTimeout(function () { 1003 if (!$slide.eq(index).hasClass('loaded')) { 1004 $slide.eq(index).prepend($this.loadVideo(src, index)); 1005 $this.addHtml(index); 1006 $slide.eq(index).addClass('loaded'); 1007 1008 if (settings.auto && settings.videoAutoplay === true) { 1009 clearInterval(interval); 1010 } 1011 } 1012 $this.loadObj(rec, index); 1013 }, time); 1014 1015 } 1016 } else { 1017 setTimeout(function () { 1018 if (!$slide.eq(index).hasClass('loaded')) { 1019 var dataHtml = null; 1020 if (settings.dynamic) { 1021 dataHtml = settings.dynamicEl[index].html; 1022 } else { 1023 dataHtml = $children.eq(index).attr('data-html'); 1024 } 1025 if (typeof dataHtml !== 'undefined' && dataHtml !== null) { 1026 var fL = dataHtml.substring(0, 1); 1027 if (fL == '.' || fL == '#') { 1028 dataHtml = $(dataHtml).html(); 1029 } else { 1030 dataHtml = dataHtml; 1031 } 1032 } 1033 if (typeof dataHtml !== 'undefined' && dataHtml !== null) { 1034 $slide.eq(index).append('<div class="video_cont" style="max-width:' + settings.videoMaxWidth + ' !important;"><div class="video">' + dataHtml + '</div></div>'); 1035 } 1036 $this.addHtml(index); 1037 $slide.eq(index).addClass('loaded complete'); 1038 1039 if (settings.auto && settings.videoAutoplay === true) { 1040 clearInterval(interval); 1041 } 1042 } 1043 $this.loadObj(rec, index); 1044 }, time); 1045 } 1046 1047 }, 1048 counter: function () { 1049 if (settings.counter === true) { 1050 var slideCount = $("#lightGallery-slider > div").length; 1051 $gallery.append("<div id='lightGallery_counter'><span id='lightGallery_counter_current'></span> / <span id='lightGallery_counter_all'>" + slideCount + "</span></div>"); 1052 } 1053 }, 1054 buildThumbnail: function () { 1055 if (settings.thumbnail === true && $children.length > 1) { 1056 var $this = this, 1057 $close = '<span class="close ib"><i class="bUi-iCn-rMv-16" aria-hidden="true"></i></span>'; 1058 if (settings.showThumbByDefault) { 1059 $close = '<span class="close ib"><i class="bUi-iCn-rMv-16" aria-hidden="true"></i></span>'; 1060 } 1061 $gallery.append('<div class="thumb_cont"><div class="thumb_info">' + $close + '</div><div class="thumb_inner"></div></div>'); 1062 $thumb_cont = $gallery.find('.thumb_cont'); 1063 $prev.after('<a class="cLthumb"></a>'); 1064 $prev.parent().addClass('hasThumb'); 1065 $gallery.find('.cLthumb').bind('click touchend', function () { 1066 $gallery.addClass('open'); 1067 if ($this.doCss() && settings.mode === 'slide') { 1068 $slide.eq(index).prevAll().removeClass('nextSlide').addClass('prevSlide'); 1069 $slide.eq(index).nextAll().removeClass('prevSlide').addClass('nextSlide'); 1070 } 1071 }); 1072 $gallery.find('.thumb_cont .close').bind('click touchend', function () { 1073 $gallery.removeClass('open'); 1074 }); 1075 var thumbInfo = $gallery.find('.thumb_info'); 1076 var $thumb_inner = $gallery.find('.thumb_inner'); 1077 var thumbList = ''; 1078 var thumbImg; 1079 if (settings.dynamic) { 1080 for (var i = 0; i < settings.dynamicEl.length; i++) { 1081 thumbImg = settings.dynamicEl[i].thumb; 1082 thumbList += '<div class="thumb"><img src="' + thumbImg + '" /></div>'; 1083 } 1084 } else { 1085 $children.each(function () { 1086 if (settings.exThumbImage === false || typeof $(this).attr(settings.exThumbImage) == 'undefined' || $(this).attr(settings.exThumbImage) === null) { 1087 thumbImg = $(this).find('img').attr('src'); 1088 } else { 1089 thumbImg = $(this).attr(settings.exThumbImage); 1090 } 1091 thumbList += '<div class="thumb"><img src="' + thumbImg + '" /></div>'; 1092 }); 1093 } 1094 $thumb_inner.append(thumbList); 1095 $thumb = $thumb_inner.find('.thumb'); 1096 $thumb.css({ 1097 'margin-right': settings.thumbMargin + 'px', 1098 'width': settings.thumbWidth + 'px' 1099 }); 1100 if (settings.animateThumb === true) { 1101 var width = ($children.length * (settings.thumbWidth + settings.thumbMargin)); 1102 $gallery.find('.thumb_inner').css({ 1103 'width': width + 'px', 1104 'position': 'relative', 1105 'transition-duration': settings.speed + 'ms' 1106 }); 1107 } 1108 $thumb.bind('click touchend', function () { 1109 usingThumb = true; 1110 var index = $(this).index(); 1111 $thumb.removeClass('active'); 1112 $(this).addClass('active'); 1113 $this.slide(index); 1114 $this.animateThumb(index); 1115 clearInterval(interval); 1116 }); 1117 thumbInfo.prepend('<span class="ib count">' + settings.lang.allPhotos + ' (' + $thumb.length + ')</span>'); 1118 if (settings.showThumbByDefault) { 1119 $gallery.addClass('open'); 1120 } 1121 } 1122 }, 1123 animateThumb: function (index) { 1124 if (settings.animateThumb === true) { 1125 var thumb_contW = $gallery.find('.thumb_cont').width(); 1126 var position; 1127 switch (settings.currentPagerPosition) { 1128 case 'left': 1129 position = 0; 1130 break; 1131 case 'middle': 1132 position = (thumb_contW / 2) - (settings.thumbWidth / 2); 1133 break; 1134 case 'right': 1135 position = thumb_contW - settings.thumbWidth; 1136 } 1137 var left = ((settings.thumbWidth + settings.thumbMargin) * index - 1) - position; 1138 var width = ($children.length * (settings.thumbWidth + settings.thumbMargin)); 1139 if (left > (width - thumb_contW)) { 1140 left = width - thumb_contW; 1141 } 1142 if (left < 0) { 1143 left = 0; 1144 } 1145 if (this.doCss()) { 1146 $gallery.find('.thumb_inner').css('transform', 'translate3d(-' + left + 'px, 0px, 0px)'); 1147 } else { 1148 $gallery.find('.thumb_inner').animate({ 1149 left: -left + "px" 1150 }, settings.speed); 1151 } 1152 } 1153 }, 1154 slideTo: function () { 1155 var $this = this; 1156 if (settings.controls === true && $children.length > 1) { 1157 $gallery.append('<div id="lightGallery-action"><a id="lightGallery-prev"></a><a id="lightGallery-next"></a></div>'); 1158 $prev = $gallery.find('#lightGallery-prev'); 1159 $next = $gallery.find('#lightGallery-next'); 1160 $prev.bind('click', function () { 1161 $this.prevSlide(); 1162
1162 clearInterval(interval); 1163 }); 1164 $next.bind('click', function () { 1165 $this.nextSlide(); 1166 clearInterval(interval); 1167 }); 1168 } 1169 }, 1170 autoStart: function () { 1171 var $this = this; 1172 if (settings.auto === true) { 1173 interval = setInterval(function () { 1174 if (index + 1 < $children.length) { 1175 index = index; 1176 } else { 1177 index = -1; 1178 } 1179 index++; 1180 $this.slide(index); 1181 }, settings.pause); 1182 } 1183 }, 1184 keyPress: function () { 1185 var $this = this; 1186 $(window).bind('keyup.lightGallery', function (e) { 1187 e.preventDefault(); 1188 e.stopPropagation(); 1189 if (e.keyCode === 37) { 1190 $this.prevSlide(); 1191 clearInterval(interval); 1192 } 1193 if (e.keyCode === 38 && settings.thumbnail === true && $children.length > 1) { 1194 if (!$gallery.hasClass('open')) { 1195 if ($this.doCss() && settings.mode === 'slide') { 1196 $slide.eq(index).prevAll().removeClass('nextSlide').addClass('prevSlide'); 1197 $slide.eq(index).nextAll().removeClass('prevSlide').addClass('nextSlide'); 1198 } 1199 $gallery.addClass('open'); 1200 } 1201 } else if (e.keyCode === 39) { 1202 $this.nextSlide(); 1203 clearInterval(interval); 1204 } 1205 if (e.keyCode === 40 && settings.thumbnail === true && $children.length > 1 && !settings.showThumbByDefault) { 1206 if ($gallery.hasClass('open')) { 1207 $gallery.removeClass('open'); 1208 } 1209 } else if (settings.escKey === true && e.keyCode === 27) { 1210 if (!settings.showThumbByDefault && $gallery.hasClass('open')) { 1211 $gallery.removeClass('open'); 1212 } else { 1213 plugin.destroy(false); 1214 } 1215 } 1216 }); 1217 }, 1218 nextSlide: function () { 1219 var $this = this; 1220 index = $slide.index($slide.eq(prevIndex)); 1221 if (index + 1 < $children.length) { 1222 index++; 1223 $this.slide(index); 1224 } else { 1225 if (settings.loop) { 1226 index = 0; 1227 $this.slide(index); 1228 } else if (settings.thumbnail === true && $children.length > 1 && !settings.showThumbByDefault) { 1229 $gallery.addClass('open'); 1230 } else { 1231 $slide.eq(index).find('.object').addClass('rightEnd'); 1232 setTimeout(function () { 1233 $slide.find('.object').removeClass('rightEnd'); 1234 }, 400); 1235 } 1236 } 1237 $this.animateThumb(index); 1238 settings.onSlideNext.call(this, plugin); 1239 }, 1240 prevSlide: function () { 1241 var $this = this; 1242 index = $slide.index($slide.eq(prevIndex)); 1243 if (index > 0) { 1244 index--; 1245 $this.slide(index); 1246 } else { 1247 if (settings.loop) { 1248 index = $children.length - 1; 1249 $this.slide(index); 1250 } else if (settings.thumbnail === true && $children.length >
1250 1 && !settings.showThumbByDefault) { 1251 $gallery.addClass('open'); 1252 } else { 1253 $slide.eq(index).find('.object').addClass('leftEnd'); 1254 setTimeout(function () { 1255 $slide.find('.object').removeClass('leftEnd'); 1256 }, 400); 1257 } 1258 } 1259 $this.animateThumb(index); 1260 settings.onSlidePrev.call(this, plugin); 1261 }, 1262 slide: function (index) { 1263 var $this = this; 1264 if (lightGalleryOn) { 1265 setTimeout(function () { 1266 $this.loadContent(index, false); 1267 }, settings.speed + 400); 1268 if (!$slider.hasClass('on')) { 1269 $slider.addClass('on'); 1270 } 1271 if (this.doCss() && settings.speed !== '') { 1272 if (!$slider.hasClass('speed')) { 1273 $slider.addClass('speed'); 1274 } 1275 if (aSpeed === false) { 1276 $slider.css('transition-duration', settings.speed + 'ms'); 1277 aSpeed = true; 1278 } 1279 } 1280 if (this.doCss() && settings.cssEasing !== '') { 1281 if (!$slider.hasClass('timing')) { 1282 $slider.addClass('timing'); 1283 } 1284 if (aTiming === false) { 1285 $slider.css('transition-timing-function', settings.cssEasing); 1286 aTiming = true; 1287 } 1288 } 1289 settings.onSlideBefore.call(this, plugin); 1290 } else { 1291 $this.loadContent(index, false); 1292 } 1293 if (settings.mode === 'slide') { 1294 var isiPad = navigator.userAgent.match(/iPad/i) !== null; 1295 if (this.doCss() && !$slider.hasClass('slide') && !isiPad) { 1296 $slider.addClass('slide'); 1297 } else if (this.doCss() && !$slider.hasClass('useLeft') && isiPad) { 1298 $slider.addClass('useLeft'); 1299 } 1300 /* if(this.doCss()){ 1301 $slider.css({ 'transform' : 'translate3d('+(-index*100)+'%, 0px, 0px)' }); 1302 }*/ 1303 if (!this.doCss() && !lightGalleryOn) { 1304 $slider.css({ 1305 left: (-index * 100) + '%' 1306 }); 1307 //$slide.eq(index).css('transition','none'); 1308 } else if (!this.doCss() && lightGalleryOn) { 1309 $slider.animate({ 1310 left: (-index * 100) + '%' 1311 }, settings.speed, settings.easing); 1312 } 1313 } else if (settings.mode === 'fade') { 1314 if (this.doCss() && !$slider.hasClass('fadeM')) { 1315 $slider.addClass('fadeM'); 1316 } else if (!this.doCss() && !$slider.hasClass('animate')) { 1317 $slider.addClass('animate'); 1318 } 1319 if (!this.doCss() && !lightGalleryOn) { 1320 $slide.fadeOut(100); 1321 $slide.eq(index).fadeIn(100); 1322 } else if (!this.doCss() && lightGalleryOn) { 1323 $slide.eq(prevIndex).fadeOut(settings.speed, settings.easing); 1324 $slide.eq(index).fadeIn(settings.speed, settings.easing); 1325 } 1326 } 1327 if (index + 1 >= $children.length && settings.auto && settings.loop === false) { 1328 clearInterval(interval); 1329 } 1330 $slide.eq(prevIndex).removeClass('current'); 1331 $slide.eq(index).addClass('current'); 1332 if (this.doCss() && settings.mode === 'slide') { 1333 if (usingThumb === false) { 1334 $('.prevSlide').removeClass('prevSlide'); 1335 $('.nextSlide').removeClass('nextSlide'); 1336 $slide.eq(index - 1).addClass('prevSlide'); 1337 $slide.eq(index + 1).addClass('nextSlide'); 1338 } else { 1339 $slide.eq(index).prevAll().removeClass('nextSlide').addClass('prevSlide'); 1340 $slide.eq(index).nextAll().removeClass('prevSlide').addClass('nextSlide'); 1341 } 1342 } 1343 if (settings.thumbnail === true && $children.length > 1) { 1344 $thumb.removeClass('active'); 1345 $thumb.eq(index).addClass('active'); 1346 } 1347 if (settings.controls && settings.hideControlOnEnd && settings.loop === false && $children.length > 1) { 1348 var l = $children.length; 1349 l = parseInt(l) - 1; 1350 if (index === 0) { 1351 $prev.addClass('disabled'); 1352 $next.removeClass('disabled'); 1353 } else if (index === l) { 1354 $prev.removeClass('disabled'); 1355 $next.addClass('disabled'); 1356 } else { 1357 $prev.add($next).removeClass('disabled'); 1358 } 1359 } 1360 prevIndex = index; 1361 lightGalleryOn === false ? settings.onOpen.call(this, plugin) : settings.onSlideAfter.call(this, plugin); 1362 setTimeout(function () { 1363 lightGalleryOn = true; 1364 }); 1365 usingThumb = false; 1366 if (settings.counter) { 1367 $("#lightGallery_counter_current").text(index + 1); 1368 } 1369 $(window).bind('resize.lightGallery', function () { 1370 setTimeout(function () { 1371 $this.animateThumb(index); 1372 }, 200); 1373 }); 1374 } 1375 }; 1376 plugin.isActive = function () { 1377 if (isActive === true) { 1378 return true; 1379 } else { 1380 return false; 1381 } 1382 1383 }; 1384 plugin.destroy = function (d) { 1385 isActive = false; 1386 d = typeof d !== 'undefined' ? false : true; 1387 settings.onBeforeClose.call(this, plugin); 1388 var lightGalleryOnT = lightGalleryOn; 1389 lightGalleryOn = false; 1390 aTiming = false; 1391 aSpeed = false; 1392 usingThumb = false; 1393 clearInterval(interval); 1394 if (d === true) { 1395 $children.off('click touch touchstart'); 1396 } 1397 $('.lightGallery').off('mousedown mouseup'); 1398 $('body').off('touchstart.lightGallery touchmove.lightGallery touchend.lightGallery'); 1399 $(window).off('resize.lightGallery keyup.lightGallery'); 1400 if (lightGalleryOnT === true) { 1401 $gallery.addClass('fadeM'); 1402 setTimeout(function () { 1403 $galleryCont.remove(); 1404 $('body').removeClass('lightGallery'); 1405 }, 500); 1406 } 1407 settings.onCloseAfter.call(this, plugin); 1408 }; 1409 lightGallery.init(); 1410 return this; 1411 }; 1412}(jQuery)); 1413window.eviivo = window.eviivo ? window.eviivo : {}; 1414window.eviivo.mapView = window.eviivo.mapView ? window.eviivo.mapView : {}; 1415 1416window.eviivo.mapView = function ($) { 1417 1418 var defaultOptions = { 1419 googleMapsApiUrl: null, 1420 lat: null, 1421 lng: null, 1422 resources: { 1423 switchToMap: "Switch to Map View", 1424 switchToImage: "Switch back to Image View" 1425 }, 1426 showMapByDefault: true 1427 }; 1428 var options; 1429 1430 var isMapScriptLoaded = false; 1431 var $mapContainer; 1432 var $mapView; 1433 var map; 1434 1435 ///<summary>Main init method of the eviivo.mapView object</summary> 1436 ///<param name="settings">external settings</param> 1437 function init(settings) { 1438 //extend internal settings with external ones 1439 options = $.extend(defaultOptions, settings); 1440 $mapContainer = $("#map-canvas"); 1441 $mapView = $("#view-address"); 1442 1443 if (options.showMapByDefault) { 1444 showMap(); 1445 } else { 1446 $mapView.on("click", function() { 1447 eviivo.popup.display({ 1448 html: "<div id='map-canvas'></div>", 1449 bodyClass: "dialogue-map" 1450 }); 1451 1452 $mapContainer = $(".mod-dialogueBox > #map-canvas"); 1453 1454 showMap(); 1455 }); 1456 } 1457 } 1458 1459 ///<summary>
1459Callback method from google maps once the google maps api script is loaded</summary> 1460 function loadMap() { 1461 if (typeof (google.maps.Map) === "function") { 1462 //only add geo location if we have coordinates 1463 if (options.lat != null && options.lng != null) { 1464 var position = new google.maps.LatLng(options.lat, options.lng); 1465 var mapOptions = { 1466 zoom: 13, 1467 center: position, 1468 disableDoubleClickZoom: true, 1469 scrollwheel: false, 1470 mapTypeControl: false, 1471 panControl: false 1472 //zoomControl: false, 1473 //streetViewControl: false 1474 }; 1475 1476 map = new google.maps.Map($mapContainer[0], mapOptions); 1477 var pin = new google.maps.Marker({ position: position, clickable: false, map: map, animation: google.maps.Animation.DROP }); 1478 google.maps.event.trigger(map, 'resize'); 1479 google.maps.event.addDomListener(window, 'resize', resizeGoogleMap); 1480 1481 } else { 1482 console.log('eviivo warning: no location data.'); 1483 } 1484 } 1485 } 1486 1487 ///<summary>Loading the google maps script</summary> 1488 function loadMapScript() { 1489 if (options.googleMapsApiUrl != "" && options.googleMapsApiUrl != null) { 1490 //use script tag appender (much safer and much simpler) 1491 $(document.body).append($("<script src=\"" + options.googleMapsApiUrl + "window.eviivo.mapView.loadMap\" type=\"text/javascript\" />")); 1492 isMapScriptLoaded = true; 1493 } 1494 } 1495 1496 function resizeGoogleMap(){ 1497 var center = map.getCenter(); 1498 google.maps.event.trigger(map, 'resize'); 1499 map.setCenter(center); 1500 } 1501 1502 ///<summary>Show map handler</summary> 1503 function showMap() { 1504 1505 //load map and script if not done already; otherwise, just load map 1506 if (!isMapScriptLoaded) { 1507 loadMapScript(); 1508 } else { 1509 loadMap(); 1510 } 1511 } 1512 1513 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 1514 return { 1515 init: init, 1516 loadMap: loadMap 1517 }; 1518}(jQuery); 1519window.eviivo = window.eviivo ? window.eviivo : {}; 1520window.eviivo.stickyContainer = window.eviivo.stickyContainer ? window.eviivo.stickyContainer : {}; 1521 1522window.eviivo.stickyContainer = function ($) { 1523 1524 var defaultOptions = { 1525 resources: { 1526 room: "Room", 1527 found: "found" 1528 } 1529 }; 1530 var options; 1531 1532 var $resultContainer; 1533 var $groupPriceLabel; 1534 var $groupContent; 1535 1536 ///<summary>Main init method of the eviivo.stickyContainer object</summary> 1537 ///<param name="settings">external settings</param> 1538 function init(settings) { 1539 //extend internal settings with external ones 1540 options = $.extend(defaultOptions, settings); 1541 1542 $resultContainer = $("#mod-results"); 1543 $groupPriceLabel = $(".results-price label"); 1544 $groupContent = $(".group-content"); 1545 1546 if ($(".radio-buttons").length > 0) { 1547 showFoundRooms(); 1548 } 1549 } 1550 1551 ///<summary>Shou found rooms</summary> 1552 function showFoundRooms() { 1553 var rooms = $groupContent.length; 1554 //clean all existing room group links 1555 $("div.mod-sticky-inner div.column a").remove(); 1556 //loop through all rooms and autogenerate the group links in the header container 1557 for (var i = 1; i <= rooms; i++) { 1558 var roomsFound = $("#group-" + i + "-content section").length; 1559 $('<a/>', { 1560 id: ("group-" + i), 1561 href: ("#group-room-" + i), 1562 class: "target-scroll selected" 1563 }).html(options.resources.room + " " + i + " <span>(<i>" + roomsFound + "</i> " + options.resources.found + ")</span>").appendTo("div.mod-sticky-inner div.column:first-child"); 1564 } 1565 } 1566 1567 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 1568 return { 1569 init: init 1570 }; 1571}(jQuery); 1572window.eviivo = window.eviivo ? window.eviivo : {}; 1573window.eviivo.smoothScrolling = window.eviivo.smoothScrolling ? window.eviivo.smoothScrolling : {}; 1574 1575window.eviivo.smoothScrolling = function ($) { 1576 1577 var defaultOptions = { 1578 resources: { 1579 } 1580 }; 1581 var options; 1582 var bodyAnimation; 1583 1584 ///<summary>Main init method of the eviivo.smoothScrolling object</summary> 1585 ///<param name="settings">external settings</param> 1586 function init(settings) { 1587 //extend internal settings with external ones 1588 options = $.extend(defaultOptions, settings); 1589 1590 $("body div.main").on("click", "a.target-scroll, #cheapestPriceCta", smoothScrollHandler); 1591 $("body div.main").on("click", "a.target-scroll, #view-address", smoothScrollHandler); 1592 $(document).on("scroll", toggleScrollTopLink); 1593 $("a[href='#top']").on("click", scrollToTop); 1594 } 1595 1596 ///<summary>the "smooth scroll" action handler with header offset</summary> 1597 function smoothScrollHandler(e, callback, obj) { 1598 e.preventDefault(); 1599 if (obj == null) { 1600 obj = this; 1601 } 1602 1603 var hash = obj.hash ? obj.hash : $(obj).data("anchor"); 1604 var $target = $(hash); 1605 1606 if ($target.length > 0) { 1607 var targetOffset = $target.offset().top; 1608 1609 // Get sticky header height dynamically 1610 var headerHeight = $('.sticky-header').outerHeight() || 0; 1611 1612 // Calculate scroll destination, minus header 1613 var delta = $(document).height() - $(window).height(); 1614 var dest = targetOffset - headerHeight; 1615 dest = dest > delta ? delta : dest; 1616 1617 if (callback == null) { 1618 $('html,body').animate({ scrollTop: dest }, 500, 'swing'); 1619 } else { 1620 //go to destination 1621 $('html,body').animate({ scrollTop: dest }, 500, 'swing', callback); 1622 } 1623 1624 // Update the URL hash without jumping 1625 if (obj.hash) { 1626 history.pushState(null, null, hash); 1627 } 1628 } 1629 1630 return false; 1631 } 1632 1633 ///<summary>toggle scroll to top link display</summary> 1634 function toggleScrollTopLink() { 1635 var scroll = $(window).scrollTop(); 1636 if (scroll >= 500) { 1637 $("html").removeClass("not-scrolled"); 1638 } else { 1639 $("html").addClass("not-scrolled"); 1640 } 1641 } 1642 1643 ///<summary>scroll to top handler</summary> 1644 function scrollToTop() { 1645 $("html, body").animate({ scrollTop: 0 }, "slow"); 1646 return false; 1647 } 1648 1649 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 1650 return { 1651 init: init, 1652 smoothScrollHandler: smoothScrollHandler 1653 }; 1654}(jQuery); 1655window.eviivo = window.eviivo ? window.eviivo : {};
1656window.eviivo.bookingFlow = window.eviivo.bookingFlow ? window.eviivo.bookingFlow : {}; 1657 1658window.eviivo.bookingFlow = function ($) { 1659 var defaultOptions = { 1660 }; 1661 var options; 1662 1663 var $bookButtons; 1664 var $submitForm; 1665 var $bookButtonMultiple; 1666 var $radioButtons; 1667 var $radioButtonLabels; 1668 var $radioButtonsInputs; 1669 var $stickyContainer; 1670 var $stickyContainerButton; 1671 var $showPricesButtons; 1672 var $viewportWidth; 1673 1674 /** 1675 * Initialize booking flow module with settings 1676 * Caches DOM elements, attaches event handlers, and sets up radio button controls 1677 * @param {Object} settings - Configuration settings to extend default options 1678 */ 1679 function init(settings) { 1680 //extend internal settings with external ones 1681 options = $.extend(defaultOptions, settings); 1682 1683 $bookButtons = $("#mod-results div.results-price button.cp-button-book"); 1684 $bookButtonMultyRoom = $("div.mod-sticky-inner button.cp-button"); 1685 $bookButtonMultiple = $("#mod-results div.mod-sticky-inner"); 1686 $submitForm = $("#submit-reservation"); 1687 $radioButtons = $(".radio-buttons"); 1688 $radioButtonLabels = $(".radio-buttons label"); 1689 $radioButtonsInputs = $(".radio-buttons input"); 1690 $stickyContainer = $("#mod-results .mod-sticky"); 1691 $stickyContainerButton = $(".mod-sticky-inner button"); 1692 $showPricesButtons = $("#mod-results div.results-price button.cp-button-showprices"); 1693 $viewportWidth = $(window).width(); 1694 1695 $bookButtons.on("click", submitOrder); 1696 $bookButtonMultyRoom.on("click", submitMultiRoomOrder); 1697 1698 // Track rate plan selection from more rates accordion 1699 $(document).on("click", ".cp-button-rates", trackRatePlanSelection); 1700 1701 if ($bookButtonMultiple.length > 0) { 1702 $bookButtonMultiple.on("click", selectMultipleOrder); 1703 $stickyContainerButton.on("click", submitForm); 1704 $radioButtonsInputs.on("change", groupPriceClickHandler); 1705 1706 1707 if ($radioButtons.length > 0) { 1708 $radioButtons.controlgroup(); 1709 $radioButtonLabels.on("click", selectorClickHandler); 1710 $radioButtonLabels.each(function () { 1711 selectorClickHandler(this); 1712 }); 1713 } 1714 $stickyContainer.fadeIn(600); 1715 groupPriceClickHandler(); 1716 } 1717 1718 $showPricesButtons.on("click", showStartDateCalendar); 1719 } 1720 1721 /** 1722 * Handle room selection in group availability results 1723 * Marks selected room item with visual indicator class and removes it from siblings 1724 * @param {Event|Element} e - Click event or DOM element triggering the selection 1725 */ 1726 function selectorClickHandler(e) { 1727 var elem = e != null ? e : this; 1728 var roomItem = $(elem).parents("section.results-item"); 1729 roomItem.siblings().removeClass("results-item-gallery-selected"); 1730 roomItem.addClass("results-item-gallery-selected"); 1731 } 1732 1733 /** 1734 * Validate multiple room selections and enable/disable form submission 1735 * Checks if all product fields have values and updates submit button state accordingly 1736 */ 1737 function groupPriceClickHandler() { 1738 selectMultipleOrder(); 1739 1740 var allProducts = $submitForm.find("input[name^='product']"); 1741 var selectedProducts = $submitForm.find("input[name^='product'][value]"); 1742 if (allProducts.length == selectedProducts.length) { 1743 $stickyContainerButton.removeAttr("disabled"); 1744 } else { 1745 $stickyContainerButton.attr("disabled", "disabled"); 1746 } 1747 } 1748 1749 1750 /** 1751 * Send Google Analytics tracking event when user clicks book 1752 * Tracks both primary and property-specific GA trackers if available 1753 */ 1754 function trackPropertyViewBookEvent() { 1755 if (typeof ga != "undefined") { 1756 ga("send", "event", "ViewProperty", "Book"); 1757 if (typeof ga.getByName == "function") { 1758 var propertyTracker = ga.getByName('propertyTracker'); 1759 if (typeof propertyTracker != "undefined") { 1760 ga("propertyTracker.send", "event", "ViewProperty", "Book"); 1761 } 1762 } 1763 1764 } 1765 } 1766 1767 /** 1768 * Handle single room booking submission with Google Analytics 4 tracking 1769 * Extracts selected product ID, detects iframe context, and submits reservation form 1770 * @this {Element} The clicked book button element 1771 */ 1772 function submitOrder() { 1773 trackPropertyViewBookEvent(); 1774 var isInIframe = (window.location != window.parent.location) ? true : false; 1775 var productId = $(this).parents(".results-price").find("input[type='hidden']"); 1776 1777 if (isInIframe) { 1778 $submitForm.attr("target", "_top"); 1779 } 1780 $submitForm.find("input[name^='product']").val(productId.val());
1781 submitForm(); 1782 } 1783 1784 1785 /** 1786 * Handle multi-room booking submission with Google Analytics 4 tracking 1787 * Detects iframe context and submits entire reservation form with all selected rooms 1788 * @this {Element} The clicked submit button element 1789 */ 1790 function submitMultiRoomOrder() { 1791 trackPropertyViewBookEvent(); 1792 var isInIframe = (window.location != window.parent.location) ? true : false; 1793 if (isInIframe) { 1794 $submitForm.attr("target", "_top"); 1795 } 1796 submitForm(); 1797 } 1798 1799 /** 1800 * Populate form with selected room and occupancy data 1801 * Iterates checked radio buttons and maps product IDs, occupant counts, and occupancy info to form fields 1802 */ 1803 function selectMultipleOrder() { 1804 var selectedRoom = $("#mod-results").find("input[type='radio']:checked"); 1805 selectedRoom.each(function (i, e) { 1806 var index = i + 1; 1807 var productId = $(e).parents(".results-price").find("input[type='hidden']"); 1808 $submitForm.find("input[name^='product-" + index + "']").val(productId.val()); 1809 var adults = $(e).parents(".group-content").find("input[name='adults']"); 1810 $submitForm.find("input[name^='adults-" + index + "']").val(adults.val()); 1811 var children = $(e).parents(".group-content").find("input[name='children']"); 1812 $submitForm.find("input[name^='children-" + index + "']").val(children.val()); 1813 var occupancyId = $(e).parents(".group-content").find("input[name='occupancy-id']"); 1814 $submitForm.find("input[name^='occupancy-id-" + index + "']").val(occupancyId.val()); 1815 }); 1816 } 1817 1818 /** 1819 * Submit the reservation form to the current page URL 1820 * Sets form action to current location and triggers form submission 1821 */ 1822 function submitForm() { 1823 $submitForm.attr("action", window.location.href); 1824 $submitForm.trigger("submit"); 1825 } 1826 1827 /** 1828 * Handle show prices button click and smooth scroll behavior 1829 * Triggers smooth scrolling only on smaller viewports (< 768px, iPad width) 1830 * @param {Event} e - The click event from show prices button 1831 */ 1832 function showStartDateCalendar(e) { 1833 if (!$viewportWidth < 768) { //smaller than ipad 1834 eviivo.smoothScrolling.smoothScrollHandler(e, function () { 1835 //$startDate.datepicker("show"); 1836 }, this); 1837 } 1838 } 1839 1840 /** 1841 * Tracks rate plan selection for Google Analytics 4 ecommerce events 1842 * Fires both 'select_item' and 'add_to_cart' events when user selects a unit rate 1843 * Reads unit data from DOM and property metadata from mod-results container 1844 * 1845 * GA4 Events Fired: 1846 * - select_item: Tracks which unit/rate was selected from the available list 1847 * - add_to_cart: Indicates user has started the booking process 1848 * 1849 * @param {Event} e - Click event from rate plan button (main rates or more rates accordion) 1850 * @returns {void} 1851 */ 1852 function trackRatePlanSelection(e) { 1853 if (!window.eviivo.googleAnalytics || !window.eviivo.googleAnalytics.isGtagAvailable()) { 1854 return; 1855 } 1856 1857 const $button = $(e.currentTarget); 1858 1859 // Try to find data on parent containers (works for both main rates and more rates) 1860 const $dataContainer = $button.closest(".mod-rateItem, .results-price-wrapper"); 1861 1862 // Get property info from the mod-results container 1863 const $modResults = $("#mod-results"); 1864 const propertyName = $modResults.data("property-name") ?? ""; 1865 const currencyCode = $modResults.data("currency-code") ?? ""; 1866 const pointOfSaleName = $modResults.data("point-of-sale-name") ?? ""; 1867 const propertyShortName = $modResults.data("property-shortname") ?? $modResults.data("property-short-name") ?? ""; 1868 const propertyType = $modResults.data("property-type") ?? options?.constants?.accommodation; 1869 const checkInDate = $modResults.data("check-in-date") ?? ""; 1870 const checkOutDate = $modResults.data("check-out-date") ?? ""; 1871 1872 // Build item using centralized Google Analytics 4 helper for consistency 1873 const itemData = window.eviivo.googleAnalytics.buildItemFromDOM($dataContainer, { 1874 propertyName: propertyName, 1875 itemCategory: options?.singularRoomTypeTranslation ?? options?.constants?.unit, 1876 itemCategory2: propertyType, 1877 itemAffiliation: pointOfSaleName, 1878 checkInDate: checkInDate, 1879 checkOutDate: checkOutDate 1880 }); 1881 1882 if (!itemData || Object.entries(itemData).length === 0) { 1883 console.warn('GA4 Tracking: Unable to build item data from DOM'); 1884 return; 1885 } 1886 1887 // Track the selection 1888 const roomListId = options?.constants?.room_list; 1889 const roomListName = options?.constants?.available_rooms; 1890 const selectItemData = { 1891 item_list_id: roomListId, 1892 item_list_name: roomListName, 1893 currency: currencyCode, 1894 value: itemData.price, 1895 items: [itemData] 1896 }; 1897 1898 if (checkInDate?.trim()) { 1899 selectItemData.check_in_date = checkInDate; 1900 } 1901 1902 if (checkOutDate?.trim()) { 1903 selectItemData.check_out_date = checkOutDate; 1904 } 1905 1906 window.eviivo.googleAnalytics.sendEvent('select_item', selectItemData); 1907 1908 // Track the "Add to Cart" (The moment they start the booking) 1909 const addToCartData = { 1910 currency: currencyCode, 1911 value: itemData.price, 1912 items: [itemData] 1913 }; 1914 1915 if (checkInDate?.trim()) { 1916 addToCartData.check_in_date = checkInDate; 1917 } 1918 1919 if (checkOutDate?.trim()) { 1920 addToCartData.check_out_date = checkOutDate; 1921 } 1922 1923 window.eviivo.googleAnalytics.sendEvent('add_to_cart', addToCartData); 1924 } 1925 1926 /** 1927 * Public API - Expose internal methods for external use 1928 * @type {Object} 1929 * @property {Function} init - Initialize booking flow module 1930 */ 1931 return { 1932 init: init 1933 }; 1934}(jQuery); 1935/*! Magnific Popup - v1.1.0 - 2016-02-20
vendor: 59,032 bytes, lines 1936-3690
1936* http://dimsemenov.com/plugins/magnific-popup/ 1937* Copyright (c) 2016 Dmitry Semenov; */ 1938; (function (factory) { 1939 if (typeof define === 'function' && define.amd) { 1940 // AMD. Register as an anonymous module. 1941 define(['jquery'], factory); 1942 } else if (typeof exports === 'object') { 1943 // Node/CommonJS 1944 factory(require('jquery')); 1945 } else { 1946 // Browser globals 1947 factory(window.jQuery || window.Zepto); 1948 } 1949}(function ($) { 1950 1951 /*>>core*/ 1952 /** 1953 * 1954 * Magnific Popup Core JS file 1955 * 1956 */ 1957 1958 1959 /** 1960 * Private static constants 1961 */ 1962 var CLOSE_EVENT = 'Close', 1963 BEFORE_CLOSE_EVENT = 'BeforeClose', 1964 AFTER_CLOSE_EVENT = 'AfterClose', 1965 BEFORE_APPEND_EVENT = 'BeforeAppend', 1966 MARKUP_PARSE_EVENT = 'MarkupParse', 1967 OPEN_EVENT = 'Open', 1968 CHANGE_EVENT = 'Change', 1969 NS = 'mfp', 1970 EVENT_NS = '.' + NS, 1971 READY_CLASS = 'mfp-ready', 1972 REMOVING_CLASS = 'mfp-removing', 1973 PREVENT_CLOSE_CLASS = 'mfp-prevent-close'; 1974 1975 1976 /** 1977 * Private vars 1978 */ 1979 /*jshint -W079 */ 1980 var mfp, // As we have only one instance of MagnificPopup object, we define it locally to not to use 'this' 1981 MagnificPopup = function () { }, 1982 _isJQ = !!(window.jQuery), 1983 _prevStatus, 1984 _window = $(window), 1985 _document, 1986 _prevContentType, 1987 _wrapClasses, 1988 _currPopupType; 1989 1990 1991 /** 1992 * Private functions 1993 */ 1994 var _mfpOn = function (name, f) { 1995 mfp.ev.on(NS + name + EVENT_NS, f); 1996 }, 1997 _getEl = function (className, appendTo, html, raw) { 1998 var el = document.createElement('div'); 1999 el.className = 'mfp-' + className; 2000 if (html) { 2001 el.innerHTML = html; 2002 } 2003 if (!raw) { 2004 el = $(el); 2005 if (appendTo) { 2006 el.appendTo(appendTo); 2007 } 2008 } else if (appendTo) { 2009 appendTo.appendChild(el); 2010 } 2011 return el; 2012 }, 2013 _mfpTrigger = function (e, data) { 2014 mfp.ev.triggerHandler(NS + e, data); 2015 2016 if (mfp.st.callbacks) { 2017 // converts "mfpEventName" to "eventName" callback and triggers it if it's present 2018 e = e.charAt(0).toLowerCase() + e.slice(1); 2019 if (mfp.st.callbacks[e]) { 2020 mfp.st.callbacks[e].apply(mfp, $.isArray(data) ? data : [data]); 2021 } 2022 } 2023 }, 2024 _getCloseBtn = function (type) { 2025 if (type !== _currPopupType || !mfp.currTemplate.closeBtn) { 2026 mfp.currTemplate.closeBtn = $(mfp.st.closeMarkup.replace('%title%', mfp.st.tClose)); 2027 _currPopupType = type; 2028 } 2029 return mfp.currTemplate.closeBtn; 2030 }, 2031 // Initialize Magnific Popup only when called at least once 2032 _checkInstance = function () { 2033 if (!$.magnificPopup.instance) { 2034 /*jshint -W020 */ 2035 mfp = new MagnificPopup(); 2036 mfp.init(); 2037 $.magnificPopup.instance = mfp; 2038 } 2039 }, 2040 // CSS transition detection, http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr 2041 supportsTransitions = function () { 2042 var s = document.createElement('p').style, // 's' for style. better to create an element if body yet to exist 2043 v = ['ms', 'O', 'Moz', 'Webkit']; // 'v' for vendor 2044 2045 if (s['transition'] !== undefined) { 2046 return true; 2047 } 2048 2049 while (v.length) { 2050 if (v.pop() + 'Transition' in s) { 2051 return true; 2052 } 2053 } 2054 2055 return false; 2056 }; 2057 2058 2059 2060 /** 2061 * Public functions 2062 */ 2063 MagnificPopup.prototype = { 2064 2065 constructor: MagnificPopup, 2066 2067 /** 2068 * Initializes Magnific Popup plugin. 2069 * This function is triggered only once when $.fn.magnificPopup or $.magnificPopup is executed 2070 */ 2071 init: function () { 2072 var appVersion = navigator.appVersion; 2073 mfp.isLowIE = mfp.isIE8 = document.all && !document.addEventListener; 2074 mfp.isAndroid = (/android/gi).test(appVersion); 2075 mfp.isIOS = (/iphone|ipad|ipod/gi).test(appVersion); 2076 mfp.supportsTransition = supportsTransitions(); 2077 2078 // We disable fixed positioned lightbox on devices that don't handle it nicely. 2079 // If you know a better way of detecting this - let me know. 2080 mfp.probablyMobile = (mfp.isAndroid || mfp.isIOS || /(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent)); 2081 _document = $(document); 2082 2083 mfp.popupsCache = {}; 2084 }, 2085 2086 /** 2087 * Opens popup 2088 * @param data [description] 2089 */ 2090 open: function (data) { 2091 2092 var i; 2093 2094 if (data.isObj === false) { 2095 // convert jQuery collection to array to avoid conflicts later 2096 mfp.items = data.items.toArray(); 2097 2098 mfp.index = 0; 2099 var items = data.items, 2100 item; 2101 for (i = 0; i < items.length; i++) { 2102 item = items[i]; 2103 if (item.parsed) { 2104 item = item.el[0]; 2105 } 2106 if (item === data.el[0]) { 2107 mfp.index = i; 2108 break; 2109 } 2110 } 2111 } else { 2112 mfp.items = $.isArray(data.items) ? data.items : [data.items]; 2113 mfp.index = data.index || 0; 2114 } 2115 2116 // if popup is already opened - we just update the content 2117 if (mfp.isOpen) { 2118 mfp.updateItemHTML(); 2119 return; 2120 } 2121 2122 mfp.types = []; 2123 _wrapClasses = ''; 2124 if (data.mainEl && data.mainEl.length) { 2125 mfp.ev = data.mainEl.eq(0); 2126 } else { 2127 mfp.ev = _document; 2128 } 2129 2130 if (data.key) { 2131 if (!mfp.popupsCache[data.key]) { 2132 mfp.popupsCache[data.key] = {}; 2133 } 2134 mfp.currTemplate = mfp.popupsCache[data.key]; 2135 } else { 2136 mfp.currTemplate = {}; 2137 } 2138 2139 2140 2141 mfp.st = $.extend(true, {}, $.magnificPopup.defaults, data); 2142 mfp.fixedContentPos = mfp.st.fixedContentPos === 'auto' ? !mfp.probablyMobile : mfp.st.fixedContentPos; 2143 2144 if (mfp.st.modal) { 2145 mfp.st.closeOnContentClick = false; 2146 mfp.st.closeOnBgClick = false; 2147 mfp.st.showCloseBtn = false; 2148 mfp.st.enableEscapeKey = false; 2149 } 2150 2151 2152 // Building markup 2153 // main containers are created only once 2154 if (!mfp.bgOverlay) { 2155 2156 // Dark overlay 2157 mfp.bgOverlay = _getEl('bg').on('click' + EVENT_NS, function () { 2158 mfp.close(); 2159 }); 2160 2161 mfp.wrap = _getEl('wrap').attr('tabindex', -1).on('click' + EVENT_NS, function (e) { 2162 if (mfp._checkIfClose(e.target)) { 2163 mfp.close(); 2164 } 2165 }); 2166 2167 mfp.container = _getEl('container', mfp.wrap); 2168 } 2169 2170 mfp.contentContainer = _getEl('content'); 2171 if (mfp.st.preloader) { 2172 mfp.preloader = _getEl('preloader', mfp.container, mfp.st.tLoading); 2173 } 2174 2175 2176 // Initializing modules 2177 var modules = $.magnificPopup.modules; 2178 for (i = 0; i < modules.length; i++) { 2179 var n = modules[i]; 2180 n = n.charAt(0).toUpperCase() + n.slice(1); 2181 mfp['init' + n].call(mfp); 2182 } 2183 _mfpTrigger('BeforeOpen'); 2184 2185 2186 if (mfp.st.showCloseBtn) { 2187 // Close button 2188 if (!mfp.st.closeBtnInside) { 2189 mfp.wrap.append(_getCloseBtn()); 2190 } else { 2191 _mfpOn(MARKUP_PARSE_EVENT, function (e, template, values, item) { 2192 values.close_replaceWith = _getCloseBtn(item.type); 2193 }); 2194 _wrapClasses += ' mfp-close-btn-in'; 2195 } 2196 } 2197 2198 if (mfp.st.alignTop) { 2199 _wrapClasses += ' mfp-align-top'; 2200 } 2201 2202 2203 2204 if (mfp.fixedContentPos) { 2205 mfp.wrap.css({ 2206 overflow: mfp.st.overflowY, 2207 overflowX: 'hidden', 2208 overflowY: mfp.st.overflowY 2209 }); 2210 } else { 2211 mfp.wrap.css({ 2212 top: _window.scrollTop(), 2213 position: 'absolute' 2214 }); 2215 } 2216 if (mfp.st.fixedBgPos === false || (mfp.st.fixedBgPos === 'auto' && !mfp.fixedContentPos)) { 2217 mfp.bgOverlay.css({ 2218 height: _document.height(), 2219 position: 'absolute' 2220 }); 2221 } 2222 2223 2224 2225 if (mfp.st.enableEscapeKey) { 2226 // Close on ESC key 2227 _document.on('keyup' + EVENT_NS, function (e) { 2228 if (e.keyCode === 27) { 2229 mfp.close(); 2230 } 2231 }); 2232 } 2233 2234 _window.on('resize' + EVENT_NS, function () { 2235 mfp.updateSize(); 2236 }); 2237 2238 2239 if (!mfp.st.closeOnContentClick) { 2240 _wrapClasses += ' mfp-auto-cursor'; 2241 } 2242 2243 if (_wrapClasses) 2244 mfp.wrap.addClass(_wrapClasses); 2245 2246 2247 // this triggers recalculation of layout, so we get it once to not to trigger twice 2248 var windowHeight = mfp.wH = _window.height(); 2249 2250 2251 var windowStyles = {}; 2252 2253 if (mfp.fixedContentPos) { 2254 if (mfp._hasScrollBar(windowHeight)) { 2255 var s = mfp._getScrollbarSize(); 2256 if (s) { 2257 windowStyles.marginRight = s; 2258 } 2259 } 2260 } 2261 2262 if (mfp.fixedContentPos) { 2263 if (!mfp.isIE7) { 2264 windowStyles.overflow = 'hidden'; 2265 } else { 2266 // ie7 double-scroll bug 2267 $('body, html').css('overflow', 'hidden'); 2268 } 2269 } 2270 2271 2272 2273 var classesToadd = mfp.st.mainClass; 2274 if (mfp.isIE7) { 2275 classesToadd += ' mfp-ie7'; 2276 } 2277 if (classesToadd) { 2278 mfp._addClassToMFP(classesToadd); 2279 } 2280 2281 // add content 2282 mfp.updateItemHTML(); 2283 2284 _mfpTrigger('BuildControls'); 2285 2286 // remove scrollbar, add margin e.t.c 2287 $('html').css(windowStyles); 2288 2289 // add everything to DOM 2290 mfp.bgOverlay.add(mfp.wrap).prependTo(mfp.st.prependTo || $(document.body)); 2291 2292 // Save last focused element 2293 mfp._lastFocusedEl = document.activeElement; 2294 2295 // Wait for next cycle to allow CSS transition 2296 setTimeout(function () { 2297 2298 if (mfp.content) { 2299 mfp._addClassToMFP(READY_CLASS); 2300 mfp._setFocus(); 2301 } else { 2302 // if content is not defined (not loaded e.t.c) we add class only for BG 2303 mfp.bgOverlay.addClass(READY_CLASS); 2304 } 2305 2306 // Trap the focus in popup 2307 _document.on('focusin' + EVENT_NS, mfp._onFocusIn); 2308 2309 }, 16); 2310 2311 mfp.isOpen = true; 2312 mfp.updateSize(windowHeight); 2313 _mfpTrigger(OPEN_EVENT); 2314 2315 return data; 2316 }, 2317 2318 /** 2319 * Closes the popup 2320 */ 2321 close: function () { 2322 if (!mfp.isOpen) return; 2323 _mfpTrigger(BEFORE_CLOSE_EVENT); 2324 2325 mfp.isOpen = false; 2326 // for CSS3 animation 2327 if (mfp.st.removalDelay && !mfp.isLowIE && mfp.supportsTransition) { 2328 mfp._addClassToMFP(REMOVING_CLASS); 2329 setTimeout(function () { 2330 mfp._close(); 2331 }, mfp.st.removalDelay); 2332 } else { 2333 mfp._close(); 2334 } 2335 }, 2336 2337 /** 2338 * Helper for close() function 2339 */ 2340 _close: function () { 2341 _mfpTrigger(CLOSE_EVENT); 2342 2343 var classesToRemove = REMOVING_CLASS + ' ' + READY_CLASS + ' '; 2344 2345 mfp.bgOverlay.detach(); 2346 mfp.wrap.detach(); 2347 mfp.container.empty(); 2348 2349 if (mfp.st.mainClass) { 2350 classesToRemove += mfp.st.mainClass + ' '; 2351 } 2352 2353 mfp._removeClassFromMFP(classesToRemove); 2354 2355 if (mfp.fixedContentPos) { 2356 var windowStyles = { marginRight: '' }; 2357 if (mfp.isIE7) { 2358 $('body, html').css('overflow', ''); 2359 } else { 2360 windowStyles.overflow = ''; 2361 } 2362 $('html').css(windowStyles); 2363 } 2364 2365 _document.off('keyup' + EVENT_NS + ' focusin' + EVENT_NS); 2366 mfp.ev.off(EVENT_NS); 2367 2368 // clean up DOM elements that aren't removed 2369 mfp.wrap.attr('class', 'mfp-wrap').removeAttr('style'); 2370 mfp.bgOverlay.attr('class', 'mfp-bg'); 2371 mfp.container.attr('class', 'mfp-container'); 2372 2373 // remove close button from target element 2374 if (mfp.st.showCloseBtn && 2375 (!mfp.st.closeBtnInside || mfp.currTemplate[mfp.currItem.type] === true)) { 2376 if (mfp.currTemplate.closeBtn) 2377 mfp.currTemplate.closeBtn.detach(); 2378 } 2379 2380 2381 if (mfp.st.autoFocusLast && mfp._lastFocusedEl) { 2382 $(mfp._lastFocusedEl).focus(); // put tab focus back 2383 } 2384 mfp.currItem = null; 2385 mfp.content = null; 2386 mfp.currTemplate = null; 2387 mfp.prevHeight = 0; 2388 2389 _mfpTrigger(AFTER_CLOSE_EVENT); 2390 }, 2391 2392 updateSize: function (winHeight) { 2393 2394 if (mfp.isIOS) { 2395 // fixes iOS nav bars https://github.com/dimsemenov/Magnific-Popup/issues/2 2396 var zoomLevel = document.documentElement.clientWidth / window.innerWidth; 2397 var height = window.innerHeight * zoomLevel; 2398 mfp.wrap.css('height', height); 2399 mfp.wH = height; 2400 } else { 2401 mfp.wH = winHeight || _window.height(); 2402 } 2403 // Fixes #84: popup incorrectly positioned with position:relative on body 2404 if (!mfp.fixedContentPos) { 2405 mfp.wrap.css('height', mfp.wH); 2406 } 2407 2408 _mfpTrigger('Resize'); 2409 2410 }, 2411 2412 /** 2413 * Set content of popup based on current index 2414 */ 2415 updateItemHTML: function () { 2416 var item = mfp.items[mfp.index]; 2417 2418 // Detach and perform modifications 2419 mfp.contentContainer.detach(); 2420 2421 if (mfp.content) 2422 mfp.content.detach(); 2423 2424 if (!item.parsed) { 2425 item = mfp.parseEl(mfp.index); 2426 } 2427 2428 var type = item.type; 2429 2430 _mfpTrigger('BeforeChange', [mfp.currItem ? mfp.currItem.type : '', type]); 2431 // BeforeChange event works like so: 2432 // _mfpOn('BeforeChange', function(e, prevType, newType) { }); 2433 2434 mfp.currItem = item; 2435 2436 if (!mfp.currTemplate[type]) { 2437 var markup = mfp.st[type] ? mfp.st[type].markup : false; 2438 2439 // allows to modify markup 2440 _mfpTrigger('FirstMarkupParse', markup); 2441 2442 if (markup) { 2443 mfp.currTemplate[type] = $(markup); 2444 } else { 2445 // if there is no markup found we just define that template is parsed 2446 mfp.currTemplate[type] = true; 2447 } 2448 } 2449 2450 if (_prevContentType && _prevContentType !== item.type) { 2451 mfp.container.removeClass('mfp-' + _prevContentType + '-holder'); 2452 } 2453 2454 var newContent = mfp['get' + type.charAt(0).toUpperCase() + type.slice(1)](item, mfp.currTemplate[type]); 2455 mfp.appendContent(newContent, type); 2456 2457 item.preloaded = true; 2458 2459 _mfpTrigger(CHANGE_EVENT, item); 2460 _prevContentType = item.type; 2461 2462 // Append container back after its content changed 2463 mfp.container.prepend(mfp.contentContainer); 2464 2465 _mfpTrigger('AfterChange'); 2466 }, 2467 2468 2469 /** 2470 * Set HTML content of popup 2471 */ 2472 appendContent: function (newContent, type) { 2473 mfp.content = newContent; 2474 2475 if (newContent) { 2476 if (mfp.st.showCloseBtn && mfp.st.closeBtnInside && 2477 mfp.currTemplate[type] === true) { 2478 // if there is no markup, we just append close button element inside 2479 if (!mfp.content.find('.mfp-close').length) { 2480 mfp.content.append(_getCloseBtn()); 2481 } 2482 } else { 2483 mfp.content = newContent; 2484 } 2485 } else { 2486 mfp.content = ''; 2487 } 2488 2489 _mfpTrigger(BEFORE_APPEND_EVENT); 2490 mfp.container.addClass('mfp-' + type + '-holder'); 2491 2492 mfp.contentContainer.append(mfp.content); 2493 }, 2494 2495 2496 /** 2497 * Creates Magnific Popup data object based on given data 2498 * @param {int} index Index of item to parse 2499 */ 2500 parseEl: function (index) { 2501 var item = mfp.items[index], 2502 type; 2503 2504 if (item.tagName) { 2505 item = { el: $(item) }; 2506 } else { 2507 type = item.type; 2508 item = { data: item, src: item.src }; 2509 } 2510 2511 if (item.el) { 2512 var types = mfp.types; 2513 2514 // check for 'mfp-TYPE' class 2515 for (var i = 0; i < types.length; i++) { 2516 if (item.el.hasClass('mfp-' + types[i])) { 2517 type = types[i]; 2518 break; 2519 } 2520 } 2521 2522 item.src = item.el.attr('data-mfp-src'); 2523 if (!item.src) { 2524 item.src = item.el.attr('href'); 2525 } 2526 } 2527 2528 item.type = type || mfp.st.type || 'inline'; 2529 item.index = index; 2530 item.parsed = true; 2531 mfp.items[index] = item; 2532 _mfpTrigger('ElementParse', item); 2533 2534 return mfp.items[index]; 2535 }, 2536 2537 2538 /** 2539 * Initializes single popup or a group of popups 2540 */ 2541 addGroup: function (el, options) { 2542 var eHandler = function (e) { 2543 e.mfpEl = this; 2544 mfp._openClick(e, el, options); 2545 }; 2546 2547 if (!options) { 2548 options = {}; 2549 } 2550 2551 var eName = 'click.magnificPopup'; 2552 options.mainEl = el; 2553 2554 if (options.items) { 2555 options.isObj = true; 2556 el.off(eName).on(eName, eHandler); 2557 } else { 2558 options.isObj = false; 2559 if (options.delegate) { 2560 el.off(eName).on(eName, options.delegate, eHandler); 2561 } else { 2562 options.items = el; 2563 el.off(eName).on(eName, eHandler); 2564 } 2565 } 2566 }, 2567 _openClick: function (e, el, options) { 2568 var midClick = options.midClick !== undefined ? options.midClick : $.magnificPopup.defaults.midClick; 2569 2570 2571 if (!midClick && (e.which === 2 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey)) { 2572 return; 2573 } 2574 2575 var disableOn = options.disableOn !== undefined ? options.disableOn : $.magnificPopup.defaults.disableOn; 2576 2577 if (disableOn) { 2578 if ($.isFunction(disableOn)) { 2579 if (!disableOn.call(mfp)) { 2580 return true; 2581 } 2582 } else { // else it's number 2583 if (_window.width() < disableOn) { 2584 return true; 2585 } 2586 } 2587 } 2588 2589 if (e.type) { 2590 e.preventDefault(); 2591 2592 // This will prevent popup from closing if element is inside and popup is already opened 2593 if (mfp.isOpen) { 2594 e.stopPropagation(); 2595 } 2596 } 2597 2598 options.el = $(e.mfpEl); 2599 if (options.delegate) { 2600 options.items = el.find(options.delegate); 2601 } 2602 mfp.open(options); 2603 }, 2604 2605 2606 /** 2607 * Updates text on preloader 2608 */ 2609 updateStatus: function (status, text) { 2610 2611 if (mfp.preloader) { 2612 if (_prevStatus !== status) { 2613 mfp.container.removeClass('mfp-s-' + _prevStatus); 2614 } 2615 2616 if (!text && status === 'loading') { 2617 text = mfp.st.tLoading; 2618 } 2619 2620 var data = { 2621 status: status, 2622 text: text 2623 }; 2624 // allows to modify status 2625 _mfpTrigger('UpdateStatus', data); 2626 2627 status = data.status; 2628 text = data.text; 2629 2630 mfp.preloader.html(text); 2631 2632 mfp.preloader.find('a').on('click', function (e) { 2633 e.stopImmediatePropagation(); 2634 }); 2635 2636 mfp.container.addClass('mfp-s-' + status); 2637 _prevStatus = status; 2638 } 2639 }, 2640 2641 2642 /* 2643 "Private" helpers that aren't private at all 2644 */ 2645 // Check to close popup or not 2646 // "target" is an element that was clicked 2647 _checkIfClose: function (target) { 2648 2649 if ($(target).hasClass(PREVENT_CLOSE_CLASS)) { 2650 return; 2651 } 2652 2653 var closeOnContent = mfp.st.closeOnContentClick; 2654 var closeOnBg = mfp.st.closeOnBgClick; 2655 2656 if (closeOnContent && closeOnBg) { 2657 return true; 2658 } else { 2659 2660 // We close the popup if click is on close button or on preloader. Or if there is no content. 2661 if (!mfp.content || $(target).hasClass('mfp-close') || (mfp.preloader && target === mfp.preloader[0])) { 2662 return true; 2663 } 2664 2665 // if click is outside the content 2666 if ((target !== mfp.content[0] && !$.contains(mfp.content[0], target))) { 2667 if (closeOnBg) { 2668 // last check, if the clicked element is in DOM, (in case it's removed onclick) 2669 if ($.contains(document, target)) { 2670 return true; 2671 } 2672 } 2673 } else if (closeOnContent) { 2674 return true; 2675 } 2676 2677 } 2678 return false; 2679 }, 2680 _addClassToMFP: function (cName) { 2681 mfp.bgOverlay.addClass(cName); 2682 mfp.wrap.addClass(cName); 2683 }, 2684 _removeClassFromMFP: function (cName) { 2685 this.bgOverlay.removeClass(cName); 2686 mfp.wrap.removeClass(cName); 2687 }, 2688 _hasScrollBar: function (winHeight) { 2689 return ((mfp.isIE7 ? _document.height() : document.body.scrollHeight) > (winHeight || _window.height())); 2690 }, 2691 _setFocus: function () { 2692 (mfp.st.focus ? mfp.content.find(mfp.st.focus).eq(0) : mfp.wrap).focus(); 2693 }, 2694 _onFocusIn: function (e) { 2695 if (e.target !== mfp.wrap[0] && !$.contains(mfp.wrap[0], e.target)) { 2696 mfp._setFocus(); 2697 return false; 2698 } 2699 }, 2700 _parseMarkup: function (template, values, item) { 2701 var arr; 2702 if (item.data) { 2703 values = $.extend(item.data, values); 2704 } 2705 _mfpTrigger(MARKUP_PARSE_EVENT, [template, values, item]); 2706 2707 $.each(values, function (key, value) { 2708 if (value === undefined || value === false) { 2709 return true; 2710 } 2711 arr = key.split('_'); 2712 if (arr.length > 1) { 2713 var el = template.find(EVENT_NS + '-' + arr[0]); 2714 2715 if (el.length > 0) { 2716 var attr = arr[1]; 2717 if (attr === 'replaceWith') { 2718 if (el[0] !== value[0]) { 2719 el.replaceWith(value); 2720 } 2721 } else if (attr === 'img') { 2722 if (el.is('img')) { 2723 el.attr('src', value); 2724 } else { 2725 el.replaceWith($('<img>').attr('src', value).attr('class', el.attr('class'))); 2726 } 2727 } else { 2728 el.attr(arr[1], value); 2729 } 2730 } 2731 2732 } else { 2733 template.find(EVENT_NS + '-' + key).html(value); 2734 } 2735 }); 2736 }, 2737 2738 _getScrollbarSize: function () { 2739 // thx David 2740 if (mfp.scrollbarSize === undefined) { 2741 var scrollDiv = document.createElement("div"); 2742 scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;'; 2743 document.body.appendChild(scrollDiv); 2744 mfp.scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth; 2745 document.body.removeChild(scrollDiv); 2746 } 2747 return mfp.scrollbarSize; 2748 } 2749 2750 }; /* MagnificPopup core prototype end */ 2751 2752 2753 2754 2755 /** 2756 * Public static functions 2757 */ 2758 $.magnificPopup = { 2759 instance: null, 2760 proto: MagnificPopup.prototype, 2761 modules: [], 2762 2763 open: function (options, index) { 2764 _checkInstance(); 2765 2766 if (!options) { 2767 options = {}; 2768 } else { 2769 options = $.extend(true, {}, options); 2770 } 2771 2772 options.isObj = true; 2773 options.index = index || 0; 2774 return this.instance.open(options); 2775 }, 2776 2777 close: function () { 2778 return $.magnificPopup.instance && $.magnificPopup.instance.close(); 2779 }, 2780 2781 registerModule: function (name, module) { 2782 if (module.options) { 2783 $.magnificPopup.defaults[name] = module.options; 2784 } 2785 $.extend(this.proto, module.proto); 2786 this.modules.push(name); 2787 }, 2788 2789 defaults: { 2790 2791 // Info about options is in docs: 2792 // http://dimsemenov.com/plugins/magnific-popup/documentation.html#options 2793 2794 disableOn: 0, 2795 2796 key: null, 2797 2798 midClick: false, 2799 2800 mainClass: '', 2801 2802 preloader: true, 2803 2804 focus: '', // CSS selector of input to focus after popup is opened 2805 2806 closeOnContentClick: false, 2807 2808 closeOnBgClick: true, 2809 2810 closeBtnInside: true, 2811 2812 showCloseBtn: true, 2813 2814 enableEscapeKey: true, 2815 2816 modal: false, 2817 2818 alignTop: false, 2819 2820 removalDelay: 0, 2821 2822 prependTo: null, 2823 2824 fixedContentPos: 'auto', 2825 2826 fixedBgPos: 'auto', 2827 2828 overflowY: 'auto', 2829 2830 closeMarkup: '<button title="%title%" type="button" class="mfp-close">×</button>', 2831 2832 tClose: 'Close (Esc)', 2833 2834 tLoading: 'Loading...', 2835 2836 autoFocusLast: true 2837 2838 } 2839 }; 2840 2841 2842 2843 $.fn.magnificPopup = function (options) { 2844 _checkInstance(); 2845 2846 var jqEl = $(this); 2847 2848 // We call some API method of first param is a string 2849 if (typeof options === "string") { 2850 2851 if (options === 'open') { 2852 var items, 2853 itemOpts = _isJQ ? jqEl.data('magnificPopup') : jqEl[0].magnificPopup, 2854 index = parseInt(arguments[1], 10) || 0; 2855 2856 if (itemOpts.items) { 2857 items = itemOpts.items[index]; 2858 } else { 2859 items = jqEl; 2860 if (itemOpts.delegate) { 2861 items = items.find(itemOpts.delegate); 2862 } 2863 items = items.eq(index); 2864 } 2865 mfp._openClick({ mfpEl: items }, jqEl, itemOpts); 2866 } else { 2867 if (mfp.isOpen) 2868 mfp[options].apply(mfp, Array.prototype.slice.call(arguments, 1)); 2869 } 2870 2871 } else { 2872 // clone options obj 2873 options = $.extend(true, {}, options); 2874 2875 /* 2876 * As Zepto doesn't support .data() method for objects 2877 * and it works only in normal browsers 2878 * we assign "options" object directly to the DOM element. FTW! 2879 */ 2880 if (_isJQ) { 2881 jqEl.data('magnificPopup', options); 2882 } else { 2883 jqEl[0].magnificPopup = options; 2884 } 2885 2886 mfp.addGroup(jqEl, options); 2887 2888 } 2889 return jqEl; 2890 }; 2891 2892 /*>>core*/ 2893 2894 /*>>inline*/ 2895 2896 var INLINE_NS = 'inline', 2897 _hiddenClass, 2898 _inlinePlaceholder, 2899 _lastInlineElement, 2900 _putInlineElementsBack = function () { 2901 if (_lastInlineElement) { 2902 _inlinePlaceholder.after(_lastInlineElement.addClass(_hiddenClass)).detach(); 2903 _lastInlineElement = null; 2904 } 2905 }; 2906 2907 $.magnificPopup.registerModule(INLINE_NS, { 2908 options: { 2909 hiddenClass: 'hide', // will be appended with `mfp-` prefix 2910 markup: '', 2911 tNotFound: 'Content not found' 2912 }, 2913 proto: { 2914 2915 initInline: function () { 2916 mfp.types.push(INLINE_NS); 2917 2918 _mfpOn(CLOSE_EVENT + '.' + INLINE_NS, function () { 2919 _putInlineElementsBack(); 2920 }); 2921 }, 2922 2923 getInline: function (item, template) { 2924 2925 _putInlineElementsBack(); 2926 2927 if (item.src) { 2928 var inlineSt = mfp.st.inline, 2929 el = $(item.src); 2930 2931 if (el.length) { 2932 2933 // If target element has parent - we replace it with placeholder and put it back after popup is closed 2934 var parent = el[0].parentNode; 2935 if (parent && parent.tagName) { 2936 if (!_inlinePlaceholder) { 2937 _hiddenClass = inlineSt.hiddenClass; 2938 _inlinePlaceholder = _getEl(_hiddenClass); 2939 _hiddenClass = 'mfp-' + _hiddenClass; 2940 } 2941 // replace target inline element with placeholder 2942 _lastInlineElement = el.after(_inlinePlaceholder).detach().removeClass(_hiddenClass); 2943 } 2944 2945 mfp.updateStatus('ready'); 2946 } else { 2947 mfp.updateStatus('error', inlineSt.tNotFound); 2948 el = $('<div>'); 2949 } 2950 2951 item.inlineElement = el; 2952 return el; 2953 } 2954 2955 mfp.updateStatus('ready'); 2956 mfp._parseMarkup(template, {}, item); 2957 return template; 2958 } 2959 } 2960 }); 2961 2962 /*>>inline*/ 2963 2964 /*>>ajax*/ 2965 var AJAX_NS = 'ajax', 2966 _ajaxCur, 2967 _removeAjaxCursor = function () { 2968 if (_ajaxCur) { 2969 $(document.body).removeClass(_ajaxCur); 2970 } 2971 }, 2972 _destroyAjaxRequest = function () { 2973 _removeAjaxCursor(); 2974 if (mfp.req) { 2975 mfp.req.abort(); 2976 } 2977 }; 2978 2979 $.magnificPopup.registerModule(AJAX_NS, { 2980 2981 options: { 2982 settings: null, 2983 cursor: 'mfp-ajax-cur', 2984 tError: '<a href="%url%">The content</a> could not be loaded.' 2985 }, 2986 2987 proto: { 2988 initAjax: function () { 2989 mfp.types.push(AJAX_NS); 2990 _ajaxCur = mfp.st.ajax.cursor; 2991 2992 _mfpOn(CLOSE_EVENT + '.' + AJAX_NS, _destroyAjaxRequest); 2993 _mfpOn('BeforeChange.' + AJAX_NS, _destroyAjaxRequest); 2994 }, 2995 getAjax: function (item) { 2996 2997 if (_ajaxCur) { 2998 $(document.body).addClass(_ajaxCur); 2999 } 3000 3001 mfp.updateStatus('loading'); 3002 3003 var opts = $.extend({ 3004 url: item.src, 3005 success: function (data, textStatus, jqXHR) { 3006 var temp = { 3007 data: data, 3008 xhr: jqXHR 3009 }; 3010 3011 _mfpTrigger('ParseAjax', temp); 3012 3013 mfp.appendContent($(temp.data), AJAX_NS); 3014 3015 item.finished = true; 3016 3017 _removeAjaxCursor(); 3018 3019 mfp._setFocus(); 3020 3021 setTimeout(function () { 3022 mfp.wrap.addClass(READY_CLASS); 3023 }, 16); 3024 3025 mfp.updateStatus('ready'); 3026 3027 _mfpTrigger('AjaxContentAdded'); 3028 }, 3029 error: function () { 3030 _removeAjaxCursor(); 3031 item.finished = item.loadError = true; 3032 mfp.updateStatus('error', mfp.st.ajax.tError.replace('%url%', item.src)); 3033 } 3034 }, mfp.st.ajax.settings); 3035 3036 mfp.req = $.ajax(opts); 3037 3038 return ''; 3039 } 3040 } 3041 }); 3042 3043 /*>>ajax*/ 3044 3045 /*>>image*/ 3046 var _imgInterval, 3047 _getTitle = function (item) { 3048 if (item.data && item.data.title !== undefined) 3049 return item.data.title; 3050 3051 var src = mfp.st.image.titleSrc; 3052 3053 if (src) { 3054 if ($.isFunction(src)) { 3055 return src.call(mfp, item); 3056 } else if (item.el) { 3057 return item.el.attr(src) || ''; 3058 } 3059 } 3060 return ''; 3061 }; 3062 3063 $.magnificPopup.registerModule('image', { 3064 3065 options: { 3066 markup: '<div class="mfp-figure">' + 3067 '<div class="mfp-close"></div>' + 3068 '<figure>' + 3069 '<div class="mfp-img"></div>' + 3070 '<figcaption>' + 3071 '<div class="mfp-bottom-bar">' + 3072 '<div class="mfp-title"></div>' + 3073 '<div class="mfp-counter"></div>' + 3074 '</div>' + 3075 '</figcaption>' + 3076 '</figure>' + 3077 '</div>', 3078 cursor: 'mfp-zoom-out-cur', 3079 titleSrc: 'title', 3080 verticalFit: true, 3081 tError: '<a href="%url%">The image</a> could not be loaded.' 3082 }, 3083 3084 proto: { 3085 initImage: function () { 3086 var imgSt = mfp.st.image, 3087 ns = '.image'; 3088 3089 mfp.types.push('image'); 3090 3091 _mfpOn(OPEN_EVENT + ns, function () { 3092 if (mfp.currItem.type === 'image' && imgSt.cursor) { 3093 $(document.body).addClass(imgSt.cursor); 3094 } 3095 }); 3096 3097 _mfpOn(CLOSE_EVENT + ns, function () { 3098 if (imgSt.cursor) { 3099 $(document.body).removeClass(imgSt.cursor); 3100 } 3101 _window.off('resize' + EVENT_NS); 3102 }); 3103 3104 _mfpOn('Resize' + ns, mfp.resizeImage); 3105 if (mfp.isLowIE) { 3106 _mfpOn('AfterChange', mfp.resizeImage); 3107 } 3108 }, 3109 resizeImage: function () { 3110 var item = mfp.currItem; 3111 if (!item || !item.img) return; 3112 3113 if (mfp.st.image.verticalFit) { 3114 var decr = 0; 3115 // fix box-sizing in ie7/8 3116 if (mfp.isLowIE) { 3117 decr = parseInt(item.img.css('padding-top'), 10) + parseInt(item.img.css('padding-bottom'), 10); 3118 } 3119 item.img.css('max-height', mfp.wH - decr); 3120 } 3121 }, 3122 _onImageHasSize: function (item) { 3123 if (item.img) { 3124 3125 item.hasSize = true; 3126 3127 if (_imgInterval) { 3128 clearInterval(_imgInterval); 3129 } 3130 3131 item.isCheckingImgSize = false; 3132 3133 _mfpTrigger('ImageHasSize', item); 3134 3135 if (item.imgHidden) { 3136 if (mfp.content) 3137 mfp.content.removeClass('mfp-loading'); 3138 3139 item.imgHidden = false; 3140 } 3141 3142 } 3143 }, 3144 3145 /** 3146 * Function that loops until the image has size to display elements that rely on it asap 3147 */ 3148 findImageSize: function (item) { 3149 3150 var counter = 0, 3151 img = item.img[0], 3152 mfpSetInterval = function (delay) { 3153 3154 if (_imgInterval) { 3155 clearInterval(_imgInterval); 3156 } 3157 // decelerating interval that checks for size of an image 3158 _imgInterval = setInterval(function () { 3159 if (img.naturalWidth > 0) { 3160 mfp._onImageHasSize(item); 3161 return; 3162 } 3163 3164 if (counter > 200) { 3165 clearInterval(_imgInterval); 3166 } 3167 3168 counter++; 3169 if (counter === 3) { 3170 mfpSetInterval(10); 3171 } else if (counter === 40) { 3172 mfpSetInterval(50); 3173 } else if (counter === 100) { 3174 mfpSetInterval(500); 3175 } 3176 }, delay); 3177 }; 3178 3179 mfpSetInterval(1); 3180 }, 3181 3182 getImage: function (item, template) { 3183 3184 var guard = 0, 3185 3186 // image load complete handler 3187 onLoadComplete = function () { 3188 if (item) { 3189 if (item.img[0].complete) { 3190 item.img.off('.mfploader'); 3191 3192 if (item === mfp.currItem) { 3193 mfp._onImageHasSize(item); 3194 3195 mfp.updateStatus('ready'); 3196 } 3197 3198 item.hasSize = true; 3199 item.loaded = true; 3200 3201 _mfpTrigger('ImageLoadComplete'); 3202 3203 } 3204 else { 3205 // if image complete check fails 200 times (20 sec), we assume that there was an error. 3206 guard++; 3207 if (guard < 200) { 3208 setTimeout(onLoadComplete, 100); 3209 } else { 3210 onLoadError(); 3211 } 3212 } 3213 } 3214 }, 3215 3216 // image error handler 3217 onLoadError = function () { 3218 if (item) { 3219 item.img.off('.mfploader'); 3220 if (item === mfp.currItem) { 3221 mfp._onImageHasSize(item); 3222 mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src)); 3223 } 3224 3225 item.hasSize = true; 3226 item.loaded = true; 3227 item.loadError = true; 3228 } 3229 }, 3230 imgSt = mfp.st.image; 3231 3232 3233 var el = template.find('.mfp-img'); 3234 if (el.length) { 3235 var img = document.createElement('img'); 3236 img.className = 'mfp-img'; 3237 if (item.el && item.el.find('img').length) { 3238 img.alt = item.el.find('img').attr('alt'); 3239 } 3240 item.img = $(img).on('load.mfploader', onLoadComplete).on('error.mfploader', onLoadError); 3241 img.src = item.src; 3242 3243 // without clone() "error" event is not firing when IMG is replaced by new IMG 3244 // TODO: find a way to avoid such cloning 3245 if (el.is('img')) { 3246 item.img = item.img.clone(); 3247 } 3248 3249 img = item.img[0]; 3250 if (img.naturalWidth > 0) { 3251 item.hasSize = true; 3252 } else if (!img.width) { 3253 item.hasSize = false; 3254 } 3255 } 3256 3257 mfp._parseMarkup(template, { 3258 title: _getTitle(item), 3259 img_replaceWith: item.img 3260 }, item); 3261 3262 mfp.resizeImage(); 3263 3264 if (item.hasSize) { 3265 if (_imgInterval) clearInterval(_imgInterval); 3266 3267 if (item.loadError) { 3268 template.addClass('mfp-loading'); 3269 mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src)); 3270 } else { 3271 template.removeClass('mfp-loading'); 3272 mfp.updateStatus('ready'); 3273 } 3274 return template; 3275 } 3276 3277 mfp.updateStatus('loading'); 3278 item.loading = true; 3279 3280 if (!item.hasSize) { 3281 item.imgHidden = true; 3282 template.addClass('mfp-loading'); 3283 mfp.findImageSize(item); 3284 } 3285 3286 return template; 3287 } 3288 } 3289 }); 3290 3291 /*>>image*/ 3292 3293 /*>>zoom*/ 3294 var hasMozTransform, 3295 getHasMozTransform = function () { 3296 if (hasMozTransform === undefined) { 3297 hasMozTransform = document.createElement('p').style.MozTransform !== undefined; 3298 } 3299 return hasMozTransform; 3300 }; 3301 3302 $.magnificPopup.registerModule('zoom', { 3303 3304 options: { 3305 enabled: false, 3306 easing: 'ease-in-out', 3307 duration: 300, 3308 opener: function (element) { 3309 return element.is('img') ? element : element.find('img'); 3310 } 3311 }, 3312 3313 proto: { 3314 3315 initZoom: function () { 3316 var zoomSt = mfp.st.zoom, 3317 ns = '.zoom', 3318 image; 3319 3320 if (!zoomSt.enabled || !mfp.supportsTransition) { 3321 return; 3322 } 3323 3324 var duration = zoomSt.duration, 3325 getElToAnimate = function (image) { 3326 var newImg = image.clone().removeAttr('style').removeAttr('class').addClass('mfp-animated-image'), 3327 transition = 'all ' + (zoomSt.duration / 1000) + 's ' + zoomSt.easing, 3328 cssObj = { 3329 position: 'fixed', 3330 zIndex: 9999, 3331 left: 0, 3332 top: 0, 3333 '-webkit-backface-visibility': 'hidden' 3334 }, 3335 t = 'transition'; 3336 3337 cssObj['-webkit-' + t] = cssObj['-moz-' + t] = cssObj['-o-' + t] = cssObj[t] = transition; 3338 3339 newImg.css(cssObj); 3340 return newImg; 3341 }, 3342 showMainContent = function () { 3343 mfp.content.css('visibility', 'visible'); 3344 }, 3345 openTimeout, 3346 animatedImg; 3347 3348 _mfpOn('BuildControls' + ns, function () { 3349 if (mfp._allowZoom()) { 3350 3351 clearTimeout(openTimeout); 3352 mfp.content.css('visibility', 'hidden'); 3353 3354 // Basically, all code below does is clones existing image, puts in on top of the current one and animated it 3355 3356 image = mfp._getItemToZoom(); 3357 3358 if (!image) { 3359 showMainContent(); 3360 return; 3361 } 3362 3363 animatedImg = getElToAnimate(image); 3364 3365 animatedImg.css(mfp._getOffset()); 3366 3367 mfp.wrap.append(animatedImg); 3368 3369 openTimeout = setTimeout(function () { 3370 animatedImg.css(mfp._getOffset(true)); 3371 openTimeout = setTimeout(function () { 3372 3373 showMainContent(); 3374 3375 setTimeout(function () { 3376 animatedImg.remove(); 3377 image = animatedImg = null; 3378 _mfpTrigger('ZoomAnimationEnded'); 3379 }, 16); // avoid blink when switching images 3380 3381 }, duration); // this timeout equals animation duration 3382 3383 }, 16); // by adding this timeout we avoid short glitch at the beginning of animation 3384 3385 3386 // Lots of timeouts... 3387 } 3388 }); 3389 _mfpOn(BEFORE_CLOSE_EVENT + ns, function () { 3390 if (mfp._allowZoom()) { 3391 3392 clearTimeout(openTimeout); 3393 3394 mfp.st.removalDelay = duration; 3395 3396 if (!image) { 3397 image = mfp._getItemToZoom(); 3398 if (!image) { 3399 return; 3400 } 3401 animatedImg = getElToAnimate(image); 3402 } 3403 3404 animatedImg.css(mfp._getOffset(true)); 3405 mfp.wrap.append(animatedImg); 3406 mfp.content.css('visibility', 'hidden'); 3407 3408 setTimeout(function () { 3409 animatedImg.css(mfp._getOffset()); 3410 }, 16); 3411 } 3412 3413 }); 3414 3415 _mfpOn(CLOSE_EVENT + ns, function () { 3416 if (mfp._allowZoom()) { 3417 showMainContent(); 3418 if (animatedImg) { 3419 animatedImg.remove(); 3420 } 3421 image = null; 3422 } 3423 }); 3424 }, 3425 3426 _allowZoom: function () { 3427 return mfp.currItem.type === 'image'; 3428 }, 3429 3430 _getItemToZoom: function () { 3431 if (mfp.currItem.hasSize) { 3432 return mfp.currItem.img; 3433 } else { 3434 return false; 3435 } 3436 }, 3437 3438 // Get element postion relative to viewport 3439 _getOffset: function (isLarge) { 3440 var el; 3441 if (isLarge) { 3442 el = mfp.currItem.img; 3443 } else { 3444 el = mfp.st.zoom.opener(mfp.currItem.el || mfp.currItem); 3445 } 3446 3447 var offset = el.offset(); 3448 var paddingTop = parseInt(el.css('padding-top'), 10); 3449 var paddingBottom = parseInt(el.css('padding-bottom'), 10); 3450 offset.top -= ($(window).scrollTop() - paddingTop); 3451 3452 3453 /* 3454 3455 Animating left + top + width/height looks glitchy in Firefox, but perfect in Chrome. And vice-versa. 3456 3457 */ 3458 var obj = { 3459 width: el.width(), 3460 // fix Zepto height+padding issue 3461 height: (_isJQ ? el.innerHeight() : el[0].offsetHeight) - paddingBottom - paddingTop 3462 }; 3463 3464 // I hate to do this, but there is no another option 3465 if (getHasMozTransform()) { 3466 obj['-moz-transform'] = obj['transform'] = 'translate(' + offset.left + 'px,' + offset.top + 'px)'; 3467 } else { 3468 obj.left = offset.left; 3469 obj.top = offset.top; 3470 } 3471 return obj; 3472 } 3473 3474 } 3475 }); 3476 3477 3478 3479 /*>>zoom*/ 3480 3481 /*>>iframe*/ 3482 3483 var IFRAME_NS = 'iframe', 3484 _emptyPage = '//about:blank', 3485 3486 _fixIframeBugs = function (isShowing) { 3487 if (mfp.currTemplate[IFRAME_NS]) { 3488 var el = mfp.currTemplate[IFRAME_NS].find('iframe'); 3489 if (el.length) { 3490 // reset src after the popup is closed to avoid "video keeps playing after popup is closed" bug 3491 if (!isShowing) { 3492 el[0].src = _emptyPage; 3493 } 3494 3495 // IE8 black screen bug fix 3496 if (mfp.isIE8) { 3497 el.css('display', isShowing ? 'block' : 'none'); 3498 } 3499 } 3500 } 3501 }; 3502 3503 $.magnificPopup.registerModule(IFRAME_NS, { 3504 3505 options: { 3506 markup: '<div class="mfp-iframe-scaler">' + 3507 '<div class="mfp-close"></div>' + 3508 '<iframe class="mfp-iframe" src="//about:blank" frameborder="0" allowfullscreen></iframe>' + 3509 '</div>', 3510 3511 srcAction: 'iframe_src', 3512 3513 // we don't care and support only one default type of URL by default 3514 patterns: { 3515 youtube: { 3516 index: 'youtube.com', 3517 id: 'v=', 3518 src: '//www.youtube.com/embed/%id%?autoplay=1' 3519 }, 3520 vimeo: { 3521 index: 'vimeo.com/', 3522 id: '/', 3523 src: '//player.vimeo.com/video/%id%?autoplay=1' 3524 }, 3525 gmaps: { 3526 index: '//maps.google.', 3527 src: '%id%&output=embed' 3528 } 3529 } 3530 }, 3531 3532 proto: { 3533 initIframe: function () { 3534 mfp.types.push(IFRAME_NS); 3535 3536 _mfpOn('BeforeChange', function (e, prevType, newType) { 3537 if (prevType !== newType) { 3538 if (prevType === IFRAME_NS) { 3539 _fixIframeBugs(); // iframe if removed 3540 } else if (newType === IFRAME_NS) { 3541 _fixIframeBugs(true); // iframe is showing 3542 } 3543 }// else { 3544 // iframe source is switched, don't do anything 3545 //} 3546 }); 3547 3548 _mfpOn(CLOSE_EVENT + '.' + IFRAME_NS, function () { 3549 _fixIframeBugs(); 3550 }); 3551 }, 3552 3553 getIframe: function (item, template) { 3554 var embedSrc = item.src; 3555 var iframeSt = mfp.st.iframe; 3556 3557 $.each(iframeSt.patterns, function () { 3558 if (embedSrc.indexOf(this.index) > -1) { 3559 if (this.id) { 3560 if (typeof this.id === 'string') { 3561 embedSrc = embedSrc.substr(embedSrc.lastIndexOf(this.id) + this.id.length, embedSrc.length); 3562 } else { 3563 embedSrc = this.id.call(this, embedSrc); 3564 } 3565 } 3566 embedSrc = this.src.replace('%id%', embedSrc); 3567 return false; // break; 3568 } 3569 }); 3570 3571 var dataObj = {}; 3572 if (iframeSt.srcAction) { 3573 dataObj[iframeSt.srcAction] = embedSrc; 3574 } 3575 mfp._parseMarkup(template, dataObj, item); 3576 3577 mfp.updateStatus('ready'); 3578 3579 return template; 3580 } 3581 } 3582 }); 3583 3584 3585 3586 /*>>iframe*/ 3587 3588 /*>>gallery*/ 3589 /** 3590 * Get looped index depending on number of slides 3591 */ 3592 var _getLoopedId = function (index) { 3593 var numSlides = mfp.items.length; 3594 if (index > numSlides - 1) { 3595 return index - numSlides; 3596 } else if (index < 0) { 3597 return numSlides + index; 3598 } 3599 return index; 3600 }, 3601 _replaceCurrTotal = function (text, curr, total) { 3602 return text.replace(/%curr%/gi, curr + 1).replace(/%total%/gi, total); 3603 }; 3604 3605 $.magnificPopup.registerModule('gallery', { 3606 3607 options: { 3608 enabled: false, 3609 arrowMarkup: '<button title="%title%" type="button" class="mfp-arrow mfp-arrow-%dir%"></button>', 3610 preload: [0, 2], 3611 navigateByImgClick: true, 3612 arrows: true, 3613 3614 tPrev: 'Previous (Left arrow key)', 3615 tNext: 'Next (Right arrow key)', 3616 tCounter: '%curr% of %total%' 3617 }, 3618 3619 proto: { 3620 initGallery: function () { 3621 3622 var gSt = mfp.st.gallery, 3623 ns = '.mfp-gallery'; 3624 3625 mfp.direction = true; // true - next, false - prev 3626 3627 if (!gSt || !gSt.enabled) return false; 3628 3629 _wrapClasses += ' mfp-gallery'; 3630 3631 _mfpOn(OPEN_EVENT + ns, function () { 3632 3633 if (gSt.navigateByImgClick) { 3634 mfp.wrap.on('click' + ns, '.mfp-img', function () { 3635 if (mfp.items.length > 1) { 3636 mfp.next(); 3637 return false; 3638 } 3639 }); 3640 } 3641 3642 _document.on('keydown' + ns, function (e) { 3643 if (e.keyCode === 37) { 3644 mfp.prev(); 3645 } else if (e.keyCode === 39) { 3646 mfp.next(); 3647 } 3648 }); 3649 }); 3650 3651 _mfpOn('UpdateStatus' + ns, function (e, data) { 3652 if (data.text) { 3653 data.text = _replaceCurrTotal(data.text, mfp.currItem.index, mfp.items.length); 3654 } 3655 }); 3656 3657 _mfpOn(MARKUP_PARSE_EVENT + ns, function (e, element, values, item) { 3658 var l = mfp.items.length; 3659 values.counter = l > 1 ? _replaceCurrTotal(gSt.tCounter, item.index, l) : ''; 3660 }); 3661 3662 _mfpOn('BuildControls' + ns, function () { 3663 if (mfp.items.length > 1 && gSt.arrows && !mfp.arrowLeft) { 3664 var markup = gSt.arrowMarkup, 3665 arrowLeft = mfp.arrowLeft = $(markup.replace(/%title%/gi, gSt.tPrev).replace(/%dir%/gi, 'left')).addClass(PREVENT_CLOSE_CLASS), 3666 arrowRight = mfp.arrowRight = $(markup.replace(/%title%/gi, gSt.tNext).replace(/%dir%/gi, 'right')).addClass(PREVENT_CLOSE_CLASS); 3667 3668 arrowLeft.click(function () { 3669 mfp.prev(); 3670 }); 3671 arrowRight.click(function () { 3672 mfp.next(); 3673 }); 3674 3675 mfp.container.append(arrowLeft.add(arrowRight)); 3676 } 3677 }); 3678 3679 _mfpOn(CHANGE_EVENT + ns, function () { 3680 if (mfp._preloadTimeout) clearTimeout(mfp._preloadTimeout); 3681 3682 mfp._preloadTimeout = setTimeout(function () { 3683 mfp.preloadNearbyImages(); 3684 mfp._preloadTimeout = null; 3685 }, 16); 3686 }); 3687 3688 3689 _mfpOn(CLOSE_EVENT + ns, function () { 3690 _document.off(ns);
3691 mfp.wrap.off('click' + ns); 3692 mfp.arrowRight = mfp.arrowLeft = null; 3693 }); 3694 3695 }, 3696 next: function () { 3697 mfp.direction = true; 3698 mfp.index = _getLoopedId(mfp.index + 1); 3699 mfp.updateItemHTML(); 3700 }, 3701 prev: function () { 3702 mfp.direction = false; 3703 mfp.index = _getLoopedId(mfp.index - 1); 3704 mfp.updateItemHTML(); 3705 }, 3706 goTo: function (newIndex) { 3707 mfp.direction = (newIndex >= mfp.index); 3708 mfp.index = newIndex; 3709 mfp.updateItemHTML(); 3710 }, 3711 preloadNearbyImages: function () { 3712 var p = mfp.st.gallery.preload, 3713 preloadBefore = Math.min(p[0], mfp.items.length), 3714 preloadAfter = Math.min(p[1], mfp.items.length), 3715 i; 3716 3717 for (i = 1; i <= (mfp.direction ? preloadAfter : preloadBefore); i++) { 3718 mfp._preloadItem(mfp.index + i); 3719 } 3720 for (i = 1; i <= (mfp.direction ? preloadBefore : preloadAfter); i++) { 3721 mfp._preloadItem(mfp.index - i); 3722 } 3723 }, 3724 _preloadItem: function (index) { 3725 index = _getLoopedId(index); 3726 3727 if (mfp.items[index].preloaded) { 3728 return; 3729 } 3730 3731 var item = mfp.items[index]; 3732 if (!item.parsed) { 3733 item = mfp.parseEl(index); 3734 } 3735 3736 _mfpTrigger('LazyLoad', item); 3737 3738 if (item.type === 'image') { 3739 item.img = $('<img class="mfp-img" />').on('load.mfploader', function () { 3740 item.hasSize = true; 3741 }).on('error.mfploader', function () { 3742 item.hasSize = true; 3743 item.loadError = true; 3744 _mfpTrigger('LazyLoadError', item); 3745 }).attr('src', item.src); 3746 } 3747 3748 3749 item.preloaded = true; 3750 } 3751 } 3752 }); 3753 3754 /*>>gallery*/ 3755 3756 /*>>retina*/ 3757 3758 var RETINA_NS = 'retina'; 3759 3760 $.magnificPopup.registerModule(RETINA_NS, { 3761 options: { 3762 replaceSrc: function (item) { 3763 return item.src.replace(/\.\w+$/, function (m) { return '@2x' + m; }); 3764 }, 3765 ratio: 1 // Function or number. Set to 1 to disable. 3766 }, 3767 proto: { 3768 initRetina: function () { 3769 if (window.devicePixelRatio > 1) { 3770 3771 var st = mfp.st.retina, 3772 ratio = st.ratio; 3773 3774 ratio = !isNaN(ratio) ? ratio : ratio(); 3775 3776 if (ratio > 1) { 3777 _mfpOn('ImageHasSize' + '.' + RETINA_NS, function (e, item) { 3778 item.img.css({ 3779 'max-width': item.img[0].naturalWidth / ratio, 3780 'width': '100%' 3781 }); 3782 }); 3783 _mfpOn('ElementParse' + '.' + RETINA_NS, function (e, item) { 3784 item.src = st.replaceSrc(item, ratio); 3785 }); 3786 } 3787 } 3788 3789 } 3790 } 3791 }); 3792 3793 /*>>retina*/ 3794 _checkInstance(); 3795})); 3796window.eviivo = window.eviivo ? window.eviivo : {}; 3797window.eviivo.imageMainGallery = window.eviivo.imageMainGallery ? window.eviivo.imageMainGallery : {}; 3798 3799window.eviivo.imageMainGallery = function ($) { 3800 3801 var defaultOptions = { 3802 initializeGalleryByDefault: true, 3803 initializeGalleryOnClick: true, 3804 initializeGalleryForRooms: true, 3805 trigger: '.trigger', 3806 heroSlide: '#hero-slider', 3807 heroNav: '#hero-nav', 3808 thumbSlider: "#hero-nav-single" 3809 }; 3810 3811 var options; 3812 var trigger; 3813 var thumbSlider; 3814 var virtualTourButton; 3815 var galleryButton; 3816 var imageGalleryContainer; 3817 var closeVTButton; 3818 3819 function init(settings) { 3820 //extend internal settings with external ones 3821 options = $.extend(defaultOptions, settings); 3822 3823 trigger = $(options.trigger); 3824 thumbSlider = $(options.thumbSlider); 3825 3826 virtualTourButton = $(".s-vt-virtual-tour"); 3827 galleryButton = $(".s-vt-image-gallery"); 3828 3829 imageGalleryContainer = $(".mfp-arrow,.mfp-content"); 3830 virtualTourContainer = $(".s-vt-container"); 3831 closeVTButton = $(".s-vt-close"); 3832 3833 if (options.initializeGalleryByDefault === true && options.initializeGalleryOnClick === true) { 3834 throw "Initializing the Gallery with both options Initialize By Default and OnClick will generate conflicts as the classes are shared."; 3835 } 3836 3837 // This condition should be used for scenarios where we want to load the gallery by DEFAULT on the page. e.g.: via pages that load the gallery on load 3838 if (options.initializeGalleryByDefault === true) { 3839 initDummyGallery(); 3840 } 3841 3842 // This function should be used for scenarios where we want to load the gallery only when a CLICK EVENT occurs. e.g.: toprooms/partners property pages 3843 if (options.initializeGalleryOnClick === true) { 3844 trigger.on('click', function () { 3845 initMainGallery(); 3846 }); 3847 } 3848 } 3849 3850 $('.results-trigger').on('click', function () { 3851 $(this).next().magnificPopup('open'); 3852 });
3853 3854 // Click on Virtual tour icon on Homepage & Photos, except for Fullscape templates 3855 $('.page-type-1 .gallery-details .virtual-tour-icon, .page-type-5 .gallery-details .virtual-tour-icon').on('click', function () { 3856 if (!$("body").hasClass("template-13", "template-14")) { 3857 var iFrameUrl = $(this).parent().parent("a").data('iframe-url'); 3858 3859 $("body").addClass("mfp-hidden"); 3860 virtualTourButton.addClass('active'); 3861 galleryButton.removeClass('active'); 3862 virtualTourContainer.removeClass("hidden"); 3863 // Pass Virtual tour URL into an iFrame 3864 vtIframe.attr("src", iFrameUrl); 3865 } 3866 }); 3867 // Click on Virtual tour icon on RoomType 3868 $('.page-type-2 .gallery-details .virtual-tour-icon').on('click', function () { 3869 $(this).closest('.lightbox-gallery-link').next().magnificPopup('open'); 3870 $('.s-vt-virtual-tour.cp-button').trigger('click'); 3871 }); 3872 3873 var vtIframe = $("#s-vt-iframe"); 3874 3875 $('.lightbox-gallery').each(function () { 3876 $(this).magnificPopup({ 3877 delegate: 'a', 3878 type: 'image', 3879 gallery: { 3880 enabled: true, 3881 navigateByImgClick: true 3882 }, 3883 fixedContentPos: true, 3884 callbacks: { 3885 open: function () { 3886 launchGallery(); 3887 }, 3888 close: function () {
3889 // Will fire when popup gallery is closed 3890 hideVirtualTour(); 3891 } 3892 } 3893 }); 3894 }); 3895 3896 function virtualTourToggle() { 3897 3898 virtualTourButton.on("click", function () { 3899 galleryButton.removeClass("active"); 3900 virtualTourButton.addClass("active"); 3901 $("body").addClass("mfp-hidden"); 3902 3903 virtualTourContainer.removeClass("hidden"); 3904 imageGalleryContainer.hide(); 3905 }); 3906 3907 galleryButton.on("click", function () { 3908 galleryButton.addClass("active"); 3909 virtualTourButton.removeClass("active"); 3910 $("body").removeClass("mfp-hidden"); 3911 3912 virtualTourContainer.addClass("hidden"); 3913 imageGalleryContainer.show(); 3914 }); 3915 3916 closeVTButton.on("click", function () { 3917 $.magnificPopup.close(); // Close popup that is currently opened (shorthand) 3918 }); 3919 } 3920 3921 /*============================= 3922 toprooms Main gallery 3923 =============================*/ 3924 3925 function initMainGallery() { 3926 $('.lightbox-gallery').magnificPopup({ 3927 delegate: 'a', 3928 type: 'image', 3929 gallery: { 3930 enabled: true, 3931 navigateByImgClick: true 3932 }, 3933 fixedContentPos: true, 3934 callbacks: { 3935 open: function () { 3936 launchGallery(); 3937 }, 3938 close: function () {
3939 // Will fire when popup gallery is closed 3940 hideVirtualTour(); 3941 } 3942 } 3943 }); 3944 } 3945 3946 /*========================================= 3947 Dummy her gallery with thumbnail slider 3948 =========================================*/ 3949 // THIS INITIATES SLIDERS FOR ONLY TEMPLATES 7 AND 12 3950 function initDummyGallery() { 3951 thumbSlider.jcarousel({ 3952 wrap: 'last' 3953 }); 3954 3955 $('.slide-prev').on("click", function () { 3956 if (window.innerWidth > 768) { 3957 thumbSlider.jcarousel('scroll', '-=3'); 3958 } 3959 // if on small screen, scroll by a sigle image 3960 else { 3961 thumbSlider.jcarousel('scroll', '-=1'); 3962 } 3963 }); 3964 3965 $('.slide-next').on("click", function () { 3966 3967 if (window.innerWidth > 768) { 3968 thumbSlider.jcarousel('scroll', '+=3'); 3969 } 3970 // if on small screen, scroll by a sigle image 3971 else { 3972 thumbSlider.jcarousel('scroll', '+=1'); 3973 } 3974 }); 3975 3976 $(".lightbox-trigger-container").magnificPopup({ 3977 delegate: 'a', 3978 type: 'image', 3979 gallery: { 3980 enabled: true, 3981 navigateByImgClick: true 3982 }, 3983 fixedContentPos: true, 3984 callbacks: { 3985 open: function () { 3986 launchGallery(); 3987 }, 3988 close: function () {
3989 // Will fire when popup gallery is closed 3990 hideVirtualTour(); 3991 } 3992 } 3993 }); 3994 } 3995 // IW template 13-14 or for inline galleries without an overlay 3996 $(".s-virtual-tour-launch").on("click", function () { 3997 $("body").addClass("virtual-tour-enabled"); 3998 3999 virtualTourContainer.removeClass("hidden"); 4000 4001 closeVTButton.on("click", function () { 4002 hideVirtualTour(); 4003 }); 4004 }); 4005 4006 $(document).on('keyup', function (e) { 4007 if (e.key === "Escape") hideVirtualTour(); 4008 }); 4009 4010 function launchGallery() { 4011 var popUpGallery = $.magnificPopup.instance, 4012 firstImage = $(popUpGallery.currItem.el); 4013 4014 var iFrameUrl = firstImage.data('iframe-url'); 4015 4016 if (iFrameUrl != null) { 4017 $("body").addClass("virtual-tour-enabled"); 4018 4019 virtualTourToggle(); 4020 4021 // Pass Virtual tour URL into an iFrame 4022 vtIframe.attr("src", iFrameUrl) 4023 } 4024 } 4025 4026 function hideVirtualTour() { 4027 // Will fire when popup gallery is closed
4028 $("body").removeClass("virtual-tour-enabled mfp-hidden"); 4029 $(".s-vt-container").addClass("hidden"); 4030 4031 // Reset buttons to a default state 4032 $(".s-vt-image-gallery").addClass("active"); 4033 $(".s-vt-virtual-tour").removeClass("active"); 4034 if (!$("body").hasClass("template-13", "template-14")) { 4035 // Clear iFrame's src 4036 vtIframe.attr("src", ""); 4037 } 4038 4039 } 4040 4041 return { 4042 init: init 4043 }; 4044}(jQuery); 4045window.eviivo = window.eviivo ? window.eviivo : {}; 4046window.eviivo.reviews = window.eviivo.reviews ? window.eviivo.reviews : {}; 4047 4048window.eviivo.reviews = function ($, ajaxHelper) { 4049 4050 var defaultOptions = { 4051 propertyReviewsUrl: "", 4052 resources: { 4053 ReadMore: "Read more", 4054 ReadLess: "Read less" 4055 } 4056 }; 4057 var ajaxRequestId = "reviewsAjaxRequest"; 4058 var options; 4059 var currentPage = 1; 4060 var pages; 4061 var isNextDisabled; 4062 var isPreviousDisabled; 4063 var $firstArrow; 4064 var $lastArrow; 4065 var $lastArrowLink; 4066 var $firstArrowLink; 4067 var $reviewsSection; 4068 ///<summary>Main init method of the eviivo.reviews object</summary> 4069 ///<param name="settings">external settings</param> 4070 function init(settings) { 4071 //extend internal settings with external ones 4072 options = $.extend(defaultOptions, settings); 4073 $reviewsSection = $(".reviews-section"); 4074 4075 if (!$reviewsSection.length) { //In cases when comments are disabled. 4076 return; 4077 } 4078 4079 pages = $reviewsSection.data("numberofpages"); 4080 4081 ajaxHelper.init(ajaxRequestId, options.ajaxLoaderResources, $reviewsSection, updatePageContent, loadPageError); 4082 4083 loadReviewWhenReviewsSectionIsVisible(); 4084 } 4085 4086 function loadReviewWhenReviewsSectionIsVisible() { 4087 if (isReviewsSectionVisible()) { 4088 navigateToPage(1); 4089 } else { 4090 $(window).scroll(function () { 4091 if (isReviewsSectionVisible()) { 4092 $(window).unbind('scroll'); 4093 navigateToPage(1); 4094 } 4095 }); 4096 } 4097 } 4098 4099 function isReviewsSectionVisible() { 4100 return $(window).scrollTop() + $(window).height() + 70 >= $reviewsSection.offset().top; 4101 } 4102 4103 function navigatePrevious() { 4104 if (currentPage - 1 > 0) { 4105 navigateToPage(currentPage - 1); 4106 return true; 4107 } 4108 return false; 4109 } 4110 4111 function navigateNext() { 4112 if (currentPage + 1 <= pages) { 4113 navigateToPage(currentPage + 1); 4114 return true; 4115 } 4116 return false; 4117 } 4118 4119 function updatePagination() { 4120 $firstArrow = $(".mod-paginate li.arrow:first-child"); 4121 $lastArrow = $(".mod-paginate li.arrow:last-child"); 4122 $firstArrowLink = $firstArrow.find("a"); 4123 $lastArrowLink = $lastArrow.find("a"); 4124 4125 $firstArrowLink.on("click", navigatePrevious); 4126 $lastArrowLink.on("click", navigateNext); 4127 4128 //$('.mod-paginate li[class!="arrow"]').remove(); 4129 4130 var currentPageHtml = $('<li class="current"><a class="link-bg" href="javascript:;">' + currentPage + '</a></li>'); 4131 currentPageHtml.insertAfter($firstArrow); 4132 4133 var pageBefore = currentPage - 1; 4134 if (pageBefore > 0) { 4135 $firstArrow.removeAttr("disabled"); 4136 $('<li><a href="javascript:eviivo.reviews.navigateToPage(' + pageBefore + ');">' + pageBefore + '</a></li>').insertAfter($firstArrow); 4137 4138 if (pageBefore - 2 >= 0) { 4139 $('<li><a href="javascript:;">…</a></li>').insertAfter($firstArrow); 4140 $('<li><a href="javascript:eviivo.reviews.navigateToPage(1);">1</a></li>').insertAfter($firstArrow); 4141 } 4142 } 4143 4144 var pageAfter = currentPage + 1; 4145 if (pageAfter <= pages) { 4146 $('<li><a href="javascript:eviivo.reviews.navigateToPage(' + pageAfter + ');">' + pageAfter + '</a></li>').insertBefore($lastArrow); 4147 } 4148 4149 if (currentPage + 2 <= pages) { 4150 $('<li><a href="javascript:;">…</a></li>').insertBefore($lastArrow); 4151 $('<li><a href="javascript:eviivo.reviews.navigateToPage(' + pages + ');">' + pages + '</a></li>').insertBefore($lastArrow); 4152 } 4153 4154 if (currentPage == 1) {
4155 $firstArrowLink.off("click"); 4156 isPreviousDisabled = true; 4157 } 4158 else if (isPreviousDisabled) { 4159 $firstArrowLink.on("click", navigatePrevious); 4160 isPreviousDisabled = false; 4161 } 4162 4163 if (currentPage == pages) { 4164 $lastArrowLink.off("click"); 4165 isNextDisabled = true; 4166 } 4167 else if (isNextDisabled) { 4168 $lastArrowLink.on("click", navigateNext); 4169 isNextDisabled = false; 4170 } 4171 } 4172 4173 function loadPageError(jqXhr, textStatus, errorThrown) { 4174 console.log(textStatus); 4175 console.log(errorThrown); 4176 } 4177 4178 function updatePageContent(data) { 4179 $reviewsSection.html(data); 4180 4181 updatePagination(); 4182 4183 //reviews read more action 4184 4185 $("#reviews-list .column-review-content p a").on("click", function () { 4186 $(this).parent().siblings(".review-item-details").slideToggle(); 4187 $(this).parent().parent().toggleClass("active"); 4188 var textDefault = options.resources.ReadMore; 4189 var textExpanded = options.resources.ReadLess; 4190 $(this).text(function (i, text) { 4191 return text === textExpanded ? textDefault : textExpanded; 4192 }); 4193 }); 4194 } 4195 4196 function loadPage() { 4197 ajaxHelper.executeAjax({ 4198 requestId: ajaxRequestId, 4199 url: options.propertyReviewsUrl + "/" + currentPage, 4200 method: "GET", 4201 cache: false, 4202 dataType: "html" 4203 }); 4204 } 4205 4206 function navigateToPage(page) { 4207 currentPage = page; 4208 loadPage(); 4209 } 4210 4211 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 4212 return { 4213 init: init, 4214 navigateToPage: navigateToPage 4215 }; 4216}(jQuery, eviivo.utils.ajaxHelper); 4217window.eviivo = window.eviivo ? window.eviivo : {}; 4218window.eviivo.popup = window.eviivo.popup ? window.eviivo.popup : {}; 4219 4220window.eviivo.popup = function ($) { 4221 4222 var defaultOptions = { 4223 resources: { 4224 }, 4225 bypassClose: false, 4226 bypassBodyHtml: false, 4227 popupBox: null, 4228 bypassHeaderHtml: false 4229 }; 4230 var options; 4231 var $popupBox; 4232 var $popupContent; 4233 var $popupClose; 4234 var $popupHeaderTitle; 4235 4236 ///<summary>Main init method of the eviivo.popup object</summary> 4237 ///<param name="settings">external settings</param> 4238 function init(settings) { 4239 //extend internal settings with external ones 4240 options = $.extend(defaultOptions, settings); 4241 $popupBox = $(".dialogue-outer"); 4242 $popupContent = $popupBox.find(".mod-dialogueBox"); 4243 $popupHeaderTitle = $popupBox.find(".dialogue-header h3"); 4244 $popupClose = $popupBox.find(".cp-link"); 4245 $popupHeaderTitle = $popupBox.find(".dialogue-header h3"); 4246 $popupClose.on("click", close); 4247 $(document).on("keydown", close); 4248 popUpVisibility(); 4249 } 4250 4251 function display(showOptions) { 4252 if (showOptions.popupBox != null && showOptions.popupBox != "") { 4253 $popupBox = $(showOptions.popupBox); 4254 $popupContent = $popupBox.find(".mod-dialogueBox"); 4255 $popupHeaderTitle = $popupBox.find(".dialogue-header h3"); 4256 $popupClose = $popupBox.find(".cp-link"); 4257 $popupHeaderTitle = $popupBox.find(".dialogue-header h3"); 4258 } 4259 4260 if (showOptions.bypassClose) { 4261 $popupClose.hide(); 4262 $popupClose.off("click", close); 4263 $(document).off("keydown", close); 4264 } else { 4265 $popupClose.show(); 4266 $popupClose.on("click", close); 4267 $(document).on("keydown", close); 4268 } 4269 4270 if (showOptions.bypassBodyHtml === false || showOptions.bypassBodyHtml == undefined) { 4271 $popupContent.html(showOptions.html); 4272 } 4273 4274 if (showOptions.bypassHeaderHtml === false || showOptions.bypassHeaderHtml == undefined) { 4275 $popupHeaderTitle.html(showOptions.headerTitle).text(); 4276 } 4277 4278 if (showOptions.bodyClass) { 4279 $popupBox.removeClass().addClass('dialogue-outer').addClass(showOptions.bodyClass); 4280 } 4281 4282 if (typeof showOptions.popupCloseCallBack === "function") { 4283 options.popupCloseCallBack = showOptions.popupCloseCallBack; 4284 } 4285 4286 $popupBox.css("display", "table"); 4287 } 4288 4289 function close(e) { 4290 if (e.which === 1 || e.keyCode === 27) { 4291 $popupContent.html(""); 4292 $popupBox.css("display", "none"); 4293 $popupHeaderTitle.text(""); 4294 if (typeof options.popupCloseCallBack === "function") { 4295 options.popupCloseCallBack(); 4296 } 4297 4298 if ($popupBox.hasClass('mod_announcement-popup')) { 4299 window.eviivo.webcore.cookieManager.setCookie("announcement_popup", "announcement_" + $('#announcement-popup_business_shortname').val() + "=1", 1); 4300 $popupBox.remove('.mod_announcement-popup'); 4301 } 4302 $("body").removeClass("overflow-hidden"); 4303 } 4304 } 4305 4306 function popUpVisibility() { 4307 if ($(".mod_announcement-popup").is(':visible')) { 4308 $("body").addClass("overflow-hidden"); 4309 } 4310 } 4311 4312 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 4313 return { 4314 init: init, 4315 display: display 4316 }; 4317}(jQuery); 4318document.addEventListener("DOMContentLoaded", function () { 4319 // Open modal 4320 function openModal(event) { 4321 event.preventDefault(); 4322 var targetId = event.currentTarget.getAttribute('id').replace('openM
4322odal-', 'moreInfoModal-'); 4323 var modal = document.getElementById(targetId); 4324 if (modal) { 4325 modal.style.display = "block"; 4326 modal.classList.add("modal-open"); 4327 document.body.classList.add("modal-open"); 4328 } 4329 } 4330 4331 // Close modal 4332 function closeModal(event) { 4333 event.preventDefault(); 4334 var targetId = event.currentTarget.getAttribute('data-target').replace('#', ''); 4335 var modal = document.getElementById(targetId); 4336 if (modal) { 4337 modal.style.display = "none"; 4338 modal.classList.remove("modal-open"); 4339 document.body.classList.remove("modal-open"); 4340 } 4341 } 4342 4343 // Close modal by modal element 4344 function closeModalByElement(modal) { 4345 if (modal) { 4346 modal.style.display = "none"; 4347 modal.classList.remove("modal-open"); 4348 document.body.classList.remove("modal-open"); 4349 } 4350 } 4351 4352 // Attach event listeners to open modal links 4353 var openModalLinks = document.querySelectorAll('.modal-rooms-toggle'); 4354 openModalLinks.forEach(function (link) { 4355 link.addEventListener('click', openModal); 4356 }); 4357 4358 // Attach event listeners to close modal links 4359 var closeModalLinks = document.querySelectorAll('.modal-close'); 4360 closeModalLinks.forEach(function (link) { 4361 link.addEventListener('click', closeModal); 4362 }); 4363 4364 // Close modal when clicking outside of it 4365 window.addEventListener('click', function (event) { 4366 if (event.target.classList.contains('modal')) { 4367 closeModalByElement(event.target); 4368 } 4369 }); 4370 4371 // Close modal when pressing the Escape key 4372 document.addEventListener('keydown', function (event) { 4373 if (event.key === "Escape") { 4374 var openModals = document.querySelectorAll('.modal.modal-open'); 4375 openModals.forEach(function (modal) { 4376 closeModalByElement(modal); 4377 }); 4378 } 4379 }); 4380}); 4381 4382document.addEventListener("DOMContentLoaded", function () { 4383 $(".accordion-toggle").on("click", function () { 4384 var target = $(this).data("target"); 4385 $("#" + target).slideToggle("slow"); 4386 $(this).toggleClass("active"); 4387 }); 4388}); 4389window.eviivo = window.eviivo ? window.eviivo : {}; 4390window.eviivo.optionSelector = window.eviivo.optionSelector ? window.eviivo.optionSelector : {}; 4391 4392window.eviivo.optionSelector = function ($) { 4393 var defaultOptions = { 4394 }; 4395 4396 var options; 4397 4398 4399 ///<summary>Main init method of the eviivo.optionSelector object</summary> 4400 ///<param name="settings">external settings</param> 4401 function init(settings) { 4402 //extend internal settings with external ones 4403 options = $.extend(defaultOptions, settings); 4404 4405 } 4406 4407 4408 4409 //Header language selector 4410 var $expandableSelector = $("header .cp-expandable"); 4411 var $headerControls = $(".cp-expandable"); 4412 var $currencyContainer = $(".currency-inner"); 4413 var $languageContainer = $(".language-inner"); 4414 var $headerTooltip = $("header .cp-tooltip"); 4415 4416 4417 $expandableSelector.on("click", function () { 4418 $(this).toggleClass("active"); 4419 $(this).siblings(".cp-tooltip").toggle(); 4420 }); 4421 4422 $headerControls.on("click", function () { 4423 if ($(this).hasClass("active")) { 4424 $(this).parent().siblings().find(".cp-expandable").removeClass("active"); 4425 $(this).parent().siblings().find(".cp-tooltip").hide(); 4426 } 4427 }); 4428 4429 /* Hide */ 4430 $(document).on("click", function (e) { 4431 4432 if ((!$(e.target).hasClass("cp-expandable") || !$(e.target).hasClass("active")) && $(e.target).parents("header .mod-selectors").length == 0) { 4433 $currencyContainer.hide(); 4434 $languageContainer.hide(); 4435 $expandableSelector.removeClass("active"); 4436 } 4437 4438 }); 4439 4440 $currencyContainer.on("click", function (evt) { 4441 evt.stopPropagation(); 4442 }); 4443 4444 $languageContainer.on("click", function (evt) { 4445 evt.stopPropagation(); 4446 });
4447 4448 $(document).on("keyup",function (e) { 4449 if (e.keyCode == 27) { 4450 4451 if (!$(e.target).hasClass(".cp-expandable.cp-expandable")) { 4452 $headerTooltip.hide(); 4453 $expandableSelector.removeClass("active"); 4454 4455 } 4456 4457 } 4458 }); 4459 //Header language selector END 4460 4461 4462 4463 4464 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 4465 return { 4466 init: init 4467 }; 4468 4469}(jQuery); 4470window.eviivo = window.eviivo ? window.eviivo : {}; 4471window.eviivo.roomImages = window.eviivo.roomImages ? window.eviivo.roomImages : {}; 4472 4473window.eviivo.roomImages = function ($) { 4474 4475 var defaultOptions = { 4476 }; 4477 4478 var options; 4479 var clickedImageSrc; 4480 4481 ///<summary>Main init method of the eviivo.roomImages object</summary> 4482 ///<param name="settings">external settings</param> 4483 function init(settings) { 4484 //extend internal settings with external ones 4485 options = $.extend(defaultOptions, settings); 4486 4487 $("div.mod-results div.results-gallery-thumbs li:first-child a").addClass("active"); 4488 4489 // Results thumbs click functionality 4490 $(".results-gallery-thumbs a").on("click", thumbnailClickHandler); 4491 } 4492 4493 ///<summary></summary> 4494 function thumbnailClickHandler() { 4495 var $obj = $(this); 4496 if (!$obj.hasClass("active")) { 4497 $obj.parents(".results-gallery-thumbs").find("a").removeClass("active"); 4498 $obj.addClass("active"); 4499 var $picture = $obj.parents("div.results-gallery").find("div.results-gallery-single picture"); 4500 if ($picture) { 4501 var src = $obj.find("img").attr("srcset"); 4502 clickedImageSrc = extractUrl(src, false); 4503 $picture.find("img, source").each(changeSourceSet); 4504 } 4505 } 4506 } 4507 4508 ///<summary></summary> 4509 function extractUrl(url, extensionOnly) { 4510 var index = url.lastIndexOf("-"); 4511 var str = url.substr(index); 4512 if (str.length > 6) { 4513 index = url.lastIndexOf("."); 4514 return extensionOnly ? url.substr(index) : url.substr(0, index); 4515 } 4516 return extensionOnly ? str : url.substr(0, index); 4517 } 4518 4519 ///<summary></summary> 4520 function changeSourceSet(i, e) { 4521 var srcExtension = $(e).attr("srcset"); 4522 srcExtension = extractUrl(srcExtension, true); 4523 if ($(e).attr("src")) { 4524 $(e).attr("src", clickedImageSrc + srcExtension); 4525 } 4526 $(e).attr("srcset", clickedImageSrc + srcExtension); 4527 } 4528 4529 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 4530 return { 4531 init: init 4532 }; 4533}(jQuery); 4534window.eviivo = window.eviivo ? window.eviivo : {}; 4535window.eviivo.instantWebMenu = window.eviivo.instantWebMenu ? window.eviivo.instantWebMenu : {}; 4536 4537window.eviivo.instantWebMenu = function ($) { 4538 4539 var defaultOptions = { 4540 navigation: '.menu', 4541 menuOuter: '.menu-toggle ul.outer-menu', 4542 menuItems: '.menu li a', 4543 iconRight: '.icon-arrow-right', 4544 iconLeft: '.icon-arrow-left', 4545 menuControls: '.nav-controls', 4546 menuCta: '.menu-toggle .cp-button', 4547 searchBar: '.mod-search', 4548 menuButton: '.cp-menuIcon', 4549 iconMenuContainer: '.menu-icon', 4550 iconMenuDefault: 'â¡', 4551 iconMenuClose: 'Ã', 4552 subMenu: '.sub-menu', 4553 hasChildren: '.has-children' 4554 }; 4555 var options; 4556 var totalWidth; 4557 4558 var $navigation; 4559 var $menuOuter; 4560 var $menuItems; 4561 var $iconRight; 4562 var $iconLeft; 4563 var $menuControls; 4564 var $menuCta; 4565 var $searchBar; 4566 // Responsive menu toggle 4567 var $menuButton; 4568 var $iconMenuContainer; 4569 var iconMenuDefault; 4570 var iconMenuClose; 4571 var $subMenu; 4572 var $hasChildren; 4573 4574 ///<summary>Main init method of the eviivo.availabilitySearch object</summary> 4575 ///<param name="settings">external settings</param> 4576 function init(settings) { 4577 4578 //extend internal settings with external ones 4579 options = $.extend(defaultOptions, settings); 4580 4581 $navigation = $(options.navigation); 4582 $menuOuter = $(options.menuOuter); 4583 $menuItems = $(options.menuItems); 4584 $iconRight = $(options.iconRight); 4585 $iconLeft = $(options.iconLeft); 4586 $menuControls = $(options.menuControls); 4587 // header book button 4588 $menuCta = $(options.menuCta); 4589 $searchBar = $(options.searchBar); 4590 // Responsive menu toggle 4591 $menuButton = $(options.menuButton); 4592 iconMenuContainer = options.iconMenuContainer; 4593 iconMenuDefault = options.iconMenuDefault; 4594 iconMenuClose = options.iconMenuClose; 4595 $hasChildren = $(options.hasChildren); 4596 $subMenu = $(options.subMenu); 4597 4598 totalWidth = 0; 4599 4600 $iconRight.on('click', clickScrollRight); 4601 $iconLeft.on('click', clickScrollLeft); 4602 4603 menuLengthCheck(); 4604 positionSubMenus(); 4605 4606 $menuCta.on("click", function () { 4607 $(this).toggleClass("active"); 4608 $searchBar.toggleClass('open'); 4609 4610 if ($menuButton.parent().hasClass("active")) { 4611 toggleBookButtonIcon($menuButton); 4612 } 4613 }); 4614 4615 $menuButton.on("click", function () { 4616 toggleBookButtonIcon($(this)); 4617 4618 if ($menuCta.hasClass('active')) { 4619 closeSearch(); 4620 } 4621 }); 4622 4623 $navigation.on('scroll', positionSubMenus); 4624 4625 // Toggle submenu on click (mobile or burger menu), exclude desktop horizontal menu 4626 $hasChildren.on('click', function (e) {
4627 var $navMenuToggle = $(this).closest('nav .menu-toggle'); 4628 var isDesktopHorizontalMenu = ($(window).width() >= 1024) && $navMenuToggle.length; 4629 4630 if (!isDesktopHorizontalMenu) { 4631 e.preventDefault(); 4632 var $subMenu = $(this).find('.sub-menu'); 4633 4634 // Close all other submenus 4635 $hasChildren.not(this).find('.sub-menu').slideUp(200); 4636 4637 // Toggle current submenu 4638 $subMenu.slideToggle(200); 4639 } 4640 }); 4641 4642 $(window).on('resize', function () { 4643 // Reset submenus when switching to desktop (only for horizontal menu) 4644 if ($(window).width() >= 1024) { 4645 $('nav .menu-toggle .sub-menu').css('display', ''); 4646 } 4647 menuLengthCheck(); 4648 positionSubMenus(); 4649 }); 4650 } 4651 4652 // Position submenus horizontally relative to their parent items 4653 function positionSubMenus() { 4654 $hasChildren.each(function () { 4655 var $parent = $(this); // get the parent li 4656 var $subMenu = $parent.find('.sub-menu'); // get the sub menu 4657 4658 if ($subMenu.length) { 4659 // Get position relative to the scrollable container 4660 var parentOffset = $parent.position().left; 4661 4662 // Position relative to parent with proper scoping 4663 $subMenu.css({ 4664 'left': parentOffset + 'px', 4665 }); 4666 } 4667 }); 4668 } 4669 4670 function closeSearch() { 4671 $menuCta.removeClass("active"); 4672 $searchBar.removeClass('open'); 4673 } 4674 4675 function initSearchBar() { 4676 $menuCta.toggleClass('active'); 4677 $searchBar.toggleClass('open'); 4678 } 4679 4680 function toggleBookButtonIcon(object) { 4681 $(object).parent().toggleClass("active"); 4682 4683 if ($menuButton.parent().hasClass('active')) { 4684 $(iconMenuContainer).text(iconMenuClose); 4685 } 4686 else { 4687 $(iconMenuContainer).text(iconMenuDefault); 4688 } 4689 } 4690 4691 function menuLengthCheck() { 4692 var menuContainer = $('nav .menu').outerWidth(true); 4693 4694 totalWidth = 0; 4695 $menuItems.each(function (i, e) { 4696 totalWidth += $(e).outerWidth(true); // adds its width to the total 4697 }); 4698 4699 if ($(window).width() > 1023) { 4700 4701 $menuOuter.css('width', (totalWidth + 1)); // I give it extra 1 pixels for to accommodate different browser rendering issues. 4702 4703 if (totalWidth > menuContainer) { 4704 menuShow(); 4705 } else { 4706 menuHide(); 4707 } 4708 } else { 4709 menuHide(); 4710 } 4711 } 4712 4713 function menuHide() { 4714 $menuOuter.removeAttr("style"); 4715 $menuControls.hide(); 4716 } 4717 4718 function menuShow() { 4719 $menuControls.show(); 4720 } 4721 4722 function clickScrollRight() { 4723 var scrollX = $navigation.scrollLeft(); 4724 $navigation.animate({ scrollLeft: scrollX + 200 }, 300); 4725 } 4726 4727 function clickScrollLeft() { 4728 var scrollX = $navigation.scrollLeft(); 4729 $navigation.animate({ scrollLeft: scrollX - 200 }, 300); 4730 } 4731 4732 4733 4734 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 4735 return { 4736 init: init, 4737 initSearchBar: initSearchBar 4738 }; 4739}(jQuery); 4740window.eviivo = window.eviivo ? window.eviivo : {}; 4741window.eviivo.promos = window.eviivo.promos ? window.eviivo.promos : {}; 4742 4743window.eviivo.promos = function ($) { 4744 4745 var defaultOptions = { 4746 resources: { 4747 apply: "Apply", 4748 unlockSpecials: "Click to unlock your special deal", 4749 enterPromoCode: "Enter your code or member ID" 4750 } 4751 }; 4752 var options; 4753 var $mainContainer; 4754 var $modOverlay; 4755 var searchTrigger; 4756 4757 function init(settings) { 4758 //extend internal settings with external ones 4759 options = $.extend(defaultOptions, settings); 4760 4761 $mainContainer = $(".main"); 4762 $('div.mod-promo button.promo-button-action').on("click", applyPromoButtonHandler); 4763 4764 checkForInvalidPromoCode(); 4765 4766 searchTrigger = options.searchTrigger; 4767 } 4768 4769 function checkForInvalidPromoCode() { 4770 var failure = jQuery("#failed_code_message"); 4771 if (failure.length > 0) { 4772 showPromoCodeInputPopup(failure.val()); 4773 } 4774 } 4775 4776 function applyPromoButtonHandler() { 4777 var bannerType = $(this).data("banner-type"); 4778 if (typeof bannerType === "string") { 4779 switch (bannerType.toLowerCase()) { 4780 case options.bannerTypes.webExclusivePromoCode.toLowerCase(): 4781 showPromoCodeInputPopup(); 4782 break; 4783 case options.bannerTypes.webExclusiveInstantDeal.toLowerCase(): 4784 showInstantDealInputPopup(); 4785 break; 4786 case options.bannerTypes.webExclusivePromoCodeUnlocked.toLowerCase(): 4787 case options.bannerTypes.webExclusivePromoCodeFail.toLowerCase(): 4788 case options.bannerTypes.webExclusiveBannerWarning.toLowerCase(): 4789 removePromotionBanner(); 4790 break; 4791 } 4792 } 4793 } 4794 4795 function removePromotionBanner() { 4796 $("#" + options.queryString.promoCode).remove(); 4797 $("#" + options.queryString.askForPromoInstantDeal).remove(); 4798 //remove from cookies too 4799 var propertyId = $("#prop-id").val(); 4800 if (typeof (propertyId) === "string") { 4801 removePropertyIdFromCookie(propertyId, options.cookie.flags.instantDeal); 4802 removePropertyIdFromCookie(propertyId, options.cookie.flags.promotionCode); 4803 } 4804 4805 searchTrigger(); 4806 } 4807 4808 function removePropertyIdFromCookie(propertyId, cookieName) { 4809 var listOfProperties = window.eviivo.webcore.cookieManager.getCookie(cookieName); 4810 if (listOfProperties != null && listOfProperties.length > 0) { 4811 var json = eviivo.utils.queryStringHelper.getAllKeys(listOfProperties, {}); 4812 eviivo.utils.queryStringHelper.deleteKey(json, "p_" + propertyId.toLowerCase()); 4813 //Sets the cookie to expiry after the session ended (no value b
4813y default does that) this kept the functionallity that was in place before replacing the cookie library 4814 window.eviivo.webcore.cookieManager.setCookie(cookieName, eviivo.utils.queryStringHelper.toString(json, false)); 4815 } 4816 } 4817 4818 //The overlay has that faded background over the entire page, so if we show it and not removing it we will not be able to interact with the page. 4819 //Same case when we already have it opened. 4820 //Just as a protection in case it's already opened, we remove it first and then we re-add it from the template. 4821 function displayPopup(popupPromoCodeTitleText, popupPromoCodeForm, popupCloseCallBack) { 4822 $modOverlay = $mainContainer.find("div.dialogue-outer"); 4823 eviivo.popup.display({ 4824 html: popupPromoCodeForm, 4825 headerTitle: popupPromoCodeTitleText, 4826 bodyClass: "dialogue-medium dialogue-height--auto", 4827 popupCloseCallBack: popupCloseCallBack 4828 }); 4829 } 4830 4831 function displayInstantDealPopup(popupPromoCodeTitleText, popupPromoCodeForm) { 4832 displayPopup(popupPromoCodeTitleText, popupPromoCodeForm); 4833 if (grecaptcha) { 4834 grecaptcha.render('grecaptchaId', 4835 { 4836 'sitekey': options.captcha.siteKey, 4837 'callback': recaptchaReceived 4838 }); 4839 } else { 4840 console.error("Error: could not load Google Recaptcha API."); 4841 } 4842 } 4843 4844 function recaptchaReceived(response) { 4845 if (response) { 4846 //force removal of promo code (in case the entered one gave error and now we want to enter instant deal only) 4847 $("#" + options.queryString.promoCode).remove(); 4848 $("#" + options.queryString.askForPromoInstantDeal).val(true); 4849 window.eviivo.webcore.cookieManager.setCookie('captcha', response, 30); 4850 searchTrigger(); 4851 } else { 4852 console.error("Error: recaptcha response not received."); 4853 } 4854 } 4855 4856 function showInstantDealInputPopup() { 4857 var unlockInstantDealTitle = options.resources.unlockSpecials; 4858 var unlockInstantDealForm = $("<div id='grecaptchaId' class='captcha'></div>"); 4859 4860 displayInstantDealPopup(unlockInstantDealTitle, unlockInstantDealForm); 4861 } 4862 4863 function removePromoCodeFromQueryString() { 4864 window.location.href = window.location.href.removeKeyFromQueryString(options.queryString.promoCode); 4865 } 4866 4867 function showPromoCodeInputPopup(failureMessage) { 4868 var popupPromoCodeTitleText = options.resources.enterPromoCode; 4869 var popupPromoCodeForm = "<input type='text' class='cp-input' id='apply-promo-code-input' placeholder='' value='' /><button class='cp-button' id='apply-promo-code' type='button'>" + options.resources.apply + "</button>"; 4870 4871 if (typeof (failureMessage) === "string" && failureMessage !== "") { 4872 popupPromoCodeForm += '<div class="field-validation-error">' + failureMessage + '</div>'; 4873 4874 displayPopup(popupPromoCodeTitleText, popupPromoCodeForm, removePromoCodeFromQueryString); 4875 4876 } else { 4877 displayPopup(popupPromoCodeTitleText, popupPromoCodeForm); 4878 } 4879 4880 4881 4882 //focus on the input straight 4883 $("#apply-promo-code-input").focus(); 4884 4885 $("#apply-promo-code-input").bind("paste", function (e) { 4886 e.preventDefault(); 4887 $("#apply-promo-code-input").val(e.originalEvent.clipboardData.getData('text').trim()) 4888 }); 4889 } 4890 4891 function onCodeValid(code) { 4892 $("#" + options.queryString.promoCode).val(code); 4893 // add spinner to indicate that someting is happening 4894 searchTrigger(); 4895 } 4896 4897 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 4898 return { 4899 init: init, 4900 onCodeValid: onCodeValid 4901 }; 4902}(jQuery); 4903window.eviivo = window.eviivo ? window.eviivo : {}; 4904window.eviivo.promos = window.eviivo.promos ? window.eviivo.promos : {}; 4905window.eviivo.promos.codeValidator = window.eviivo.promos.codeValidator ? window.eviivo.promos.codeValidator : {}; 4906 4907window.eviivo.promos.codeValidator = function ($) { 4908 var selectors = { 4909 applyPromoCodeButton: "#apply-promo-code", 4910 applyPromoCodeInput: "#apply-promo-code-input", 4911 errorArea: "div.field-validation-error", 4912 popUpApplyPromoCodeButton: "div.mod-dialogueBox #apply-promo-code", 4913 popUpApplyPromoCodeInput: "div.mod-dialogueBox #apply-promo-code-input" 4914 }; 4915 4916 var options; 4917 4918 /* 4919 * IMPORTANT: 4920 * Do NOT add the 'g' flag in this regex. 4921 * 4922 * The 'g' flag makes the regex "remember" where it last stopped searching. 4923 * If you test the same string multiple times, the regex may start in the middle, which can lead to it mistakenly saying a valid code is wrong. 4924 * Removing the 'g' flag ensures that every test starts from the beginning of the string. 4925 */ 4926 var promoCodeValidationRule = new RegExp("^[a-zA-Z0-9\-\_@\#]+$", "i"); 4927 4928 /** 4929 * Validates the promo code entered in the promo code input field. 4930 * 4931 * This function retrieves the promo code value from the input field defined by 4932 * `selectors.applyPromoCodeInput` and removes any existing error styling or messages. 4933 * It then checks if the promo code is empty or fails to meet the pattern defined by
4934 * `promoCodeValidationRule`. If the promo code is invalid, it adds an "invalid" CSS class 4935 * to the input field and inserts an error message after the promo code button. If the promo 4936 * code passes validation, this returns true. 4937 * 4938 * @returns {boolean} Returns true if the promo code is valid; otherwise, false. 4939 */ 4940 function validatePromoCode() { 4941 const $applyInput = $(selectors.applyPromoCodeInput); 4942 const $errorArea = $(selectors.errorArea); 4943 const code = $applyInput.val(); 4944 4945 $applyInput.removeClass("invalid"); 4946 $errorArea.remove(); 4947 4948 let isInvalid = false; 4949 let errorMessage = ""; 4950 4951 if (code === "") { 4952 errorMessage = options.resources.promoValidationEmptyCode; 4953 isInvalid = true; 4954 } else if (promoCodeValidationRule.test(code) === false) { 4955 errorMessage = options.resources.promoValidationMessage; 4956 isInvalid = true; 4957 } 4958 4959 if (isInvalid === true) { 4960 $applyInput.addClass('invalid'); 4961 $("<div class='field-validation-error'>" + errorMessage + "</div>").insertAfter($(selectors.applyPromoCodeButton)); 4962 } else { 4963 return true; 4964 } 4965 4966 return false; 4967 } 4968 4969 /** 4970 * Handles the application of a promo code. 4971 * 4972 * This function is invoked when a guest triggers an event, such as pressing the Enter key (key code 13) 4973 * or performing a left mouse click (which code 1). It validates the promo code using the 4974 * 'validatePromoCode' function. If the promo code is valid and a callback function is provided 4975 * in the 'options' object, the function retrieves the current promo code via 'getCode' 4976 * and passes it to the callback. The function always returns false to prevent any default behavior. 4977 * 4978 * @param {Event} e - The event object triggered by the guest's interaction. 4979 * @returns {boolean} Returns false to stop the default event propagation or true if the promo code is valid. 4980 */ 4981 function applyPromoCodeHandler(e) { 4982 // If the guest clicked on enter (13) or left mouse click (1). 4983 if (e != null && (e.keyCode === 13 || e.which === 1)) { 4984 const isPromoCodeValid = validatePromoCode(); 4985 4986 if (isPromoCodeValid === true && 4987 typeof options.callback === "function") { 4988 options.callback(getCode()); 4989 4990 return true; 4991 } 4992 } 4993 4994 return false; 4995 } 4996 4997 /** 4998 * Retrieves the current promo code entered by the guest. 4999 * 5000 * This function selects the promo code input element defined by 5001 * 'selectors.applyPromoCodeInput' and returns its current value. 5002 * 5003 * @returns {string} The value of the promo code input field. 5004 */ 5005 function getCode() { 5006 const $applyInput = $(selectors.applyPromoCodeInput); 5007 return $applyInput.val(); 5008 } 5009 5010 function init(settings) { 5011 //extend internal settings with external ones 5012 options = $.extend({}, settings); 5013 5014 // These two events are specifically for pop up promos in the search page. 5015 $(document).on("click", selectors.popUpApplyPromoCodeButton, applyPromoCodeHandler); 5016 $(document).on("keyup", selectors.popUpApplyPromoCodeInput, applyPromoCodeHandler); 5017 } 5018 5019 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 5020 return { 5021 init: init 5022 }; 5023}(jQuery); 5024window.eviivo = window.eviivo ? window.eviivo : {}; 5025window.eviivo.announcement = window.eviivo.announcement ? window.eviivo.announcement : {}; 5026 5027window.eviivo.announcement = function ($) { 5028 5029 var options; 5030 var defaultOptions = { 5031 openInNewTab: false 5032 } 5033 var announcementLink; 5034 var searchUrl = ""; 5035 var querystringHelper; 5036 5037 function init(settings) { 5038 options = $.extend(defaultOptions, settings); 5039 searchUrl = options.searchUrl; 5040 announcementLink = $('#promo-code-link'); 5041 5042 announcementLink.on('click', function () { 5043 window.eviivo.webcore.cookieManager.setCookie("announcement_popup", "announcement_" + options.propertyShortName + "=1", 1); 5044 searchUrl = setUrl(); 5045 5046 if (options.linkAddress != "") { 5047 searchUrl = options.linkAddress; 5048 } 5049 5050 if (options.openInNewTab) { 5051 window.open(searchUrl); 5052 return; 5053 } 5054 5055 if (options.promoCode == "") { 5056 window.open(searchUrl, "_self"); 5057 return; 5058 } 5059 5060 window.location.href = searchUrl; 5061 }); 5062 } 5063 5064 function setUrl() { 5065 querystringHelper = eviivo.utils.queryStringHelper; 5066 if (typeof querystringHelper === "undefined") { 5067 console.warn("eviivo.utils.queryStringHelper was not loaded therefore this page might not work properly"); 5068 } 5069 5070 var src = window.location.href; 5071 var originalQueryString = querystringHelper.getAllKeys(src); 5072 var queryStrings = jQuery.extend({}, originalQueryString); 5073 5074 querystringHelper.addKey(queryStrings, "pce", options.promoCode); 5075 5076 return searchUrl += querystringHelper.toString(queryStrings); 5077 } 5078 5079 return { 5080 init: init, 5081 } 5082}(jQuery); 5083window.eviivo = window.eviivo ? window.eviivo : {}; 5084window.eviivo.customPage = window.eviivo.customPage ? window.eviivo.customPage : {}; 5085 5086window.eviivo.customPage = function ($) { 5087 var selectors = { 5088 collapsibleArticles: ".mod-article.collapsible h4" 5089 }; 5090
5091 function init() { 5092 toggleCollapsedClassEvent(); 5093 bookingTermsInit(); 5094 } 5095 5096 5097 /** 5098 * Toggles the 'collapsed' class on click event for collapsible articles. 5099 */ 5100 function toggleCollapsedClassEvent() { 5101 const collapsedClassName = "collapsed"; 5102 5103 jQuery(selectors.collapsibleArticles).on('click', function () { 5104 const elementIndex = jQuery(selectors.collapsibleArticles).index(this); 5105 5106 // Remove 'collapsed' class from all collapsible articles except the clicked one 5107 jQuery(selectors.collapsibleArticles) 5108 .filter(index => index != elementIndex) 5109 .removeClass(collapsedClassName); 5110 5111 jQuery(this).toggleClass(collapsedClassName); 5112 }); 5113 } 5114 5115 function bookingTermsInit() { 5116 5117 jQuery('#bookingTermsAndConditionsLink').on("click", function () { 5118 jQuery.ajax({ 5119 type: "GET", 5120 url: jQuery(this).data("get-popup-data-url"), 5121 dataType: "html", 5122 success: function (response) { 5123 eviivo.popup.display({ 5124 html: response, 5125 bodyClass: "dialogue-large is--scrollable space-pre-line" 5126 }); 5127 }, 5128 error: function (response) { 5129 eviivo.popup.display({ 5130 html: response, 5131 bodyClass: "dialogue-medium is--scrollable" 5132 }); 5133 } 5134 }); 5135 return false; 5136 }); 5137 } 5138 5139 return { 5140 init: init, 5141 } 5142}(jQuery); 5143(function ($, sr) { 5144 5145 // debouncing function from John Hann 5146 // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ 5147 var debounce = function (func, threshold, execAsap) { 5148 var timeout; 5149 5150 return function debounced() { 5151 var obj = this, args = arguments; 5152 function delayed() { 5153 if (!execAsap) 5154 func.apply(obj, args); 5155 timeout = null; 5156 }; 5157 5158 if (timeout) 5159 clearTimeout(timeout); 5160 else if (execAsap) 5161 func.apply(obj, args); 5162 5163 timeout = setTimeout(delayed, threshold || 100); 5164 }; 5165 } 5166 // smartresize 5167 jQuery.fn[sr] = function (fn) { return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); }; 5168 5169})(jQuery, 'smartresize'); 5170 5171window.eviivo = window.eviivo ? window.eviivo : {}; 5172window.eviivo.combinedInventory = window.eviivo.combinedInventory ? window.eviivo.combinedInventory : {}; 5173 5174window.eviivo.combinedInventory = function ($) { 5175 let options, 5176 defaultOptions = { 5177 resources: { 5178 soldout: "Sold Out", 5179 select: "Select" 5180 } 5181 }, 5182 attributes = { 5183 checked: "checked", 5184 id: "id", 5185 name: "name", 5186 disabled: "disabled" 5187 }, 5188 classes = { 5189 checked: "ui-checkboxradio-checked", 5190 active: "ui-state-active" 5191 }, 5192 selectors = { 5193 roomInventory: { 5194 radio: { 5195 items: 'input[type="radio"].s-inventory-item', 5196 groupByName: (name) => `input[type="radio"].s-inventory-item[name='${name}']`, 5197 }, 5198 labelById: (id) => `label[for='${id}']`, 5199 }, 5200 checked: ":checked", 5201 }, 5202 elements = { 5203 roomInventory: { 5204 radio: { 5205 items: $(), 5206 } 5207 }, 5208 }, 5209 events = { 5210 change: "change" 5211 }, 5212 combinedInventoryManager = { 5213 totalRealInventoryAmount: 0, 5214 inventorySelections: [], 5215 unitTypeMap: {} 5216 }; 5217 5218 /** 5219 * Main initialise function. 5220 * @param {any} settings - combined inventory settings. 5221 */ 5222 function init(settings) { 5223 options = $.extend(defaultOptions, settings); 5224 initElements(); 5225 initCombinedInventoryManager(); 5226 initListeners(); 5227 } 5228 5229 /**
5230 * Initialises elements needed for combined inventory. 5231 */ 5232 function initElements() { 5233 elements.roomInventory.radio.items = $(selectors.roomInventory.radio.items); 5234 } 5235 5236 /** 5237 * Initialises the combined inventory manager which holds information needed to manage combined inventory such as: 5238 * total real inventory amount, inventory selection and unit type information (e.g. room type amount consumed). 5239 */ 5240 function initCombinedInventoryManager() { 5241 // Build the combined inventory manager's unit type map, this holds details such as a unit type's consumed amount and real available amount. 5242 elements.roomInventory.radio.items.each(function () { 5243 const data = $(this).data(); 5244 if (data?.rtid && !combinedInventoryManager.unitTypeMap[data.rtid]) { 5245 combinedInventoryManager.unitTypeMap[data.rtid] = { 5246 amountAvailable: data.aa == null ? 0 : data.aa, 5247 amountConsumed: data.ac == null ? 0 : data.ac, 5248 amountRealAvailable: data.ara == null ? 0 : data.ara 5249 }; 5250 } 5251 }); 5252 5253 // JIRA EVDEV-35528 - If the search returned no real availability, use the max inventory count for the property which is the count of all real inventory units for the property. 5254 combinedInventoryManager.totalRealInventoryAmount = options.maxInventory; 5255 5256 // Add each pre-selected inventory item to the inventory manager's selections. 5257 elements.roomInventory.radio.items.each(function () { 5258 const element = $(this); 5259 if (element.is(selectors.checked)) { 5260 combinedInventoryManager.inventorySelections.push({ id: element.prop(attributes.id), amountConsumed: element.data().ac }); 5261 } 5262 }); 5263 5264 // We could already be over the total real inventory amount due to pre-selections, so enforce availability to deal with this. 5265 enforceAvailabilityOnInventoryItems(); 5266 } 5267 5268 /** 5269 * Initialises event listeners needed for managing combined inventory. 5270 */ 5271 function initListeners() { 5272 /** 5273 * Change event handler that will trigger on each room selection. 5274 * @param {any} event - The change event. 5275 */ 5276 const handleRoomSelection = (event) => { 5277 const element = $(event.target); 5278 5279 // Set text to 'selected' for the selection's associated label. 5280 setLabelTextForInputElement(element, options.resources.select); 5281 5282 // Grab the selections id. 5283 const id = element.prop(attributes.id); 5284 5285 // Add the selection to the manager so we can keep track of what is currently selected when working out the availability to display. 5286 combinedInventoryManager.inventorySelections.push({ id: id, amountConsumed: element.data().ac }); 5287 5288 // Enforce the new availability onto the inventory items, passing in the newly selected element's id 5289 // so that we don't enforce availability onto the inventory item that we just selected. 5290 enforceAvailabilityOnInventoryItems(id); 5291 } 5292 5293 // Attach the handler to the change event for each radio button. 5294 elements.roomInventory.radio.items.each(function () { 5295 $(this).off(events.change).on(events.change, handleRoomSelection); 5296 }); 5297 } 5298 5299 /** 5300 * Function to set the text content of a specified DOM element to the provided value. 5301 * @param {jQuery} element - The jQuery element for the input. 5302 * @param {string} value - The text content. 5303 */ 5304 const setLabelTextForInputElement = (element, value) => { 5305 element.siblings(selectors.roomInventory.labelById(element.attr(attributes.id))).text(value); 5306 } 5307 5308 /** 5309 * Returns the inventory selections sorted by the amount consumed in descending order. 5310 * @returns {Array} Sorted array of inventory selections. 5311 */ 5312 const sortSortedSelectionsByConsumed = () => { 5313 return combinedInventoryManager.inventorySelections.sort((a, b) => { 5314 if (a.amountConsumed < b.amountConsumed) { 5315 return 1; 5316 } 5317 if (a.amountConsumed > b.amountConsumed) { 5318 return -1; 5319 } 5320 return 0; 5321 }); 5322 } 5323 5324 /**
5325 * Enforces the current availability onto all inventory items, unless a 'selectedId' is provided, in which case the corresponding inventory item will be skipped. 5326 * @param {string} selectedId - (Optional) An id we wish to exclude from having current availability rules applied to it, used when a new selection has just been made 5327 * to avoid applying current availability to the item that was just selected. 5328 */ 5329 const enforceAvailabilityOnInventoryItems = (selectedId = null) => { 5330 // Firstly, if we have a 'selectedId' we need to remove any existing selection for this selections radio group. 5331 if (selectedId != null) { 5332 const newlySelectedItem = $(`#${selectedId}`); 5333 const currentlySelectedInventory = $(selectors.roomInventory.radio.groupByName(newlySelectedItem.attr(attributes.name))); 5334 5335 currentlySelectedInventory.each(function () { 5336 // Remove/Deselect this inventory selection: 5337 const element = $(this); 5338 const id = element.prop(attributes.id); 5339 5340 // If the selectedId matches the current inventory item's id skip this inventory item as we don't want to remove the item we just selected. 5341 if (id === selectedId) { 5342 return; 5343 } 5344 5345 // 1. Remove the current selection from the inventory manager. 5346 combinedInventoryManager.inventorySelections = combinedInventoryManager.inventorySelections.filter(_ => _.id !== id); 5347 5348 // 2. Set text to 'soldout' for the selection's associated label. 5349 setLabelTextForInputElement(element, options.resources.soldout); 5350 5351 // 3. Uncheck the radio button if it's checked and remove any checked/active classes that may be present. 5352 if (element.is(selectors.checked)) { 5353 element.prop(attributes.checked, false); 5354 element.siblings(selectors.roomInventory 5355 .labelById(id)) 5356 .removeClass([classes.checked, classes.active]); 5357 } 5358 5359 // 4. Disable the input 5360 element.attr(attributes.disabled, attributes.disabled); 5361 }); 5362 } 5363 5364 // Next, check whether the amount consumed by the current selections is valid by summing the amount consumed of all current selections and 5365 // comparing it to the combined inventory manager's total real inventory amount. 5366 let selectionsTotalAmountConsumed = combinedInventoryManager.inventorySelections.reduce((acc, item) => acc + item.amountConsumed, 0); 5367 5368 // If the current selections amount consumed is greater than the total real inventory amount we need to resolve this by deselecting some 5369 // selections until the selection amount consumed is valid again. 5370 if (selectionsTotalAmountConsumed > combinedInventoryManager.totalRealInventoryAmount) { 5371 // Sort the current inventory selections by their amount consumed, this way we will be removing the fewest selections possible. 5372 const sortedSelections = sortSortedSelectionsByConsumed(); 5373 5374 for (i = 0; i < sortedSelections.length; i++) { 5375 // If the selections amount consumed is now less than or equal to the total real inventory amount break out the loop as 5376 // we do not need to deselect anymore inventory. 5377 if (selectionsTotalAmountConsumed <= combinedInventoryManager.totalRealInventoryAmount) { 5378 break; 5379 } 5380 5381 const selection = sortedSelections[i]; 5382 5383 // If a selectedId was provided and it matches the current selection's id skip this inventory item. 5384 if (selectedId != null && selection.id === selectedId) { 5385 continue; 5386 } 5387 5388 const element = $(`#${selection.id}`); 5389 5390 // Remove/Deselect this inventory selection: 5391 5392 // 1. Remove the current selection from the inventory manager.
5393 combinedInventoryManager.inventorySelections = combinedInventoryManager.inventorySelections.filter(_ => _.id !== selection.id); 5394 5395 // 2. Reduce the selections amount consumed by this selection's amount consumed. 5396 selectionsTotalAmountConsumed -= selection.amountConsumed; 5397 5398 // 3. Set text to 'soldout' for the selection's associated label. 5399 setLabelTextForInputElement(element, options.resources.soldout); 5400 5401 // 4. Uncheck the radio button if it's checked and remove any checked/active classes that may be present. 5402 if (element.is(selectors.checked)) { 5403 element.prop(attributes.checked, false); 5404 element.siblings(selectors.roomInventory 5405 .labelById(element.attr(attributes.id))) 5406 .removeClass([classes.checked, classes.active]); 5407 } 5408 5409 // 5. Disable the input 5410 element.attr(attributes.disabled, attributes.disabled); 5411 } 5412 } 5413 5414 // Lastly, enforce availability onto any unselected inventory by checking whether any inventory item that is not selected has an amount consumed 5415 // greater than the remaining available inventory. If it does we need to mark it as sold out, otherwise mark it as selected. 5416 5417 // Calculate the remaining available inventory. 5418 const remainingAvailableInventory = combinedInventoryManager.totalRealInventoryAmount - selectionsTotalAmountConsumed; 5419 5420 elements.roomInventory.radio.items.each(function () { 5421 const element = $(this); 5422 // We only care about unchecked elements here as the checked elements have been dealt with above. 5423 if (!element.is(selectors.checked)) { 5424 const amountConsumed = element.data().ac; 5425 5426 const currentlySelectedInventoryForGroup = $(selectors.roomInventory.radio.groupByName(element.attr(attributes.name))) 5427 .not(element) 5428 .filter(selectors.checked) 5429 .toArray() 5430 .reduce((acc, item) => acc + ($(item).data().ac || 0), 0); 5431 5432 const isSoldOut = amountConsumed > (remainingAvailableInventory + currentlySelectedInventoryForGroup); 5433 5434 // Any other units displayed that have a consumed inventory of HIGHER than the remaining available inventory should be marked as Sold Out. 5435 setLabelTextForInputElement(element, 5436 isSoldOut 5437 ? options.resources.soldout 5438 : options.resources.select); 5439 5440 combinedInventoryManager.inventorySelections = combinedInventoryManager.inventorySelections.filter(_ => _.id !== element.attr(attributes.id)); 5441 5442 // Disable the input if soldout 5443 isSoldOut 5444 ? element.attr(attributes.disabled, attributes.disabled) 5445 : element.removeAttr(attributes.disabled); 5446 } 5447 }); 5448 } 5449 5450 return { 5451 init: init 5452 }; 5453}(jQuery); 5454window.eviivo = window.eviivo ? window.eviivo : {}; 5455window.eviivo.language = window.eviivo.language ? window.eviivo.language : {}; 5456 5457window.eviivo.language = function ($) { 5458 var defaultOptions = { 5459 }; 5460 5461 var options; 5462 var selectors = { 5463 languageOptions: ".mod-language .language-inner span" 5464 } 5465 5466 function init(settings) { 5467 options = $.extend(defaultOptions, settings); 5468 5469 initEvents(); 5470 } 5471 5472 /** 5473 * Attaches click event handlers to all language option elements. 5474 * When a language option is clicked, it invokes onLanguageOptionClick 5475 * with the clicked element to initiate the language change process. 5476 */ 5477 function initEvents() { 5478 // Listen for clicks on any element matching the language options selector 5479 $(selectors.languageOptions).on('click', function () { 5480 onLanguageOptionClick($(this)); 5481 }); 5482 } 5483 5484 /** 5485 * Handles click events on a language option element by sending a request to change 5486 * the application's culture and reloading the page upon success. 5487 * 5488 * @param {jQuery} element - The jQuery-wrapped element that was clicked. It must have 5489 * a data attribute "language-code" containing the target culture code. 5490 */ 5491 function onLanguageOptionClick(element) { 5492 // Retrieve the desired language code from the clicked element 5493 const languageCode = element.data("language-code"); 5494 5495 // Send a POST request to change the culture on the server side 5496 $.post(options.languageChangeUrl, { 5497 cultureCode: languageCode 5498 }, function (data) { 5499 // If the server confirms success, reload the page to apply the new culture 5500 if (data && data.isSuccess === true) { 5501 location.reload(); 5502 } 5503 }) 5504 } 5505 5506 return { 5507 init: init 5508 }; 5509}(jQuery);
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.