// Тихая кнопка «Помахать»: общий счётчик для всех посетителей хранится на сервере (/api/waves, /api/wave). // Если API недоступен (например, сайт открыт без бэкенда), блок просто прячется, остальная страница не страдает. (function () { var row = document.getElementById('wave-row'); var btn = document.getElementById('wave-btn'); var num = document.getElementById('wave-n'); var line = document.getElementById('wave-count'); if (!row || !btn || !num) return; var count = null, busy = false, msgTimer = null; var reduce = matchMedia('(prefers-reduced-motion: reduce)').matches; var fmt = function (n) { return n.toLocaleString('ru-RU'); }; // число плавно «докручивается» до нового значения function setCount(n) { var from = count === null ? n : count; count = n; if (reduce || from === n) { num.textContent = fmt(n); return; } var t0 = performance.now(); (function step(now) { var k = Math.min(1, (now - t0) / 350); num.textContent = fmt(Math.round(from + (n - from) * k)); if (k < 1) requestAnimationFrame(step); })(t0); } function say(text) { // короткое сообщение вместо счётчика clearTimeout(msgTimer); line.textContent = text; msgTimer = setTimeout(function () { line.textContent = 'Уже помахали: '; line.appendChild(num); }, 2200); } function plusOne() { // маленькое «+1» уплывает вверх if (reduce) return; var s = document.createElement('span'); s.className = 'wave-plus'; s.textContent = '+1'; s.setAttribute('aria-hidden', 'true'); btn.appendChild(s); s.addEventListener('animationend', function () { s.remove(); }); } fetch('/api/waves').then(function (r) { if (!r.ok) throw 0; return r.json(); }) .then(function (d) { setCount(d.count); }) .catch(function () { row.classList.add('off'); }); btn.addEventListener('click', function () { if (busy || count === null) return; busy = true; btn.disabled = true; var prev = count; setCount(count + 1); btn.classList.remove('waving'); void btn.offsetWidth; btn.classList.add('waving'); plusOne(); fetch('/api/wave', { method: 'POST' }).then(function (r) { if (r.status === 429) { say('Не так быстро, раз в секунду'); throw 0; } if (!r.ok) throw new Error(r.status); return r.json(); }).then(function (d) { setCount(d.count); }) .catch(function (e) { count = prev; num.textContent = fmt(prev); if (e !== 0) say('Не засчиталось, попробуй ещё раз'); }) .then(function () { setTimeout(function () { busy = false; btn.disabled = false; }, 1000); }); }); })();