/******/ (() => { // webpackBootstrap /******/ "use strict"; ;// ../konstanzoficial/assets/javascripts/components/utilities.js // =============================================================== // ADIÇÃO MANUAL DE ASSET // =============================================================== // Usado para incluir assets no código de forma manual, conforme a necessidade function addAsset(source, onloadCallback) { if (!source || source === '') return console.error(`addAsset: caminho não definido.`) if (source.includes('.js')) { const scriptTag = document.createElement('script') scriptTag.setAttribute('src', source); if (onloadCallback && typeof onloadCallback === 'function') { scriptTag.onload = onloadCallback } return document.body.appendChild(scriptTag) } else if (source.includes('.css')) { const linkTag = document.createElement('link') linkTag.setAttribute('rel', 'stylesheet') linkTag.setAttribute('type', 'text/css') linkTag.setAttribute('href', source) return document.head.appendChild(linkTag) } else { const error = `addAsset: Erro ao criar o asset. Tipo de script não definido, ou não possui tratamento para este tipo de asset.` return console.error(error, source) } } // =============================================================== // BUSCA O CARRINHO ATIVO // =============================================================== async function getCart() { try { const response = await fetch('/carrinho'); const cart = await response.json(); return cart; } catch (error) { console.error('Erro ao buscar os dados do carrinho'); console.error(error); } } // =============================================================== // SERIALIZE ARRAY // =============================================================== function serializeArray(form) { const formData = new FormData(form); const data = {}; for (const [name, value] of formData) { data[name] = value } const formBody = []; for (const key in data) { const encodeKey = encodeURIComponent(key) const encodeValue = encodeURIComponent(data[key]) formBody.push(`${encodeKey}=${encodeValue}`) } return formBody = formBody.join('&') } // =============================================================== // URL ENCODE FORM DATA // =============================================================== function urlencodeFormData(formData) { let string = ''; function encode(s) { return encodeURIComponent(s).replace(/%20/g, '+'); } for (const pair of formData.entries()) { if (typeof pair[1] == 'string') { string += (string ? '&' : '') + encode(pair[0]) + '=' + encode(pair[1]); } } return string; } // =============================================================== // SLIDE TOGGLE // =============================================================== function slideToggle(contentWrapper, content, duration = 500) { let initialHeight = window.getComputedStyle(contentWrapper).height; if (initialHeight == '0px') { return slideDown(contentWrapper, content, duration); } else { return slideUp(contentWrapper, duration); } } // =============================================================== // SLIDE UP // =============================================================== function slideUp(contentWrapper, duration = 500) { contentWrapper.style.height = '0px'; contentWrapper.style.transition = `height ${duration} ease`; } // =============================================================== // SLIDE DOWN // =============================================================== function slideDown(contentWrapper, content, duration = 500) { let innerHeight = content.clientHeight; contentWrapper.style.height = `${innerHeight}px`; contentWrapper.style.transition = `height ${duration} ease`; } // =============================================================== // UPDATE DISCOUNT IN PRODUCT BLOCK // =============================================================== function updatePriceBlock() { const priceEls = document.querySelectorAll('[data-init-price]'); if (priceEls == null) return; priceEls.forEach((priceEl) => { const discount = priceEl.dataset.discountPercent; priceEl.dispatchEvent(new Event('change')); // discount if (discount != '0') { priceEl.style.setProperty('--discount', `'-${discount}%'`); } }); } // =============================================================== // PREÇO POR AJAX // =============================================================== function getPriceProd() { var selectors = document.querySelectorAll('[data-update-price]'); var attr = 'update-price'; if (selectors.length() > 0) { selectors.forEach((selector) => { var prodId = selector.dataset(attr); var url = '/produto/preco/' + prodId; if (prodId != '' && prodId != null) { $.ajax({ url: url, type: 'GET', }) .done((resp) => { selector.innerHTML = resp; }) .fail((resp) => { console.error(resp); }); } }); } } // =============================================================== // DEBOUNCE // =============================================================== /* Debounce retorna uma função que enquanto continuar sendo chamada não é executada A função só será executada quando para de ser chamada por N milisegundos Útil para melhorar a performance de códigos que são executados muitas vezes por segundo, como o $(window).resize() Ex: $(window).resize(debounce(function() { // código a ser executado }, 500)) No exemplo acima a função só será executada 500ms depois do último resize Abra o link abaixo e redimensione a janela branca e acompanhe o output do console Exemplo codepen: https://codepen.io/valkervieira/pen/oNgqyWY Um caso comum de uso é em lojas onde a seleção de um filtro na página de tag recarrega automáticamente a página Com o debounce o usuário pode escolher vários filtros rapidamente e a página só recarrega quando parar de escolher */ function debounce(func, wait, immediate) { var timeout; immediate || (immediate = true); return function () { var context = this, args = arguments; var later = function () { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; } // =============================================================== // THROTTLE // =============================================================== /* Throttle diminui a frequencia que uma função é executada Enquanto no debounce a função só é executada quando para de ser chamada, no throttle ela continua sendo executada só que em um intervalo mínimo de N milisegundos (default = 250) Ex: $(window).resize(throttle() { // código a ser executado }, 500) No exemplo acima a função resize é chamada várias vezes por segundo mas só é executada 1 vez a cada 500ms Abra o link abaixo, redimensione a janela branca e acompanhe o console Exemplo codepen: https://codepen.io/valkervieira/pen/yLyKEPW Um caso comum de uso é checar se o scroll passou de um determinado ponto, para fixar um header ou alterar algum elemento do DOM */ function throttle(fn, threshhold, scope) { threshhold || (threshhold = 250); var last, deferTimer; return function () { var context = scope || this; var now = +new Date(), args = arguments; if (last && now < last + threshhold) { // hold on to it clearTimeout(deferTimer); deferTimer = setTimeout(function () { last = now; fn.apply(context, args); }, threshhold); } else { last = now; fn.apply(context, args); } }; } // =============================================================== // FORMAT MONEY // =============================================================== function formatMoney(value) { // FORMATA UM VALOR return ( 'R$ ' + value .toFixed(2) .replace('.', ',') .replace(/(\d)(?=(\d{3})+\,)/g, '$1.') ); } // =============================================================== // FORMAT VALUE // =============================================================== const formatValue = function (value = '') { let parsedValue = value; if (typeof value === 'number') { parsedValue = value.toFixed(2).toString(); } return parsedValue.replace('.', ','); }; // =============================================================== // VALIDA QUANTIDADE // =============================================================== function validateQuantity(_val) { // VALIDA SE A QUANTIDADE INFORMADA É UM NÚMERO if (!isNaN(_val)) { if (parseInt(_val) > 0) { return true; } } return false; } // =============================================================== // CLEAR NUMBER // =============================================================== function getClearNumber(_val) { // RETORNA UM NÚMERO LIMPO COMO INT if (!isNaN(_val)) { var clearNumber = parseInt(_val); return clearNumber; } return false; } // =============================================================== // BUSCA // =============================================================== function setSearch() { const toggleButton = document.querySelectorAll('[data-toggle-search]'); const search = document.querySelector('[data-search-container]'); const input = document.querySelector('[data-search-input]'); toggleButton.length > 0 && toggleButton.forEach(button => { button.addEventListener('click', () => { if (search.classList.contains('open')) { search.classList.remove('open'); } else { search.classList.add('open'); setTimeout(() => { input.focus() }, 400); } }) }) } ;// ../konstanzoficial/assets/javascripts/common/cartDrawer.js const CartDrawer = { root: document.querySelector('#component-cart-drawer-root'), buttons: document.querySelectorAll('[data-toggle-cart]'), countWrapper: document.querySelector('[data-cart-count]'), settings: window.cartDrawerSettings || false, setCartDrawer: function() { const { settings, root } = CartDrawer; if (!root || !settings) return; // Define frete grátis const freeShipping = (settings.freeShippingValue > 1) ? settings.freeShippingValue : 0 // Inicia o componente const componentCartDrawer = new Vnda.Component.CartDrawer({ anchor: 'right', display: 'list', startOpen: false, titleCart: 'Carrinho de compras', disableShippingCalculation: true, freeShipping }); // Renderiza o componente componentCartDrawer.render(root); // Salva instância para acesso global window.cartDrawerSettings.instance = componentCartDrawer CartDrawer.settings = window.cartDrawerSettings // dispara evento de carregamento, escutado por CartDrawer.show() root.dispatchEvent(new Event('vnda:cart-drawer-loaded')) }, loadComponent: function() { const { settings } = CartDrawer; addAsset(settings.script, CartDrawer.setCartDrawer); addAsset(settings.styles); }, handleCartButton: function(button) { // Evita múltiplos cliques caso o carrinho precisa ser instanciado primeiro if (button.classList.contains('-loading')) { return; } // Abre o cart drawer button.classList.add('-loading'); CartDrawer.show(() => { button.classList.remove('-loading') }) }, show: function (callback) { const { root } = CartDrawer; // No mobile, fecha o menu primeiro if (window.mmenu) window.mmenu.close() // Instancia o componente, caso ainda não exista if (!CartDrawer.settings.instance) CartDrawer.loadComponent(); // Observa criação da instância inicial, caso não tenha if (CartDrawer.settings.instance === false) { root.addEventListener('vnda:cart-drawer-loaded', () => { CartDrawer.settings.instance.open() if (typeof callback === 'function') callback() }) } else { // Já possui cart drawer instanciado, retorna abertura CartDrawer.settings.instance.open(); if (typeof callback === 'function') callback() } }, getCartItens: async function () { try { const response = await fetch('/carrinho/itens'); const itens = await response.json() return itens; } catch (error) { console.error('Erro ao buscar a quantidade de produtos do carrinho'); console.error(error); } }, updateCartCount: async function (_itemsCount = null) { let items = _itemsCount; if (_itemsCount == null) items = await CartDrawer.getCartItens(); this.countWrapper.innerHTML = items; }, init: function () { const _this = this; const { buttons } = _this; // Atualiza o contador de itens do carrinho _this.updateCartCount(); if (buttons.length > 0) buttons.forEach(button => { button.addEventListener('click', () => { _this.handleCartButton(button) }) }) }, }; /* harmony default export */ const cartDrawer = (CartDrawer); ;// ../konstanzoficial/assets/javascripts/common/footer.js const Footer = { handleSubmenu: function () { // toggle menu footer nivel 1 const menus = document.querySelectorAll( '[data-action="toggle-menu-footer"]' ); menus.forEach((menu) => { menu.addEventListener('click', () => { menu.classList.toggle('open'); }); }); }, init: function () { const _this = this; _this.handleSubmenu(); }, }; /* harmony default export */ const footer = (Footer); ;// ../konstanzoficial/assets/javascripts/common/header.js const Header = { lastScrollTop: -1, scrollTop: window.pageYOffset, header: document.querySelector('#header'), setScroll: function (scrollTop, lastScrollTop) { const _this = this; if (scrollTop <= 0) { _this.header.classList.add('scroll-up'); _this.header.classList.remove('scroll-down'); _this.header.classList.add('on-top'); } else { if (_this.header.classList.contains('on-top')) { _this.header.classList.remove('on-top'); } if (scrollTop > lastScrollTop) { _this.header.classList.add('scroll-down'); _this.header.classList.remove('scroll-up'); } else { _this.header.classList.add('scroll-up'); _this.header.classList.remove('scroll-down'); } } }, init: function () { const _this = this; const { lastScrollTop, scrollTop } = _this; _this.setScroll(scrollTop, lastScrollTop); // cuida quando a página scrolla pra atualizar o header window.addEventListener('scroll', function () { const newSt = window.pageYOffset; _this.setScroll(newSt, lastScrollTop); _this.lastScrollTop = newSt; }); }, }; /* harmony default export */ const header = (Header); ;// ../konstanzoficial/node_modules/mmenu-light/esm/modules/match-media-toggler/index.js /** * Class for a match media toggler. */ var MmToggler = /** @class */ (function () { /** * Create the match media. * * @param {string} mediaquery Media query to use. */ function MmToggler(mediaquery) { var _this = this; this.listener = function (evnt) { (evnt.matches ? _this.matchFns : _this.unmatchFns).forEach(function (listener) { listener(); }); }; this.toggler = window.matchMedia(mediaquery); this.toggler.addListener(this.listener); this.matchFns = []; this.unmatchFns = []; } /** * Add a function to the list, * also fires the added function. * * @param {Function} match Function to fire when the media query matches. * @param {Function} unmatch Function to fire when the media query does not match. */ MmToggler.prototype.add = function (match, unmatch) { this.matchFns.push(match); this.unmatchFns.push(unmatch); (this.toggler.matches ? match : unmatch)(); }; return MmToggler; }()); /* harmony default export */ const match_media_toggler = (MmToggler); ;// ../konstanzoficial/node_modules/mmenu-light/esm/modules/helpers.js /** * Convert a list to an array. * * @param {NodeList|HTMLCollection} list The list or collection to convert into an array. * @return {array} The array. */ var r = function (list) { return Array.prototype.slice.call(list); }; /** * Find elements in the given context. * * @param {string} selector The query selector to search for. * @param {HTMLElement} [context=document] The context to search in. * @return {HTMLElement[]} The found list of elements. */ var helpers_$ = function (selector, context) { return r((context || document).querySelectorAll(selector)); }; ;// ../konstanzoficial/node_modules/mmenu-light/esm/modules/sliding-panels-navigation/index.js var prefix = 'mm-spn'; /** * Class for navigating in a mobile menu. */ var MmSlidingPanelsNavigation = /** @class */ (function () { /** * Class for navigating in a mobile menu. * * @param {HTMLElement} node HTMLElement for the menu. * @param {string} title The title for the menu. * @param {string} selectedClass The class for selected listitems. * @param {boolean} slidingSubmenus Whether or not to use sliding submenus. * @param {string} theme The color scheme for the menu. */ function MmSlidingPanelsNavigation(node, title, selectedClass, slidingSubmenus, theme) { this.node = node; this.title = title; this.slidingSubmenus = slidingSubmenus; this.selectedClass = selectedClass; // Add classname. this.node.classList.add(prefix); this.node.classList.add(prefix + "--" + theme); this.node.classList.add(prefix + "--" + (this.slidingSubmenus ? 'navbar' : 'vertical')); this._setSelectedl(); this._initAnchors(); } Object.defineProperty(MmSlidingPanelsNavigation.prototype, "prefix", { /** Prefix for the class. */ get: function () { return prefix; }, enumerable: false, configurable: true }); /** * Open the given panel. * * @param {HTMLElement} panel Panel to open. */ MmSlidingPanelsNavigation.prototype.openPanel = function (panel) { /** Parent LI for the panel. */ var listitem = panel.parentElement; // Sliding submenus if (this.slidingSubmenus) { /** Title above the panel to open. */ var title_1 = panel.dataset.mmSpnTitle; // Opening the main level UL. if (listitem === this.node) { this.node.classList.add(prefix + "--main"); } // Opening a sub level UL. else { this.node.classList.remove(prefix + "--main"); // Find title from parent LI. if (!title_1) { r(listitem.children).forEach(function (child) { if (child.matches('a, span')) { title_1 = child.textContent; } }); } } // Use the default title. if (!title_1) { title_1 = this.title; } // Set the title. this.node.dataset.mmSpnTitle = title_1; // Unset all panels from being opened and parent. helpers_$("." + prefix + "--open", this.node).forEach(function (open) { open.classList.remove(prefix + "--open"); open.classList.remove(prefix + "--parent"); }); // Set the current panel as being opened. panel.classList.add(prefix + "--open"); panel.classList.remove(prefix + "--parent"); // Set all parent panels as being parent. var parent_1 = panel.parentElement.closest('ul'); while (parent_1) { parent_1.classList.add(prefix + "--open"); parent_1.classList.add(prefix + "--parent"); parent_1 = parent_1.parentElement.closest('ul'); } } // Vertical submenus else { /** Whether or not the panel is currently opened. */ var isOpened = panel.matches("." + prefix + "--open"); // Unset all panels from being opened and parent. helpers_$("." + prefix + "--open", this.node).forEach(function (open) { open.classList.remove(prefix + "--open"); }); // Toggle the current panel. panel.classList[isOpened ? 'remove' : 'add'](prefix + "--open"); // Set all parent panels as being opened. var parent_2 = panel.parentElement.closest('ul'); while (parent_2) { parent_2.classList.add(prefix + "--open"); parent_2 = parent_2.parentElement.closest('ul'); } } }; /** * Initiate the selected listitem / open the current panel. */ MmSlidingPanelsNavigation.prototype._setSelectedl = function () { /** All selected LIs. */ var listitems = helpers_$('.' + this.selectedClass, this.node); /** The last selected LI. */ var listitem = listitems[listitems.length - 1]; /** The opened UL. */ var panel = null; if (listitem) { panel = listitem.closest('ul'); } if (!panel) { panel = this.node.querySelector('ul'); } this.openPanel(panel); }; /** * Initialize the click event handlers. */ MmSlidingPanelsNavigation.prototype._initAnchors = function () { var _this = this; /** * Clicking an A in the menu: prevent bubbling up to the LI. * * @param {HTMLElement} target The clicked element. * @return {boolean} handled Whether or not the event was handled. */ var clickAnchor = function (target) { if (target.matches('a')) { return true; } return false; }; /** * Click a LI or SPAN in the menu: open its submenu (if present). * * @param {HTMLElement} target The clicked element. * @return {boolean} Whether or not the event was handled. */ var openSubmenu = function (target) { /** Parent listitem for the submenu. */ var listitem; // Find the parent listitem. if (target.closest('span')) { listitem = target.parentElement; } else if (target.closest('li')) { listitem = target; } else { listitem = false; } if (listitem) { r(listitem.children).forEach(function (panel) { if (panel.matches('ul')) { _this.openPanel(panel); } }); return true; } return false; }; /** * Click the menu (the navbar): close the last opened submenu. * * @param {HTMLElement} target The clicked element. * @return {boolean} Whether or not the event was handled. */ var closeSubmenu = function (target) { /** The opened ULs. */ var panels = helpers_$("." + prefix + "--open", target); /** The last opened UL. */ var panel = panels[panels.length - 1]; if (panel) { /** The second to last opened UL. */ var parent_3 = panel.parentElement.closest('ul'); if (parent_3) { _this.openPanel(parent_3); return true; } } return false; }; this.node.addEventListener('click', function (evnt) { var target = evnt.target; var handled = false; handled = handled || clickAnchor(target); handled = handled || openSubmenu(target); handled = handled || closeSubmenu(target); if (handled) { evnt.stopImmediatePropagation(); } }); }; return MmSlidingPanelsNavigation; }()); /* harmony default export */ const sliding_panels_navigation = (MmSlidingPanelsNavigation); ;// ../konstanzoficial/node_modules/mmenu-light/esm/modules/offcanvas-drawer/index.js var offcanvas_drawer_prefix = 'mm-ocd'; /** * Class for off-canvas behavior. */ var MmOffCanvasDrawer = /** @class */ (function () { /** * Class for off-canvas drawer. * * @param {HTMLElement} [node] The element to put in the drawer. * @param {String} [position=left] The position of the drawer, can be "left" or "right". */ function MmOffCanvasDrawer(node, position) { var _this = this; if (node === void 0) { node = null; } // Create the wrapper. this.wrapper = document.createElement('div'); this.wrapper.classList.add("" + offcanvas_drawer_prefix); this.wrapper.classList.add(offcanvas_drawer_prefix + "--" + position); // Create the drawer. this.content = document.createElement('div'); this.content.classList.add(offcanvas_drawer_prefix + "__content"); this.wrapper.append(this.content); // Create the backdrop. this.backdrop = document.createElement('div'); this.backdrop.classList.add(offcanvas_drawer_prefix + "__backdrop"); this.wrapper.append(this.backdrop); // Add the nodes to the
. document.body.append(this.wrapper); if (node) { this.content.append(node); } // Click the backdrop. var close = function (evnt) { _this.close(); evnt.stopImmediatePropagation(); }; this.backdrop.addEventListener('touchstart', close, { passive: true }); this.backdrop.addEventListener('mousedown', close, { passive: true }); } Object.defineProperty(MmOffCanvasDrawer.prototype, "prefix", { /** Prefix for the class. */ get: function () { return offcanvas_drawer_prefix; }, enumerable: false, configurable: true }); /** * Open the drawer. */ MmOffCanvasDrawer.prototype.open = function () { this.wrapper.classList.add(offcanvas_drawer_prefix + "--open"); document.body.classList.add(offcanvas_drawer_prefix + "-opened"); }; /** * Close the drawer. */ MmOffCanvasDrawer.prototype.close = function () { this.wrapper.classList.remove(offcanvas_drawer_prefix + "--open"); document.body.classList.remove(offcanvas_drawer_prefix + "-opened"); }; return MmOffCanvasDrawer; }()); /* harmony default export */ const offcanvas_drawer = (MmOffCanvasDrawer); ;// ../konstanzoficial/node_modules/mmenu-light/esm/core/index.js /** * Class for a lightweight mobile menu. */ var MmenuLight = /** @class */ (function () { /** * Create a lightweight mobile menu. * * @param {HTMLElement} menu HTML element for the menu. * @param {string} [mediaQuery='all'] Media queury to match for the menu. */ function MmenuLight(menu, mediaQuery) { if (mediaQuery === void 0) { mediaQuery = 'all'; } // Store the menu node. this.menu = menu; // Create the toggler instance. this.toggler = new match_media_toggler(mediaQuery); } /** * Add navigation for the menu. * * @param {object} options Options for the navigation. */ MmenuLight.prototype.navigation = function (options) { var _this = this; // Only needs to be done ones. if (!this.navigator) { options = options || {}; var _a = options.title, title = _a === void 0 ? 'Menu' : _a, _b = options.selectedClass, selectedClass = _b === void 0 ? 'Selected' : _b, _c = options.slidingSubmenus, slidingSubmenus = _c === void 0 ? true : _c, _d = options.theme, theme = _d === void 0 ? 'light' : _d; this.navigator = new sliding_panels_navigation(this.menu, title, selectedClass, slidingSubmenus, theme); // En-/disable this.toggler.add(function () { return _this.menu.classList.add(_this.navigator.prefix); }, function () { return _this.menu.classList.remove(_this.navigator.prefix); }); } return this.navigator; }; /** * Add off-canvas behavior to the menu. * * @param {object} options Options for the off-canvas drawer. */ MmenuLight.prototype.offcanvas = function (options) { var _this = this; // Only needs to be done ones. if (!this.drawer) { options = options || {}; var _a = options.position, position = _a === void 0 ? 'left' : _a; this.drawer = new offcanvas_drawer(null, position); /** Original location in the DOM for the menu. */ var orgLocation_1 = document.createComment('original menu location'); this.menu.after(orgLocation_1); // En-/disable this.toggler.add(function () { // Move the menu to the drawer. _this.drawer.content.append(_this.menu); }, function () { // Close the drawer. _this.drawer.close(); // Move the menu to the original position. orgLocation_1.after(_this.menu); }); } return this.drawer; }; return MmenuLight; }()); /* harmony default export */ const core = (MmenuLight); ;// ../konstanzoficial/node_modules/mmenu-light/src/mmenu-light.js /*! * Mmenu Light * mmenujs.com/mmenu-light * * Copyright (c) Fred Heusschen * www.frebsite.nl * * License: CC-BY-4.0 * http://creativecommons.org/licenses/by/4.0/ */ // The module // Export module /* harmony default export */ const mmenu_light = (core); // Global namespace window.MmenuLight = core; ;// ../konstanzoficial/assets/javascripts/common/menuMobile.js const MenuMobile = { setMenuMobile: function () { const mmenuWrapper = document.querySelector('#mm-menu-mobile'); const mmenu = new mmenu_light(mmenuWrapper); //precisa do navigation e do drawer const navigator = mmenu.navigation({ title: 'Menu', }); const drawer = mmenu.offcanvas(); document .querySelector('[data-action="open-menu-mobile"]') .addEventListener('click', (e) => { e.preventDefault(); drawer.open(); }); document .querySelector('[data-action="close-menu-mobile"]') .addEventListener('click', (e) => { e.preventDefault(); drawer.close(); }); window.mmenu = drawer; }, init: function () { const _this = this; _this.setMenuMobile(); }, }; /* harmony default export */ const menuMobile = (MenuMobile); ;// ../konstanzoficial/assets/javascripts/components/popup.js // Javascript genérico para qualquer popup que precise ser criado na loja const Popup = { togglePopup: function (popupType) { document .querySelector(`[data-popup="${popupType}"].popup`) .classList.toggle('-open'); }, init: function () { var _this = this; var toggleButtons = document.querySelectorAll( '[data-action="toggle-popup"]' ); // toggle popup toggleButtons.forEach((button) => { button.addEventListener('click', (e) => { e.preventDefault(); let popupType = button.dataset.popup; _this.togglePopup(popupType); }); }); }, }; /* harmony default export */ const popup = (Popup); ;// ../konstanzoficial/assets/javascripts/common/webforms.js const Webforms = { submitWebForm: async function (form, email) { const formData = serializeArray(form); const textBtnDefault = form.querySelector('.action').innerHTML; if (typeof email == 'undefined' || email == '') { email = form.querySelector('[name="email"]').value; } form.querySelector('[name="reply_to"]').value = email; if (!form.classList.contains('sending')) { form.classList.add('sending'); form.classList.remove('error'); form.querySelector('.action').innerHTML = 'Enviando'; try { const response = await fetch('/webform', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', }, body: formData }) if (response.ok) { console.info('Formulário enviado com sucesso'); form.classList.add('sent'); form.querySelector('.msg-success').classList.add('-visible'); form.querySelector('.action').innerHTML = 'Enviado'; form.reset(); setTimeout(function () { form.classList.remove('sent'); form.querySelector('.msg-success').classList.remove('-visible'); form.querySelector('.action').innerHTML = textBtnDefault; }, 3500); } else { const text = await response.text(); console.error(`Ocorreu um erro no envio do formulário. Erro ${response.status}`); console.error(text); form.classList.add('error'); form.querySelector('.msg-error').classList.add('-visible'); form.querySelector('.action').innerHTML = 'Falha ao enviar'; setTimeout(function () { form.classList.remove('error'); form.querySelector('.msg-error').classList.remove('-visible'); form.querySelector('.action').innerHTML = textBtnDefault; }, 3500); } } catch (error) { console.error('Erro ao enviar o webform'); console.error(error); form.classList.add('error'); form.querySelector('.msg-error').classList.add('-visible'); form.querySelector('.action').innerHTML = 'Falha ao enviar'; setTimeout(function () { form.classList.remove('error'); form.querySelector('.msg-error').classList.remove('-visible'); form.querySelector('.action').innerHTML = textBtnDefault; }, 3500); } form.classList.remove('sending'); } }, submitNotify: async function (form) { const formData = serializeArray(form); const btnTextDefault = form.querySelector('.action').innerHTML; if (!form.classList.contains('sending')) { form.classList.remove('error'); form.classList.add('sending'); form.querySelector('.action').innerHTML = 'Enviando'; try { const response = await fetch('/lista_de_espera', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', }, body: formData }) if (response.ok) { form.classList.remove('sending'); // Envia um webform de aviso de cadastro console.log('Lista de espera enviada com sucesso'); Webforms.submitWebForm(form); } else { const text = await response.text(); console.error(`Ocorreu um erro ao enviar o formulário de Lista de Espera. Erro ${response.status}`) console.error(text); form.classList.add('error'); form.querySelector('.msg-error').classList.add('-visible'); form.querySelector('.action').innerHTML = 'Falha ao enviar'; setTimeout(() => { form.classList.remove('error'); form.querySelector('.msg-error').classList.remove('-visible'); form.querySelector('.action').innerHTML = btnTextDefault; }, 3500); } } catch (error) { console.error('Falha ao enviar lista de espera. Verificar integração.'); console.error(error); form.classList.add('error'); form.querySelector('.msg-error').classList.add('-visible'); form.querySelector('.action').innerHTML = 'Falha ao enviar'; setTimeout(() => { form.classList.remove('error'); form.querySelector('.msg-error').classList.remove('-visible'); form.querySelector('.action').innerHTML = btnTextDefault; }, 3500); } form.classList.remove('sending'); } }, showMessage: function (input, form) { const alertMessage = form.querySelector('[data-msg]'); alertMessage.classList.add('-visible'); alertMessage.innerText = 'Preencha os campos obrigatórios'; input.classList.add('-emphasis') setTimeout(() => { alertMessage.classList.remove('-visible'); alertMessage.innerText = ''; input.classList.remove('-emphasis') }, 2000) }, validateForm: function (vndaInput, form) { let submitAllowed = true; // Permite o envio se o input vnda estiver vazio if (vndaInput.value == '') { vndaInput.setAttribute('required', false); // Valida se os campos obrigatórios estão preenchidos const requiredFields = form.querySelectorAll('[required]:not([required=false])'); requiredFields.forEach(input => { if (input.value == '') { submitAllowed = false; Webforms.showMessage(input, form); } if (input.type == 'checkbox' && input.checked == false) { submitAllowed = false; Webforms.showMessage(input, form); } }) if (submitAllowed) form.id == 'form-notify' ? Webforms.submitNotify(form) : Webforms.submitWebForm(form) } }, setWebForms: function () { const webForms = document.querySelectorAll('[data-webform]'); webForms.length > 0 && webForms.forEach(form => { const button = form.querySelector('button'); const fieldset = form.querySelector('fieldset'); // Desabilita os campos do formulário, através do fieldset, quando o input vnda recebe algum valor let vndaInput = form.querySelector('input[name="vnda"]'); vndaInput.addEventListener('input', () => fieldset.setAttribute('disabled', true)) // Realiza o envio através do clique e da tecla enter sobre o botão do formulário button && button.addEventListener('click', () => Webforms.validateForm(vndaInput, form)); button && button.addEventListener('keypress', (e) => { if (e.key === 'Enter') Webforms.validateForm(vndaInput, form); }) }) }, init: function () { const _this = this; _this.setWebForms(); }, }; /* harmony default export */ const webforms = (Webforms); ;// ../konstanzoficial/assets/javascripts/components/store.js const Store = { openCartDrawer: true, openCartDrawerMobile: true, addProduct: function(form, parent, callback) { // Se tiver customizações, ajusta campos de texto que não foram preenchidos const customizations = form.querySelectorAll('[data-customization]') const quantity = Number(form.querySelector('[name="quantity"]').value) if (customizations.length > 0) customizations.forEach(custom => { const type = custom.getAttribute('type') if (type !== 'checkbox' || type !== 'radio') { if (custom.value === '') { custom.setAttribute('disabled', true) } } }) if (customizations.length > 0 && quantity > 1) { // Item com personalização e com quantidade alta Store.addItemsCustom(form, parent, callback) } else { // Item com quantidade 1, com ou sem personalização Store.addItem(form, parent, callback) } }, addItemsCustom: async function(form, parent, callback) { const _this = this; const btnComprar = parent.querySelector('[data-action="add-cart"]'); const boxResponse = parent.querySelector('[data-form-product] .msg-response:not(.resp-validate)'); const quantity = Number(form.querySelector('[name="quantity"]').value) let hasCustoms = false let customs = {} // Prepara o item e suas personalizações let data = { items: [ { sku: form.querySelector('[name="sku"]').value, quantity, customizations: [] } ]} const customizations = form.querySelectorAll('[data-customization]') if (customizations.length > 0) customizations.forEach(custom => { const disabled = custom.getAttribute('disabled') const type = custom.getAttribute('type') let checked = true; if (type !== 'text') checked = custom.checked if (checked && disabled !== 'true') { hasCustoms = true if (type === 'text') { customs[custom.getAttribute('data-customization-name')] = custom.value } else { customs[custom.getAttribute('data-customization-name')] = custom.getAttribute('value') } } }) if (hasCustoms) { for (let index = 0; index < quantity; index++) { data.items[0].customizations.push(customs) } } const json_data = JSON.stringify(data) console.info('addItemsCustom'); // console.log(json_data) // Envia o produto com suas personalizações if (!btnComprar.classList.contains('-adding')) { btnComprar.classList.add('-adding') try { const response = await fetch('/carrinho/adicionar/kit', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: json_data, }); const html = await response.text(); if (response.ok) { console.log('addItemsCustom - success'); if (typeof callback == 'function') { callback('produto-adicionado', html, boxResponse, form); } else { _this.addItemResult('produto-adicionado', html, boxResponse, form); } } else { console.log('addItemsCustom - server não reconheceu adição'); if (typeof callback == 'function') { callback('erro-adicionar', error, boxResponse, form); } else { _this.addItemResult('erro-adicionar', error, boxResponse, form); } } } catch (error) { console.log('addItemsCustom - error'); console.error(error); if (typeof callback == 'function') { callback('erro-adicionar', error, boxResponse, form); } else { _this.addItemResult('erro-adicionar', error, boxResponse, form); } } customizations.forEach(custom => { custom.removeAttribute('disabled') }) btnComprar.classList.remove('-adding'); } }, addItem: async function (form, parent, callback) { const _this = this; const btnComprar = parent.querySelector('[data-action="add-cart"]'); const urlAdd = '/carrinho/adicionar'; const formData = urlencodeFormData(new FormData(form)); const boxResponse = parent.querySelector('[data-form-product] .msg-response:not(.resp-validate)'); console.info('addItem'); // console.info(formData); if (!btnComprar.classList.contains('-adding')) { btnComprar.classList.add('-adding'); try { const response = await fetch(urlAdd, { method: 'POST', headers: { 'Accept': 'application/json, text/javascript, */*; q=0.0', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', }, body: formData, }); const html = await response.text(); console.log('addItem - success'); if (typeof callback == 'function') { callback('produto-adicionado', html, boxResponse, form); } else { _this.addItemResult('produto-adicionado', html, boxResponse, form); } } catch (error) { console.log('addItem - error'); console.error(error); if (typeof callback == 'function') { callback('erro-adicionar', error, boxResponse, form); } else { _this.addItemResult('erro-adicionar', error, boxResponse, form); } } btnComprar.classList.remove('-adding'); } }, deleteItem: async function (itemId, item, removeItemResult) { const _this = this; if (!item.classList.contains('-removing')) { item.classList.add('-removing'); try { const response = await fetch('/carrinho', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ _method: 'delete', item_id: itemId, }), }); console.log('success'); if (window.innerWidth > 768) { if (_this.openCartDrawer) { cartDrawer.updateCartCount(); } } else { if (_this.openCartDrawerMobile) { cartDrawer.updateCartCount(); } } if (typeof removeItemResult == 'function') { removeItemResult(item); } } catch (error) { console.log('Erro ao remover item do carrinho'); console.log(error); } item.classList.remove('-removing'); } }, validateFormProduct: function (form) { // VALIDA O FORM DE PRODUTO PARA VER SE O PRODUTO PODE SER ADICIONADO var btnComprar = form.querySelector('[data-action="add-cart"]'); var validated = true; var errors = []; if (btnComprar.dataset.available == 'false') { validated = false; errors.push(''); } else { // verifica se o sku foi selecionado if (form.querySelector('[name="sku"]').value == '') { validated = false; errors.push('Selecione um atributo para o produto'); } // verifica se a quantidade é válida if (form.querySelector('input[name="quantity"]').value <= 0) { validated = false; errors.push('Quantidade indisponível'); } } return { validated, errors }; }, setRespValidateProduct: function (resp, form, boxResponse) { let htmlErrors = ''; if (resp.validated) { boxResponse.innerHTML = ''; } else { for (var i = resp.errors.length - 1; i >= 0; i--) { htmlErrors += '' + resp.errors[i] + ''; } boxResponse.innerHTML = htmlErrors; } }, addItemResult: function (typeResult, result, boxResponse, form) { const _this = this; if (typeResult == 'produto-adicionado') { const btnComprar = form.querySelector('[data-action="add-cart"]'); if (btnComprar) btnComprar.classList.add('success'); setTimeout(function () { if (btnComprar) btnComprar.classList.remove('success'); if (btnComprar) btnComprar.innerHTML = btnComprar.dataset.textAvailable; }, 3500); // CartDrawer.updateCartCount(); if (window.innerWidth > 768) { if (_this.openCartDrawer) { cartDrawer.show(); } else { setTimeout(() => { window.location.href = urlCart; }, 150); } } else { if (_this.openCartDrawerMobile) { cartDrawer.show(); } else { setTimeout(() => { window.location.href = urlCart; }, 150); } } // Verifica se há popup de compra rápida ativo. Se tiver, fecha ele, retirar ou comentar se não for utilizado const popupPurchase = document.querySelector('[data-popup-purchase]'); if (popupPurchase != null) { if (popupPurchase.classList.contains('-active')) { popupPurchase.querySelector('[data-close-popup-purchase]').click(); } } } else if (typeResult == 'erro-adicionar') { if (typeof boxResponse != 'undefined' && boxResponse.length > 0) { window.scrollTo({ top: boxResponse.offsetTop, behavior: 'smooth', }); boxResponse.classList.add('error'); boxResponse.classList.remove('success'); boxResponse.querySelector('span').innerHTML = 'Ocorreu um erro, tente novamente.'; } } }, }; /* harmony default export */ const store = (Store); ;// ../konstanzoficial/assets/javascripts/components/productPurchase.js // "productVariants" é inicializado em layout.liquid, e todos os arquivos liquid que montam // product_block populam este objeto com as variantes dos produtos em tela const ProductPurchase = { productVariants, products: document.querySelectorAll('[data-product-purchase]'), // Verifica as combinações possíveis entre os atributos, com base no atributo que foi clicado checkCombinations: function (currentProduct, selectedAttribute) { const productId = currentProduct.getAttribute('data-product-id'); const selectedAttr = Number(selectedAttribute.getAttribute('data-attribute-index')); const variants = ProductPurchase.productVariants[`${productId}`]; // Busca todos os atributos do produto const attributes = { attr1: [...currentProduct.querySelectorAll('[data-attribute-index="1"]')], attr2: [...currentProduct.querySelectorAll('[data-attribute-index="2"]')], attr3: [...currentProduct.querySelectorAll('[data-attribute-index="3"]')], }; // Busca os atuais atributos selecionados const selectedAttributes = { attr1: currentProduct.querySelector('[data-attribute="1"] input:checked + label'), attr2: currentProduct.querySelector('[data-attribute="2"] input:checked + label'), attr3: currentProduct.querySelector('[data-attribute="3"] input:checked + label'), }; // Altera o selected para o atributo que foi clicado selectedAttributes[`attr${selectedAttr}`] = selectedAttribute; // Atualiza as classes dos atributos disponíveis/indisponíveis // Ao clicar em uma opção de attr1, atualiza os attrs 2 e 3 switch (selectedAttr) { case 1: ProductPurchase.markAttributes([...attributes.attr2], 2, variants, selectedAttributes, currentProduct); ProductPurchase.markAttributes([...attributes.attr3], 3, variants, selectedAttributes, currentProduct); break; case 2: ProductPurchase.markAttributes([...attributes.attr3], 3, variants, selectedAttributes, currentProduct); break; case 3: break; } }, // Marca os atributos como disponiveis/indisponiveis com base nas combinações do checkCombinations markAttributes: function (attributes, index, variants, selectedAttributes, currentProduct) { if (attributes != null && attributes.length > 0) { // Remove os atributos já selecionados que não devem ser analizados, // com base no index do atributo que está sendo analizado. Usado para filtrar // as variantes e diminuir o número de possíveis combinações que definem como disponível/indisponível switch (index) { case 1: selectedAttributes.attr2 = null; selectedAttributes.attr3 = null; break; case 2: selectedAttributes.attr3 = null; break; case 3: break; } attributes.forEach((attr) => { const value = attr.getAttribute('data-attribute-value'); const property = `property${index}`; let available = false; // Adiciona o atributo iterado nos atributos usados para buscar variantes válidas const attrsToCheck = { ...selectedAttributes, [`attr${index}`]: attr, }; // Filtra as variantes para somente as que possuem a combinação de atributos válida const validVariants = variants.filter((variant) => { const properties = variant.properties; let validAttr1 = false; let validAttr2 = false; let validAttr3 = false; if (attrsToCheck.attr1 == null) validAttr1 = true; if (attrsToCheck.attr2 == null) validAttr2 = true; if (attrsToCheck.attr3 == null) validAttr3 = true; if (properties.property1 == null) validAttr1 = true; if (properties.property2 == null) validAttr2 = true; if (properties.property3 == null) validAttr3 = true; if (validAttr1 == false) { const valueToCheck = attrsToCheck.attr1.getAttribute('data-attribute-value'); if (properties.property1.value == valueToCheck) validAttr1 = true; } if (validAttr2 == false) { const valueToCheck = attrsToCheck.attr2.getAttribute('data-attribute-value'); if (properties.property2.value == valueToCheck) validAttr2 = true; } if (validAttr3 == false) { const valueToCheck = attrsToCheck.attr3.getAttribute('data-attribute-value'); if (properties.property3.value == valueToCheck) validAttr3 = true; } if (validAttr1 && validAttr2 && validAttr3) return variant; }); attr.classList.remove('-available'); attr.classList.remove('-unavailable'); attr.classList.remove('-disabled'); if (validVariants.length > 0) { // Encontrou variantes, marca como disponível/indisponível com base nas variantes validVariants.forEach((variant) => { if (variant.properties[property]) { if (variant.properties[property].value == value) { variant.available == false ? (available = false) : (available = true); } } else { available = false; } }); available ? attr.classList.add('-available') : attr.classList.add('-unavailable'); } else if (validVariants.length === 0) { // Não encontrou nenhuma variante, então desabilita a opção. // Se esta opção estava selecionada, clica na primeira opção disponível entre os demais attr.classList.add('-disabled'); const value = attr.getAttribute('data-attribute-value'); const input = currentProduct.querySelector(`[data-attribute="${index}"] input[value="${value}"]`); if (input.checked) { input.checked = false; setTimeout(() => { const firstAvailable = currentProduct.querySelector( `label[data-attribute-index="${index}"]:not(.-disabled)` ); if (firstAvailable) firstAvailable.click(); }, 50); } } }); } }, // Procura por SKU válido dentre os atributos escolhidos checkSelection: function (currentProduct) { const productId = currentProduct.getAttribute('data-product-id'); const variants = ProductPurchase.productVariants[`${productId}`]; const selectedAttrs = { attr1: currentProduct.querySelector('[data-attribute="1"] input:checked'), attr2: currentProduct.querySelector('[data-attribute="2"] input:checked'), attr3: currentProduct.querySelector('[data-attribute="3"] input:checked'), }; let available = false; for (let index = 0; index < variants.length; index++) { const variant = variants[index]; let attr1 = false; let attr2 = false; let attr3 = false; let attr1Value = null; let attr2Value = null; let attr3Value = null; // Verifica se algum dos atributos é inexistente no cadastro if (variant.properties.property1 == null) attr1 = true; if (variant.properties.property2 == null) attr2 = true; if (variant.properties.property3 == null) attr3 = true; //Salva o valor de cada um dos atributos/propriedes da própria variante em questão if (variant.properties.property1) attr1Value = variant.properties.property1.value; if (variant.properties.property2) attr2Value = variant.properties.property2.value; if (variant.properties.property3) attr3Value = variant.properties.property3.value; // Verifica se a variante possui os atributos selecionados if (!attr1 && attr1Value != null && selectedAttrs.attr1 != null) { if (attr1Value == selectedAttrs.attr1.value) attr1 = true; } if (!attr2 && attr2Value != null && selectedAttrs.attr2 != null) { if (attr2Value == selectedAttrs.attr2.value) attr2 = true; } if (!attr3 && attr3Value != null && selectedAttrs.attr3 != null) { if (attr3Value == selectedAttrs.attr3.value) attr3 = true; } // Se achou variante compatível com os atributos selecionados, atualiza if (attr1 && attr2 && attr3) { available = true; const event = new CustomEvent('vnda:sku-change', { detail: { sku: variant.sku }}) const productContainer = currentProduct.closest('[data-product-box]'); currentProduct.querySelector('[name="sku"]').value = variant.sku; currentProduct.dispatchEvent(event); if (productContainer) productContainer.dispatchEvent(event); // Reinicializa o input de quantidade para 1 currentProduct.querySelector('[name="quantity"]').value = 1; // Remove a mensagem de quantidade máxima quando altera a variante if (currentProduct.querySelector('.msg-response .msg-error').classList.contains('-visible')) { currentProduct.querySelector('.msg-response .msg-error').innerHTML = ''; currentProduct.querySelector('.msg-response .msg-error').classList.remove('-visible'); } // Configura a quantidade máxima do input conforme a variante selecionada currentProduct.querySelector('[name="quantity"]').setAttribute('data-available-quantity', variant.quantity); // Configura produto disponível/indisponível if (variant.available) { ProductPurchase.setAvailable(currentProduct); } else { ProductPurchase.setUnavailable(currentProduct, true, variant.sku); } // Atualiza as últimas quantidades disponíveis ProductPurchase.setLastUnits(currentProduct, variant); // Atualiza o preço do componente ProductPurchase.updatePrice(currentProduct, variant); break; } } // Quando não encontra variante if (!available) ProductPurchase.setUnavailable(currentProduct, false); }, // Define produto como disponível pra compra setAvailable: function (currentProduct) { const addButton = currentProduct.querySelector('[data-action="add-cart"]'); const currentProductWrapper = currentProduct.closest('[data-product-box]'); let formNotify = null; if (currentProductWrapper) { formNotify = currentProductWrapper.querySelector('[data-form-notify]'); } if (addButton) { // addButton.setAttribute('data-available', true); addButton.classList.add('-available'); addButton.classList.remove('-unavailable'); addButton.innerHTML = addButton.getAttribute('data-text-available'); } if (formNotify) formNotify.classList.remove('-active'); }, // Define produto como indisponível pra compra setUnavailable: function (currentProduct, showNotify, sku) { const addButton = currentProduct.querySelector('[data-action="add-cart"]'); const currentProductWrapper = currentProduct.closest('[data-product-box]'); const showFormNotify = showNotify || false; let formNotify = null; if (currentProductWrapper) { formNotify = currentProductWrapper.querySelector('[data-form-notify]'); } if (addButton) { // addButton.setAttribute('data-available', false); addButton.classList.remove('-available'); addButton.classList.add('-unavailable'); addButton.innerHTML = addButton.getAttribute('data-text-unavailable'); } if (formNotify) { if (showFormNotify && sku != undefined && sku != null) { formNotify.classList.add('-active'); formNotify.querySelector('[name="sku"]').value = sku; } else { formNotify.classList.remove('-active'); } } }, // Atualiza o componente de preço do produto updatePrice: function (currentProduct, variant) { const productMainContainer = currentProduct.closest('[data-product-box]'); if (productMainContainer == null) return; const priceEl = productMainContainer.querySelector('[data-init-price]'); if (priceEl == null) return; const groupShop = productMainContainer.getAttribute('data-prod-group-shop'); priceEl.setAttribute('data-sku', variant.sku); priceEl.setAttribute('data-price', variant.price); priceEl.dispatchEvent(new Event('change')); const discount = priceEl.dataset.discountPercent; // Coloca a porcentagem de desconto no produto if (discount != '0') { priceEl.style.setProperty('--discount', `'-${discount}%'`); } // atualiza preço final do compre junto se existir if (groupShop != null) { productMainContainer.setAttribute('data-price', variant.price); productMainContainer.setAttribute('data-sale-price', variant.sale_price); productMainContainer.setAttribute('data-available', variant.available); productMainContainer.dispatchEvent(new Event('vnda:group-shop-price-update')); } }, // Marca o primeiro atributo como disponível/indisponível, e seleciona os atributos // da primeira variante disponível markFirstVariant: function (currentProduct) { const productId = currentProduct.getAttribute('data-product-id'); const variants = [...ProductPurchase.productVariants[`${productId}`]]; if (variants.length === 1) { // possui somente uma variante e não possui atributos, // define como disponível/indisponível com base na variante em si if (currentProduct.querySelectorAll('.prod-option').length === 0) { // Atualiza as últimas quantidades disponíveis ProductPurchase.setLastUnits(currentProduct, variants[0]); return variants[0].available ? ProductPurchase.setAvailable(currentProduct) : ProductPurchase.setUnavailable(currentProduct, true, variants[0].sku); } } else if (variants.length === 0) { // Produto não possui variantes, define como indisponível return ProductPurchase.setUnavailable(currentProduct); } if (variants.length > 0) { const allAttr1 = [...currentProduct.querySelectorAll('[data-attribute="1"] [data-attribute-value]')]; const allAttr2 = [...currentProduct.querySelectorAll('[data-attribute="2"] [data-attribute-value]')]; const allAttr3 = [...currentProduct.querySelectorAll('[data-attribute="3"] [data-attribute-value]')]; // Prepara para caso o atributo 1 não esteja em uso, scout por outros let attrToCheck = { index: false, options: [] }; if (allAttr3.length > 0) attrToCheck = { index: 'property3', options: allAttr3 }; if (allAttr2.length > 0) attrToCheck = { index: 'property2', options: allAttr2 }; if (allAttr1.length > 0) attrToCheck = { index: 'property1', options: allAttr1 }; // Baseado na ordem dos atributos, busca por variantes válidas if (attrToCheck.options.length > 0) { for (let index = 0; index < attrToCheck.options.length; index++) { const attribute = attrToCheck.options[index]; const value = attribute.getAttribute('data-attribute-value'); const availableVariants = ProductPurchase.productVariants[`${productId}`].filter((variant) => { if (variant.properties[attrToCheck.index] && variant.properties[attrToCheck.index].value && variant.properties[attrToCheck.index].value == value && variant.available ) return variant; }); if (availableVariants.length > 0) { attribute.classList.add('-available'); } else { // O produto não tem variantes disponíveis attribute.classList.add('-unavailable'); } } // Marca os atributos da primeira variante disponível, //com base na ordem que os atributos se apresentam na tela let hasFirstAvailable = false; for (const attribute of attrToCheck.options) { if (attribute.classList.contains('-available')) { attribute.click(); hasFirstAvailable = true; if (attrToCheck.index == 'property1') { for (const attr2 of allAttr2) { if (attr2.classList.contains('-available')) { attr2.click(); break; } } for (const attr3 of allAttr3) { if (attr3.classList.contains('-available')) { attr3.click(); break; } } } if (attrToCheck.index == 'property2') { for (const attr3 of allAttr3) { if (attr3.classList.contains('-available')) { attr3.click(); break; } } } break; } } // Quando não há nenhuma first variant com estoque, marca a primeira //opção indisponível, para exibir como indisponível if (!hasFirstAvailable) { attrToCheck.options[0].click(); if (attrToCheck.index == 'property1') { if (allAttr2.length > 0) allAttr2[0].click(); if (allAttr3.length > 0) allAttr3[0].click(); } if (attrToCheck.index == 'property2') { if (allAttr3.length > 0) allAttr3[0].click(); } } } } }, // Configura a mesagem de ultimas unidades disponíveis setLastUnits: function (currentProduct, variant) { const lastUnitsWrapper = currentProduct.querySelector('.last-units'); const availableQuantity = variant.quantity; if (availableQuantity <= 5) { if (!availableQuantity) { lastUnitsWrapper.innerHTML = ''; lastUnitsWrapper.classList.remove('-visible'); } else if (availableQuantity > 1) { lastUnitsWrapper.innerHTML = `Últimas ${availableQuantity} unidades disponíveis`; lastUnitsWrapper.classList.add('-visible'); } else { lastUnitsWrapper.innerHTML = 'Última unidade disponível'; lastUnitsWrapper.classList.add('-visible'); } } else { lastUnitsWrapper.innerHTML = ''; lastUnitsWrapper.classList.remove('-visible'); } }, // Configura o seletor de quantidade e suas mensagens setQuantitySelector: function (currentProduct) { const error = currentProduct.querySelector('.msg-response .msg-error'); const buttonPlus = currentProduct.querySelector('[data-qtd-plus]'); const buttonMinus = currentProduct.querySelector('[data-qtd-minus]'); const input = currentProduct.querySelector('[name="quantity"]'); if (buttonPlus != null && buttonMinus != null) { buttonPlus.addEventListener('click', () => { const current = Number(input.value) + 1; const availableQuantity = Number(input.dataset.availableQuantity); if (!availableQuantity) { error.innerHTML = 'Produto em falta no estoque'; error.classList.add('-visible'); } else if (current > availableQuantity) { // Se o produto atingiu a quantidade máxima em estoque mostra uma mensagem error.innerHTML = 'Quantidade máxima em estoque.'; error.classList.add('-visible'); } else { input.value = current; input.setAttribute('value', current); } }); buttonMinus.addEventListener('click', () => { const current = Number(input.value) - 1; if (current > 0) { input.value = current; input.setAttribute('value', current); // Se está mostrando a mensagem de quantidade máxima, remove ela if (error.classList.contains('-visible')) { error.classList.remove('-visible'); } } }); } }, // Envia o formulário de compra submitForm: function (currentProduct) { currentProduct.addEventListener('submit', (event) => { event.preventDefault(); const groupShop = currentProduct.closest('[data-product-box]').getAttribute('data-prod-group-shop'); if (groupShop == null) { // Adição normal de produto - Compre Junto é controlado em outro componente const form = currentProduct; const parent = form.closest('[data-product-box]'); const respValidated = store.validateFormProduct(form); const boxResponse = parent.querySelector('.msg-response'); if (respValidated.validated) { store.addProduct(form, parent); } else { //console.info('não foi'); store.setRespValidateProduct(respValidated, form, boxResponse); } } }); }, // Lida com popup de compra rápida, retirar ou comentar caso não for utilizado popupPurchase: { popupEl: document.querySelector('[data-popup-purchase]'), currentOpenProduct: false, open: function (productBox) { // Salva o produto atual para referência futura ProductPurchase.popupPurchase.currentOpenProduct = productBox; // Clona elementos do produto e referencia o form de compra dele const clonedImages = productBox.querySelector('.images').cloneNode(true); const clonedName = productBox.querySelector('.name').cloneNode(true); const clonedPrice = productBox.querySelector('.price').cloneNode(); const purchaseForm = productBox.querySelector('[data-product-purchase]'); // Joga os elementos do produto dentro do popup const contentBox = this.popupEl.querySelector('.product'); const leftBox = document.createElement('div'); const rightBox = document.createElement('div'); const moreLink = document.createElement('a') moreLink.classList.add('more-link') moreLink.innerText = 'Mais detalhes' moreLink.setAttribute('href', productBox.querySelector('a').getAttribute('href')) let priceWrapper = document.createElement('div'); priceWrapper.classList.add('price-wrapper'); priceWrapper.appendChild(clonedPrice); leftBox.appendChild(clonedImages); rightBox.appendChild(clonedName); rightBox.appendChild(priceWrapper); rightBox.appendChild(purchaseForm); rightBox.appendChild(moreLink) contentBox.appendChild(leftBox); contentBox.appendChild(rightBox); // Atualiza o componente de preço dentro do popup e exibe clonedPrice.setAttribute('data-was-processed', false); window.Vnda.Component.Price.update(); this.popupEl.classList.add('-active'); }, close: function () { // CHAMAR UMA ÚNICA VEZ // Instancia funcionamento ao clicar no botão de fechar o popup const popup = this.popupEl; const closeButtons = popup.querySelectorAll('[data-close-popup-purchase]'); closeButtons.forEach((button) => { if (button.getAttribute('data-processed') == 'false') { button.addEventListener('click', () => { popup.classList.remove('-active'); setTimeout(() => { // Retorna o formulário para o produto correspondente const purchaseForm = popup.querySelector('[data-product-purchase]'); ProductPurchase.popupPurchase.currentOpenProduct.appendChild(purchaseForm); ProductPurchase.popupPurchase.currentOpenProduct = false; // Limpa os dados do produto do popup const popupProductEl = popup.querySelector('.product'); while (popupProductEl.firstChild) { popupProductEl.removeChild(popupProductEl.firstChild); } }, 300); }); button.setAttribute('data-processed', true); } }); }, }, init: function (update) { const triggerUpdate = update || false; if (triggerUpdate) { this.products = document.querySelectorAll('[data-product-purchase]'); this.productVariants = productVariants; } const { products, checkCombinations, checkSelection, markFirstVariant, submitForm, setQuantitySelector } = this; // Instancia funcionamento de fechar o popup de compra rápida, retirar ou comentar caso não for utilizado ProductPurchase.popupPurchase.close(); if (products.length > 0) products.forEach((product) => { if (product.getAttribute('data-processed') == 'false') { const attrOptions = product.querySelectorAll('[data-attribute-value]'); const attrInputs = product.querySelectorAll('input'); // Remarca demais atributos ao clicar em uma opção attrOptions.forEach((attr) => { attr.addEventListener('click', () => { checkCombinations(product, attr); }); }); // Verifica variante para combinação de atributos selecionados attrInputs.forEach((input) => { input.addEventListener('change', () => { checkSelection(product); }); }); setQuantitySelector(product); markFirstVariant(product); submitForm(product); product.setAttribute('data-processed', true); // Abre o popup de compra rápida, retirar ou comentar caso não for utilizado const openPopupPurchaseButton = product.parentElement.querySelector('[data-open-popup-purchase]'); if (openPopupPurchaseButton != null) { openPopupPurchaseButton.addEventListener('click', () => { ProductPurchase.popupPurchase.open(product.parentElement); }); } } }); window.ProductPurchase = ProductPurchase }, }; /* harmony default export */ const productPurchase = (ProductPurchase); ;// ../konstanzoficial/assets/javascripts/components/vndaComponents.js // =============================================================== // NEWSLETTER POPUP // =============================================================== const NewsletterComponent = { root: document.querySelector('#component-popup-newsletter-root'), settings: window.popupNewsletterSettings || false, loaded: false, setPopupNewsletter: function() { const { settings, root } = NewsletterComponent // Define propriedades do componente let maxWidth = settings.maxWidth; let imageUrl = settings.imageUrl; let title = settings.title; let description = settings.description; let subdomain = settings.subdomain; let successMessage = settings.success; // Inicia o componente const componentNewsletterPopup = new Vnda.Component.NewsletterPopup({ maxWidth: maxWidth, maxHeight: 500, imageUrl: imageUrl, imagePosition: 'left', popupPosition: 'center', title: title, description: description, textBtnSubmit: 'Enviar', classBtnSubmit: 'button-newsletter', formKey: `${subdomain}-newsletter`, hasNameField: false, hasLastNameField: false, hasDateOfBirthField: false, hasPhoneField: false, successMessage: successMessage, delay: 500, frequency: '7', language: 'pt-BR', }); // Renderiza o componente e marca como carregado componentNewsletterPopup.render(root); NewsletterComponent.loaded = true }, loadPopupNewsletter: function() { if (!NewsletterComponent.loaded) { const { settings } = NewsletterComponent; addAsset(settings.script, NewsletterComponent.setPopupNewsletter) addAsset(settings.styles) } }, init: function() { const { root, settings } = this; if (!root || !settings) return; const eventType = (window.innerWidth <= 1024) ? 'scroll' : 'mousemove' window.addEventListener(eventType, () => { NewsletterComponent.loadPopupNewsletter() }, { once: true }) } } // =============================================================== // PREÇO // =============================================================== // Carrega o componente de preço quando um product-block ou product-infos entra em tela const PriceComponent = { script: window.priceComponent || false, loaded: false, init: function() { if (!PriceComponent.script) return; const productContainers = document.querySelectorAll('[data-product-box]'); if (productContainers.length === 0) return; const observer = new IntersectionObserver(entries => { if (entries[0].isIntersecting) { if (!PriceComponent.loaded) { addAsset(PriceComponent.script) PriceComponent.loaded = true } observer.disconnect() } }, { threshold: 0.1 }); productContainers.forEach(product => { observer.observe(product) }) } } ;// ../konstanzoficial/assets/javascripts/californio.js // import Wishlist from './californio.wishlist.js'; const Californio = { showBrand: function () { console.info( `%c💻 Califórnio - MKT Digital %c\n🌐 https://www.californio.com.br/`, `color: white; background: linear-gradient(90deg, #333, #666); font-size: 16px; font-weight: bold; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; text-shadow: 1px 1px 2px rgba(0,0,0,0.3); padding: 6px 12px; margin: 8px 0px 2px; border-radius: 6px;`, `color: gray; font-size: 12px; font-weight: 500; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; padding: 6px 12px; margin: 2px 0px; border-radius: 6px;` ); }, //-------------------------------------------------------------------------------- // Função para desabilitar o scroll da página disableScroll: () => { document.body.style.overflow = 'hidden'; }, //-------------------------------------------------------------------------------- // Função para habilitar o scroll da página enableScroll: () => { document.body.style.overflow = 'auto'; }, //-------------------------------------------------------------------------------- // Função responsável por exibir a descrição da primeira parte do produto handleFirstDescription: function () { // Seleciona o título da primeira parte da descrição const descriptionPartsTitle = Array.from(document.querySelectorAll(`.description-parts-title`)); // Verifica se há títulos de partes da descrição descriptionPartsTitle.forEach((title) => { // Obtém o próximo elemento irmão do título (container) let container = title.nextElementSibling; // Verifica se o container possui a classe visible if (container.classList.contains(`visible`)) { // Se o container possuir a classe visible, altera a altura do container para a altura do parágrafo próximo ao título let textHeight = title.nextElementSibling .querySelector(`.description-parts-text`) .getBoundingClientRect().height; container.style.height = `${textHeight}px`; } }); }, // Função responsável por exibir a descrição completa do produto handleDescriptionParts: function () { // Seleciona todos os títulos das partes da descrição const descriptionPartsTitle = Array.from(document.querySelectorAll(`.description-parts-title`)); // Verifica se há títulos de partes da descrição if (descriptionPartsTitle) { // Adiciona um evento de click em cada título descriptionPartsTitle.forEach((title) => { title.addEventListener(`click`, function (event) { // Adiciona ou remove a classe open no título title.classList.toggle(`open`); // Obtém o próximo elemento irmão do título (parágrafo) let container = title.nextElementSibling; // Adiciona ou remove a classe visible no parágrafo container.classList.toggle(`visible`); // Se o parágrafo estiver visível, altera a altura do container para a altura do parágrafo if (container.classList.contains(`visible`)) { let textHeight = container.querySelector(`.description-parts-text`).getBoundingClientRect().height; container.style.height = `${textHeight}px`; } else { // Se o parágrafo não estiver visível, altera a altura do container para 0 container.style.height = `0px`; } }); }); } }, // Função responsável por lidar com a largura do popup de guia de tamanhos handleSizeGuidePopupWidth: function () { // Função responsável por ajustar a largura do popup de guia de tamanhos function adjustSizeGuidePopupWidth() { // Seleciona o popup de guia de tamanhos const sizeGuidePopup = document.querySelector(`.popup-guia-medidas`); // Verifica se o popup de guia de tamanhos existe e está visível if (sizeGuidePopup) { // Seleciona o wrapper do popup, o conteúdo do popup e a imagem do popup const popupWrapper = sizeGuidePopup.querySelector(`.popup-wrapper`); const popupContent = popupWrapper.querySelector(`.content`); const popupImage = popupContent.querySelector(`.image`); // Obtém as dimensões da imagem do popup let popupImageRect = popupImage.getBoundingClientRect(); // Ajusta a largura e altura do wrapper do popup popupWrapper.style.width = `calc(${popupImageRect.width}px + 6rem)`; popupWrapper.style.height = `calc(${popupImageRect.height}px + 6rem)`; // Ajusta a largura do container que recebe a imagem do popup popupImage.style.width = `100%`; } } // Seleciona o botão de guia de tamanhos const buttonSizeGuide = document.querySelector(`.btn-medidas`); if (buttonSizeGuide) { // Adiciona um evento de click no botão de guia de tamanhos buttonSizeGuide.addEventListener(`click`, () => { adjustSizeGuidePopupWidth(); }); } }, // Função responsável por lidar com os filtros no mobile handleFilters: function () { let page = document.querySelector('body').getAttribute('data-page'); // Verifica se a largura da tela é menor que 768px e se a página é de tag if (window.innerWidth < 768 && page === 'tag') { const openFilterButton = document.querySelector('#open-filter-mobile'); const componentFilter = document.querySelector('#component-products-filter-root'); openFilterButton.addEventListener('click', () => { openFilterButton.classList.toggle('-active'); componentFilter.classList.toggle('-visible'); }); } }, setZoom: function () { /** * Função que aplica zoom nas imagens */ const images = document.querySelectorAll('.swiper-wrapper .item-image'); if (window.innerWidth < 992) { return; } function getMousePosition(event, element) { const rect = element.getBoundingClientRect(); const mouseX = event.clientX - rect.left; const mouseY = event.clientY - rect.top; return { x: mouseX, y: mouseY }; } function getTouchPosition(event, element) { const rect = element.getBoundingClientRect(); const touchX = event.touches[0].clientX - rect.left; const touchY = event.touches[0].clientY - rect.top; return { x: touchX, y: touchY }; } images.forEach((image) => { const imageAnchor = image.children[0]; let scale = 2; let touchStartTime = 0; const handleMouseMove = (event) => { const { x, y } = getMousePosition(event, image); const rect = image.getBoundingClientRect(); const offsetXPercent = (x / rect.width) * 100; const offsetYPercent = (y / rect.height) * 100; const anchorX = (offsetXPercent - 50) * (scale - 1); const anchorY = (offsetYPercent - 50) * (scale - 1); imageAnchor.style.transform = `translate(${-anchorX}%, ${-anchorY}%) scale(${scale})`; }; const handleTouchMove = (event) => { const { x, y } = getTouchPosition(event, image); const rect = image.getBoundingClientRect(); const offsetXPercent = (x / rect.width) * 100; const offsetYPercent = (y / rect.height) * 100; const anchorX = (offsetXPercent - 50) * (scale - 1); const anchorY = (offsetYPercent - 50) * (scale - 1); imageAnchor.style.transform = `translate(${-anchorX}%, ${-anchorY}%) scale(${scale})`; }; if (window.innerWidth < 992) { image.addEventListener('touchstart', (event) => { touchStartTime = new Date().getTime(); }); image.addEventListener('touchend', (event) => { event.preventDefault(); const touchEndTime = new Date().getTime(); const touchDuration = touchEndTime - touchStartTime; if (touchDuration < 200) { // Verifica se o toque foi rápido if (imageAnchor.classList.contains('-zoomed')) { imageAnchor.classList.remove('-zoomed'); document.body.style.overflow = 'visible'; const zoomedImage = imageAnchor.querySelector('.swiper-zoom-target'); const lowResSrc = zoomedImage.getAttribute('data-zoom-src'); const highResSrc = zoomedImage.getAttribute('srcset'); zoomedImage.setAttribute('srcset', lowResSrc); zoomedImage.setAttribute('data-zoom-src', highResSrc); imageAnchor.style.cursor = `all-scroll`; imageAnchor.style.transform = `translate(0, 0) scale(1)`; image.removeEventListener('touchmove', handleTouchMove); } else { imageAnchor.classList.add('-zoomed'); document.body.style.overflow = 'hidden'; const zoomedImage = imageAnchor.querySelector('.swiper-zoom-target'); const lowResSrc = zoomedImage.getAttribute('srcset'); const highResSrc = zoomedImage.getAttribute('data-zoom-src'); zoomedImage.setAttribute('srcset', highResSrc); zoomedImage.setAttribute('data-zoom-src', lowResSrc); imageAnchor.style.cursor = `all-scroll`; imageAnchor.style.transform = `translate(0, 0) scale(${scale})`; image.addEventListener('touchmove', handleTouchMove); } } }); } else { image.addEventListener('mouseenter', (event) => { event.preventDefault(); imageAnchor.classList.add('-zoomed'); // document.body.style.overflow = 'hidden'; const zoomedImage = imageAnchor.querySelector('.swiper-zoom-target'); const lowResSrc = zoomedImage.getAttribute('srcset'); const highResSrc = zoomedImage.getAttribute('data-zoom-src'); zoomedImage.setAttribute('srcset', highResSrc); zoomedImage.setAttribute('data-zoom-src', lowResSrc); imageAnchor.style.cursor = `all-scroll`; imageAnchor.style.transform = `translate(0, 0) scale(${scale})`; image.addEventListener('mousemove', handleMouseMove); }); image.addEventListener('mouseleave', () => { imageAnchor.classList.remove('-zoomed'); // document.body.style.overflow = 'visible'; // document.body.style.overflowX = 'hidden'; const zoomedImage = imageAnchor.querySelector('.swiper-zoom-target'); const lowResSrc = zoomedImage.getAttribute('data-zoom-src'); const highResSrc = zoomedImage.getAttribute('srcset'); zoomedImage.setAttribute('srcset', lowResSrc); zoomedImage.setAttribute('data-zoom-src', highResSrc); imageAnchor.style.cursor = `all-scroll`; imageAnchor.style.transform = `translate(0, 0) scale(1)`; image.removeEventListener('mousemove', handleMouseMove); }); } }); }, //-------------------------------------------------------------------------------- poupAlert: function () { const alert = document.querySelector('#alert'); const closeButton = alert.querySelector('.close-btn'); // Função que armazena nos cookies a data da última vez que o alerta foi fechado function setAlertToCookies() { document.cookie = `alertClosed=${new Date().getTime()};`; } // Função que recupera a data da última vez que o alerta foi fechado function getAlertFromCookies() { let cookies = document.cookie.split(';'); let alertClosed = 0; // Percorre todos os cookies e verifica se há algum cookie com a string alertClosed cookies.forEach((cookie) => { if (cookie.includes('alertClosed')) { alertClosed = parseInt(cookie.split('=')[1]); } }); return alertClosed; } if (alert) { // Verifica se o alerta foi fechado nas últimas 24 horas if (getAlertFromCookies()) { let currentTime = new Date().getTime(); let timeDifference = currentTime - getAlertFromCookies(); let twentyFourHours = 86400000; if (timeDifference < twentyFourHours) { return; } } // Verifica se o alerta existe e remove a classe 'hidden' alert.classList.remove('hidden'); Californio.disableScroll(); // Adiciona um evento de click no botão de fechar closeButton.addEventListener('click', () => { setAlertToCookies(); alert.classList.add('hidden'); Californio.enableScroll(); }); } }, //-------------------------------------------------------------------------------- init: function () { const _this = this; _this.showBrand(); _this.handleFirstDescription(); _this.handleDescriptionParts(); _this.handleSizeGuidePopupWidth(); _this.handleFilters(); _this.setZoom(); // _this.poupAlert(); // Conteúdo em desenvolvimento // Wishlist.init(); }, }; // Californio.init(); /* harmony default export */ const californio = (Californio); ;// ../konstanzoficial/assets/javascripts/main.js let threshold = 100; if (window.innerWidth < 768) threshold = 400; window.lazyLoadOptions = { elements_selector: '.lazy', threshold, }; window.addEventListener( 'LazyLoad::Initialized', (event) => { window.lazyLoadInstance = event.detail.instance; }, { passive: true } ); // ========================================== // Inicialização // ========================================== console.log( '%cVnda - Tecnologia em Ecommerce', 'color: #f88d5b; font-size: 15px; font-family: "Verdana", sans-serif; font-weight: bold;' ); window.addEventListener('DOMContentLoaded', () => { cartDrawer.init(); header.init(); footer.init(); popup.init(); productPurchase.init(); PriceComponent.init(); webforms.init(); NewsletterComponent.init(); setSearch(); if (window.innerWidth < 992) menuMobile.init(); californio.init(); }); /******/ })() ; //# sourceMappingURL=main.js.map