// Testimonials (3D cards on scroll), FAQ, final CTA, Footer // Voices: quotes are real and anonymous. The avatars are illustrated characters, not photos of the clients. const VOICES = [ { q: "The first useful version was in our supervisors' hands within weeks, not months.", n: 'Operations lead', r: 'SME client', img: '/uploads/voice_avatar_1.webp', badge: 'Weeks, not months', bl: 'Time to first version', bg: 'var(--accent)' }, { q: 'They understood why our Excel file existed before trying to replace it.', n: 'Manager', r: 'SME client', img: '/uploads/voice_avatar_2.webp', badge: 'Excel → system', bl: 'Workflow first', bg: '#1d1d1d' }, { q: 'The dashboard finally gave management and the floor the same numbers.', n: 'Director', r: 'SME client', img: '/uploads/voice_avatar_3.webp', badge: 'One set of numbers', bl: 'Management + team', bg: '#e6dfd1' }, ]; function Voices() { const reduced = useReducedMotion(); const sectionRef = React.useRef(null); const stickyRef = React.useRef(null); const cardRefs = React.useRef([]); const n = VOICES.length; useScrollFrame(() => { const section = sectionRef.current; const sticky = stickyRef.current; if (!section || !sticky) return; if (reduced) { cardRefs.current.forEach((c) => c && c.removeAttribute('style')); return; } const p = pinProgress(section); // Title fades out over the first slice, then each card swings in and out. sticky.style.setProperty('--t', clamp01(p * n * 3).toFixed(3)); const span = 1 / n; cardRefs.current.forEach((c, i) => { if (!c) return; const local = (p - i * span) / span; // 0..1 inside this card's window let ry = 90; let o = 0; if (local > 0 && local < 1) { if (local < 0.3) { const k = local / 0.3; ry = 90 * (1 - k); o = k; } else if (local < 0.75 || i === n - 1) { ry = 0; o = 1; } else { const k = (local - 0.75) / 0.25; ry = -90 * k; o = 1 - k; } } else if (local >= 1 && i === n - 1) { ry = 0; o = 1; } c.style.setProperty('--ry', `${ry.toFixed(2)}deg`); c.style.setProperty('--o', o.toFixed(3)); c.style.visibility = o < 0.01 ? 'hidden' : 'visible'; }); }, [reduced]); return (

Words from our clients.

{VOICES.map((v, i) => (
(cardRefs.current[i] = el)} className="voice" style={{ margin: 0 }}>
))}
); } const FAQS = [ { q: 'Which industries do you work with?', a: 'Businesses of any industry. If your team runs on spreadsheets, chat groups and paper forms, we can turn that workflow into one system your people actually use.' }, { q: 'How does the instant quote work?', a: 'Our online estimator asks about your company and project, follows up with 5-8 questions, verifies your email with a 6-digit code, then sends an itemised quotation and a project proposal as PDFs. It is free and takes about 5 minutes.' }, { q: 'Can you replace our Excel files gradually?', a: 'Yes. We usually start with one painful spreadsheet, turn it into a small internal system, then connect the next workflow once the team is comfortable.' }, { q: 'Do we need technical staff to use what you build?', a: 'No. Every system ships with training and handover. We design for front-line staff, admin teams and management, not just technical users.' }, { q: "What if we don't know what to automate first?", a: 'We map the workflow, review the files and reports you already use, then rank the first few systems by business impact and rollout difficulty.' }, { q: 'Can you work with our existing tools?', a: 'Almost certainly. We integrate with accounting, inventory, barcode workflows, WhatsApp Business, existing ERP systems and most modern SaaS tools.' }, { q: 'What if the project takes longer than expected?', a: 'We will tell you before continuing if the scope exceeds the estimate.' }, { q: 'Will there be surprise charges?', a: 'No. There is no extra work or extra charge without your prior approval.' }, { q: 'Where are you based and who actually does the work?', a: 'Kuala Lumpur. Senior builders work directly with your team from discovery through launch.' }, ]; function FAQ() { const [open, setOpen] = React.useState(0); return (

FAQ

Common questions.

Can't find what you're looking for? Drop us a note.

{FAQS.map((f, i) => { const isOpen = open === i; return (

{f.a}

); })}
); } const CTA_WORDS = ["Let's", 'build', 'your', 'next', 'idea']; const CTA_PILL_AFTER = 2; // Closing call to action: words rise out of their masks, the pill pops in and its sun knob slides across and lights up, and // an accent "horizon" arc rises behind the buttons as the section scrolls into view. function CTA() { const reduced = useReducedMotion(); const ref = React.useRef(null); useScrollFrame(() => { const el = ref.current; if (!el) return; if (reduced) { el.style.removeProperty('--e'); return; } const r = el.getBoundingClientRect(); const vh = window.innerHeight; el.style.setProperty('--e', clamp01((vh - r.top) / (vh * 0.85)).toFixed(4)); }, [reduced]); return (

{CTA_WORDS.join(' ')}

Book a 30-min call

or email {SALES_EMAIL}

); } function Footer() { const cols = [ ['Explore', [['Solutions', '#solutions'], ['Work', '#work'], ['Process', '#process'], ['Why us', '#why'], ['FAQ', '#faq']]], ['Get started', [['Instant quote', QUOTE_URL], ['Book a 30-min call', contactMailto], ['Privacy notice', '/privacy/en/']]], ['Contact', [[SALES_EMAIL, `mailto:${SALES_EMAIL}`], ['KLCC, Kuala Lumpur', null]]], ]; return ( ); } Object.assign(window, { Voices, FAQ, CTA, Footer });