/* ============================================================
Agency BRN — Growth Systems + FAQ sections
============================================================ */
/* ---------- Growth Systems ---------- */
function GrowthSystems() {
const { t } = React.useContext(window.LanguageContext);
const icons = [
,
,
,
,
,
,
];
return (
{t.growth.eyebrow}
{t.growth.h2A}
{t.growth.h2Grad}
{t.growth.h2B}
{t.growth.lede}
{t.growth.items.map((item, i) => (
{icons[i]}
0{i + 1}
{item.title}
{item.body}
))}
);
}
/* ---------- FAQ ---------- */
function FAQSection() {
const { t } = React.useContext(window.LanguageContext);
const [open, setOpen] = React.useState(0);
const faqSchema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": t.faq.items.map(item => ({
"@type": "Question",
"name": item.q,
"acceptedAnswer": { "@type": "Answer", "text": item.a }
})),
};
return (
{t.faq.eyebrow}
{t.faq.h2}
{t.faq.lede}
{t.cta}
{t.faq.items.map((item, i) => (
{
if (e.target.open) setOpen(i);
}}
>
{item.q}
{item.a}
))}
);
}
window.GrowthSystems = GrowthSystems;
window.FAQSection = FAQSection;
/* ---------- Sticky mobile CTA ---------- */
function StickyMobileCta() {
const { t } = React.useContext(window.LanguageContext);
const [visible, setVisible] = React.useState(false);
React.useEffect(() => {
const onScroll = () => {
// Show after scrolling past hero, hide near footer/contact
const y = window.scrollY;
const doc = document.documentElement;
const docH = doc.scrollHeight - window.innerHeight;
const contact = document.getElementById("contact");
const contactTop = contact ? contact.getBoundingClientRect().top + window.scrollY : Infinity;
setVisible(y > 600 && y < contactTop - 200 && y < docH - 400);
};
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
return () => window.removeEventListener("scroll", onScroll);
}, []);
return (
);
}
window.StickyMobileCta = StickyMobileCta;
/* ---------- Animated counter hook (count-up on view) ---------- */
window.useCountUp = function(target, duration = 1400, decimals = 0) {
const [value, setValue] = React.useState(0);
const ref = React.useRef(null);
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
React.useEffect(() => {
if (reduced) { setValue(target); return; }
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 = (t) => {
const p = Math.min(1, (t - start) / duration);
const eased = 1 - Math.pow(1 - p, 3);
setValue(target * eased);
if (p < 1) requestAnimationFrame(tick);
else setValue(target);
};
requestAnimationFrame(tick);
io.disconnect();
}
});
}, { threshold: 0.4 });
io.observe(el);
return () => io.disconnect();
}, [target, duration, reduced]);
const formatted = decimals > 0 ? value.toFixed(decimals) : Math.round(value).toLocaleString();
return [formatted, ref];
};