import { prefersReducedMotion } from "../shared/media.js"; export function initHeaderScroll() { const header = document.querySelector(".header"); if (!header) return; function updateHeaderScrollPadding() { const inner = header.querySelector(".header__inner"); let h; if (header.classList.contains("header--scrolled") && inner) { const r = inner.getBoundingClientRect(); h = Math.ceil(r.bottom + 20); } else { h = Math.ceil(header.getBoundingClientRect().height); } document.documentElement.style.setProperty("--header-offset", `${Math.max(h, 52)}px`); } function getScrollPaddingTopPx() { const raw = getComputedStyle(document.documentElement).scrollPaddingTop; const px = Number.parseFloat(raw); return Number.isFinite(px) ? px : 0; } /** Вертикальный скролл к якорю с учётом `scroll-padding-top` на `html` (запас под шапку). */ function scrollWindowToElementTop(el, behavior) { updateHeaderScrollPadding(); const pad = getScrollPaddingTopPx(); const top = el.getBoundingClientRect().top + window.scrollY - pad; window.scrollTo({ top: Math.max(0, top), behavior }); } /** Smooth-скролл к якорю + одна подстройка после окончания (layout / смена режима шапки). */ function scrollToInPageAnchor(el) { const reduced = prefersReducedMotion(); if (reduced) { scrollWindowToElementTop(el, "auto"); return; } let settled = false; const settle = () => { if (settled) return; updateHeaderScrollPadding(); const pad = getScrollPaddingTopPx(); const y = Math.max(0, el.getBoundingClientRect().top + window.scrollY - pad); if (Math.abs(window.scrollY - y) <= 2) { settled = true; return; } settled = true; window.scrollTo({ top: y, behavior: "auto" }); }; scrollWindowToElementTop(el, "smooth"); window.addEventListener("scrollend", settle, { passive: true, once: true }); window.setTimeout(settle, 550); } function onScroll() { const y = window.scrollY; const scrollDown = y > onScroll.lastY + 1; const headerHeight = Math.max(52, Math.ceil(header.getBoundingClientRect().height || 0)); const floatStartY = Math.ceil(headerHeight * 1.35); if (onScroll.isFloating) { if (y <= Math.floor(headerHeight * 0.45)) onScroll.isFloating = false; } else if (scrollDown && y > floatStartY) { onScroll.isFloating = true; } const hadScrolled = header.classList.contains("header--scrolled"); header.classList.toggle("header--scrolled", onScroll.isFloating); if (hadScrolled !== header.classList.contains("header--scrolled")) updateHeaderScrollPadding(); onScroll.lastY = y; } onScroll.lastY = window.scrollY; onScroll.isFloating = false; document.querySelectorAll('a[href^="#"]').forEach((link) => { link.addEventListener("click", (e) => { const id = link.getAttribute("href"); if (!id || id === "#") return; const el = document.querySelector(id); if (!el) return; e.preventDefault(); scrollToInPageAnchor(el); history.pushState(null, "", id); }); }); window.addEventListener("scroll", onScroll, { passive: true }); onScroll(); if (typeof ResizeObserver !== "undefined") { const roHeader = new ResizeObserver(() => updateHeaderScrollPadding()); roHeader.observe(header); const headerInner = header.querySelector(".header__inner"); if (headerInner) roHeader.observe(headerInner); } window.addEventListener("resize", updateHeaderScrollPadding, { passive: true }); window.addEventListener( "load", () => { updateHeaderScrollPadding(); const raw = location.hash; if (!raw || raw.length < 2) return; let id; try { id = decodeURIComponent(raw.slice(1)); } catch { return; } if (!id) return; const el = document.getElementById(id); if (!el) return; requestAnimationFrame(() => scrollWindowToElementTop(el, "auto")); }, { once: true }, ); updateHeaderScrollPadding(); }