/* global React */ // ========== CATEGORIES ========== const ALL_PRODUCTS = (window.PRODUCTS_DATA || []); const _byCat = (g) => ALL_PRODUCTS.filter(p => p.catGroup === g); const _firstImg = (g, fallback) => (_byCat(g)[0]?.local_img) || fallback; // Transparent hero art per category (overrides catalog thumbs for cleaner tiles). const CATS = [ { name: 'Screens', count: _byCat('Screens').length, img: 'assets/privacy-iphone-transparent.png', accent: 'var(--ink)', dark: true, transparent: true }, { name: 'Power', count: _byCat('Power').length, img: 'assets/powerbank-transparent.png', accent: 'var(--gold)', transparent: true }, { name: 'Watch', count: _byCat('Watch').length, img: _firstImg('Watch','assets/watch-case.jpg'), accent: '#fff' }, { name: 'Stylus', count: _byCat('Stylus').length, img: _firstImg('Stylus','assets/pencil.jpg'), accent: 'var(--cream-2)' }, { name: 'Camera', count: _byCat('Camera').length, img: 'assets/ar-screen-transparent.png', accent: '#fff', transparent: true }, { name: 'Accessories', count: _byCat('Accessories').length, img: 'assets/charger-transparent.png', accent: 'var(--cream-2)', transparent: true }, ]; function Categories() { return (

Everything your
phone needs.

View all {ALL_PRODUCTS.length} products →
{CATS.map((c, i) => (
0{i+1} / CATEGORY

{c.name}

{c.count} items
{c.name}
))}
); } // ========== BESTSELLERS ========== // Curated picks from real Lito catalog (one per category, mixed) const _pick = (g, n=3) => _byCat(g).slice(0, n); const _bg = ['#fff','var(--cream-2)']; const _curated = [ ..._pick('Screens',2), ..._pick('Power',2), ..._pick('Watch',1), ..._pick('Stylus',1), ..._pick('Camera',1), ..._pick('Accessories',1), ].slice(0,8); const PRODUCTS = _curated.map((p, i) => ({ id: p.id, slug: p.slug, cat: p.catGroup, name: p.name, price: p.price, orig: p.orig, img: p.local_img, // Second image for hover swap. Falls back to the primary so every card still // animates on hover (different scale start/end → looks like a real swap). // Upload a true second photo per product in super-admin to get a different // image revealed on hover, like DailyObjects. img2: (p.images && p.images.find(im => im.url && im.url !== p.local_img)?.url) || p.local_img, color: _bg[i % 2] })); function Bestsellers() { return (
◆ WHAT EVERYONE'S BUYING

Top of the drop.

{PRODUCTS.map(p => (
{p.name} {p.orig && (
−{Math.round((1 - p.price/p.orig) * 100)}%
)}
{p.cat.toUpperCase()}
{p.name}
₹{p.price.toLocaleString('en-IN')} {p.orig && ₹{p.orig.toLocaleString('en-IN')}}
{/* Explicit "Add to cart" button — replaces the easy-to-miss "+" circle */}
))}
); } // ========== TESTIMONIALS ========== const REVIEWS = [ { name: 'Aarav S.', phone: 'Samsung S24 Ultra', rating: 5, text: 'Dropped my phone on tiles. Glass took a chip, screen is mint. Doing its job.' }, { name: 'Anaya K.', phone: 'iPhone 15 Pro', rating: 5, text: 'Edges are flush. Feels like the phone. Fingerprints don\'t stick. Premium for the price.' }, { name: 'Vihaan M.', phone: 'Apple Watch Ultra', rating: 5, text: 'You can\'t even tell it\'s there. Touch is 1:1. Gym + office daily, zero scratches.' }, { name: 'Rohan V.', phone: 'iPhone 15 Pro Max', rating: 5, text: 'The privacy one is a cheat code on the metro. Nobody\'s reading over your shoulder.' }, ]; function Reviews() { return (
◆ 12,400 REVIEWS · 4.8 / 5

Real phones.
Real saves.

Read all →
{REVIEWS.map((r, i) => (
{'★'.repeat(r.rating)}

"{r.text}"

{r.name}
{r.phone}
Verified
))}
); } // ========== TRUST STRIP ========== function Trust() { const items = [ { big: '4–9', sub: 'DAY DELIVERY', note: 'Pan India' }, { big: '3', sub: 'DAY RETURNS', note: 'No questions' }, { big: '12.4K', sub: 'REVIEWS', note: '4.8★ avg' }, { big: 'COD', sub: 'AVAILABLE', note: 'All orders' }, ]; return (
{items.map((it, i) => (
0 ? 24 : 0 }}>
{it.big}
{it.sub}
{it.note}
))}
); } // ========== CTA ========== function CTA() { return (
◆ THE BUNDLE · APR 2026

Outfit the
whole device.

Pair a case, a screen, and a cable. 15% off the bundle. Free shipping. One kit, fully protected.

Build your bundle → Shop all
); } 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.
) starts playback and hides this. */} {needsTap && (
)} {/* Gradient + overlaid product mini-card (DailyObjects style) */}
{img && } {p.name} {fmt(p.price)} {p.orig && Number(p.orig) > Number(p.price) && ( {fmt(p.orig)} )}
{/* Add to Cart strip + dropdown caret (caret links to product page) */}
); } // ========== 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 (
◆ ON THE GRID · @LITO

Follow the drops.

@lito on instagram →
{items.slice(0, 8).map(p => ( {p.caption {/* Hover overlay with caption + IG glyph */}
{p.caption || ''}
))}
); } Object.assign(window, { Categories, Bestsellers, Reviews, Trust, CTA, InstagramGrid, VideoShop, Footer, PRODUCTS, CATS });