const { useState, useCallback, useEffect } = React; // Injected by the plugin via wp_add_inline_script — see koi_audit_enqueue_assets() in koi-audit-tool.php const CONFIG = window.KoiAuditConfig || {}; const API = (CONFIG.apiBase || '/wp-json/koi-audit/v1').replace(/\/$/, ''); const LOADING_MESSAGES = [ 'Loading the page…', 'Capturing a preview…', 'Measuring paint timing…', 'Checking layout stability…', 'Running technical checks…', 'Scanning search readiness…', 'Compiling the report…', ]; function scoreColor(score) { if (score >= 90) return 'var(--green)'; if (score >= 50) return 'var(--coral)'; return 'var(--magenta)'; } function verdictCopy(score) { if (score >= 90) return { title: 'Your site is fast.', body: 'This is not where you\u2019re losing leads. Worth checking conversion flow and AI visibility next.' }; if (score >= 50) return { title: 'There\u2019s real money on the table.', body: 'Visitors are waiting longer than they should. Every extra second of load time is visitors who leave before they see your offer.' }; return { title: 'This is actively costing you leads.', body: 'A site this slow loses a meaningful share of visitors before the page even finishes loading.' }; } function CheckIcon() { return ( ); } function XIcon() { return ( ); } function LockIcon() { return ( ); } function SparkleIcon() { return ( ); } function Gauge({ score }) { const r = 58; const c = 2 * Math.PI * r; const pct = Math.max(0, Math.min(100, score)); const offset = c - (pct / 100) * c; const color = scoreColor(pct); return (
{Math.round(pct)}
/ 100
); } function Loader() { const [i, setI] = useState(0); useEffect(() => { const id = setInterval(() => setI((v) => (v + 1) % LOADING_MESSAGES.length), 1700); return () => clearInterval(id); }, []); return (
Running the check
{LOADING_MESSAGES[i]}
); } function Metric({ label, value }) { return (
{label}
{value ?? '—'}
); } function PreviewCard({ url, screenshot }) { if (!screenshot) return null; return (
{url}
Screenshot of the site as it loaded during the test
); } function AiReportCard({ aiReport }) { if (!aiReport) return null; return (
AI-generated

{aiReport.headline}

{aiReport.summary}

    {(aiReport.actionPlan || []).map((item, idx) => (
  1. {idx + 1}
    {item.title}
    {item.reason}
  2. ))}
); } function CtaBanner() { if (!CONFIG.ctaUrl) return null; return (
Want this fixed instead of just flagged?
{CONFIG.ctaLabel || 'Book a call'}
); } // --------------------------------------------------------------------------- // Full report — used both for the visitor's emailed link (?r=TOKEN) and the // admin preview (CONFIG.previewToken). Everything here already passed through // /submit, so no email gate — this view is about proving the token and rendering. // --------------------------------------------------------------------------- function FullReport({ token, preview }) { const [status, setStatus] = useState('loading'); const [errorMsg, setErrorMsg] = useState(''); const [data, setData] = useState(null); const [aiReport, setAiReport] = useState(null); useEffect(() => { const params = new URLSearchParams({ token }); if (preview) params.set('preview', '1'); const headers = preview && CONFIG.nonce ? { 'X-WP-Nonce': CONFIG.nonce } : {}; fetch(`${API}/report?${params.toString()}`, { headers }) .then(async (res) => { const json = await res.json().catch(() => null); if (!res.ok || !json?.success) throw new Error(json?.message || 'This report could not be loaded.'); setData(json.data); setAiReport(json.aiReport || null); setStatus('ready'); }) .catch((err) => { setErrorMsg(err.message || 'This report could not be loaded.'); setStatus('error'); }); }, [token, preview]); if (status === 'loading') return ; if (status === 'error') return
{errorMsg}
; const verdict = verdictCopy(data.score); return (

{verdict.title}

{verdict.body}

Core metrics
{data.checks.length > 0 && (
Technical checks
{data.checks.map((c, idx) => (
{c.pass ? : } {c.label}
))}
)} {data.topIssues.length > 0 && (
Top issues found
{data.topIssues.map((issue, idx) => (
{String(idx + 1).padStart(2, '0')}
{issue.title}
{issue.description}
{issue.savingsMs != null && (
~{issue.savingsMs}ms of load time at stake
)}
))}
)}
AI & search visibility
Crawlability & readiness
{data.seoScore != null ? Math.round(data.seoScore) : '—'}
How easily search engines and AI crawlers can read, index, and quote this page — based on real crawlability and metadata signals.
Full Website Audit
Live test of whether ChatGPT and Perplexity actually mention this business in real answers — included in the full Website Audit.
{!preview && }
Scored on {data.strategy} via Google PageSpeed Insights (Lighthouse).
); } // --------------------------------------------------------------------------- // Scan flow — the URL check, the partial report, and the email capture that // unlocks the rest via the emailed link. Nothing here calls Anthropic or // Google directly; both live behind /check and /submit on this same plugin. // --------------------------------------------------------------------------- function ScanApp() { const [url, setUrl] = useState(''); const [strategy, setStrategy] = useState('mobile'); const [status, setStatus] = useState(''); const [isError, setIsError] = useState(false); const [loading, setLoading] = useState(false); const [checkId, setCheckId] = useState(null); const [partial, setPartial] = useState(null); const [firstName, setFirstName] = useState(''); const [email, setEmail] = useState(''); const [consent, setConsent] = useState(false); const [submitting, setSubmitting] = useState(false); const [submitError, setSubmitError] = useState(''); const [sent, setSent] = useState(false); const runCheck = useCallback(async (e) => { e.preventDefault(); if (!url.trim()) return; let target = url.trim(); if (!/^https?:\/\//i.test(target)) target = 'https://' + target; setLoading(true); setIsError(false); setStatus(''); setPartial(null); setCheckId(null); setSent(false); setSubmitError(''); try { const params = new URLSearchParams({ url: target, strategy }); const res = await fetch(`${API}/check?${params.toString()}`); const json = await res.json().catch(() => null); if (!res.ok || !json?.partial) { throw new Error(json?.error || `Request failed (${res.status})`); } setCheckId(json.checkId); setPartial(json.partial); } catch (err) { setIsError(true); setStatus(err.message || 'Something went wrong.'); } finally { setLoading(false); } }, [url, strategy]); const submitEmail = useCallback(async (e) => { e.preventDefault(); if (!email.trim() || !checkId) return; if (CONFIG.consentRequired && !consent) { setSubmitError('Please tick the box to receive your report.'); return; } setSubmitting(true); setSubmitError(''); try { const res = await fetch(`${API}/submit`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ checkId, email: email.trim(), firstName: firstName.trim(), consent, website_hp: '', // honeypot — left blank by real visitors }), }); const json = await res.json().catch(() => ({})); if (!res.ok || !json.success) { throw new Error(json.message || `Request failed (${res.status})`); } setSent(true); } catch (err) { setSubmitError(err.message || 'Something went wrong.'); } finally { setSubmitting(false); } }, [email, firstName, consent, checkId]); const verdict = partial ? verdictCopy(partial.score) : null; return (
setUrl(e.target.value)} disabled={loading} className="field-url" />
{status &&
{status}
} {loading && } {partial && (

{verdict.title}

{verdict.body}

Core metrics
Technical checks
{partial.checksPassed} of {partial.checksTotal} checks passed
{partial.firstIssue && (
Top issue found
01
{partial.firstIssue.title}
{partial.firstIssue.description}
{partial.firstIssue.savingsMs != null && (
~{partial.firstIssue.savingsMs}ms of load time at stake
)}
{partial.moreIssues > 0 && (
+{partial.moreIssues} more in your full report
)}
)} {!sent ? (
Get your full report

The itemized checklist, every issue with estimated savings, and a prioritized action plan — emailed to you now.

setFirstName(e.target.value)} disabled={submitting} /> setEmail(e.target.value)} disabled={submitting} />
{submitError &&
{submitError}
}
) : (
Check your inbox

We{'\u2019'}ve sent your full report to {email}. It usually arrives within a minute or two.

)}
)}
); } function App() { const params = new URLSearchParams(window.location.search); const token = params.get('r') || CONFIG.previewToken || null; if (token) return ; return ; } ReactDOM.createRoot(document.getElementById('root')).render();