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 (
);
}
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 (
);
}
function PreviewCard({ url, screenshot }) {
if (!screenshot) return null;
return (
);
}
function AiReportCard({ aiReport }) {
if (!aiReport) return null;
return (
AI-generated
{aiReport.headline}
{aiReport.summary}
{(aiReport.actionPlan || []).map((item, idx) => (
{idx + 1}
{item.title}
{item.reason}
))}
);
}
function CtaBanner() {
if (!CONFIG.ctaUrl) return null;
return (
);
}
// ---------------------------------------------------------------------------
// 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 (
setStrategy('mobile')}
>Mobile
setStrategy('desktop')}
>Desktop
{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.
) : (
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( );