(() => { const body = document.body; const themeMeta = document.querySelector('meta[name="theme-color"]'); const themeToggle = document.querySelector(".theme-toggle"); const themeToggleLabel = themeToggle ? themeToggle.querySelector(".theme-toggle__label") : null; const menuToggle = document.querySelector(".menu-toggle"); const sideMenu = document.getElementById("side-menu"); const sideMenuBackdrop = document.querySelector(".side-menu-backdrop"); const sideMenuClose = document.querySelector(".side-menu__close"); const scrollTopButton = document.querySelector(".scroll-top"); const galleryImages = Array.from(document.querySelectorAll(".gallery-card img")); const THEME_STORAGE_KEY = "indexnew-theme"; const LIGHTBOX_TRANSITION_MS = 220; const LIGHTBOX_MIN_SCALE = 1; const LIGHTBOX_MAX_SCALE = 4; const LIGHTBOX_SCALE_STEP = 0.25; let lightbox = null; let lightboxViewport = null; let lightboxImage = null; let lightboxCaption = null; let lightboxZoomLevel = null; let zoomInButton = null; let zoomOutButton = null; let activeGalleryImage = null; let previousFocusedElement = null; let fittedWidth = 0; let fittedHeight = 0; let zoomLevel = LIGHTBOX_MIN_SCALE; const openMenu = () => { if (!sideMenu || !sideMenuBackdrop || !menuToggle) { return; } sideMenu.classList.add("is-open"); sideMenu.setAttribute("aria-hidden", "false"); sideMenuBackdrop.hidden = false; requestAnimationFrame(() => { sideMenuBackdrop.classList.add("is-open"); }); body.classList.add("menu-open"); menuToggle.setAttribute("aria-expanded", "true"); }; const closeMenu = () => { if (!sideMenu || !sideMenuBackdrop || !menuToggle) { return; } sideMenu.classList.remove("is-open"); sideMenu.setAttribute("aria-hidden", "true"); sideMenuBackdrop.classList.remove("is-open"); body.classList.remove("menu-open"); menuToggle.setAttribute("aria-expanded", "false"); window.setTimeout(() => { if (!sideMenuBackdrop.classList.contains("is-open")) { sideMenuBackdrop.hidden = true; } }, 250); }; const syncScrollTopVisibility = () => { if (!scrollTopButton) { return; } scrollTopButton.classList.toggle("is-visible", window.scrollY > 140); }; const applyTheme = (theme) => { const isLight = theme !== "dark"; body.classList.toggle("light-theme", isLight); if (themeToggle) { themeToggle.setAttribute("aria-pressed", String(isLight)); } if (themeToggleLabel) { // Показываем, на КАКУЮ тему переключиться (т.е. противоположную) themeToggleLabel.textContent = isLight ? "Тёмная" : "Светлая"; } if (themeMeta) { themeMeta.setAttribute("content", isLight ? "#f4f1e8" : "#000000"); } }; const resolveTheme = () => { try { const savedTheme = window.localStorage.getItem(THEME_STORAGE_KEY); return savedTheme === "dark" ? "dark" : "light"; } catch (error) { return "light"; } }; // ====== Reveal animation via IntersectionObserver ====== const setupReveal = () => { const revealElements = Array.from(document.querySelectorAll(".reveal")); if (!revealElements.length || typeof IntersectionObserver === "undefined") { // Fallback: show all immediately revealElements.forEach((el) => el.classList.add("is-visible")); return; } const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add("is-visible"); observer.unobserve(entry.target); } }); }, { threshold: 0.08, rootMargin: "0px 0px -40px 0px" } ); revealElements.forEach((el) => observer.observe(el)); }; const clamp = (value, min, max) => Math.min(Math.max(value, min), max); const syncLightboxZoom = () => { if (!lightboxImage) { return; } lightboxImage.style.width = `${Math.round(fittedWidth * zoomLevel)}px`; lightboxImage.style.height = `${Math.round(fittedHeight * zoomLevel)}px`; if (lightboxZoomLevel) { lightboxZoomLevel.textContent = `${Math.round(zoomLevel * 100)}%`; } if (zoomInButton) { zoomInButton.disabled = zoomLevel >= LIGHTBOX_MAX_SCALE; } if (zoomOutButton) { zoomOutButton.disabled = zoomLevel <= LIGHTBOX_MIN_SCALE; } }; const fitLightboxImage = () => { if (!activeGalleryImage) { return; } const naturalWidth = activeGalleryImage.naturalWidth || activeGalleryImage.width || 1; const naturalHeight = activeGalleryImage.naturalHeight || activeGalleryImage.height || 1; const availableWidth = Math.min(window.innerWidth - 48, 1084); const availableHeight = Math.max(window.innerHeight - 220, 240); const fitRatio = Math.min(availableWidth / naturalWidth, availableHeight / naturalHeight, 1); fittedWidth = naturalWidth * fitRatio; fittedHeight = naturalHeight * fitRatio; syncLightboxZoom(); }; const setZoomLevel = (nextZoomLevel) => { if (!lightboxViewport || !activeGalleryImage) { return; } const clampedZoomLevel = clamp(Number(nextZoomLevel.toFixed(2)), LIGHTBOX_MIN_SCALE, LIGHTBOX_MAX_SCALE); if (clampedZoomLevel === zoomLevel) { return; } const viewportWidth = lightboxViewport.clientWidth; const viewportHeight = lightboxViewport.clientHeight; const centerX = lightboxViewport.scrollLeft + viewportWidth / 2; const centerY = lightboxViewport.scrollTop + viewportHeight / 2; const zoomRatio = clampedZoomLevel / zoomLevel; zoomLevel = clampedZoomLevel; syncLightboxZoom(); lightboxViewport.scrollLeft = Math.max(0, centerX * zoomRatio - viewportWidth / 2); lightboxViewport.scrollTop = Math.max(0, centerY * zoomRatio - viewportHeight / 2); }; const closeLightbox = () => { if (!lightbox || lightbox.hidden) { return; } lightbox.classList.remove("is-open"); body.classList.remove("lightbox-open"); window.setTimeout(() => { if (!lightbox || lightbox.classList.contains("is-open")) { return; } lightbox.hidden = true; activeGalleryImage = null; fittedWidth = 0; fittedHeight = 0; zoomLevel = LIGHTBOX_MIN_SCALE; if (lightboxImage) { lightboxImage.removeAttribute("src"); lightboxImage.style.width = ""; lightboxImage.style.height = ""; } if (lightboxCaption) { lightboxCaption.textContent = ""; } }, LIGHTBOX_TRANSITION_MS); if (previousFocusedElement instanceof HTMLElement) { previousFocusedElement.focus({ preventScroll: true }); } previousFocusedElement = null; }; const openLightbox = (image) => { if (!lightbox || !lightboxViewport || !lightboxImage || !lightboxCaption) { return; } activeGalleryImage = image; zoomLevel = LIGHTBOX_MIN_SCALE; previousFocusedElement = document.activeElement instanceof HTMLElement ? document.activeElement : null; lightboxImage.src = image.currentSrc || image.src; lightboxImage.alt = image.alt || ""; const galleryCard = image.closest(".gallery-card"); const captionNode = galleryCard ? galleryCard.querySelector("figcaption") : null; lightboxCaption.textContent = captionNode ? captionNode.textContent.trim() : (image.alt || ""); lightboxViewport.scrollLeft = 0; lightboxViewport.scrollTop = 0; fitLightboxImage(); lightbox.hidden = false; requestAnimationFrame(() => { if (!lightbox) { return; } lightbox.classList.add("is-open"); const closeButton = lightbox.querySelector("[data-lightbox-close]"); if (closeButton) { closeButton.focus(); } }); body.classList.add("lightbox-open"); }; const setupGalleryZoom = () => { if (!galleryImages.length) { return; } galleryImages.forEach((image) => { image.tabIndex = 0; image.setAttribute("role", "button"); image.setAttribute("aria-haspopup", "dialog"); image.setAttribute("aria-label", `${image.alt || "Фото"}. Открыть для увеличения`); image.addEventListener("click", () => openLightbox(image)); image.addEventListener("keydown", (event) => { if (event.key !== "Enter" && event.key !== " ") { return; } event.preventDefault(); openLightbox(image); }); }); lightbox = document.createElement("div"); lightbox.className = "gallery-lightbox"; lightbox.hidden = true; lightbox.innerHTML = `