/* ===========================================================
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 (
);
}
/* 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 (
);
})}
);
}
function StepCard({ step, direction, accent, side }) {
if (direction === "kinetic") {
return (
);
}
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}
COORDS
23.022° N · 72.571° E
);
}
window.useReveal = useReveal;
window.useParallax = useParallax;
window.Marquee = Marquee;
window.RoadmapLine = RoadmapLine;
window.MapBlock = MapBlock;