/* global React, ReactDOM */
const { useState, useEffect, useRef } = React;
/* ---------------- Nav ---------------- */
function Nav() {
const [scrolled, setScrolled] = useState(false);
const [activeLink, setActiveLink] = useState(null); // no link active until user clicks one
useEffect(() => {
const on = () => setScrolled(window.scrollY > 40);
window.addEventListener("scroll", on, { passive: true });
on();
return () => window.removeEventListener("scroll", on);
}, []);
const links = [
{ href: "#work", label: "Work" },
{ href: "#services", label: "Services" },
{ href: "#process", label: "Process" },
{ href: "#capabilities", label: "Capabilities" },
{ href: "#contact", label: "Contact" },
];
return (
);
}
function ArrowSm() {
return (
);
}
function ArrowLg({color = "white"}) {
return (
);
}
/* ---------------- Hero Lamp ---------------- */
function HeroLamp() {
const [theme, setTheme] = useState(
(typeof document !== "undefined" &&
document.documentElement.getAttribute("data-theme")) || "light"
);
const [animKey, setAnimKey] = useState(0);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
const obs = new MutationObserver(() => {
const next =
document.documentElement.getAttribute("data-theme") || "light";
setTheme(next);
setAnimKey((k) => k + 1);
});
obs.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});
return () => obs.disconnect();
}, []);
const src =
theme === "dark" ? "assets/dark-mode.png" : "assets/light-mode.png";
return (
<>
{/* Ambient glow rendered on
via portal — escapes .hero overflow */}
{mounted &&
ReactDOM.createPortal(
,
document.body
)}
>
);
}
/* ---------------- Hero ---------------- */
function Hero() {
return (
Digital agency · Est. 2020 · Dhaka, BD
Live Digital Services
We design
brands
that actually
move markets.
A note from the studio
Grabsoft is a 40-strong team of researchers, strategists, designers and engineers building digital products, brands and growth engines for ambitious teams across 35+ countries.
Combined client valuation
);
}
function SwatchDot() {
return (
);
}
function AnimatedNum({ to, duration = 1400 }) {
const ref = useRef(null);
const [val, setVal] = useState(0);
useEffect(() => {
const el = ref.current;
if (!el) return;
let started = false;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting && !started) {
started = true;
const start = performance.now();
const tick = (now) => {
const t = Math.min(1, (now - start) / duration);
const eased = 1 - Math.pow(1 - t, 3);
setVal(Math.round(to * eased));
if (t < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}
});
}, { threshold: 0.4 });
io.observe(el);
return () => io.disconnect();
}, [to, duration]);
return {val} ;
}
/* ---------------- Marquee ---------------- */
function Marquee() {
const items = ["UI/UX Design", "Branding", "Web Development", "App Engineering", "Software Systems", "SEO & Growth", "Media Buying", "2D / 3D Motion"];
const row = (
{items.map((s, i) => (
{s}
✦
))}
);
return (
);
}
window.Nav = Nav;
window.Hero = Hero;
window.HeroLamp = HeroLamp;
window.Marquee = Marquee;
window.AnimatedNum = AnimatedNum;
window.ArrowSm = ArrowSm;
window.ArrowLg = ArrowLg;
/* ---------------- Video Reel ---------------- */
function VideoReel() {
const [playing, setPlaying] = useState(true);
const [unmuted, setUnmuted] = useState(false);
const VIDEO_ID = "Nxb2KYTWLrM";
const params = new URLSearchParams({
autoplay: "1",
mute: unmuted ? "0" : "1",
loop: "1",
playlist: VIDEO_ID,
controls: "0",
modestbranding: "1",
rel: "0",
playsinline: "1",
iv_load_policy: "3",
enablejsapi: "1",
});
const src = `https://www.youtube-nocookie.com/embed/${VIDEO_ID}?${params.toString()}`;
return (
The studio reel · 2025
Six years of shipping , in ninety seconds.
{playing ? (
) : (
setPlaying(true)} aria-label="Play studio reel">
)}
LIVE · grabsoft studio reel
);
}
window.VideoReel = VideoReel;