);
}
function Footer() {
const cols = [
{ title: 'Shop', items: [
['Screen Protectors', '#screens'],
['Cables & Chargers', '#bestsellers'],
['Powerbanks', '#bestsellers'],
['Watch Accessories', '#bestsellers'],
['iPad Stylus', '#bestsellers'],
['Camera Lens', '#bestsellers'],
]},
{ title: 'Support', items: [
['Shipping Policy', '/shipping-policy'],
['Returns Policy', '/returns-policy'],
['Privacy Policy', '/privacy-policy'],
['Terms & Conditions', '/terms'],
['WhatsApp 98798 73001', 'https://wa.me/919879873001'],
]},
{ title: 'Company', items: [
['About Lito', '#'],
['Made in India', '#'],
['Reviews', '#'],
['Email info@lito.in', 'mailto:info@lito.in'],
]},
];
return (
);
}
// ========== VIDEO SHOP ("Tap to shop what you see") ==========
// Manual carousel (no auto-rotate). Videos always auto-play muted/looped.
// 5 visible per page on desktop, 2 on mobile. Arrows BELOW the strip.
// Each tile has a transparent product mini-card overlaid bottom-left, and a
// full-width black "Add To Cart" + dropdown caret strip beneath.
const { useState: useStateV, useEffect: useEffectV, useRef: useRefV } = React;
function VideoShop() {
const [items, setItems] = useStateV([]);
const [ready, setReady] = useStateV(false);
const [page, setPage] = useStateV(0);
const railRef = useRefV(null);
useEffectV(() => {
fetch('/api/home-videos')
.then(r => r.ok ? r.json() : [])
.then(d => setItems(Array.isArray(d) ? d : []))
.catch(() => setItems([]))
.finally(() => setReady(true));
}, []);
if (!ready || !items.length) return null;
// Page sizing: 5 desktop, 2 mobile (CSS controls per-tile flex-basis;
// here we just compute the page count for the dot indicator)
const isMobile = typeof window !== 'undefined' && window.matchMedia('(max-width: 720px)').matches;
const perPage = isMobile ? 2 : 5;
const pages = Math.max(1, Math.ceil(items.length / perPage));
const scrollToPage = (p) => {
const next = Math.max(0, Math.min(pages - 1, p));
setPage(next);
const rail = railRef.current;
if (!rail) return;
rail.scrollTo({ left: rail.clientWidth * next, behavior: 'smooth' });
};
// Keep dot state in sync if user pans by dragging on touch
const onScroll = () => {
const rail = railRef.current;
if (!rail) return;
const p = Math.round(rail.scrollLeft / Math.max(1, rail.clientWidth));
if (p !== page) setPage(p);
};
return (
◆ STRAIGHT FROM THE COUNTER
Tap to shop what you see.
{/* Horizontal scroll-snap rail. CSS gives each tile a viewport-fraction
width so exactly perPage tiles fit the rail. When there are FEWER
items than fit a page (e.g. only 1 video), we center the rail so it
doesn't sit lonely on the left edge. */}
{items.map(p => )}
{/* Controls — only shown when more than one page exists */}
{pages > 1 && (
{Array.from({ length: pages }).map((_, i) => (
)}
);
}
function VideoTile({ p }) {
const fmt = (n) => '₹' + Number(n || 0).toLocaleString('en-IN');
const img = p.local_img && (p.local_img.startsWith('/') || p.local_img.startsWith('http')) ? p.local_img : (p.local_img ? '/' + p.local_img : '');
const videoRef = useRefV(null);
const [needsTap, setNeedsTap] = useStateV(false);
// Triple-redundant autoplay: imperative attributes + play() on every
// possible event + a visible "tap to play" overlay if the browser still
// refuses. The overlay is the final guarantee — works on Brave, low-power
// mode, every browser policy.
useEffectV(() => {
const v = videoRef.current;
if (!v) return;
v.muted = true;
v.defaultMuted = true;
v.playsInline = true;
v.setAttribute('muted', '');
v.setAttribute('playsinline', '');
v.setAttribute('webkit-playsinline', '');
const tryPlay = () => {
const promise = v.play();
if (promise && typeof promise.then === 'function') {
promise.then(() => setNeedsTap(false)).catch(() => setNeedsTap(true));
}
};
tryPlay();
v.addEventListener('loadedmetadata', tryPlay);
v.addEventListener('canplay', tryPlay);
v.addEventListener('playing', () => setNeedsTap(false));
const io = new IntersectionObserver((entries) => {
for (const e of entries) {
if (e.isIntersecting) tryPlay();
else { try { v.pause(); } catch {} }
}
}, { threshold: 0.15 });
io.observe(v);
const wakeAll = () => tryPlay();
document.addEventListener('touchstart', wakeAll, { once: true, passive: true });
document.addEventListener('click', wakeAll, { once: true });
document.addEventListener('scroll', wakeAll, { once: true, passive: true });
return () => {
io.disconnect();
v.removeEventListener('loadedmetadata', tryPlay);
v.removeEventListener('canplay', tryPlay);
document.removeEventListener('touchstart', wakeAll);
document.removeEventListener('click', wakeAll);
document.removeEventListener('scroll', wakeAll);
};
}, []);
const onTilePress = (e) => {
if (!needsTap) return;
e.preventDefault();
e.stopPropagation();
const v = videoRef.current;
if (!v) return;
v.muted = true; v.playsInline = true;
const p = v.play();
if (p && typeof p.then === 'function') p.then(() => setNeedsTap(false)).catch(() => {});
};
return (
// No inline `flex` — width is locked by CSS `.videoshop-tile` so the
// 5-col desktop / 2-col mobile rule is never overridden by inline styles.
);
}
// ========== INSTAGRAM FEED ==========
// Talks to /api/instagram (server caches Meta Graph response 10 min). Renders
// a 4-up grid on desktop / 2-up on mobile. Each tile clicks through to the
// post on instagram.com. The whole section is hidden if no posts come back
// (so a missing/expired token never leaves a broken-looking band on the page).
const { useState: useStateI, useEffect: useEffectI } = React;
function InstagramGrid() {
const [items, setItems] = useStateI([]);
const [ready, setReady] = useStateI(false);
useEffectI(() => {
fetch('/api/instagram')
.then(r => r.ok ? r.json() : { items: [] })
.then(d => setItems(Array.isArray(d.items) ? d.items.slice(0, 8) : []))
.catch(() => setItems([]))
.finally(() => setReady(true));
}, []);
if (!ready) return null; // first paint: render nothing (no flash)
if (!items.length) return null; // no posts → hide the section entirely
return (