/* =========================================================== SHARED — reveal hooks, marquee, motion-line for Pravi Way =========================================================== */ const { useState, useEffect, useRef, useLayoutEffect } = React; /* IntersectionObserver-based reveal */ function useReveal(rootRef) { useEffect(() => { if (!rootRef.current) return; const els = rootRef.current.querySelectorAll('.reveal, .reveal-l, .reveal-r'); const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add('in'); } else { // re-reveal on scroll up too (so navigating between pages re-plays) // but only if user has scrolled past again } }); }, { threshold: 0.15, rootMargin: "0px 0px -80px 0px" }); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }, []); } /* Mouse-tracked parallax helper */ function useParallax(ref, strength = 14) { useEffect(() => { if (!ref.current) return; const el = ref.current; const onMove = (e) => { const rect = el.getBoundingClientRect(); const cx = rect.left + rect.width / 2; const cy = rect.top + rect.height / 2; const dx = (e.clientX - cx) / rect.width; const dy = (e.clientY - cy) / rect.height; el.style.setProperty('--px', `${-dx * strength}px`); el.style.setProperty('--py', `${-dy * strength}px`); }; window.addEventListener('mousemove', onMove); return () => window.removeEventListener('mousemove', onMove); }, [strength]); } /* Marquee — duplicates children for seamless loop */ function Marquee({ children, speed = "normal", reverse = false, className = "" }) { return (
{children} {children}
); } /* SVG roadmap with progress-trace tied to scroll position */ function RoadmapLine({ steps, accent = "#ea7321", lineColor = "rgba(0,0,0,0.18)", direction = "editorial" }) { const wrapRef = useRef(null); const lineRef = useRef(null); const [progress, setProgress] = useState(0); useEffect(() => { const wrap = wrapRef.current; if (!wrap) return; const onScroll = () => { const rect = wrap.getBoundingClientRect(); const vh = window.innerHeight; // Progress 0 when top of wrap is at bottom of viewport, 1 when bottom of wrap is at top const total = rect.height + vh * 0.6; const passed = Math.min(Math.max(vh * 0.8 - rect.top, 0), total); setProgress(passed / total); }; onScroll(); window.addEventListener('scroll', onScroll, { passive: true }); return () => window.removeEventListener('scroll', onScroll); }, []); // Determine layout: alternating left/right with center spine return (
{/* center spine */}
{steps.map((s, i) => { const left = i % 2 === 0; const dotProgress = (i + 0.5) / steps.length; const active = progress >= dotProgress - 0.05; return (
{left && }
{!left && }
); })}
); } function StepCard({ step, direction, accent, side }) { if (direction === "kinetic") { return (

{step.t}

{step.d}

); } if (direction === "architect") { return (
STEP / {step.n}

{step.t}

{step.d}

); } if (direction === "civic") { return (
· STEP {step.n} ·

{step.t}

{step.d}

); } // editorial default return (
Step № {step.n}

{step.t}

{step.d}

); } /* Shared map block — abstract animated dotted map (brand-led) */ function MapBlock({ direction }) { const styles = { editorial: { bg: "var(--paper-deep)", ink: "var(--pravi-blue)", muted: "var(--rule)", accent: "var(--pravi-orange)" }, kinetic: { bg: "var(--white)", ink: "var(--pravi-blue)", muted: "var(--rule)", accent: "var(--pravi-orange)" }, architect: { bg: "var(--paper)", ink: "var(--pravi-blue)", muted: "var(--rule)", accent: "var(--pravi-orange)" }, civic: { bg: "var(--white)", ink: "var(--pravi-blue)", muted: "var(--rule)", accent: "var(--pravi-orange)" }, }[direction]; const P = window.PRAVI; // Generate a grid of dots const cols = 60, rows = 22; const dots = []; for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { // make some empty zones to suggest landmass const dx = x - cols * 0.28; const dy = y - rows * 0.5; const r = Math.sqrt(dx * dx + dy * dy); if (r > 9 && Math.random() > 0.55) continue; dots.push({ x: x / cols, y: y / rows }); } } return (
SECTION · LOCATE US

Find us in Ahmedabad.

{P.brand.address}
EMAIL
{P.brand.email}
PHONE
{P.brand.phone}
COORDS
23.022° N · 72.571° E
{dots.map((d, i) => ( ))} {/* office pin */} ▸ AHMEDABAD PRAVI HQ · 380058 {/* a few city pings */} {[[440, 100, "DELHI"], [490, 220, "MUMBAI"], [520, 270, "BENGALURU"]].map(([x, y, name], i) => ( {name} ))}
); } window.useReveal = useReveal; window.useParallax = useParallax; window.Marquee = Marquee; window.RoadmapLine = RoadmapLine; window.MapBlock = MapBlock;