// HomeMotion.jsx — motion layer for the homepage: staggered reveals, a live engagement
// timeline, a global threat-map band, animated section dividers, and a tilt wrapper.
// ── Stagger: reveals direct children one after another when scrolled into view
const Stagger = ({ children, step = 70, style, className }) => {
const ref = React.useRef(null);
const [on, setOn] = React.useState(false);
React.useEffect(() => {
const el = ref.current; if (!el) return;
const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setOn(true); obs.disconnect(); } }, { threshold: 0.12 });
obs.observe(el); return () => obs.disconnect();
}, []);
return (
{React.Children.map(children, (c, i) => c == null ? c : (
{c}
))}
);
};
// ── Tilt: subtle 3D tilt that follows the cursor, with a moving highlight
const Tilt = ({ children, max = 4, style }) => {
const ref = React.useRef(null);
const [t, setT] = React.useState({ rx: 0, ry: 0, gx: 50, gy: 50, on: false });
const onMove = (e) => {
const r = ref.current.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width, py = (e.clientY - r.top) / r.height;
setT({ rx: (0.5 - py) * max * 2, ry: (px - 0.5) * max * 2, gx: px * 100, gy: py * 100, on: true });
};
const onLeave = () => setT({ rx: 0, ry: 0, gx: 50, gy: 50, on: false });
return (
);
};
// ── SectionEdge with a travelling light
const LiveEdge = () => (
);
// ── Engagement timeline: a live, looping view of one engagement from scoping to retest
const TL_STEPS = [
{ d: 'Day 0', t: 'Scoping call', m: '30 minutes. Scope, roles, and objectives agreed.', icon: 'chat' },
{ d: 'Day 2', t: 'Fixed-fee proposal', m: 'Statement of work and rules of engagement signed.', icon: 'layers' },
{ d: 'Day 4', t: 'Kickoff', m: 'Your tester joins the shared channel. Test accounts ready.', icon: 'user' },
{ d: 'Day 5', t: 'Testing begins', m: 'Daily updates. Critical findings disclosed within one hour.', icon: 'target' },
{ d: 'Day 14', t: 'Report and readout', m: 'Executive summary and technical report walked through live.', icon: 'code' },
{ d: 'Day 41', t: 'Free retest', m: 'Every High and Critical verified fixed. Letter refreshed.', icon: 'check' },
];
const EngagementTimeline = () => {
const ref = React.useRef(null);
const [seen, setSeen] = React.useState(false);
const [i, setI] = React.useState(0);
React.useEffect(() => {
const el = ref.current; if (!el) return;
const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setSeen(true); obs.disconnect(); } }, { threshold: 0.25 });
obs.observe(el); return () => obs.disconnect();
}, []);
React.useEffect(() => { if (!seen) return; const t = setInterval(() => setI(x => (x + 1) % TL_STEPS.length), 2200); return () => clearInterval(t); }, [seen]);
const pct = (i / (TL_STEPS.length - 1)) * 100;
return (
Timeline
From first call to verified fix
A typical two-week engagement, with the free retest included.
{/* Track */}
{TL_STEPS.map((s, k) => {
const active = k === i, done = k < i;
return (
);
})}
);
};
// ── Global reach band: ThreatMap canvas with three live counters
const GlobalBand = () => {
const ref = React.useRef(null);
const [seen, setSeen] = React.useState(false);
React.useEffect(() => {
const el = ref.current; if (!el) return;
const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setSeen(true); obs.disconnect(); } }, { threshold: 0.2 });
obs.observe(el); return () => obs.disconnect();
}, []);
const Counter = ({ to, suffix = '', label }) => {
const [v, setV] = React.useState(0);
React.useEffect(() => {
if (!seen) return; const s = performance.now(); let raf;
const tick = (t) => { const p = Math.min(1, (t - s) / 1400); setV(Math.round(to * (1 - Math.pow(1 - p, 3)))); if (p < 1) raf = requestAnimationFrame(tick); };
raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf);
}, [seen]);
return (
{v.toLocaleString()}{suffix}
{label}
);
};
return (
The team
Built for how your team works
Engagements are delivered remotely and scheduled around your working hours, with your data handled according to your residency requirements.
{/* Coverage facts drawn from this page (Services, WhyUs, FAQ), not client statistics */}
);
};
// ── Floating "book a call" pill that appears after the hero and hides near the contact form
const FloatingCta = () => {
const [show, setShow] = React.useState(false);
React.useEffect(() => {
const onScroll = () => {
const c = document.getElementById('contact');
const nearContact = c ? c.getBoundingClientRect().top < window.innerHeight : false;
setShow(window.scrollY > window.innerHeight * 0.9 && !nearContact);
};
onScroll(); window.addEventListener('scroll', onScroll, { passive: true }); return () => window.removeEventListener('scroll', onScroll);
}, []);
return (
Scoping calls this week
Get a quote
);
};
Object.assign(window, { Stagger, Tilt, LiveEdge, EngagementTimeline, GlobalBand, FloatingCta });