setTimeout(() => { $("body").addClass("body--loaded"); }, 420); $(document).on( "click", "button:not(.bzb-window-refresh):not(.bzb-window-reset):not(.bzb-window-close), [data-href]", function (e) { var $self = $(this); if ($self.is(".btn-disabled")) { return; } if ($self.closest("[data-ripple]")) { e.stopPropagation(); } var initPos = $self.css("position"), offs = $self.offset(), x = e.pageX - offs.left, y = e.pageY - offs.top, dia = Math.min(this.offsetHeight, this.offsetWidth, 100), // start diameter $ripple = $("
", {class: "ripple", appendTo: $self}); if (!initPos || initPos === "static") { $self.css({position: "relative"}); } $("
", { class: "rippleWave", css: { background: $self.data("ripple"), width: dia, height: dia, left: x - dia / 2, top: y - dia / 2, }, appendTo: $ripple, one: { animationend: function () { $ripple.remove(); }, }, }); } ); $(function () { function initTimer($el) { if ($el.data('timer-initialized')) return; // чтобы не инициализировать дважды $el.data('timer-initialized', true); const endTs = parseInt($el.data('end-date')) * 1000; const $hours = $el.find('.flipdown-hours'); const $minutes = $el.find('.flipdown-minutes'); const $seconds = $el.find('.flipdown-seconds'); let interval; // объявляем заранее function update() { const now = Date.now(); let diff = Math.floor((endTs - now) / 1000); if (diff <= 0) { $hours.text('00'); $minutes.text('00'); if ($seconds.length) $seconds.text('00'); clearInterval(interval); return; } const h = Math.floor(diff / 3600); const m = Math.floor((diff % 3600) / 60); const s = diff % 60; $hours.text(String(h).padStart(2, '0')); $minutes.text(String(m).padStart(2, '0')); if ($seconds.length) $seconds.text(String(s).padStart(2, '0')); } const intervalTime = $seconds.length ? 1000 : 30 * 1000; update(); // сразу обновляем таймер interval = setInterval(update, intervalTime); // теперь interval уже объявлен } // Инициализация существующих таймеров $('.flipdown[data-end-date]').each(function () { initTimer($(this)); }); // Следим за динамически добавляемыми элементами const observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { $(mutation.addedNodes).each(function () { const $node = $(this); if ($node.is('.flipdown[data-end-date]')) { initTimer($node); } $node.find('.flipdown[data-end-date]').each(function () { initTimer($(this)); }); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); }); class MouseSelection { constructor(options = {}) { this.$selection = null; this.startX = 0; this.startY = 0; this.options = options; this.animationFrame = null; this.init(); } init() { $(document).on('mousedown', (e) => this.onMouseDown(e)); $(document).on('click', () => this.clearHighlights()); } onMouseDown(e) { if ($(e.target).closest('.window, .app__left').length) return; this.startX = e.pageX; this.startY = e.pageY; this.$selection = $('
').appendTo('body').css({ left: this.startX, top: this.startY, width: 0, height: 0 }); $(document).on('mousemove.selection', (ev) => this.onMouseMove(ev)); $(document).on('mouseup.selection', () => this.onMouseUp()); e.preventDefault(); } onMouseMove(e) { const x = Math.min(e.pageX, this.startX); const y = Math.min(e.pageY, this.startY); const w = Math.abs(e.pageX - this.startX); const h = Math.abs(e.pageY - this.startY); if (!this.animationFrame) { this.animationFrame = requestAnimationFrame(() => { if (this.$selection) { this.$selection.css({left: x, top: y, width: w, height: h}); this.highlightElements(); } this.animationFrame = null; }); } } highlightElements() { if (!this.$selection) return; const $elements = $(this.options.highlightSelector || ''); const selectionRect = this.$selection[0].getBoundingClientRect(); $elements.each(function () { const $el = $(this); if ($el.closest('.window, .app__left').length) return; const elRect = this.getBoundingClientRect(); const intersects = selectionRect.left < elRect.right && selectionRect.right > elRect.left && selectionRect.top < elRect.bottom && selectionRect.bottom > elRect.top; $el.toggleClass('selected-element', intersects); }); } clearHighlights() { $(this.options.highlightSelector || '').removeClass('selected-element'); } onMouseUp() { if (this.$selection) this.$selection.remove(); this.$selection = null; $(document).off('.selection'); } } // Инициализация new MouseSelection({highlightSelector: '.app__content div'}); $(document).on("click", "[data-href]", function () { setTimeout(() => { document.location.href = $(this).data("href"); }, 250); }); $(document).on("focus", "[data-mask]", function () { if ($(this).data("init") !== undefined) return; $(this).mask($(this).data("mask")); $(this).data("init", true); if ($(this).val() == 0) { if ($(this).attr("data-placebefore") == undefined) { $(this).attr("data-placebefore", $(this).attr("placeholder")); } $(this).attr("placeholder", "+7 (___) ___ __-__"); } }); $(document).on("click", "[data-mask]", function () { $(this).focus().get(0).setSelectionRange(0, 0); }); $(function () { if ($("body:has([data-focus]")) { var input = $("[data-focus]"); input.focus(); try { const length = input.val().length; input[0].setSelectionRange(length, length); } catch (error) { } } }); $(document).on("paste", ".form input", function () { const $current = $(this); setTimeout(function () { const $form = $current.closest(".form"); const $inputs = $form.find("input:visible:enabled"); const currentIndex = $inputs.index($current); if (currentIndex !== -1 && currentIndex + 1 < $inputs.length) { $inputs.eq(currentIndex + 1).focus(); } // } else { // const $button = $form.find("button:visible:enabled").first(); // if ($button.length) { // $button.focus(); // } // } }, 0); }); // Функция инициализации Tippy на элементе const initTippy = (element) => { if (!element._tippy) { tippy(element, { content: element.dataset.tippy, interactive: element.dataset.tippyInteractive === "true", allowHTML: element.dataset.tippyHtml === "true", placement: element.dataset.tippyAlign || "top", theme: element.dataset.tippyTheme || undefined }); } }; function initPeity(el) { const $el = $(el); // читаем массив из data-peity let raw = $el.attr("data-peity") || ""; let arr; try { arr = JSON.parse(raw); } catch (e) { arr = raw.split(",").map(Number); } if (!Array.isArray(arr) || arr.length === 0) return; // записываем числа внутрь для peity $el.text(arr.join(",")); // строим график const type = $el.attr("data-peity-type") || "line"; let config = { width: $el.attr("data-peity-width") || 50, height: $el.attr("data-peity-height") || 24 }; if (getCookie("darkTheme") === "true") { config.stroke = "#ffb13b"; config.fill = "rgba(255,164,59,0.2)"; } $el.peity(type, config); } // Инициализация на уже существующих элементах document.querySelectorAll("[data-tippy]").forEach(initTippy); // Наблюдатель за новыми элементами const tippyObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { if (node.matches("[data-tippy]")) { initTippy(node); } node.querySelectorAll?.("[data-tippy]").forEach(initTippy); } }); }); }); tippyObserver.observe(document.body, {childList: true, subtree: true}); // инициализация + MutationObserver (как раньше) document.querySelectorAll("[data-peity]").forEach(initPeity); const peityObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { if (node.matches("[data-peity]")) initPeity(node); node.querySelectorAll?.("[data-peity]").forEach(initPeity); } }); }); }); peityObserver.observe(document.body, {childList: true, subtree: true}); // Копирование с изменением текста tippy $(document).on("click", "[data-copy]", function () { const el = $(this); const textToCopy = el.data("copy"); const tipInstance = el[0]._tippy; const originalText = el.data("tippy"); const showCopied = () => { tipInstance.setContent("Copied"); tipInstance.show(); setTimeout(() => { tipInstance.setContent(originalText); }, 1500); }; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(textToCopy) .then(showCopied) .catch((err) => console.error("Ошибка копирования", err)); } else { // Fallback для старых браузеров или без HTTPS const tempInput = $(""); $("body").append(tempInput); tempInput.val(textToCopy).select(); try { document.execCommand("copy"); showCopied(); } catch (err) { console.error("Ошибка копирования (fallback)", err); } tempInput.remove(); } }); $(function () { const $nav = $('.nav'); const $float = $nav.find('.nav__float'); const $items = $nav.find('.nav__item'); /** * Перемещает и масштабирует nav__float под указанный элемент * @param {jQuery} $target - элемент навигации, под который нужно переместить подсветку */ function moveFloat($target) { if (!$target.length) return; const {top, left} = $target.position(); const width = $target.outerWidth(); const height = $target.outerHeight(); $float.css({ top: top, width: `${width}px`, height: `${height}px` }); } /** * Устанавливает активный пункт меню * @param {jQuery} $item - пункт меню, который нужно сделать активным */ function setActiveItem($item) { $items.removeClass('item--active'); $item.addClass('item--active'); // moveFloat($item); } // События $items.on('click', function () { setActiveItem($(this)); }); // Инициализация позиции moveFloat($items.filter('.item--active')); // Обновление позиции при ресайзе окна $(window).on('resize', function () { moveFloat($items.filter('.item--active')); }); }); $(document).on("click", ".js-account-logout", function () { $(this).addClass("disabled"); $.ajax({ url: '/api.php?r=auth/auth_logout&public=1', dataType: 'json', success: function (response) { if (response.status === 'ok') { location.reload(); } else { alert(response); } }, error: function (jqXHR, textStatus, errorThrown) { console.error('AJAX error:', textStatus, errorThrown); } }); }); // Проверка и установка куки function setCookie(name, value, days) { const d = new Date(); d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000); document.cookie = name + "=" + value + ";path=/;expires=" + d.toUTCString(); } function getCookie(name) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); if (match) return match[2]; return null; } // Функция для применения темы при загрузке function applyDarkTheme() { if (getCookie("darkTheme") === "true") { $(document).find(".js-dark-theme-checkbox").prop("checked", true); $("html").addClass("site--dark-theme"); } } // Делегируем событие на динамический контент $(document).on("change", ".js-dark-theme-checkbox", function () { if ($(this).is(":checked")) { $("html").addClass("site--dark-theme"); setCookie("darkTheme", "true", 365); } else { $("html").removeClass("site--dark-theme"); setCookie("darkTheme", "false", 365); } }); $(document).on("click", ".js-change-password", function () { const localW = new Window(); localW.bindForm({ window: { title: 'Сменить пароль', controls: { reset: false, close: true }, animation: true, allow_html: true }, fields: [ {name: 'old_password', type: 'password', placeholder: 'Старый пароль', required: true}, {name: 'new_password', type: 'password', placeholder: 'Новый пароль', required: true} ], submitText: 'Сменить пароль', url: '/api.php?r=user_change_password&public=1', method: 'POST', onSuccess: () => { localW.close(); } }); }); $(document).on("click", ".js-change-locate", function () { var $btn = $(this); $btn.prop("disabled", true); $.post('/api.php?r=toggle_locate&public=1', function (result) { if (result.status !== "ok") return; document.location.reload(); }, "json").fail(function () { $btn.prop("disabled", false); }); }); $(document).on("click", ".js-add-contact", function () { const localW = new Window(); localW.bindForm({ window: { title: 'Add Contact', controls: { reset: false, close: true }, animation: true, allow_html: true, size: { width: 241 } }, fields: [ { name: 'telegram_id', type: 'text', tippy: 'Telegram account ID for support via @', placeholder: 'Telegram Contact', required: true }, ], submitText: 'Set Contact', url: '/api.php?r=user_change_contact&public=1', method: 'POST', onSuccess: (response) => { if (response.contact !== undefined) { slideText($(".js-aside-user-contact"), response.contact); } else { document.location.reload(); } localW.close(); } }); }); const generateUUID = () => { return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); } const updateTextInput = () => { $('input.input-text').each(function () { var $this = $(this); var text = $this.val() || $this.attr('placeholder') || ''; var $span = $('').text(text).css({ 'visibility': 'hidden', 'white-space': 'pre', 'font': $this.css('font') }).appendTo('body'); $this.width($span.width() + 10); $span.remove(); }); } function getCol(colName) { return $(`.col-${colName}`); } function setBalance(amount) { slideText($("#user-balance"), amount); } function showNumbersError($window) { $window.bindForm({ window: { title: 'Нет свободных номеров', controls: {reset: false, close: true}, animation: true, allow_html: true, size: {width: 450} }, submitAllow: false, appendSubtitle: false, appendFormHTML: `
Пока что все свободные номера заняли, пожалуйста, повторите попытку покупки чуть позже
` }); } const slideText = ($el, newHtml) => { gsap.to($el, { y: "-100%", opacity: 0, duration: 0.3, ease: "power2.in", onComplete: function () { $el.html(newHtml); gsap.set($el, {y: "100%", opacity: 0}); gsap.to($el, { y: "0%", opacity: 1, duration: 0.3, ease: "power2.out" }); } }); } const col = ($col, options = {}) => { // options: { text: "something", classes: ["active", "highlight"] } let selector = `.col-${$col}`; // Если указаны дополнительные классы, добавляем их к селектору if (options.classes && options.classes.length) { selector += options.classes.map(cls => `.${cls}`).join(''); } let elements = $(selector); // Если нужен поиск по тексту if (options.text) { const text = options.text; elements = elements.filter(function () { return $(this).text().includes(text); }); } return elements; };