// Shared components + hooks for Sohail Merchant portfolio // Loaded on every page. Exposes globals via window. const DS = () => window.KrystalDesignSystem_a15719; // ---- Site data (edit here) ---- const SITE = { name: 'Sohail Merchant', role: 'Graphic Designer & Web Developer', city: 'Vadodara, India', email: 'hello@sohailmerchant.com', phone: '+91 98765 43210', social: { instagram: 'https://instagram.com/', linkedin: 'https://linkedin.com/in/', facebook: 'https://facebook.com/', }, }; window.SITE = SITE; // ---- Dark mode (persisted) ---- function useDarkMode() { const [dark, setDark] = React.useState(() => { try { return localStorage.getItem('sm-theme') === 'dark'; } catch (e) { return false; } }); React.useEffect(() => { document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light'); try { localStorage.setItem('sm-theme', dark ? 'dark' : 'light'); } catch (e) {} }, [dark]); return [dark, () => setDark(d => !d)]; } window.useDarkMode = useDarkMode; // ---- Scroll reveal hook ---- function useReveal() { React.useEffect(() => { const els = document.querySelectorAll('[data-reveal]'); if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { els.forEach(el => el.classList.add('is-in')); return; } const io = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('is-in'); io.unobserve(e.target); } }); }, { threshold: 0.12 }); els.forEach(el => io.observe(el)); return () => io.disconnect(); }); } window.useReveal = useReveal; // ---- Animated counter ---- function Counter({ to, suffix = '', duration = 1600 }) { const ref = React.useRef(null); const [val, setVal] = React.useState(0); React.useEffect(() => { const el = ref.current; if (!el) return; let raf, started = false; const run = () => { const t0 = performance.now(); const tick = (now) => { const p = Math.min(1, (now - t0) / duration); const eased = 1 - Math.pow(1 - p, 3); setVal(Math.round(to * eased)); if (p < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); }; const io = new IntersectionObserver((es) => { es.forEach(e => { if (e.isIntersecting && !started) { started = true; run(); io.unobserve(el); } }); }, { threshold: 0.5 }); io.observe(el); return () => { io.disconnect(); cancelAnimationFrame(raf); }; }, [to, duration]); return {val.toLocaleString()}{suffix}; } window.Counter = Counter; // ---- Section title block ---- function SectionHead({ eyebrow, title, sub, center }) { return (
{sub}
}