// Marketing site — shared helpers (scroll reveal, count-up, marquee, icons, logo).
const { useState, useEffect, useRef } = React;
const EASE = "cubic-bezier(.16,1,.3,1)";
const reduceMo = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function Icon({ name, size = 18, style = {} }) {
return ;
}
/** Reveal: staggered fade + rise. Fires on mount via a short timer so it is
reliable in every environment (offscreen capture iframes throttle IO/scroll).
Real-browser feel: sections fade up in sequence as the page settles. */
function Reveal({ children, delay = 0, y = 26, style = {}, as = "div" }) {
const [inv, setInv] = useState(false);
useEffect(() => {
const t = setTimeout(() => setInv(true), reduceMo() ? 0 : delay + 60);
return () => clearTimeout(t);
}, []);
const El = as;
return (
{children}
);
}
/** Kinetic headline: each line masked, slides up in sequence on mount. */
function Kinetic({ lines, style = {}, lineStyle = {}, baseDelay = 120, step = 90 }) {
const [go, setGo] = useState(false);
useEffect(() => { const t = setTimeout(() => setGo(true), 60); return () => clearTimeout(t); }, []);
const reduce = reduceMo();
return (
{lines.map((ln, i) => (
{ln}
))}
);
}
/** Count up to value when scrolled into view. */
function Stat({ value, suffix = "", label, accent = false }) {
const ref = useRef(null);
const [n, setN] = useState(0);
useEffect(() => {
if (reduceMo()) { setN(value); return; }
let raf, start;
const t = setTimeout(() => {
const tick = (ts) => {
if (start == null) start = ts;
const p = Math.min(1, (ts - start) / 1100);
setN(value * (1 - Math.pow(1 - p, 3)));
if (p < 1) raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
}, 280);
return () => { clearTimeout(t); cancelAnimationFrame(raf); };
}, [value]);
return (
{Math.round(n).toLocaleString()}{suffix}
{label}
);
}
/** Infinite horizontal marquee band. */
function Marquee({ items, sep = "✺", reverse = false, style = {}, fontSize = "clamp(22px,3.5vw,51px)" }) {
const run = items.concat(items);
return (
{run.map((it, i) => (
{it}
{sep}
))}
);
}
function Logo({ size = 19, light = false }) {
return (
G
THE GANGSA
);
}
Object.assign(window, { Icon, Reveal, Kinetic, Stat, Marquee, Logo });