// 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 (
{eyebrow &&
{eyebrow}
}

{title}

{sub &&

{sub}

}
); } window.SectionHead = SectionHead; // ---- Top nav ---- function Nav({ active }) { const { Button, Switch } = DS(); const [dark, toggleDark] = useDarkMode(); const [open, setOpen] = React.useState(false); const links = [ { label: 'Home', href: 'index.html', key: 'home' }, { label: 'Work', href: 'Work.html', key: 'work' }, { label: 'About', href: 'About.html', key: 'about' }, { label: 'Blog', href: 'Blog.html', key: 'blog' }, { label: 'Contact', href: 'Contact.html', key: 'contact' }, ]; return (
SM
Sohail.
{open &&
{links.map(l => {l.label})}
}
); } window.Nav = Nav; // ---- Footer ---- function Footer() { const socials = [ { icon: 'instagram', href: SITE.social.instagram, label: 'Instagram' }, { icon: 'linkedin', href: SITE.social.linkedin, label: 'LinkedIn' }, { icon: 'facebook', href: SITE.social.facebook, label: 'Facebook' }, ]; const cols = [ { h: 'Explore', links: [['Home', 'index.html'], ['Work', 'Work.html'], ['About', 'About.html'], ['Blog', 'Blog.html']] }, { h: 'Services', links: [['Branding & logos', 'Work.html'], ['Web development', 'Work.html'], ['UI/UX design', 'Work.html'], ['Digital marketing', 'Work.html']] }, ]; return ( ); } window.Footer = Footer; // refresh lucide icons after render window.refreshIcons = () => { if (window.lucide) window.lucide.createIcons(); };