// Act 3 — The 8 features, each ~13s // Each follows the same pattern: kicker (2s) → mock animates (8s) → payoff (2s) // Feature timings within the act (starting at global 30s): // 30-43s F1: GPS // 43-56s F2: Approvals // 56-69s F3: Collections // 69-82s F4: KPI // 82-95s F5: Ask Fajr (AI) // 95-108s F6: Doctors database // 108-121s F7: Samples // 121-134s F8: Payroll // ══════════ Feature scaffold: header + payoff reused across shots ══════════ function FeatureKicker({ num, problem, appearAt = 0 }) { const { localTime } = useSprite(); const t = clamp((localTime - appearAt) / 0.6, 0, 1); return (
المشكلة: {problem}
); } function FeaturePayoff({ text, appearAt }) { const { localTime } = useSprite(); if (localTime < appearAt) return null; const t = clamp((localTime - appearAt) / 0.6, 0, 1); return (
✓ {text}
); } // ────────────────────────────────────────────────────────── // F1: GPS — "زيارات وهمية" // ────────────────────────────────────────────────────────── function Feature1_GPS() { const { localTime } = useSprite(); const distT = clamp((localTime - 4.0) / 2.5, 0, 1); const distance = Math.round(85 - (85 - 18) * Easing.easeOutCubic(distT)); const verified = localTime > 7.0; const vT = clamp((localTime - 7.0) / 0.5, 0, 1); // scan const scanFired = React.useRef(false); React.useEffect(() => { if (localTime > 3.8 && localTime < 3.95 && !scanFired.current) { scanFired.current = true; if (window.FajrAudio) window.FajrAudio.play('scan'); } if (localTime < 3.7) scanFired.current = false; }, [localTime]); const successFired = React.useRef(false); React.useEffect(() => { if (verified && localTime < 7.15 && !successFired.current) { successFired.current = true; if (window.FajrAudio) window.FajrAudio.play('success'); } if (!verified) successFired.current = false; }, [localTime, verified]); const pulseT = (localTime * 1.3) % 1; const pulseSize = 1 + pulseT * 2.5; const pulseOpacity = 1 - pulseT; return ( <>
{/* Big map card */}
LIVE GPS VERIFICATION
● LIVE
{/* Scan sweep */} {localTime > 3.8 && localTime < 6.5 && (() => { const t = ((localTime - 3.8) / 1.8) % 1; return (
); })()} {/* Coords top-right */}
33.3152° N — 44.3661° E
{/* Current pin (user) */} {localTime > 2.0 && (() => { // Animate pin position from far to close const moveT = clamp((localTime - 2.0) / 3.0, 0, 1); const xPct = 25 + (45 - 25) * Easing.easeInOutCubic(moveT); const yPct = 65 + (50 - 65) * Easing.easeInOutCubic(moveT); return (
REP
); })()} {/* Clinic target pin */}
عيادة د. أحمد
{/* Distance chip */}
📍 DISTANCE: {distance}m
{/* Verified stamp */} {verified && (
✓ VERIFIED
)}
); } // ────────────────────────────────────────────────────────── // F2: Approvals chain // ────────────────────────────────────────────────────────── function Feature2_Approvals() { const { localTime } = useSprite(); const stages = [ { name: 'المندوب', role: 'REP', doneAt: 2.2 }, { name: 'المشرف', role: 'SUPERVISOR', doneAt: 3.4 }, { name: 'المدير التجاري', role: 'COMMERCIAL', doneAt: 4.6 }, { name: 'المدير العام', role: 'GENERAL', doneAt: 5.8 }, ]; return ( <>
ORDER #ORD-2026-01247 · APPROVAL CHAIN
PROCESSING
{/* Chain */}
{stages.map((s, i) => { const isDone = localTime >= s.doneAt; const t = isDone ? clamp((localTime - s.doneAt) / 0.4, 0, 1) : 0; return (
{isDone ? '✓' : i + 1}
{s.name}
{s.role}
{isDone && (
{['09:12','09:45','10:23','11:04'][i]}
)}
{i < stages.length - 1 && (
)} ); })}
{/* Duration badge */} {localTime > 6.5 && (() => { const t = clamp((localTime - 6.5) / 0.5, 0, 1); return (
3 DAYS 1h 52m
); })()}
); } // ────────────────────────────────────────────────────────── // F3: Collections with unique receipt // ────────────────────────────────────────────────────────── function Feature3_Collections() { const { localTime } = useSprite(); const receiptT = clamp((localTime - 1.5) / 0.8, 0, 1); const numberT = clamp((localTime - 2.5) / 1.0, 0, 1); const stampT = clamp((localTime - 3.8) / 0.4, 0, 1); const uploadT = clamp((localTime - 5.0) / 0.8, 0, 1); const matchT = clamp((localTime - 7.0) / 0.5, 0, 1); const stampFired = React.useRef(false); React.useEffect(() => { if (localTime > 3.8 && localTime < 3.95 && !stampFired.current) { stampFired.current = true; if (window.FajrAudio) window.FajrAudio.play('pop', { freq: 200, gain: 0.3 }); } if (localTime < 3.7) stampFired.current = false; }, [localTime]); const matchFired = React.useRef(false); React.useEffect(() => { if (localTime > 7.0 && localTime < 7.15 && !matchFired.current) { matchFired.current = true; if (window.FajrAudio) window.FajrAudio.play('success'); } if (localTime < 6.9) matchFired.current = false; }, [localTime]); const receiptNumber = 'RC-2026-0847'; const charsShown = Math.floor(numberT * receiptNumber.length); const shownNumber = receiptNumber.slice(0, charsShown); return ( <>
{/* Left: system generated receipt */}
SYSTEM-GENERATED RECEIPT
#{shownNumber} {charsShown < receiptNumber.length && }
HASH: a7f3 · b921 · c4d8
الصيدلية صيدلية النور
المبلغ 500,000 IQD
{/* System stamp */} {stampT > 0 && (
🔒 غير قابل للتعديل · SYSTEM-LOCKED
)}
{/* Right: upload stamped receipt photo */}
UPLOADED PHARMACY RECEIPT
{/* Fake photo of paper receipt */}
صيدلية النور — إيصال
RC# RC-2026-0847
AMOUNT: 500,000 IQD
DATE: 15/04/2026
{/* Pharmacy stamp */} {uploadT > 0.5 && (
PHARMACY
STAMP
)}
{/* Match indicator */} {matchT > 0 && (
✓ المبلغ مطابق — 500,000 IQD
)}
); } // ────────────────────────────────────────────────────────── // F4: KPI // ────────────────────────────────────────────────────────── function Feature4_KPI() { const { localTime } = useSprite(); const reps = [ { name: 'أحمد', target: 87, color: FAJR.gold, appearAt: 2.0 }, { name: 'سارة', target: 92, color: FAJR.green, appearAt: 2.6, best: true }, { name: 'خالد', target: 64, color: FAJR.orange, appearAt: 3.2 }, ]; return ( <>
PERFORMANCE KPI · APRIL 2026
{reps.map((r, i) => { const t = clamp((localTime - r.appearAt) / 1.0, 0, 1); const val = Math.round(r.target * Easing.easeOutCubic(t)); const rank = ['+1', '1', '+2'][i]; return (
6 ? FAJR.green : FAJR.lineDark}`, position: 'relative', opacity: clamp((localTime - r.appearAt) / 0.4, 0, 1), transform: `translateY(${(1 - clamp((localTime - r.appearAt) / 0.4, 0, 1)) * 20}px)`, boxShadow: r.best && localTime > 6.0 ? `0 0 40px rgba(127,193,138,.2)` : 'none', transition: 'all 0.6s', }}> {/* Star for best */} {r.best && localTime > 6.0 && (
)} {/* Avatar */}
{r.name[0]}
{r.name}
SALES REP
{/* Bar */}
PERFORMANCE {val}%
{/* Stats grid */}
{[84, 96, 52][i]}
زيارة
{[78, 91, 61][i]}%
تارغت
); })}
); } Object.assign(window, { FeatureKicker, FeaturePayoff, Feature1_GPS, Feature2_Approvals, Feature3_Collections, Feature4_KPI, });