(function () {
'use strict';
/* =====================================================
GALERIA
===================================================== */
const gallery =
document.getElementById(
'page-image-gallery'
);
if (
!gallery ||
gallery.dataset.initialized === 'true'
) {
return;
}
gallery.dataset.initialized =
'true';
/* =====================================================
ELEMENTOS
===================================================== */
const mainImage =
gallery.querySelector(
'.page-image-gallery__image'
);
const imageWrapper =
gallery.querySelector(
'.page-image-gallery__image-wrapper'
);
const counter =
gallery.querySelector(
'.page-image-gallery__counter'
);
const thumbnails =
gallery.querySelector(
'.page-image-gallery__thumbnails'
);
const closeControl =
gallery.querySelector(
'.page-image-gallery__close'
);
const previousControl =
gallery.querySelector(
'.page-image-gallery__arrow--previous'
);
const nextControl =
gallery.querySelector(
'.page-image-gallery__arrow--next'
);
const stage =
gallery.querySelector(
'.page-image-gallery__stage'
);
/* =====================================================
CONFIGURAÇÃO
===================================================== */
const EXCLUDED_SELECTOR = [
'#page-image-gallery',
'header',
'footer',
'nav',
'.no-page-gallery',
'.elementor-location-header',
'.elementor-location-footer',
'.elementor-widget-icon',
'.elementor-widget-social-icons',
'.elementor-widget-google_maps',
'.elementor-lightbox',
'.dialog-widget',
'.leaflet-container'
].join(',');
/* =====================================================
ESTADO
===================================================== */
let galleryItems = [];
let currentIndex = 0;
let previousFocusedElement = null;
let touchStartX = 0;
let refreshTimer = null;
/* =====================================================
URL
===================================================== */
function normalizeURL(url) {
if (!url) {
return '';
}
const clean =
String(url)
.trim()
.replace(
/^['"]|['"]$/g,
''
);
try {
return new URL(
clean,
window.location.href
).href;
} catch (error) {
return clean;
}
}
function isImageURL(url) {
if (!url) {
return false;
}
const clean =
String(url)
.split('?')[0]
.split('#')[0];
return /\.(avif|webp|jpe?g|png|gif|svg)$/i
.test(clean);
}
/* =====================================================
CHAVE DE DUPLICADOS
===================================================== */
function getDuplicateKey(url) {
if (!url) {
return '';
}
try {
const parsed =
new URL(
url,
window.location.href
);
let path =
decodeURIComponent(
parsed.pathname
).toLowerCase();
path = path
.replace(
/-\d+x\d+(?=(?:\.[a-z0-9]+){1,2}$)/i,
''
)
.replace(
/-scaled(?=(?:\.[a-z0-9]+){1,2}$)/i,
''
)
.replace(
/-rotated(?=(?:\.[a-z0-9]+){1,2}$)/i,
''
);
return (
parsed.hostname
.toLowerCase() +
path
);
} catch (error) {
return String(url)
.split('?')[0]
.split('#')[0]
.toLowerCase();
}
}
/* =====================================================
SRCSET
===================================================== */
function getLargestSrcsetImage(
srcset
) {
if (!srcset) {
return '';
}
const sources =
srcset
.split(',')
.map(
function (item) {
const parts =
item
.trim()
.split(/\s+/);
return {
url:
parts[0] || '',
width:
parseFloat(
parts[1]
) || 0
};
}
)
.filter(
function (item) {
return item.url;
}
)
.sort(
function (
a,
b
) {
return (
a.width -
b.width
);
}
);
return sources.length
? sources[
sources.length - 1
].url
: '';
}
/* =====================================================
SOURCE
===================================================== */
function getImageSource(image) {
const link =
image.closest(
'a[href]'
);
if (link) {
const href =
link.getAttribute(
'href'
);
if (
isImageURL(
href
)
) {
return normalizeURL(
href
);
}
}
const srcset =
image.getAttribute(
'srcset'
) ||
image.getAttribute(
'data-srcset'
) ||
image.getAttribute(
'data-lazy-srcset'
);
return normalizeURL(
image.getAttribute(
'data-full'
) ||
image.getAttribute(
'data-large-file'
) ||
getLargestSrcsetImage(
srcset
) ||
image.currentSrc ||
image.getAttribute(
'data-src'
) ||
image.getAttribute(
'data-lazy-src'
) ||
image.getAttribute(
'src'
) ||
''
);
}
/* =====================================================
IGNORAR
===================================================== */
function shouldIgnore(
element
) {
if (!element) {
return true;
}
return Boolean(
element.matches(
EXCLUDED_SELECTOR
) ||
element.closest(
EXCLUDED_SELECTOR
)
);
}
/* =====================================================
SVG
===================================================== */
function createSearchIcon() {
const icon =
document.createElement(
'span'
);
icon.className =
'page-gallery-hover-icon';
icon.innerHTML = `
`;
return icon;
}
/* =====================================================
CRIAR HOVER
===================================================== */
function createHover(
holder
) {
if (
!holder ||
shouldIgnore(
holder
)
) {
return;
}
if (
holder.querySelector(
':scope > .page-gallery-hover-overlay'
)
) {
return;
}
const style =
window.getComputedStyle(
holder
);
if (
style.position ===
'static'
) {
holder.style.position =
'relative';
}
if (
style.display ===
'inline'
) {
holder.style.display =
'block';
}
holder.classList.add(
'page-gallery-hover-target'
);
const overlay =
document.createElement(
'span'
);
overlay.className =
'page-gallery-hover-overlay';
overlay.setAttribute(
'aria-hidden',
'true'
);
overlay.appendChild(
createSearchIcon()
);
holder.appendChild(
overlay
);
}
/* =====================================================
HOLDER DE UMA IMAGEM
===================================================== */
function getImageHolder(
image
) {
/*
* CARROSSEL ELEMENTOR
*/
const slide =
image.closest(
'.swiper-slide'
);
if (slide) {
return (
image.closest(
'a'
) ||
image.closest(
'.swiper-slide-inner'
) ||
image.parentElement
);
}
/*
* GALERIA BÁSICA
*/
if (
image.closest(
'.elementor-image-gallery, .elementor-widget-image-gallery, .gallery'
)
) {
return (
image.closest(
'a'
) ||
image.closest(
'.gallery-icon'
) ||
image.closest(
'.gallery-item'
)
);
}
/*
* IMAGEM NORMAL
*/
return (
image.closest(
'a'
) ||
image.parentElement
);
}
/* =====================================================
PREPARAR IMAGEM
===================================================== */
function prepareImage(
image,
key
) {
if (!image) {
return;
}
image.classList.add(
'page-gallery-clickable'
);
image.setAttribute(
'data-page-gallery-key',
key
);
const holder =
getImageHolder(
image
);
if (holder) {
holder.classList.add(
'page-gallery-clickable'
);
holder.setAttribute(
'data-page-gallery-key',
key
);
createHover(
holder
);
}
}
/* =====================================================
CARROSSEL DE IMAGENS
ESTA É A PARTE IMPORTANTE:
Lê data-swiper-slide-index
para recuperar a ordem original
definida no Elementor.
===================================================== */
function collectImageCarousels(
items,
usedKeys
) {
document
.querySelectorAll(
'.elementor-widget-image-carousel'
)
.forEach(
function (carousel) {
if (
shouldIgnore(
carousel
)
) {
return;
}
const slides =
Array.from(
carousel.querySelectorAll(
'.swiper-slide'
)
);
if (
!slides.length
) {
return;
}
/*
* Guardamos uma imagem
* por índice original.
*
* Isto elimina os clones
* criados pelo Swiper.
*/
const indexedSlides =
new Map();
const fallbackSlides =
[];
slides.forEach(
function (
slide,
domIndex
) {
const image =
slide.querySelector(
'img'
);
if (
!image ||
shouldIgnore(
image
)
) {
return;
}
const source =
getImageSource(
image
);
if (!source) {
return;
}
const key =
getDuplicateKey(
source
);
if (!key) {
return;
}
prepareImage(
image,
key
);
/*
* Índice original
* fornecido pelo Swiper.
*/
const rawIndex =
slide.getAttribute(
'data-swiper-slide-index'
);
if (
rawIndex !==
null &&
rawIndex !==
''
) {
const originalIndex =
parseInt(
rawIndex,
10
);
if (
!Number.isNaN(
originalIndex
) &&
!indexedSlides.has(
originalIndex
)
) {
indexedSlides.set(
originalIndex,
{
source:
source,
key:
key,
index:
originalIndex
}
);
}
return;
}
/*
* Fallback para
* carrosséis sem índice.
*/
fallbackSlides.push({
source:
source,
key:
key,
index:
domIndex
});
}
);
let orderedSlides =
[];
/*
* Se temos índices Swiper,
* ordenamos 0,1,2,3...
*/
if (
indexedSlides.size
) {
orderedSlides =
Array
.from(
indexedSlides.values()
)
.sort(
function (
a,
b
) {
return (
a.index -
b.index
);
}
);
} else {
/*
* Sem índices:
* usamos ordem DOM,
* removendo repetidos.
*/
const fallbackKeys =
new Set();
fallbackSlides.forEach(
function (item) {
if (
fallbackKeys.has(
item.key
)
) {
return;
}
fallbackKeys.add(
item.key
);
orderedSlides.push(
item
);
}
);
}
/*
* Adicionamos ao lightbox
* exatamente nessa ordem.
*/
orderedSlides.forEach(
function (item) {
if (
usedKeys.has(
item.key
)
) {
return;
}
usedKeys.add(
item.key
);
items.push({
source:
item.source,
key:
item.key,
sourceType:
'carousel'
});
}
);
}
);
}
/* =====================================================
GALERIAS BÁSICAS
===================================================== */
function collectBasicGalleries(
items,
usedKeys
) {
document
.querySelectorAll(
'.elementor-widget-image-gallery'
)
.forEach(
function (galleryWidget) {
if (
shouldIgnore(
galleryWidget
)
) {
return;
}
galleryWidget
.querySelectorAll(
'.gallery-item img'
)
.forEach(
function (image) {
const source =
getImageSource(
image
);
if (!source) {
return;
}
const key =
getDuplicateKey(
source
);
if (!key) {
return;
}
prepareImage(
image,
key
);
if (
usedKeys.has(
key
)
) {
return;
}
usedKeys.add(
key
);
items.push({
source:
source,
key:
key,
sourceType:
'gallery'
});
}
);
}
);
}
/* =====================================================
OUTRAS IMAGENS
===================================================== */
function collectNormalImages(
items,
usedKeys
) {
document
.querySelectorAll(
'.elementor img'
)
.forEach(
function (image) {
if (
shouldIgnore(
image
)
) {
return;
}
/*
* Já foi tratada por
* um carrossel.
*/
if (
image.closest(
'.elementor-widget-image-carousel'
)
) {
return;
}
/*
* Já foi tratada por
* uma galeria básica.
*/
if (
image.closest(
'.elementor-widget-image-gallery'
)
) {
return;
}
const source =
getImageSource(
image
);
if (!source) {
return;
}
const key =
getDuplicateKey(
source
);
if (!key) {
return;
}
prepareImage(
image,
key
);
if (
usedKeys.has(
key
)
) {
return;
}
usedKeys.add(
key
);
items.push({
source:
source,
key:
key,
sourceType:
'image'
});
}
);
}
/* =====================================================
BACKGROUNDS
===================================================== */
function getBackgroundURL(
element
) {
const background =
window
.getComputedStyle(
element
)
.backgroundImage;
if (
!background ||
background ===
'none'
) {
return '';
}
const match =
background.match(
/url\(["']?(.*?)["']?\)/
);
return match
? normalizeURL(
match[1]
)
: '';
}
function collectBackgrounds(
items,
usedKeys
) {
document
.querySelectorAll(
'.elementor .e-con, .elementor .elementor-element'
)
.forEach(
function (element) {
if (
shouldIgnore(
element
)
) {
return;
}
/*
* Não tratamos o próprio
* widget de carrossel como
* background.
*/
if (
element.closest(
'.elementor-widget-image-carousel'
)
) {
return;
}
const source =
getBackgroundURL(
element
);
if (!source) {
return;
}
const key =
getDuplicateKey(
source
);
if (!key) {
return;
}
element.classList.add(
'page-gallery-clickable'
);
element.setAttribute(
'data-page-gallery-key',
key
);
createHover(
element
);
if (
usedKeys.has(
key
)
) {
return;
}
usedKeys.add(
key
);
items.push({
source:
source,
key:
key,
sourceType:
'background'
});
}
);
}
/* =====================================================
CONSTRUIR LISTA FINAL
===================================================== */
function collectImages() {
const items = [];
const usedKeys =
new Set();
/*
* PRIORIDADE:
*
* 1. Carrosséis
* 2. Galerias
* 3. Imagens normais
* 4. Backgrounds
*/
collectImageCarousels(
items,
usedKeys
);
collectBasicGalleries(
items,
usedKeys
);
collectNormalImages(
items,
usedKeys
);
collectBackgrounds(
items,
usedKeys
);
return items;
}
/* =====================================================
IMAGEM CLICADA
===================================================== */
function findClickedIndex(
element
) {
const target =
element.closest(
'[data-page-gallery-key]'
);
if (!target) {
return -1;
}
const key =
target.getAttribute(
'data-page-gallery-key'
);
if (!key) {
return -1;
}
return galleryItems
.findIndex(
function (item) {
return (
item.key ===
key
);
}
);
}
/* =====================================================
MINIATURAS
===================================================== */
function createThumbnails() {
thumbnails.innerHTML =
'';
galleryItems.forEach(
function (
item,
index
) {
const thumbnail =
document.createElement(
'div'
);
const image =
document.createElement(
'img'
);
thumbnail.className =
'page-image-gallery__thumbnail';
thumbnail.setAttribute(
'role',
'button'
);
thumbnail.setAttribute(
'tabindex',
'0'
);
thumbnail.setAttribute(
'aria-label',
'Abrir imagem ' +
(
index + 1
)
);
image.src =
item.source;
image.alt =
'';
image.loading =
'lazy';
thumbnail.appendChild(
image
);
activateControl(
thumbnail,
function () {
currentIndex =
index;
updateGallery();
}
);
thumbnails.appendChild(
thumbnail
);
}
);
}
/* =====================================================
ATUALIZAR
===================================================== */
function updateGallery() {
if (
!galleryItems.length
) {
return;
}
if (
currentIndex =
galleryItems.length
) {
currentIndex =
0;
}
const item =
galleryItems[
currentIndex
];
mainImage.classList.add(
'is-changing'
);
window.setTimeout(
function () {
mainImage.onload =
function () {
mainImage
.classList
.remove(
'is-changing'
);
};
mainImage.onerror =
function () {
mainImage
.classList
.remove(
'is-changing'
);
};
mainImage.src =
item.source;
mainImage.alt =
'';
if (
mainImage.complete
) {
mainImage
.classList
.remove(
'is-changing'
);
}
},
80
);
counter.textContent =
(
currentIndex + 1
)
+ ' / ' +
galleryItems.length;
thumbnails
.querySelectorAll(
'.page-image-gallery__thumbnail'
)
.forEach(
function (
thumbnail,
index
) {
const active =
index ===
currentIndex;
thumbnail
.classList
.toggle(
'is-active',
active
);
if (active) {
thumbnail.scrollIntoView({
behavior:
'smooth',
block:
'nearest',
inline:
'center'
});
}
}
);
const multiple =
galleryItems.length > 1;
previousControl.hidden =
!multiple;
nextControl.hidden =
!multiple;
thumbnails.hidden =
!multiple;
preloadAdjacent();
}
/* =====================================================
PRELOAD
===================================================== */
function preloadAdjacent() {
if (
galleryItems.length <
2
) {
return;
}
const previous =
(
currentIndex -
1 +
galleryItems.length
) %
galleryItems.length;
const next =
(
currentIndex +
1
) %
galleryItems.length;
[
previous,
next
].forEach(
function (index) {
const preload =
new Image();
preload.src =
galleryItems[
index
].source;
}
);
}
/* =====================================================
ABRIR
===================================================== */
function openGallery(
element
) {
galleryItems =
collectImages();
const index =
findClickedIndex(
element
);
if (
index < 0
) {
return;
}
currentIndex =
index;
previousFocusedElement =
document.activeElement;
createThumbnails();
updateGallery();
gallery.classList.add(
'is-open'
);
gallery.setAttribute(
'aria-hidden',
'false'
);
document
.documentElement
.classList
.add(
'page-image-gallery-open'
);
document
.body
.classList
.add(
'page-image-gallery-open'
);
closeControl.focus();
}
/* =====================================================
FECHAR
===================================================== */
function closeGallery() {
gallery.classList.remove(
'is-open'
);
gallery.setAttribute(
'aria-hidden',
'true'
);
document
.documentElement
.classList
.remove(
'page-image-gallery-open'
);
document
.body
.classList
.remove(
'page-image-gallery-open'
);
mainImage.removeAttribute(
'src'
);
if (
previousFocusedElement &&
typeof previousFocusedElement.focus ===
'function'
) {
previousFocusedElement
.focus();
}
}
/* =====================================================
NAVEGAÇÃO
===================================================== */
function previousImage() {
currentIndex--;
updateGallery();
}
function nextImage() {
currentIndex++;
updateGallery();
}
function activateControl(
element,
callback
) {
element.addEventListener(
'click',
callback
);
element.addEventListener(
'keydown',
function (event) {
if (
event.key ===
'Enter' ||
event.key ===
' '
) {
event.preventDefault();
callback();
}
}
);
}
activateControl(
closeControl,
closeGallery
);
activateControl(
previousControl,
previousImage
);
activateControl(
nextControl,
nextImage
);
/* =====================================================
CLIQUE
===================================================== */
document.addEventListener(
'click',
function (event) {
if (
document.body
.classList
.contains(
'elementor-editor-active'
)
) {
return;
}
if (
gallery.contains(
event.target
)
) {
return;
}
if (
event.ctrlKey ||
event.metaKey ||
event.shiftKey ||
event.altKey
) {
return;
}
const target =
event.target.closest(
'[data-page-gallery-key]'
);
if (!target) {
return;
}
event.preventDefault();
event.stopPropagation();
openGallery(
target
);
},
true
);
/* =====================================================
CLIQUE FORA
===================================================== */
gallery.addEventListener(
'click',
function (event) {
if (
event.target ===
gallery ||
event.target ===
stage ||
event.target ===
imageWrapper
) {
closeGallery();
}
}
);
/* =====================================================
TECLADO
===================================================== */
document.addEventListener(
'keydown',
function (event) {
if (
!gallery
.classList
.contains(
'is-open'
)
) {
return;
}
if (
event.key ===
'Escape'
) {
closeGallery();
}
if (
event.key ===
'ArrowLeft'
) {
previousImage();
}
if (
event.key ===
'ArrowRight'
) {
nextImage();
}
}
);
/* =====================================================
SWIPE MOBILE
===================================================== */
stage.addEventListener(
'touchstart',
function (event) {
touchStartX =
event
.changedTouches[0]
.screenX;
},
{
passive: true
}
);
stage.addEventListener(
'touchend',
function (event) {
const end =
event
.changedTouches[0]
.screenX;
const difference =
end -
touchStartX;
if (
Math.abs(
difference
) 0
) {
previousImage();
} else {
nextImage();
}
},
{
passive: true
}
);
/* =====================================================
INICIALIZAÇÃO
===================================================== */
function refresh() {
galleryItems =
collectImages();
}
function scheduleRefresh() {
window.clearTimeout(
refreshTimer
);
refreshTimer =
window.setTimeout(
refresh,
200
);
}
if (
document.readyState ===
'loading'
) {
document.addEventListener(
'DOMContentLoaded',
refresh
);
} else {
refresh();
}
window.addEventListener(
'load',
refresh
);
window.setTimeout(
refresh,
500
);
window.setTimeout(
refresh,
1500
);
/*
* Apanha o Elementor/Swiper
* depois de inicializar.
*/
const observer =
new MutationObserver(
scheduleRefresh
);
observer.observe(
document.body,
{
subtree:
true,
childList:
true,
attributes:
true,
attributeFilter: [
'src',
'srcset',
'style',
'data-src',
'data-srcset',
'data-swiper-slide-index'
]
}
);
})();
document.addEventListener('DOMContentLoaded', function () {
/* =========================================
ENCONTRAR LINK "RESERVE JÁ"
========================================= */
function getReserveLink(card) {
const links = card.querySelectorAll('a[href]');
/* Primeiro tenta encontrar especificamente
o botão/link que contém "RESERVE JÁ" */
for (const link of links) {
const texto = link.textContent
.trim()
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '');
if (
texto.includes('reserve ja') ||
texto.includes('reservar')
) {
return link;
}
}
/* Caso não encontre pelo texto,
usa o último link existente no card */
if (links.length) {
return links[links.length - 1];
}
return null;
}
/* =========================================
CLIQUE NO CARD
========================================= */
document.addEventListener('click', function (event) {
const card = event.target.closest('.container-link');
if (!card) return;
/* Não executar dentro do editor Elementor */
if (
document.body.classList.contains('elementor-editor-active')
) {
return;
}
/* Se clicares diretamente num link,
botão ou elemento interativo,
deixa-o funcionar normalmente */
if (
event.target.closest(
'a[href], button, input, textarea, select, label'
)
) {
return;
}
const reserveLink = getReserveLink(card);
if (!reserveLink) return;
const url = reserveLink.href;
const target = reserveLink.target;
if (target === '_blank') {
window.open(
url,
'_blank',
'noopener,noreferrer'
);
} else {
window.location.href = url;
}
});
/* =========================================
ACESSIBILIDADE / TECLADO
========================================= */
document.querySelectorAll('.container-link').forEach(function (card) {
const reserveLink = getReserveLink(card);
if (!reserveLink) return;
card.setAttribute('role', 'link');
card.setAttribute('tabindex', '0');
card.addEventListener('keydown', function (event) {
if (event.key !== 'Enter') return;
event.preventDefault();
const link = getReserveLink(card);
if (!link) return;
if (link.target === '_blank') {
window.open(
link.href,
'_blank',
'noopener,noreferrer'
);
} else {
window.location.href = link.href;
}
});
});
});
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla.