// Shared React components: Header, Footer, Breadcrumbs, Badges, Icons
const { useState, useEffect, useRef, useMemo, Fragment } = React;
// Случайный класс цвета из палитры employees (a0…a6). Фиксируется на сессию,
// чтобы при переходе между страницами аватар не «мигал» разными цветами.
function getMeAvatarClass() {
try {
const key = 'pe-me-avatar-idx';
let idx = sessionStorage.getItem(key);
if (idx === null) {
idx = String(Math.floor(Math.random() * 7));
sessionStorage.setItem(key, idx);
}
return 'a' + idx;
} catch (e) {
return 'a' + Math.floor(Math.random() * 7);
}
}
// ===== ICONS =====
const Icon = {
Search: (p) => ,
Menu: (p) => ,
Heart: (p) => ,
HeartFill: (p) => ,
BarChart: (p) => ,
Cart: (p) => ,
Bell: (p) => ,
Copy: (p) => ,
Minus: (p) => ,
Plus: (p) => ,
Star: (p) => ,
ChevronDown: (p) => ,
ChevronLeft: (p) => ,
ChevronRight: (p) => ,
Close: (p) => ,
Grid: (p) => ,
List: (p) => ,
Table: (p) => ,
Share: (p) => ,
Truck: (p) => ,
Card: (p) => ,
Shield: (p) => ,
Box: (p) => ,
Info: (p) => ,
Check: (p) => ,
Phone: (p) => ,
Mail: (p) => ,
Clock: (p) => ,
};
// ===== LOGO (real Figma SVG) =====
function Logo() {
return (
);
}
// ===== useIsMobile hook =====
// Реактивно отслеживает viewport ≤ 767 px. Используется в Header / Footer,
// чтобы автоматически делегировать рендер на window.MobileHeader /
// window.MobileFooter, если они зарегистрированы (mobile.jsx подключён).
function useIsMobile() {
const [m, setM] = React.useState(() => {
if (typeof window === 'undefined' || !window.matchMedia) return false;
return window.matchMedia('(max-width: 767px)').matches;
});
React.useEffect(() => {
if (!window.matchMedia) return;
const mq = window.matchMedia('(max-width: 767px)');
const handler = (e) => setM(e.matches);
if (mq.addEventListener) mq.addEventListener('change', handler);
else if (mq.addListener) mq.addListener(handler);
return () => {
if (mq.removeEventListener) mq.removeEventListener('change', handler);
else if (mq.removeListener) mq.removeListener(handler);
};
}, []);
return m;
}
// ===== HEADER (auto-responsive wrapper) =====
function Header(props) {
const isMobile = useIsMobile();
if (isMobile && window.MobileHeader) {
return React.createElement(window.MobileHeader, props);
}
return React.createElement(HeaderDesktop, props);
}
function HeaderDesktop({ cartCount = 3, favCount = 7, compareCount = 2 }) {
const [compact, setCompact] = React.useState(false);
const [query, setQuery] = React.useState('');
const searchInputRef = React.useRef(null);
const compactRef = React.useRef(false);
const tickingRef = React.useRef(false);
React.useEffect(() => {
// Hysteresis thresholds: enter compact at 80px, exit at 40px.
// The gap prevents flicker when header shrink reduces page height
// and bounces scrollY back across a single threshold.
const ENTER = 80;
const EXIT = 40;
const update = () => {
tickingRef.current = false;
const y = window.scrollY;
const now = compactRef.current;
let next = now;
if (!now && y > ENTER) next = true;
else if (now && y < EXIT) next = false;
if (next !== now) {
compactRef.current = next;
setCompact(next);
}
};
const onScroll = () => {
if (tickingRef.current) return;
tickingRef.current = true;
window.requestAnimationFrame(update);
};
window.addEventListener('scroll', onScroll, { passive: true });
update();
return () => window.removeEventListener('scroll', onScroll);
}, []);
return (