/* global React */ const { useState, useEffect, useRef } = React; function Portal({ onEnter }) { const [phraseIdx, setPhraseIdx] = useState(0); const [fading, setFading] = useState(false); const phrases = window.PORTAL_PHRASES || [""]; const ringRef = useRef(null); const scrollRef = useRef(null); const [radius, setRadius] = useState(240); const [scrollY, setScrollY] = useState(0); useEffect(() => { const measure = () => { const el = ringRef.current; if (!el) return; const r = el.getBoundingClientRect(); const size = Math.min(r.width, r.height); setRadius(Math.max(150, size * 0.5)); }; measure(); window.addEventListener("resize", measure); return () => window.removeEventListener("resize", measure); }, []); useEffect(() => { const el = scrollRef.current; if (!el) return; let raf; const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => setScrollY(el.scrollTop)); }; el.addEventListener("scroll", onScroll, { passive: true }); return () => {el.removeEventListener("scroll", onScroll);cancelAnimationFrame(raf);}; }, []); useEffect(() => { const iv = setInterval(() => { setFading(true); setTimeout(() => { setPhraseIdx((i) => (i + 1) % phrases.length); setFading(false); }, 1100); }, 4200); return () => clearInterval(iv); }, [phrases.length]); // scroll-reveal effect on sections useEffect(() => { const obs = new IntersectionObserver((entries) => { entries.forEach((e) => {if (e.isIntersecting) e.target.classList.add("revealed");}); }, { threshold: 0.12, root: scrollRef.current }); document.querySelectorAll('.reveal').forEach((el) => obs.observe(el)); return () => obs.disconnect(); }, []); const thresholds = [ { key: "gallery", dev: "दर्शन", en: "Gallery", meta: "the archive of glances", angle: -90 }, { key: "themes", dev: "विषय", en: "Themes", meta: "philosophical explorations", angle: -30 }, { key: "prints", dev: "मुद्रण", en: "Prints", meta: "owning fragments of silence", angle: 30 }, { key: "journal", dev: "लेखन", en: "Journal", meta: "thoughts from wandering", angle: 90 }, { key: "about", dev: "परिचय", en: "About", meta: "the witness behind the lens", angle: 150 }, { key: "feedback", dev: "प्रतिक्रिया", en: "Feedback", meta: "leave emotional residue", angle: 210 }]; const all = window.DARSHAN || []; // Five curated photographs become the editorial "preface" spreads. // The remaining fifteen drift below as a horizontal ribbon (all twenty visible). const featured = [all[0], all[3], all[10], all[6], all[15]].filter(Boolean); const driftLane = all; // marquee shows all twenty const themePreviews = [ { key: "street", dev: "मार्ग", en: "Street", photo: all[10], line: "the city is a slow scripture" }, { key: "travel", dev: "यात्रा", en: "Travel", photo: all[6], line: "every road is a small pilgrimage" }, { key: "documentary", dev: "साक्ष्य", en: "Documentary", photo: all[4], line: "to witness is the oldest verb" }, { key: "nature", dev: "प्रकृति", en: "Nature", photo: all[15], line: "the divine wears weather today" }, { key: "sacred", dev: "पावन", en: "The Sacred", photo: all[11], line: "ordinary things, lit from inside" }]; return (
{/* ─── Act I · HERO — pure text composition, small ॐ above ─── */}
दर्शन · sacred seeing

welcome, traveller — you have been seen, long before you arrived.

THESE PHOTOGRAPHS WERE MADE WITH ONE BEATING HEART, AND THEY WILL OPEN TO ANOTHER.
and they will only open to another.

— {phrases[phraseIdx]} —
{/* Scroll cue */}
scroll, gently
{/* ─── Act II · The Twenty — editorial preface + drifting ribbon ─── */}
· i. · विंशति — the twenty

an archive of my photographs

MADE ON FOOT ACROSS BHARAT, BETWEEN SPANS OF 5 YEARS
five glances below — the remaining fifteen drift past at the bottom.

{/* Editorial spreads — five curated photographs at varied scales */}
{/* spread I — asymmetric pair */} {featured[0] && featured[1] &&
onEnter("gallery")} data-cursor>
{featured[0].title}
№ {featured[0].id}
{featured[0].dev} {featured[0].title}
{featured[0].place} · {featured[0].year}
onEnter("gallery")} data-cursor>
{featured[1].title}
№ {featured[1].id}
{featured[1].dev} {featured[1].title}
{featured[1].place} · {featured[1].year}
} {/* spread II — single full-bleed wide */} {featured[2] &&
onEnter("gallery")} data-cursor>
{featured[2].title}
№ {featured[2].id}
{featured[2].dev} {featured[2].title}
{featured[2].place} · {featured[2].year}
} {/* spread III — pair + inline epigraph */} {featured[3] && featured[4] &&
onEnter("gallery")} data-cursor>
{featured[3].title}
№ {featured[3].id}
{featured[3].dev} {featured[3].title}
{featured[3].place} · {featured[3].year}

each frame is asking for one breath,
no more — and no less.

— a note pinned above the darkroom
onEnter("gallery")} data-cursor>
{featured[4].title}
№ {featured[4].id}
{featured[4].dev} {featured[4].title}
{featured[4].place} · {featured[4].year}
}
{/* Drifting ribbon — every photograph passes through, slowly */}
सर्व all twenty — drifting past
{/* ─── Act IV · the five themes (philosophical chapters) ─── */}
· ii. · विषय — the five chambers

Five exploration, one looking.

the work is gathered into five philosophical chapters — each a slow river, not a category.

{themePreviews.map((t, i) => )}
{/* ─── Act V · the yantra — the spiritual portal ─── */}
· iii. · द्वारम् — the six thresholds

choose where to enter.

each chamber holds the same archive, lit from a different angle.

{thresholds.map((t) => { const rad = t.angle * Math.PI / 180; const x = Math.cos(rad) * radius; const y = Math.sin(rad) * radius; return ( ); })}
“ darshan — to see, and in seeing, be seen ”
{/* ─── Act VI · the witness ─── */}
· iv. · परिचय — the witness
{all[2]?.dev} {all[2]?.place}

Rahul Malviya

I work in street, travel, and documentary photography — and I love the quiet detail shots of nature. It has been five years since I first held my Fujifilm X-T200; photography has been the ride that taught me to see life with a different lens.

This site is a souvenir to the world: a small, organised reflection of mine, so that you may see a fragment of what I have been lucky enough to witness.

5years
9cities
20photographs
1camera
{/* ─── Final invitation ─── */}

enter the gallery —
walk slowly, please.

राहुल मालविया Rahul Malviya
fujifilm x-t200 · five years on the road · still learning to look.
MMXX — MMXXVI · दर्शन · all photographs made with permission, gratitude, and bare feet.
); } function Marquee({ lane, speed, direction, scrollY }) { const doubled = [...lane, ...lane, ...lane]; return (
{doubled.map((p, i) =>
{p.title}
{p.dev} {p.place}
)}
); } function Yantra() { return ( {const a = i / 12 * Math.PI * 2 - Math.PI / 2;return `${(Math.cos(a) * 88).toFixed(2)},${(Math.sin(a) * 88).toFixed(2)}`;}).join(" ")} fill="none" stroke="#c9a96a" strokeOpacity="0.12" strokeWidth="0.3" /> {Array.from({ length: 24 }).map((_, i) => { const a = i / 24 * Math.PI * 2; const x1 = Math.cos(a) * 36,y1 = Math.sin(a) * 36; const x2 = Math.cos(a) * 92,y2 = Math.sin(a) * 92; return ; })} ); } window.Portal = Portal;