/** * Блик по буквам логотипа в navbar (тёмная тема) — тот же drawShimmer, что в wesp-logo-loader. */ (function () { const LOGO_SRC = "/static/logo2.png"; const CYCLE_MS = 3000; const SHIMMER_MS = 1000; const START_DELAY_MS = 350; const mounts = new WeakMap(); function isDarkTheme() { return document.documentElement.getAttribute("data-theme") === "dark"; } function prefersReducedMotion() { return globalThis.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? false; } function loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = () => reject(new Error(`Failed to load: ${src}`)); img.src = src; }); } function disposeMount(link) { const state = mounts.get(link); if (!state) return; state.disposed = true; if (state.rafId) cancelAnimationFrame(state.rafId); state.canvas?.remove(); link.classList.remove("nav-logo-link--canvas-shimmer"); mounts.delete(link); } async function mountCanvasShimmer(link) { if (!isDarkTheme() || prefersReducedMotion()) return; disposeMount(link); let logoModule; try { logoModule = await import("/static/js/wesp-logo-loader.js?v=2"); } catch { return; } const { createNavLogoShimmerBuffer, drawShimmer } = logoModule; let img; try { img = await loadImage(LOGO_SRC); } catch { return; } const canvas = document.createElement("canvas"); canvas.className = "nav-logo__shimmer-canvas"; canvas.setAttribute("aria-hidden", "true"); link.classList.add("nav-logo-link--canvas-shimmer"); link.appendChild(canvas); const ctx = canvas.getContext("2d"); const state = { disposed: false, rafId: 0, canvas, logoCanvas: null, layout: null, startAt: performance.now() + START_DELAY_MS, lastW: 0, lastH: 0, }; mounts.set(link, state); function rebuildBuffer() { const w = Math.max(1, Math.round(link.clientWidth)); const h = Math.max(1, Math.round(link.clientHeight)); if (w === state.lastW && h === state.lastH && state.logoCanvas) return; state.lastW = w; state.lastH = h; const dpr = Math.min(globalThis.devicePixelRatio || 1, 2); canvas.width = Math.round(w * dpr); canvas.height = Math.round(h * dpr); canvas.style.width = `${w}px`; canvas.style.height = `${h}px`; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); const built = createNavLogoShimmerBuffer(img, w, h, [255, 255, 255]); state.logoCanvas = built.logoCanvas; state.layout = built.layout; } function tick(now) { if (state.disposed) return; if (!isDarkTheme()) { disposeMount(link); return; } rebuildBuffer(); const w = state.lastW; const h = state.lastH; ctx.clearRect(0, 0, w, h); const elapsed = now - state.startAt; if (elapsed >= 0 && state.logoCanvas && state.layout) { const cycle = elapsed % CYCLE_MS; if (cycle < SHIMMER_MS) { const shimmerT = cycle / SHIMMER_MS; ctx.drawImage(state.logoCanvas, 0, 0, w, h); drawShimmer(ctx, state.layout, state.logoCanvas, shimmerT, false); } } state.rafId = requestAnimationFrame(tick); } rebuildBuffer(); state.rafId = requestAnimationFrame(tick); } function init() { if (prefersReducedMotion()) return; document .querySelectorAll(".mobile-navbar .nav-logo-link, .mobile-brand .nav-logo-link") .forEach((link) => { if (isDarkTheme()) { mountCanvasShimmer(link); } else { disposeMount(link); } }); } function onThemeChange() { init(); } window.WespNavLogoShimmer = { init, disposeMount }; const themeObserver = new MutationObserver(onThemeChange); themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"], }); /* init вызывается из wesp-zootech-nav.js после вставки navbar и из wesp-page-enter после enter-done */ })();