Fix My Webshite — Free Website Audit & AI Rebuild | fixmywebshite.com
free website audit · real google data · no sugarcoating

Is your website
embarrassing you?

paste your url below. we’ll scan it, score it across 95 signals, and tell you exactly what’s wrong — in plain english, without being polite about it. then fix it for £5.

your url
crawl:
this page only
full site (up to 20 pages)
compare rivals
speed check
rebuild with claude
build a new website

Pick a style.
Your webshite gets fixed.

£5 on your phone bill · no account · no card · 4 complete website versions + deployment guide · you own the code forever
💳
£5 — added to your phone bill. That’s it.
No credit card. No account. No subscription. You pay £5 once — it appears on your next phone bill just like a premium text. Works on O2, EE, Vodafone and Three.
What you get: we scan your site, build you a complete new website optimised for 95 SEO signals, give you 3 more versions in different styles, and download a step-by-step guide to put it live. A web developer charges £300–£800 for this.
choose your style — you’ll get all 4 versions after paying
Modern Dark
Clean, bold, tech-forward. Coral accents on near-black.
Warm & Friendly
Cream background, terracotta. Cafes, therapists, local trades.
Minimal White
Ultra-clean. White space does the work. Professional services.
Bold & Vibrant
Deep purple, electric accents. Gyms, agencies, nightlife.
Natural & Organic
Greens and cream. Wellness, food, sustainability businesses.
Luxury & Premium
Dark chocolate, gold. High-end salons, property, fine dining.
you’ll receive all 4 versions in different styles after paying
O2 · EE · Vodafone · Three · added to your next bill · no subscription · cancel anytime = never needed
no credit card no account needed no monthly fee ready in ~3 minutes
page speed checker

Your website is
probably too slow.

lab scores vs real-world crux field data. Google ranks you on real-world. this is the only free tool showing both clearly side by side.
https://
lighthouse scores & core web vitals
lab · lighthouse (simulated) real-world · CrUX (Google ranks you on this)
mobile performance
mobile lighthouse
41
poor · target 90+
Mobile Core Web Vitals
LCP · largest contentful paint
LAB
5.8s
REAL
6.1s
Poor — target under 2.5s. Fix: compress hero image to WebP under 200KB.
CLS · cumulative layout shift
LAB
0.04
REAL
0.03
Good — target under 0.1. No action needed.
INP · interaction to next paint
LAB
280ms
REAL
no data
Needs work — target under 200ms. Reduce JS bundle size.
TTFB · time to first byte
LAB
1.2s
REAL
1.4s
Needs work — target under 0.8s. Add page caching or upgrade hosting.
desktop performance
desktop lighthouse
74
needs work · target 90+
Desktop Core Web Vitals
LCP
LAB
2.4s
REAL
2.7s
Needs work — just above 2.5s good threshold.
CLS
LAB
0.02
REAL
0.01
Good — stable layout. No action needed.
FCP
LAB
0.9s
REAL
1.1s
Good — under 1.8s threshold.
TTFB
LAB
1.1s
REAL
1.3s
Needs work — above 0.8s target.
accessibility
92
good
Image alt text4 missing
Colour contrast✓ pass
ARIA labels✓ pass
Tap target sizes✓ pass
best practice
95
excellent
HTTPS✓ enabled
No browser errors✓ clean
Deprecated APIs✓ none
CSP headersmissing
performance opportunities
Compress hero image — hero-banner.jpg is 2.4MB
Convert to WebP, max 1440px wide, target under 200KB. Estimated LCP saving: 3.8 seconds.
critical
Eliminate render-blocking resources — 3 stylesheets, 2 scripts
Move stylesheets to async and scripts to defer. Estimated saving: 0.9s off FCP.
important
Serve next-gen formats — 6 JPEG images identified
Converting all 6 to WebP would save 840KB total bandwidth.
important
HTTPS enabled · no mixed content detected
All resources served over HTTPS. Secure connection ranking signal is healthy.
good
domain & trust signals
ssl certificate
Valid
Let’s Encrypt · expires in 247 days · HSTS enabled
domain age
6 yrs
Registered 2018 · expires 2027 · DNSSEC enabled
email security
2 missing
SPF ✓ · DMARC missing · no DKIM found
Compress images to WebP free — fastest single LCP fix

Converting a 2MB JPEG to WebP under 200KB typically cuts real-world LCP from 5–7 seconds to under 2 seconds.

competitor comparison — up to 3

Why is that lot
outranking you?

every ranking factor side by side — speed, content depth, backlinks, keywords, social, schema, questions.
your site
your site
competitor 1
competitor 2
competitor 3
full comparison — all ranking signals
signal mybakery.co.uk
67
your site
bristol-artisans.co.uk
81
competitor 2
—
competitor 3
—
page speed & core web vitals
speed score↓ 41↑ 78——
mobile LCP (field)↓ 6.1s↑ 1.9s——
CLS✓ 0.04✓ 0.02——
TTFB↓ 1.4s↑ 0.6s——
seo scores
overall SEO↓ 67↑ 81——
on-page↓ 71↑ 77——
technical↑ 88↓ 82——
schema↓ 5↑ 68——
content depth
word count (homepage)↓ 620↑ 1,480——
total pages↓ 20↑ 34——
avg words/page↓ 480↑ 1,100——
backlinks & authority
domain authority (OPR)↓ 2.4↑ 3.8——
referring domains↓ 47↑ 85——
keywords
est. ranked keywords↓ ~42↑ ~118——
top keyword position↑ #4↑ #2——
social & local presence
Google Business Profilemissing✓ verified——
LocalBusiness schema✗✓——
security & trust
HTTPS✓✓——
DMARC record✗ missing✓ present——
gap analysis — fix these first

1. Mobile LCP 6.1s vs their 1.9s — compress hero image to WebP under 200KB. 30 minutes. Biggest ranking gain available.
2. No LocalBusiness schema — they show hours and ratings in results, you don’t. 15-minute fix.
3. 620 vs 1,480 words per page — add dedicated content pages.
4. No Google Business Profile — required for “near me” Maps visibility.
question keyword gap — queries they rank for that you don’t
Questions are high-intent queries (how, what, where, best). Your competitor ranks for 14 question keywords — you rank for 3. Each is a FAQ page or FAQ schema opportunity.
where to buy sourdough bread bristol
your position #4 · ~90 searches/mo
how to order wedding cake bristol
competitor ranks #3 · you don’t appear · ~210/mo · add FAQ page
best artisan bakery in bristol
competitor ranks #2 · you at #8 · ~320/mo · add AggregateRating schema
artisan bread delivery bristol
competitor ranks #5 · you not found · ~180/mo · add dedicated delivery page
See every keyword your competitor ranks for that you don’t — Semrush Keyword Gap

Semrush Keyword Gap surfaces every phrase a competitor ranks for that your site misses, sorted by monthly search volume.

free seo checker

How bad is it,
really?

instant 0–100 score from live google api data. no login needed.
https://
67
needs work · 3 critical issues found
speed41
on-page71
technical88
schema5
live Google API data · ai fix instructions · keyword density · always free · no login needed
shareable seo report

Proof your site
got better.

a stable public URL showing your current audit data. share with a developer, send to a client, post your improvement.
mybakery.co.uk
audited Jun 2026 · fixmywebshite.com · live Google PageSpeed Insights API
✓ HTTPS ✓ no malware ✗ 3 critical issues ↑ +29 pts improvement
67
overall
page speed
41
LCP 6.1s · poor
on-page seo
71
OG tags missing
technical
88
DMARC missing
schema
5
7 types missing
critical issues
✗Mobile LCP 6.1s — compress hero to WebP
✗No LocalBusiness schema — 7 types missing
✗Open Graph + Twitter Card absent
✗DMARC record missing
passing checks
✓HTTPS · SSL valid 247 days · HSTS
✓Technical SEO 88/100
✓Zero broken internal links
✓CLS 0.03 — stable layout
✓Google Safe Browsing — clean
backlinks
50
bbc.co.uk ✓
keywords
55
#4 artisan bakery
kw density
78
4 keywords not in H1
vs rivals
58
−14 pt gap · fixable
core web vitals — real-world (google ranking signal)
LCP
6.1s
target <2.5s
poor · ranking penalty
CLS
0.03
target <0.1
good ✓
INP
—
target <200ms
no field data
TTFB
1.4s
target <0.8s
needs work
key metrics
word count (homepage)
620
target 1,000+ words
pages crawled
20
full site crawl
domain authority
2.4
OpenPageRank 0–10
SSL expiry
247d
expires Dec 2026
top keyword rank
#4
artisan bakery bristol
referring domains
47
Common Crawl estimate
improvements since last audit (may → jun)
+8 ptsOn-page SEO — added alt text to 7 images, fixed anchor text on 4 links
+3 ptsPage speed — deferred 2 non-critical scripts, saving 0.4s FCP
+2 ptsTechnical — added canonical tag to 3 pages that were missing it
next fixCompress hero image to WebP — estimated +12 pts speed score
keyword density

Are you using words
Google cares about?

1, 2 and 3-word phrase frequency · density percentage · distribution across title, description, H1–H3 · stuffing detection · all free, no API needed.
total words
1,464
excl. stop words
top density
2.05%
“bakery” — healthy
stuffing
none
all within 0.5–3%
missing H1
4
top keywords not in H1
target keyword distribution
keywordcountdensitytitledescH1H2H3
bakery302.05%✓✓✓✓✓
bristol261.78%✓✓✓✓✗
artisan211.43%✗✗✓✓✗
sourdough181.23%✗✗✗✓✓
delivery130.89%✗✗✗✓✓
one-word phrases — top 10
#keywordcountdensitytitledescH1H2H3
1bakery302.05%✓✓✓✓✓
2bristol261.78%✓✓✓✓✗
3artisan211.43%✗✗✓✓✗
4sourdough181.23%✗✗✗✓✓
5bread130.89%✗✗✗✓✗
6cakes130.89%✗✗✗✓✗
7delivery130.89%✗✗✗✓✓
8order120.82%✗✗✗✗✓
9fresh110.75%✗✗✗✓✗
10handmade90.62%✗✓✗✗✗
no keyword stuffing detected
two-word phrases — top 10
#phrasecountdensitytitledescH1H2H3
1artisan bakery140.96%✗✓✓✓✗
2bakery bristol130.89%✓✓✓✓✗
3sourdough bread110.75%✗✗✗✓✓
4wedding cakes80.55%✗✗✗✓✗
5order online70.48%✗✗✗✗✓
6bristol delivery70.48%✗✗✗✗✓
7fresh bread70.48%✗✗✗✓✗
8celebration cakes60.41%✗✗✗✓✗
9baked daily50.34%✗✗✗✗✗
10local delivery50.34%✗✗✗✗✗
Content optimisation based on what top-10 pages all have in common — Surfer SEO

Surfer SEO’s Content Editor shows which specific semantic terms, headings and entities the top-10 ranking pages all share.

industry directory — ranked by improvement

Sites actually
getting better.

opt-in only. sites ranked by improvement trajectory — not raw score. a site that went from 38 to 72 ranks above one sitting at 80 that never improves.
food & hospitality local services health & wellness professional e-commerce tech & saas creative & media
add your site
run a free audit, choose your industry, and opt in. rank updates every time you re-audit.
seo for complete beginners

Your website’s a mess.
Here’s how to fix it.

you don’t need to understand SEO. you just need to follow these steps. no coding knowledge required.
1️⃣
your website
enter your url
2️⃣
what’s wrong
plain english
3️⃣
chatgpt prompt
paste & follow
4️⃣
new website
paste the code
🌐

First — what’s your website address?

This is the bit you type into a browser to get to your site — like mybakery.co.uk or www.johnplumbing.com. Don’t include the “https://” part, just the address.

website
examples: mybakery.co.uk · janecoaching.com · bestplumber-london.co.uk
takes about 5 seconds · no signup needed · completely free
`; document.body.appendChild(m); m.addEventListener('click', e => { if (e.target === m) m.style.display = 'none'; }); } const domEl = document.getElementById('reapplyDomain'); if (domEl) domEl.textContent = domain; const rErr = document.getElementById('reapplyErr'); const rOk = document.getElementById('reapplyOk'); const rForm = document.getElementById('reapplyForm'); const rFooter = document.getElementById('reapplyFooter'); if (rErr) rErr.style.display = 'none'; if (rOk) rOk.style.display = 'none'; if (rForm) rForm.style.display = 'block'; if (rFooter) rFooter.innerHTML = ''; m.style.display = 'flex'; } function closeReapplyModal() { const m = document.getElementById('reapplyModal'); if (m) m.style.display = 'none'; } async function submitReapply() { const email = document.getElementById('reapplyEmail')?.value?.trim() || ''; const reason = document.getElementById('reapplyReason')?.value?.trim() || ''; const err = document.getElementById('reapplyErr'); const ok = document.getElementById('reapplyOk'); if (err) err.style.display = 'none'; if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { if (err) { err.textContent = 'Please enter a valid email address.'; err.style.display = 'block'; } return; } if (reason.length < 20) { if (err) { err.textContent = 'Please provide more detail (at least 20 characters).'; err.style.display = 'block'; } return; } const btn = document.getElementById('reapplySubmitBtn'); if (btn) { btn.innerHTML = ''; btn.disabled = true; } await new Promise(r => setTimeout(r, 900)); if (ok) { ok.textContent = 'Application submitted. Will be reviewed within 48 hours.'; ok.style.display = 'block'; } const rForm = document.getElementById('reapplyForm'); if (rForm) rForm.style.display = 'none'; const rFooter = document.getElementById('reapplyFooter'); if (rFooter) rFooter.innerHTML = ''; } // ═══ OVERRIDE buildDir to use modal system ══════════════════════════════════ const _origBuildDir = buildDir; window.buildDir = function(cat) { // Use the removedDomains2 set for tracking const el = document.getElementById('dirList'); if (!el) return; const DIR = window.DIR_DATA || []; const filtered = DIR.filter(d => d.cat === (cat || 'food')); const sorted = [...filtered].sort((a, b) => { const ta = a.scores[a.scores.length - 1] - a.scores[0]; const tb = b.scores[b.scores.length - 1] - b.scores[0]; return tb - ta; }); let html = ''; sorted.forEach((e, i) => { const rank = i + 1; const latest = e.scores[e.scores.length - 1]; const delta = latest - e.scores[0]; const col = sCol(latest); const rc = rank === 1 ? 'r1' : rank === 2 ? 'r2' : rank === 3 ? 'r3' : e.verified ? 'vf' : ''; const rnc = rank === 1 ? 'r1c' : rank === 2 ? 'r2c' : rank === 3 ? 'r3c' : ''; const maxS = Math.max(...e.scores); const sparks = e.scores.map((s, si) => `
` ).join(''); if (removedDomains2.has(e.d)) { html += `
${e.d} — listing removed
`; return; } html += `
${rank}
${e.d.substring(0,2).toUpperCase()}
${e.d}${e.verified?'verified':''}
${e.desc}
${sparks}
${e.scores.join(' → ')}
${latest}
+${delta} pts
`; if (rank === 2) { html += `
to reach #1: fix your LCP to under 2.5s and add LocalBusiness schema — estimated +14 pts. check your speed →
`; } }); el.innerHTML = html; document.querySelectorAll('.cat-t').forEach(t => { t.onclick = () => { document.querySelectorAll('.cat-t').forEach(x => x.classList.remove('on')); t.classList.add('on'); window.buildDir(t.dataset.cat); }; }); }; // make DIR_DATA available globally window.DIR_DATA = [ {d:'thegreenplate.co.uk',desc:'Plant-based restaurant, Bristol',scores:[42,58,67,74],cat:'food',verified:true}, {d:'mapleleafcatering.co.uk',desc:'Corporate catering, London',scores:[63,70,77],cat:'food',verified:true}, {d:'freshpressejuice.co.uk',desc:'Cold-press juice, UK-wide',scores:[33,45,58,70],cat:'food',verified:true}, {d:'coastalcafe.co.uk',desc:'Seaside café, Cornwall',scores:[51,57,62],cat:'food',verified:false}, {d:'coastalcleaning.co.uk',desc:'Commercial cleaning, South West',scores:[55,63,71],cat:'local',verified:true}, {d:'northstarpetcare.co.uk',desc:'Dog grooming and boarding, York',scores:[41,53,62],cat:'local',verified:false}, {d:'lumenspa.co.uk',desc:'Holistic therapy, London',scores:[38,49,61,72],cat:'health',verified:true}, {d:'zestpilates.co.uk',desc:'Reformer pilates, Edinburgh',scores:[29,44,58],cat:'health',verified:false}, {d:'accordaccountants.co.uk',desc:'Small business accountancy, Leeds',scores:[61,68,75],cat:'professional',verified:true}, {d:'cloudshifttech.co.uk',desc:'Cloud infrastructure, SaaS',scores:[55,64,72,80],cat:'tech',verified:true}, {d:'shopspring.co.uk',desc:'Seasonal homewares, e-commerce',scores:[40,51,60,69],cat:'ecommerce',verified:false}, {d:'inkandanchor.co.uk',desc:'Graphic design studio, Brighton',scores:[38,50,62,73],cat:'creative',verified:true}, ]; // ═══ FULL SPEED PAGE: accessibility + best practice dials ══════════════════ // Override drawSpeedDials to also draw dials 3 and 4 const _origSpeedDials = drawSpeedDials; window.drawSpeedDials = function() { [[1,41,140],[2,74,140],[3,92,110],[4,95,110]].forEach(([i,s,sz]) => { drawDial('speedDial'+i, s, sz, sCol(s)); }); }; // ═══ FULL REPORT PAGE: all 8 category dials + overall ═════════════════════ window.drawReportDials = function() { drawDial('rD0', 67, 130, sCol(67)); drawDial('rD1', 41, 80, '#FF6B6B'); drawDial('rD2', 71, 80, sCol(71)); drawDial('rD3', 88, 80, sCol(88)); drawDial('rD4', 5, 80, '#FF6B6B'); drawDial('rD5', 50, 80, sCol(50)); drawDial('rD6', 55, 80, sCol(55)); drawDial('rD7', 78, 80, sCol(78)); drawDial('rD8', 58, 80, sCol(58)); }; // ═══ DIRECTORY: add to directory from audit banner ═════════════════════════ window.addToDirectory = function() { const sel = document.getElementById('dirCatSelect'); if (!sel || !sel.value) { if (sel) { sel.style.borderColor = 'var(--coral)'; setTimeout(() => sel.style.borderColor = '', 1500); } return; } const catLabel = sel.options[sel.selectedIndex].text; const form = document.getElementById('dirBannerForm'); const confirm = document.getElementById('dirBannerConfirm'); if (form) form.style.display = 'none'; if (confirm) { confirm.style.display = 'flex'; confirm.innerHTML = ` mybakery.co.uk added to ${catLabel} — re-audit any time to update your rank · view directory →`; } }; // ═══ QUESTION KEYWORDS in compare page ════════════════════════════════════ // These are already in the HTML. Wire internal links: document.querySelectorAll('.il').forEach(el => { const page = el.getAttribute('title')?.replace('go to ',''); if (page) el.onclick = () => goPage(page); }); // ═══ RE-INIT after patch ══════════════════════════════════════════════════ // Re-run directory build with enhanced version setTimeout(() => { window.buildDir('food'); // Ensure speed dials draw if already on speed page if (document.getElementById('page-speed')?.classList.contains('active')) { window.drawSpeedDials(); } // Ensure report dials draw if already on report page if (document.getElementById('page-report')?.classList.contains('active')) { window.drawReportDials(); } }, 200); console.log('webshite patch: directory modals + full speed/report dials + all functionality loaded ✓'); // ═══ BEGINNERS 4-STEP FLOW ══════════════════════════════════════════════════ let _begUrl = ''; let _begStep = 1; const BEG_ISSUES_PLAIN = [ { id:'speed', emoji:'🐢', title:'Your website loads too slowly', plain:'When someone visits your site on their phone, it takes too long to appear. People give up and go to a competitor. Google also penalises slow websites by pushing them lower in search results.', severity:'critical', fix:'Your main photo is probably too large. It needs to be compressed (made smaller) without losing quality.' }, { id:'schema', emoji:'📍', title:'Google doesn\'t know basic facts about your business', plain:'There\'s a special invisible label you can put on your website that tells Google your address, phone number, opening hours and what type of business you are. Without it, you can\'t show up in local searches like "bakery near me", and your listing looks bare compared to competitors who have star ratings and hours showing.', severity:'critical', fix:'Adding something called "LocalBusiness schema" — a small piece of code — fixes this in about 15 minutes.' }, { id:'og', emoji:'📱', title:'When someone shares your website on Facebook or WhatsApp, it looks broken', plain:'Every time someone shares a link to your site on social media, it should show a nice picture and description. Right now it\'s showing a blank box or random text — which means fewer people click it.', severity:'important', fix:'Adding a few lines of code called "Open Graph tags" fixes this instantly.' }, { id:'content', emoji:'📝', title:'Your website doesn\'t say enough', plain:'Google reads your website like a person would. If there\'s not much text on your pages, Google thinks your site doesn\'t have much useful information — and shows other, more detailed sites above yours instead.', severity:'important', fix:'Adding more helpful text to your key pages — things like "what we do", "who we help", "frequently asked questions" — can make a big difference.' }, { id:'dmarc', emoji:'📧', title:'Your email address can be faked by scammers', plain:'Without a security setting called DMARC, anyone in the world can send emails pretending to be from your email address. This could damage your reputation and is a simple 5-minute fix in your domain settings.', severity:'warning', fix:'A small DNS record needs to be added to your domain. Your web host can usually do this for you.' } ]; const BEG_BUSINESS_IMPACTS = { high: 'People who search for what you do can\'t find you — they\'re finding your competitors instead. When someone types "{url}" or searches for your type of business nearby, you\'re probably appearing on page 2 or 3 of Google, where almost nobody looks. These problems are costing you customers every single day.', medium: 'Your website is visible on Google but not performing as well as it could be. You\'re probably appearing in searches but losing clicks to competitors who have better-looking results with star ratings and more detail.', low: 'Your website is doing reasonably well. There are a few improvements that would help you rank higher and look more professional in search results.' }; function begUrlChanged() { // just enable the button style } function begStep1Next() { const urlInput = document.getElementById('begUrl'); const url = (urlInput?.value || '').trim(); if (!url) { urlInput?.focus(); urlInput.style.borderColor = 'var(--coral)'; setTimeout(() => urlInput.style.borderColor = '', 1500); return; } _begUrl = url.replace(/^https?:\/\//i, '').replace(/\/$/, ''); begGoStep(2); } function begGoStep(n) { _begStep = n; // hide all panels [1,2,3,4].forEach(i => { const panel = document.getElementById('bstep' + i); if (panel) panel.style.display = i === n ? 'block' : 'none'; const tab = document.getElementById('btab' + i); if (tab) { if (i === n) { tab.style.background = 'rgba(255,107,107,.08)'; tab.querySelector('div:nth-child(2)').style.color = 'var(--coral)'; } else { tab.style.background = ''; tab.querySelector('div:nth-child(2)').style.color = 'var(--snow3)'; } } }); if (n === 2) begBuildStep2(); if (n === 3) begBuildStep3(); if (n === 4) begBuildStep4(); } function begBuildStep2() { const urlEl2 = document.getElementById('beg-url-display'); if (urlEl2) urlEl2.textContent = _begUrl || 'your website'; // Use actual scores from SCORES if available, otherwise demo const score = (typeof SCORES !== 'undefined') ? SCORES.overall : 67; const scoreEl = document.getElementById('begScoreNum'); if (scoreEl) { scoreEl.textContent = score; scoreEl.style.color = score >= 70 ? 'var(--lime)' : score >= 50 ? 'var(--amber)' : 'var(--coral)'; } // Verdict let verdictTitle, verdictSimple; if (score < 40) { verdictTitle = 'your website has serious problems'; verdictSimple = 'We found multiple things stopping people from finding you on Google. The good news: all of them can be fixed, and ChatGPT can walk you through each one.'; } else if (score < 60) { verdictTitle = 'your website needs some important fixes'; verdictSimple = 'There are a few issues holding your website back in Google search results. Nothing unfixable — in fact most of these are things ChatGPT can help you sort in an afternoon.'; } else if (score < 75) { verdictTitle = 'your website is okay, but losing ground to competitors'; verdictSimple = 'Your site works, but it\'s missing some things that your competitors probably have. Fixing them could push you noticeably higher in Google results.'; } else { verdictTitle = 'your website is doing well — just a few improvements left'; verdictSimple = 'Good news — your site is in decent shape. There are still a few tweaks that would improve your Google ranking further.'; } const vtEl = document.getElementById('begVerdictTitle'); const vsEl = document.getElementById('begVerdictSimple'); if (vtEl) vtEl.textContent = verdictTitle; if (vsEl) vsEl.textContent = verdictSimple; if (vtEl) vtEl.style.color = score >= 70 ? 'var(--lime)' : score >= 50 ? 'var(--amber)' : 'var(--coral)'; // Issues list const issueEl = document.getElementById('begIssueList'); if (issueEl) { const borderCol = {critical:'var(--coral)', important:'var(--amber)', warning:'#a0a'}; const bgCol = {critical:'rgba(255,107,107,.06)', important:'rgba(255,179,71,.06)', warning:'rgba(160,0,160,.04)'}; const labelCol = {critical:'var(--coral)', important:'var(--amber)', warning:'var(--violet)'}; const labelTxt = {critical:'critical', important:'important', warning:'worth fixing'}; issueEl.innerHTML = BEG_ISSUES_PLAIN.map(issue => `
${issue.emoji}
${issue.title}
${labelTxt[issue.severity]}
${issue.plain}
→ The fix: ${issue.fix}
`).join(''); } // Business impact const biEl = document.getElementById('begBusinessImpact'); if (biEl) { const severity = score < 50 ? 'high' : score < 70 ? 'medium' : 'low'; biEl.innerHTML = BEG_BUSINESS_IMPACTS[severity].replace('{url}', _begUrl || 'your website'); } } function begBuildStep3() { const urlEl3 = document.getElementById('beg-url-display3'); if (urlEl3) urlEl3.textContent = _begUrl || 'your website'; const prompt = `Hi ChatGPT! I need your help fixing my website's SEO. I have zero technical knowledge so please explain everything in plain English and walk me through each step one at a time. My website is: ${_begUrl || 'my website'} A website audit tool found these problems with my website. Please help me fix each one: PROBLEM 1 — MY WEBSITE LOADS TOO SLOWLY 🐢 My website takes too long to load, especially on phones. This is hurting my Google ranking. Please tell me: What causes this? What exactly should I do to fix it? What tools do I need? Walk me through it step by step. PROBLEM 2 — GOOGLE DOESN'T KNOW BASIC FACTS ABOUT MY BUSINESS 📍 I'm missing something called "LocalBusiness schema" — a small piece of code that tells Google my address, phone number, opening hours and business type. Without it I can't appear in local searches. Please: Write me the exact LocalBusiness schema code I need for my website at ${_begUrl || 'my website'}. Ask me for my business details (name, address, phone, hours, type of business) then write the complete code I need to paste in. PROBLEM 3 — SHARING MY WEBSITE ON SOCIAL MEDIA LOOKS BROKEN 📱 When someone shares a link to my site on Facebook, WhatsApp or LinkedIn, it shows a blank image and random text instead of something nice. This is because I'm missing "Open Graph tags". Please: Write me the exact Open Graph tag code I need, and tell me exactly where to paste it. Ask me for my business name, a description and what image I want to show. PROBLEM 4 — MY WEBSITE DOESN'T SAY ENOUGH 📝 My website doesn't have enough text on it, so Google thinks it's not very informative. I need more helpful content. Please: Help me write 300–400 words of additional helpful text I can add to my homepage. Ask me about my business, what I do, who my customers are, and where I'm based. PROBLEM 5 — MY EMAIL CAN BE FAKED BY SCAMMERS 📧 I'm missing something called a "DMARC record" in my domain settings. Without it, scammers can send emails pretending to be from my email address. Please: Tell me exactly what to do to add a DMARC record. Ask me who my domain is registered with (GoDaddy, Namecheap, etc.) so you can give me the right instructions. --- IMPORTANT — WHEN WE'RE DONE: After we've worked through all of these, please write me a complete, single HTML file of my improved website homepage that includes all the fixes — the schema markup, Open Graph tags, proper headings, and all the additional content we wrote together. The file should start with . I'll use that file in the next step. Ready? Let's start with Problem 1. Please explain it simply and ask me any questions you need before we begin.`; const promptEl = document.getElementById('begPromptBox'); if (promptEl) promptEl.textContent = prompt; window._begPromptText = prompt; } function begCopyPrompt() { const text = window._begPromptText || document.getElementById('begPromptBox')?.textContent || ''; navigator.clipboard?.writeText(text).then(() => { const btn = document.getElementById('begCopyBtn'); if (btn) { const orig = btn.innerHTML; btn.innerHTML = 'copied!'; btn.style.background = 'var(--limed)'; btn.style.color = 'var(--lime)'; setTimeout(() => { btn.innerHTML = orig; btn.style.background = ''; btn.style.color = ''; }, 2500); } }).catch(() => { // fallback const el = document.getElementById('begPromptBox'); if (el) { const range = document.createRange(); range.selectNode(el); window.getSelection().removeAllRanges(); window.getSelection().addRange(range); } }); } function begBuildStep4() { const urlEl4 = document.getElementById('beg-url-display4'); if (urlEl4) urlEl4.textContent = _begUrl || 'your website'; const urlBar = document.getElementById('begPreviewUrlBar'); if (urlBar) urlBar.textContent = _begUrl || 'yourwebsite.com'; // reset preview document.getElementById('begPreviewWrap').style.display = 'none'; document.getElementById('begStep4Hint').style.display = 'block'; } function begCodeChanged() { const code = document.getElementById('begCodePaste')?.value?.trim() || ''; const hintEl = document.getElementById('begStep4Hint'); if (hintEl) hintEl.style.display = code.length > 50 ? 'none' : 'block'; } function begPreviewCode() { const code = document.getElementById('begCodePaste')?.value?.trim() || ''; if (!code) { alert('Please paste your website code first!'); return; } if (!code.includes('<') && !code.includes('DOCTYPE')) { alert('That doesn\'t look like HTML code. Make sure you\'ve copied everything from ChatGPT starting with '); return; } const frame = document.getElementById('begPreviewFrame'); if (frame) frame.srcdoc = sanitiseForPreview(code); document.getElementById('begPreviewWrap').style.display = 'block'; document.getElementById('begStep4Hint').style.display = 'none'; document.getElementById('begPreviewWrap').scrollIntoView({ behavior: 'smooth', block: 'start' }); window._begPastedCode = code; } function begDownloadCode() { const code = window._begPastedCode || document.getElementById('begCodePaste')?.value?.trim() || ''; if (!code) { alert('Please paste and preview your code first!'); return; } const blob = new Blob([code], { type: 'text/html' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = (_begUrl || 'my-website').replace(/[^a-z0-9]/gi, '-') + '-improved.html'; a.click(); URL.revokeObjectURL(a.href); } function begCopyCode() { const code = window._begPastedCode || document.getElementById('begCodePaste')?.value?.trim() || ''; if (!code) { alert('Please paste your code first!'); return; } navigator.clipboard?.writeText(code).catch(() => {}); } // Style tab transitions on hover document.querySelectorAll('.beg-step-tab').forEach(tab => { tab.addEventListener('mouseenter', function() { if (!this.classList.contains('active')) this.style.background = 'rgba(255,255,255,.03)'; }); tab.addEventListener('mouseleave', function() { if (!this.classList.contains('active')) this.style.background = ''; }); }); // ═══════════════════════════════════════════════════════════════════════════ // REBUILD WITH CLAUDE — FULL FLOW // PSI scan → HTML fetch → smart questions → Claude API → preview + deploy // ═══════════════════════════════════════════════════════════════════════════ const RB_QUESTIONS_SCHEMA = [ { id:'businessName', label:'Business name', hint:'The exact name you trade under', type:'text', placeholder:'e.g. Johnson\'s Bakery' }, { id:'businessType', label:'What type of business?', hint:'What do you sell or do?', type:'select', options:['Local service (plumber, cleaner, etc.)','Restaurant / café / bakery','Retail shop','Professional services (accountant, solicitor, etc.)','Health & wellness','Creative / media agency','E-commerce store','Trades (builder, electrician, etc.)','Other'] }, { id:'location', label:'Where are you based?', hint:'Town/city and country — used in local SEO signals', type:'text', placeholder:'e.g. Bristol, UK' }, { id:'targetKeywords', label:'What would your ideal customer type into Google?', hint:'Think like a customer who doesn\'t know your name', type:'text', placeholder:'e.g. "artisan bakery bristol" or "emergency plumber london"' }, { id:'phone', label:'Phone number', hint:'Goes in schema markup — helps local search', type:'text', placeholder:'e.g. 0117 123 4567' }, { id:'address', label:'Street address', hint:'Physical location — skip if you\'re fully online', type:'text', placeholder:'e.g. 12 Corn Street, Bristol BS1 1HT' }, { id:'openingHours', label:'Opening hours', hint:'Skip if you\'re not a walk-in business', type:'text', placeholder:'e.g. Mon-Fri 9am-5pm, Sat 10am-3pm' }, { id:'uniqueSelling', label:'What makes you different from competitors?', hint:'Your honest best answer — will become headline copy', type:'textarea',placeholder:'e.g. Only bakery in Bristol using 100-year-old sourdough starter, open 7 days' }, { id:'services', label:'List your main products or services', hint:'One per line — these become your page sections', type:'textarea',placeholder:'e.g.\nSourdough bread\nWedding cakes\nCoffee & pastries\nLocal delivery' }, { id:'testimonial', label:'A real customer quote (if you have one)', hint:'Even one strong testimonial lifts conversions significantly', type:'textarea',placeholder:'e.g. "Best bread in Bristol — we drive 20 minutes just for their sourdough." — Sarah M.' }, { id:'email', label:'Contact email', hint:'For the contact section of your site', type:'text', placeholder:'e.g. hello@mybusiness.co.uk' }, { id:'colourPref', label:'Brand colours (if you have them)', hint:'Hex codes or just describe them — leave blank to let Claude choose', type:'text', placeholder:'e.g. #FF6B6B coral and dark navy, or "green and gold"' }, ]; // ═══════════════════════════════════════════════════════════════════════════ // COMPLETE REBUILD FLOW — template → payment → scan → questions → build × 4 // ═══════════════════════════════════════════════════════════════════════════ let _rbState = { url:'', psiData:null, fetchedHtml:'', prefilled:{}, aiSignals:{}, images:[], style:'modern professional dark', answers:{}, builtCode:'', scoreBefore:null, selectedTemplate:'modern', codeFormat:'single', allBuilds:[], currentBuildIdx:0, uploadedImages:[], // { dataUrl, name, type } paid: false, }; // ═══════════════════════════════════════════════════════════════════════════ // SCAN → FACTS (code does the crawling; Gemini only does the reasoning) // ═══════════════════════════════════════════════════════════════════════════ // ── PAGE FETCHING ───────────────────────────────────────────────────────── // If we're running inside WordPress, the server fetches for us. PHP is not a // browser, so the same-origin policy simply does not apply — no CORS, no proxies. // Standalone, we fall back to public proxies (unreliable, fine for local testing). const FMW = window.FMW_API || null; const CORS_PROXIES = [ { name:'allorigins', fn: u => `https://api.allorigins.win/raw?url=${encodeURIComponent(u)}` }, { name:'allorigins-json',fn: u => `https://api.allorigins.win/get?url=${encodeURIComponent(u)}`, json:'contents' }, { name:'corsproxy', fn: u => `https://corsproxy.io/?url=${encodeURIComponent(u)}` }, { name:'codetabs', fn: u => `https://api.codetabs.com/v1/proxy/?quest=${encodeURIComponent(u)}` }, { name:'jina', fn: u => `https://r.jina.ai/${u}` }, ]; let _proxyLog = []; function _looksLikeHtml(t){ return !!t && t.length > 400 && /<\/html>|]|]|]/i.test(t); } async function rbFetchHtml(fullUrl){ _proxyLog = []; // ── Preferred path: WordPress server-side fetch ── if (FMW){ const t0 = Date.now(); try{ const r = await fetch(`${FMW.root}fetch?url=${encodeURIComponent(fullUrl)}`, { headers: { 'X-WP-Nonce': FMW.nonce }, signal: AbortSignal.timeout(30000), }); const d = await r.json(); const ms = Date.now()-t0; if (d && d.ok && _looksLikeHtml(d.html)){ _proxyLog.push({name:'WordPress server', ok:true, why:`${(d.html.length/1024).toFixed(0)}kb${d.cached?' (cached)':''}`, ms}); console.log('[scan] fetched server-side', ms+'ms'); return d.html; } _proxyLog.push({name:'WordPress server', ok:false, why:(d && d.message) || 'no HTML returned', ms}); return ''; // trust the server. Don't fall back to junk proxies. }catch(e){ _proxyLog.push({name:'WordPress server', ok:false, why:e.message.slice(0,50), ms:Date.now()-t0}); return ''; } } // ── Fallback: public proxies (standalone / local testing only) ── for (const p of CORS_PROXIES){ const t0 = Date.now(); try{ const r = await fetch(p.fn(fullUrl), { signal: AbortSignal.timeout(14000) }); const ms = Date.now()-t0; if (!r.ok){ _proxyLog.push({name:p.name, ok:false, why:`HTTP ${r.status}`, ms}); continue; } let text = await r.text(); if (p.json){ try { text = JSON.parse(text)[p.json] || ''; } catch { _proxyLog.push({name:p.name, ok:false, why:'bad JSON wrapper', ms}); continue; } } if (!_looksLikeHtml(text)){ _proxyLog.push({name:p.name, ok:false, why:text.length<400?`only ${text.length} bytes`:'not HTML', ms}); continue; } _proxyLog.push({name:p.name, ok:true, why:`${(text.length/1024).toFixed(0)}kb`, ms}); return text; }catch(e){ const ms = Date.now()-t0; _proxyLog.push({name:p.name, ok:false, why:/abort|timeout/i.test(e.message)?'timed out':'blocked (CORS/network)', ms}); } } return ''; } // Show exactly what happened, per proxy. No guessing. function rbScanFailed(url){ const stepsEl = document.getElementById('rb-scan-steps'); const prog = document.getElementById('rb-scan-prog'); if (prog) prog.style.width = '100%'; if (!stepsEl) return; const isFile = location.protocol === 'file:'; const allCors = _proxyLog.length && _proxyLog.every(p => /CORS|network/i.test(p.why)); const rows = _proxyLog.map(p => `
${p.name} ${p.why} ${p.ms}ms
`).join(''); let diagnosis; if (isFile) { diagnosis = `You're opening this as a file. Browsers block cross-origin requests from file://. Serve it over http instead:
python3 -m http.server 8000
then open http://localhost:8000/webshite.html
`; } else if (allCors) { diagnosis = `Every proxy was blocked by the browser. If you're inside an embedded preview or iframe sandbox, cross-origin fetches are blocked regardless of the proxy. Run it on a real origin — localhost or a deployed URL.`; } else { diagnosis = `The proxies reached the network but couldn't return the page. Most likely ${_esc(url)} blocks bots (Cloudflare/WAF), or the free proxies are rate-limiting. Public CORS proxies are unreliable by nature — this is exactly why production needs your own fetch endpoint.`; } stepsEl.innerHTML = `
Couldn't read ${_esc(url)}
what each proxy did
${rows || '
no attempts logged
'}
${diagnosis}
`; } function rbRetryScan(){ const u=_rbState.url; if(!u){ rbShowStage('rb-stage-url'); return; } rbShowStage('rb-stage-scanning'); const su=document.getElementById('rb-scanning-url'); if(su) su.textContent=u; rbRunScan(u); } // ── Deterministic extraction: no AI, no tokens, no hallucination ─────────── function rbDeepExtract(html, baseUrl){ const pre={}, images=[]; if(!html) return {prefilled:pre, images}; const origin=(()=>{try{return new URL(baseUrl).origin}catch{return ''}})(); const strip=s=>s.replace(/<[^>]+>/g,' ').replace(/\s+/g,' ').trim(); const dec=s=>s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,"'").replace(/ /g,' '); const get=(re,g=1)=>{const m=html.match(re);return m?dec(m[g]).trim():''}; pre._title = get(/]*>([^<]{1,200})<\/title>/i); pre._metaDesc = get(/]+name=["']description["'][^>]+content=["']([^"']{1,400})["']/i); pre._ogTitle = get(/]+property=["']og:title["'][^>]+content=["']([^"']{1,200})["']/i); pre._ogImage = get(/]+property=["']og:image["'][^>]+content=["']([^"']+)["']/i); pre._canonical= get(/]+rel=["']canonical["'][^>]+href=["']([^"']+)["']/i); // headings const heads=[]; for(const lvl of [1,2,3]){ const re=new RegExp(`]*>([\\s\\S]{1,300}?)<\\/h${lvl}>`,'gi'); [...html.matchAll(re)].forEach(m=>{const t=dec(strip(m[1])).slice(0,200);if(t)heads.push({level:lvl,text:t})}); } pre._headings=heads; pre._h1=heads.find(h=>h.level===1)?.text||''; pre._h2s=heads.filter(h=>h.level===2).map(h=>h.text).slice(0,10); // body copy (strip chrome) const body=html.replace(//gi,'').replace(//gi,'') .replace(//gi,'').replace(//gi,''); const paras=[...body.matchAll(/]*>([\s\S]{40,1200}?)<\/p>/gi)] .map(m=>dec(strip(m[1]))).filter(p=>p.length>40 && !/cookie|privacy policy|terms and conditions/i.test(p)); pre._paragraphs=paras.slice(0,14); pre._wordCount=paras.join(' ').split(/\s+/).filter(Boolean).length; // list items (usually services) pre._listItems=[...body.matchAll(/]*>([^<]{8,140})<\/li>/gi)] .map(m=>dec(strip(m[1]))).filter(t=>t.length>8 && !/^(home|about|contact|menu|blog|login)$/i.test(t)).slice(0,25); // contact const tel=html.match(/href=["']tel:([^"']+)["']/i) || html.match(/(?:tel|phone|call)[:\s]*([+0]\d[\d\s\-().]{7,17}\d)/i); if(tel) pre.phone=tel[1].trim(); const em=html.match(/href=["']mailto:([^"'?]+)["']/i) || html.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})(?=["'<\s])/); if(em && !/example\.|yoursite|domain\.com/i.test(em[1])) pre.email=em[1].trim(); const addr=html.match(/(\d+[A-Za-z]?\s+[A-Z][A-Za-z\s]{2,40}(?:Street|St|Road|Rd|Lane|Ln|Avenue|Ave|Drive|Way|Close|Court|Square|Place)[,\s]+[A-Za-z\s,]{2,40}(?:[A-Z]{1,2}\d[\dA-Z]?\s?\d[A-Z]{2}))/); if(addr) pre.address=addr[1].replace(/\s+/g,' ').trim(); // socials const socials={}; for(const [k,re] of Object.entries({ facebook:/https?:\/\/(?:www\.)?facebook\.com\/[^"'\s<>]+/i, instagram:/https?:\/\/(?:www\.)?instagram\.com\/[^"'\s<>]+/i, linkedin:/https?:\/\/(?:www\.)?linkedin\.com\/[^"'\s<>]+/i, youtube:/https?:\/\/(?:www\.)?youtube\.com\/[^"'\s<>]+/i, twitter:/https?:\/\/(?:www\.)?(?:twitter|x)\.com\/[^"'\s<>]+/i, })){ const m=html.match(re); if(m) socials[k]=m[0]; } pre._socials=socials; // existing JSON-LD const ld=[...html.matchAll(/]+application\/ld\+json[^>]*>([\s\S]*?)<\/script>/gi)] .map(m=>{try{return JSON.parse(m[1])}catch{return null}}).filter(Boolean); pre._existingSchema=[...new Set(ld.map(b=>b['@type']).flat().filter(Boolean))].join(', '); const lb=ld.find(b=>/LocalBusiness|Restaurant|Store|ProfessionalService|Bakery/.test(JSON.stringify(b['@type']||''))); if(lb){ pre.businessName ||= lb.name; pre.phone ||= lb.telephone; pre.email ||= lb.email; pre.openingHours ||= [lb.openingHours].flat().filter(Boolean).join(', '); if(lb.aggregateRating) pre._rating=`${lb.aggregateRating.ratingValue}/5 (${lb.aggregateRating.reviewCount||'?'} reviews)`; if(lb.priceRange) pre._priceRange=lb.priceRange; } // images, scored (html.match(/]+>/gi)||[]).forEach(tag=>{ const src=(tag.match(/(?:data-)?src=["']([^"']+)["']/i)||[])[1]; if(!src) return; let u=src.startsWith('//')?'https:'+src : src.startsWith('/')?origin+src : src.startsWith('http')?src : origin+'/'+src; if(u.startsWith('data:')||/\.(svg|ico|gif)(\?|$)/i.test(u)) return; const alt=dec((tag.match(/alt=["']([^"']*)["']/i)||[])[1]||''); const w=+(tag.match(/width=["']?(\d+)/i)||[])[1]||0; const h=+(tag.match(/height=["']?(\d+)/i)||[])[1]||0; if((w&&w<100)||(h&&h<100)) return; const key=(u.split('/').pop()+' '+alt).toLowerCase(); let score=50, type='content'; if(alt.length>3) score+=20; if(/hero|banner|main|cover|featured|header/.test(key)){score+=25;type='hero'} if(/logo/.test(key)){score-=30;type='logo'} if(/icon|btn|arrow|placeholder|spinner|pixel/.test(key)) score-=45; if(/team|staff|founder|owner|portrait/.test(key)) type='person'; if(/product|gallery|service|work/.test(key)) type='product'; if(w>600||h>400) score+=20; images.push({url:u, alt, type, score, w, h}); }); const seen=new Set(); const dedup=images.filter(i=>{const k=i.url.split('?')[0]; if(seen.has(k))return false; seen.add(k); return true}) .filter(i=>i.score>25).sort((a,b)=>b.score-a.score); pre._images={ hero:dedup.filter(i=>i.type==='hero').slice(0,3), logo:dedup.filter(i=>i.type==='logo').slice(0,1), people:dedup.filter(i=>i.type==='person').slice(0,3), content:dedup.filter(i=>['content','product'].includes(i.type)).slice(0,8), }; pre._imageCount=(html.match(/`
${t}
`).join(''); const step=(i,s)=>{const el=document.getElementById('ss'+i);if(!el)return; el.className='rb-scan-step '+s; const ic=el.querySelector('i'); ic.className=s==='done'?'ti ti-check':s==='active'?'ti ti-loader-2':s==='fail'?'ti ti-x':'ti ti-circle'; ic.style.animation=s==='active'?'spin .8s linear infinite':''; if(s==='fail') el.style.color='var(--coral)';}; // 1. fetch html — try several proxies, and be HONEST if they all fail step(0,'active'); prog.style.width='8%'; const html = await rbFetchHtml(full); _rbState.fetchedHtml = html; if (!html) { step(0,'fail'); rbScanFailed(url); return; } step(0,'done'); prog.style.width='20%'; // 2. deterministic extraction (code) step(1,'active'); const {prefilled,images}=rbDeepExtract(html, full); _rbState.prefilled=prefilled; _rbState.images=images; step(1,'done'); prog.style.width='34%'; // 3. lighthouse (code) step(2,'active'); try{ const r=await fetch(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(full)}&strategy=mobile&category=performance&category=seo&category=accessibility`,{signal:AbortSignal.timeout(25000)}); const psi=await r.json(); const cats=psi?.lighthouseResult?.categories||{}; const auds=psi?.lighthouseResult?.audits||{}; prefilled._psiPerf=Math.round((cats.performance?.score||0)*100); prefilled._psiSEO =Math.round((cats.seo?.score||0)*100); prefilled._psiA11y=Math.round((cats.accessibility?.score||0)*100); prefilled._lcp=auds['largest-contentful-paint']?.displayValue; prefilled._topOpps=Object.values(auds).filter(a=>a.score!=null&&a.score<0.85&&a.title) .sort((a,b)=>(a.score||1)-(b.score||1)).slice(0,5).map(a=>a.title); _rbState.psiData={mobile:psi}; _rbState.scoreBefore=prefilled._psiPerf; }catch{ /* PSI down or blocked — carry on, it's optional */ } step(2,'done'); prog.style.width='48%'; // 4. SEO rules (code, not AI) step(3,'active'); _rbState.seoIssues=rbSeoRules(prefilled, html); step(3,'done'); prog.style.width='60%'; // 5. AI: extract facts (the ONE call that needs language understanding) step(4,'active'); prog.style.width='70%'; try{ _rbState.facts=await aiExtractFacts(prefilled, url); }catch(e){ console.warn('fact extraction failed:', e.message); _rbState.facts=factsFromExtractionOnly(prefilled); _rbState.factsError=e.message; } step(4,'done'); prog.style.width='88%'; // 6. gaps → questions (derived from the facts we got) step(5,'active'); _rbState.facts=addGapQuestions(_rbState.facts, prefilled); step(5,'done'); prog.style.width='100%'; await new Promise(r=>setTimeout(r,300)); rbShowStage('rb-stage-facts'); renderFactList(); } // ── SEO rule engine: deterministic, zero tokens ─────────────────────────── function rbSeoRules(p, html){ const out=[]; const add=(sev,t,d)=>out.push({sev,title:t,detail:d}); if(!p._title) add('fail','No page title','Google has nothing to show in results.'); else if(p._title.length<30||p._title.length>62) add('warn',`Title is ${p._title.length} chars`,'Aim for 52–60 characters.'); if(!p._metaDesc) add('fail','No meta description','Google will invent one from your page text.'); else if(p._metaDesc.length<120||p._metaDesc.length>165) add('warn',`Meta description is ${p._metaDesc.length} chars`,'Aim for 148–160 characters.'); if(!p._h1) add('fail','No H1 heading','The main heading tells Google what the page is about.'); if(!p._ogImage) add('fail','No social share image','Links to your site show a blank box on Facebook and WhatsApp.'); if(!p._existingSchema) add('fail','No schema markup','Google cannot see your address, hours or phone as structured data.'); if(!p.phone) add('warn','No phone number found','Customers and Google both want a contact number.'); if(!p.address) add('warn','No address found','Required to appear in local "near me" searches.'); if((p._wordCount||0)<400) add('fail',`Thin content — about ${p._wordCount||0} words`,'Google treats short pages as low value. Aim for 1,200+.'); if(html && !/loading=["']lazy["']/i.test(html) && (p._imageCount||0)>3) add('warn','Images not lazy-loaded','Slows down first paint on mobile.'); if(p._psiPerf!=null && p._psiPerf<50) add('fail',`Performance score ${p._psiPerf}/100`,'Very slow. Google demotes slow sites.'); (p._topOpps||[]).forEach(o=>add('warn',o,'Flagged by Google Lighthouse.')); return out; } // ── Fallback if AI is unavailable: build facts from the crawl alone ──────── function factsFromExtractionOnly(p){ const f=[], mk=(text,category,source,confidence)=>({ id:'f'+Math.random().toString(36).slice(2,9), text, category, source, confidence, status:'approved', origin:'scan' }); if(p.businessName) f.push(mk(p.businessName,'business','page title',90)); if(p.phone) f.push(mk('Phone: '+p.phone,'contact','page contact link',95)); if(p.email) f.push(mk('Email: '+p.email,'contact','page contact link',95)); if(p.address) f.push(mk('Address: '+p.address,'contact','page footer',85)); if(p.openingHours) f.push(mk('Opening hours: '+p.openingHours,'operations','schema markup',90)); if(p._rating) f.push(mk('Rating: '+p._rating,'trust','schema markup',95)); (p._listItems||[]).slice(0,12).forEach(s=>f.push(mk(s,'service','page list item',60))); (p._h2s||[]).slice(0,6).forEach(h=>f.push(mk(h,'service','page heading',55))); return f; } // ── THE AI CALL: crawl data in, structured facts out ────────────────────── async function aiExtractFacts(p, url){ const evidence=[ p.businessName && `TITLE: ${p._title}`, p._metaDesc && `META DESCRIPTION: ${p._metaDesc}`, p._h1 && `MAIN HEADING: ${p._h1}`, ...(p._h2s||[]).map(h=>`SUB-HEADING: ${h}`), ...(p._paragraphs||[]).slice(0,10).map(t=>`BODY TEXT: ${t.slice(0,320)}`), ...(p._listItems||[]).slice(0,20).map(t=>`LIST ITEM: ${t}`), p.phone && `PHONE FOUND: ${p.phone}`, p.email && `EMAIL FOUND: ${p.email}`, p.address && `ADDRESS FOUND: ${p.address}`, p.openingHours && `HOURS FOUND: ${p.openingHours}`, p._rating && `RATING FOUND: ${p._rating}`, p._priceRange && `PRICE RANGE FOUND: ${p._priceRange}`, Object.entries(p._socials||{}).map(([k,v])=>`SOCIAL: ${k} ${v}`).join('\n'), ].filter(Boolean).join('\n'); const prompt=`You are a business intelligence extraction specialist. Below is raw content scraped from the website ${url}. Extract every FACTUAL statement about this business. RULES — these are absolute: - NEVER invent information. If it is not in the evidence, do not state it. - One fact per item. Never combine two unrelated facts. - Preserve the original meaning exactly. Do not embellish. - Do not include marketing fluff ("we are passionate about quality") — only checkable facts. - If something is implied but not stated, lower the confidence score. Categories to use: business, contact, service, product, trust, operations, location For each fact give: text - the fact, as a short plain sentence category - one of the categories above source - which piece of evidence it came from (quote the label, e.g. "LIST ITEM") confidence - 0-100, how certain you are this is a real stated fact reasoning - one short sentence explaining why you extracted it EVIDENCE: ${evidence} Return ONLY a JSON array. No markdown, no commentary. Example shape: [{"text":"Fits bathrooms","category":"service","source":"LIST ITEM","confidence":94,"reasoning":"Listed under services on the page."}]`; const raw=await callAI(prompt,{json:true, maxTokens:4000, temperature:0.2}); let arr; try{ arr=JSON.parse(aiCleanCode(raw)); } catch{ const m=aiCleanCode(raw).match(/\[[\s\S]*\]/); if(!m) throw new Error('AI did not return valid JSON'); arr=JSON.parse(m[0]); } if(!Array.isArray(arr)) throw new Error('AI did not return an array of facts'); return arr.filter(f=>f&&f.text).map(f=>({ id:'f'+Math.random().toString(36).slice(2,9), text:String(f.text).slice(0,300), category:String(f.category||'business'), source:String(f.source||'website scan'), confidence:Math.max(0,Math.min(100,+f.confidence||50)), reasoning:String(f.reasoning||''), status:'approved', origin:'ai', })); } // ── Gaps: what a good site needs that we did not find ───────────────────── const GAP_RULES=[ {key:/years|trading|established|since|founded/i, q:'How many years have you been trading?', why:'Years in business is a strong trust signal and Google E-E-A-T factor.'}, {key:/price|cost|from £|quote/i, q:'What do your prices start from?', why:'AI search engines quote specific prices. Vague pricing gets skipped.'}, {key:/guarantee|warranty|promise/i, q:'Do you offer any guarantee or warranty?', why:'Guarantees increase conversion and appear in AI-generated answers.'}, {key:/area|serve|cover|radius|local/i, q:'Which areas do you cover?', why:'Location signals are essential for local search results.'}, {key:/hour|open|monday|weekend/i, q:'What are your opening hours?', why:'Required for Google local results and the "open now" filter.'}, {key:/certif|accredit|registered|qualified/i, q:'Any certifications or accreditations?', why:'Named credentials get cited directly by ChatGPT and Perplexity.'}, {key:/review|testimonial|rating|customer said/i,q:'Can you paste one real customer review?', why:'Real quotes make your content citable by AI search.'}, {key:/emergency|24|out of hours|weekend/i, q:'Do you offer emergency or out-of-hours work?', why:'High-intent search term that many competitors miss.'}, ]; function addGapQuestions(facts, p){ const blob=(facts.map(f=>f.text).join(' ')+' '+JSON.stringify(p)).toLowerCase(); const qs=GAP_RULES.filter(r=>!r.key.test(blob)).map(r=>({ id:'q'+Math.random().toString(36).slice(2,9), text:'', question:r.q, why:r.why, category:'question', status:'needs_answer', origin:'gap', })); return [...facts, ...qs]; } // ═══════════════════════════════════════════════════════════════════════════ // FACT LIST UI — the editable business knowledge document // ═══════════════════════════════════════════════════════════════════════════ let _factFilter='all', _factSearch=''; const CAT_META={ business:{c:'var(--coral)', l:'business'}, contact:{c:'var(--ice)', l:'contact'}, service:{c:'var(--lime)', l:'service'}, product:{c:'var(--lime)', l:'product'}, trust:{c:'var(--amber)', l:'trust'}, operations:{c:'var(--violet)',l:'operations'}, location:{c:'var(--ice)', l:'location'}, question:{c:'var(--amber)', l:'question'}, }; function renderSeoStrip(){ const issues=_rbState.seoIssues||[]; const el=document.getElementById('fact-seo-strip'); if(!el) return; if(!issues.length){ el.style.display='none'; return; } const fails=issues.filter(i=>i.sev==='fail').length; el.style.display='block'; el.innerHTML=`
we also found ${issues.length} SEO problem${issues.length>1?'s':''} on your current site
${fails} critical
${issues.slice(0,4).map(i=>`
→${_esc(i.title)}
`).join('')} ${issues.length>4?`
+ ${issues.length-4} more — all fixed automatically in your new site
`:''}
`; } function renderFactList(){ renderSeoStrip(); const facts=_rbState.facts||[]; const wrap=document.getElementById('fact-list'); const meta=document.getElementById('fact-meta'); if(!wrap) return; const answered=facts.filter(f=>f.status==='needs_answer'&&f.text).length; const pending =facts.filter(f=>f.status==='needs_answer'&&!f.text).length; const approved=facts.filter(f=>f.status==='approved').length; if(meta){ meta.innerHTML=` ${approved} facts ${pending?` · ${pending} questions to answer`:''} ${answered?` · ${answered} answered`:''} ${_rbState.factsError?` · AI unavailable — showing scan data only`:''} `; } const q=_factSearch.toLowerCase(); const shown=facts.filter(f=>{ if(_factFilter==='questions' && f.status!=='needs_answer') return false; if(_factFilter!=='all' && _factFilter!=='questions' && f.category!==_factFilter) return false; if(q && !((f.text||'')+(f.question||'')).toLowerCase().includes(q)) return false; return true; }); if(!shown.length){ wrap.innerHTML=`
nothing here — try a different filter
`; return; } wrap.innerHTML=shown.map(f=>f.status==='needs_answer'?_qRow(f):_fRow(f)).join(''); } function _fRow(f){ const m=CAT_META[f.category]||CAT_META.business; const conf=f.confidence??100; const confCol=conf>=85?'var(--lime)':conf>=60?'var(--amber)':'var(--coral)'; return `
${_esc(f.text)}
${f.origin==='ai'||f.origin==='scan' ? `` : `you`}
`; } function _qRow(f){ const done=!!f.text; return `
${_esc(f.question)}
`; } const _esc=s=>String(s||'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); // ── edit / delete / add ──────────────────────────────────────────────────── function factEdit(id,val){ const f=(_rbState.facts||[]).find(x=>x.id===id); if(!f) return; const t=val.trim(); if(!t){ factDelete(id,true); return; } if(t!==f.text){ f.text=t; f.origin='user'; f.confidence=100; f.source='you edited this'; renderFactList(); } } function factAnswer(id,val){ const f=(_rbState.facts||[]).find(x=>x.id===id); if(!f) return; const was=!!f.text; f.text=val.trim(); if(was!==!!f.text) { renderFactList(); const el=document.querySelector(`[data-id="${id}"] input`); if(el){el.focus();el.setSelectionRange(el.value.length,el.value.length);} } else { const meta=document.getElementById('fact-meta'); if(meta) renderFactList(); } } function factDelete(id,skipConfirm){ const f=(_rbState.facts||[]).find(x=>x.id===id); if(!f) return; const label=f.status==='needs_answer'?'Skip this question?':`Delete "${(f.text||'').slice(0,50)}"?`; if(!skipConfirm && !confirm(label)) return; _rbState.facts=_rbState.facts.filter(x=>x.id!==id); renderFactList(); } function factAdd(){ _rbState.facts=_rbState.facts||[]; const f={id:'f'+Math.random().toString(36).slice(2,9),text:'',category:'business', source:'you added this',confidence:100,status:'approved',origin:'user'}; _rbState.facts.push(f); renderFactList(); setTimeout(()=>{ const el=document.querySelector(`[data-id="${f.id}"] .fact-text`); if(el){ el.focus(); const r=document.createRange(); r.selectNodeContents(el); const s=getSelection(); s.removeAllRanges(); s.addRange(r); } },40); } // ── provenance popover: the trust mechanic ──────────────────────────────── function factSource(id){ const f=(_rbState.facts||[]).find(x=>x.id===id); if(!f) return; const conf=f.confidence??100; const col=conf>=85?'var(--lime)':conf>=60?'var(--amber)':'var(--coral)'; _modal(`
fact
${_esc(f.text)}
where we found it
${_esc(f.source)}
how it was found
${f.origin==='ai'?'AI read your website and extracted this':f.origin==='scan'?'Found directly in your page code':'You added or edited this'}
confidence
${conf}%
${f.reasoning?`
AI reasoning
${_esc(f.reasoning)}
`:''} ${conf<70?`
Low confidence — please check this is correct before building. Click the text to edit it.
`:''} `); } function factWhy(id){ const f=(_rbState.facts||[]).find(x=>x.id===id); if(!f) return; _modal(`
we're asking
${_esc(f.question)}
why it matters
${_esc(f.why)}
You can skip this — we just won't be able to include it on your new site.
`); } function _modal(html){ const old=document.getElementById('fact-modal'); if(old) old.remove(); const d=document.createElement('div'); d.id='fact-modal'; d.style.cssText='position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.72);display:flex;align-items:center;justify-content:center;padding:1.5rem;animation:pg .18s ease'; d.onclick=e=>{if(e.target===d)d.remove()}; d.innerHTML=`
${html}
`; document.body.appendChild(d); } function factSetFilter(f,el){ _factFilter=f; document.querySelectorAll('.fact-filter').forEach(x=>x.classList.remove('on')); el.classList.add('on'); renderFactList(); } function factSetSearch(v){ _factSearch=v; renderFactList(); } // ── proceed: only approved facts go to the builder ──────────────────────── function factsConfirm(){ const facts=_rbState.facts||[]; const unanswered=facts.filter(f=>f.status==='needs_answer'&&!f.text); if(unanswered.length){ if(!confirm(`${unanswered.length} question${unanswered.length>1?'s':''} still unanswered.\n\nThose details won't appear on your new site. Continue anyway?`)) return; } // approved knowledge = the source of truth. Nothing else reaches the builder. _rbState.approvedFacts=facts .filter(f=>f.text && f.text.trim()) .map(f=>({text:f.text.trim(), category:f.category==='question'?'business':f.category})); rbShowStage('rb-stage-questions'); if(typeof rbBuildQuestions==='function') rbBuildQuestions(_rbState.prefilled||{}); if(typeof rbBuildAIBoostQuestions==='function') rbBuildAIBoostQuestions(''); } // ── restored: scan summary + questions form ─────────────────────────────── function rbBuildScanSummary(){ /* superseded by the fact list */ } function rbBuildQuestions(pre){ const el=document.getElementById('rb-questions'); if(!el) return; el.innerHTML=RB_QUESTIONS_SCHEMA.map(q=>{ const v=(pre&&pre[q.id])||''; const input = q.type==='select' ? '' : q.type==='textarea' ? '' : ''; return '
'+q.label+'
'+(q.hint||'')+'
'+input+'
'; }).join(''); } // ── Template selection ───────────────────────────────────────────────────── function selectTemplate(el) { document.querySelectorAll('.tmpl-card').forEach(c=>c.classList.remove('selected')); el.classList.add('selected'); _rbState.selectedTemplate = el.dataset.tmpl; } // ── Code format selection ────────────────────────────────────────────────── function selectCodeFmt(el) { document.querySelectorAll('.code-fmt-opt').forEach(c=>c.classList.remove('selected')); el.classList.add('selected'); _rbState.codeFormat = el.dataset.fmt; } // ── Go to payment stage ──────────────────────────────────────────────────── function rbGoToPayment() { rbShowStage('rb-stage-payment'); } // ── DCB Payment flow (Fonix integration point) ───────────────────────────── async function dcbSendCode() { const phone = (document.getElementById('dcbPhone')?.value||'').trim().replace(/\s/g,''); const err = document.getElementById('dcb-err'); if (err) err.style.display='none'; if (!phone || phone.replace(/\D/g,'').length < 10) { if(err){err.textContent='Please enter a valid UK mobile number';err.style.display='block';} return; } const btn = document.getElementById('dcb-send-btn'); if(btn){btn.innerHTML=' sending…';btn.disabled=true;} // PRODUCTION: call your backend → Fonix API → send SMS with code // POST /api/dcb/initiate { phone, amount: 500, currency: 'GBP', description: 'fixmywebshite.com website build' } // Fonix sends SMS: "Reply YES to pay £5 for your fixmywebshite.com website build. Ref: XXXXXX" // User replies YES → Fonix webhook hits your backend → you mark session as paid // DEMO: simulate code send await new Promise(r=>setTimeout(r,1200)); if(btn){btn.innerHTML=' code sent — check your texts';btn.style.background='var(--limed)';btn.style.color='var(--lime)';} const vb = document.getElementById('dcb-verify-block'); if(vb) vb.style.display='block'; // Store phone for verification _rbState._dcbPhone = phone; } async function dcbVerify() { const code = (document.getElementById('dcbCode')?.value||'').trim(); const err = document.getElementById('dcb-err'); if(err) err.style.display='none'; if(!code || code.length<4){ if(err){err.textContent='Please enter the 6-digit code from your text';err.style.display='block';} return; } // PRODUCTION: POST /api/dcb/verify { phone, code } → Fonix confirms → mark paid // Demo: any 6-digit code works await new Promise(r=>setTimeout(r,900)); _rbState.paid = true; rbShowStage('rb-stage-url'); } function dcbDemoBypass() { _rbState.paid = true; _rbState.skippedPayment = true; rbShowStage('rb-stage-url'); const b = document.getElementById('rb-paid-banner'); if (b) { b.style.background = 'rgba(139,124,255,.08)'; b.style.borderColor = 'rgba(139,124,255,.3)'; b.innerHTML = '
testing mode — payment skipped
'; } } function rbShowStage(id) { ['rb-stage-template','rb-stage-payment','rb-stage-url','rb-stage-scanning', 'rb-stage-facts','rb-stage-questions','rb-stage-building','rb-stage-output'].forEach(s=>{ const el=document.getElementById(s); if(el) el.style.display = s===id?'block':'none'; }); } // ── Image upload ────────────────────────────────────────────────────────── function handleImgSelect(e) { Array.from(e.target.files).forEach(addUploadedImage); } function handleImgDrop(e) { e.preventDefault(); document.getElementById('img-drop-zone').style.borderColor=''; Array.from(e.dataTransfer.files).filter(f=>f.type.startsWith('image/')).forEach(addUploadedImage); } function addUploadedImage(file) { if(_rbState.uploadedImages.length>=5) return; const reader = new FileReader(); reader.onload = ev => { const dataUrl = ev.target.result; const obj = { dataUrl, name: file.name, type: file.type, size: file.size }; _rbState.uploadedImages.push(obj); renderImgPreviews(); }; reader.readAsDataURL(file); } function renderImgPreviews() { const el = document.getElementById('img-previews'); if(!el) return; el.innerHTML = _rbState.uploadedImages.map((img,i)=>`
${img.name.substring(0,12)}
`).join(''); } function removeUploadedImg(i) { _rbState.uploadedImages.splice(i,1); renderImgPreviews(); } // ── Scan URL ────────────────────────────────────────────────────────────── function rbScanUrl() { let raw = (document.getElementById('rbUrl')?.value||'').trim().toLowerCase().replace(/^https?:\/\//,'').replace(/\/$/,''); if(!raw){ rbSkipScan(); return; } _rbState.url = raw; _rbState.codeFormat = document.querySelector('.code-fmt-opt.selected')?.dataset?.fmt || 'single'; _rbState._builder = document.getElementById('rbBuilder')?.value||''; _rbState._host = document.getElementById('rbHost')?.value||''; rbShowStage('rb-stage-scanning'); document.getElementById('rb-scanning-url').textContent = raw; rbRunScan(raw); } function rbSkipScan() { _rbState.url = ''; _rbState.codeFormat = document.querySelector('.code-fmt-opt.selected')?.dataset?.fmt||'single'; _rbState._builder = document.getElementById('rbBuilder')?.value||''; _rbState._host = document.getElementById('rbHost')?.value||''; _rbState.facts = addGapQuestions([], {}); _rbState.seoIssues = []; rbShowStage('rb-stage-facts'); renderFactList(); } function rbPickStyle(el){ document.querySelectorAll('.rb-style-opt').forEach(o=>o.classList.remove('selected')); el.classList.add('selected'); _rbState.style = el.dataset.style; } // ── AI Boost Questions — dynamic based on business type ────────────────── const AI_BOOST_QUESTIONS = [ { id:'aiPrice', label:'What do your prices / packages start from?', hint:'Specific prices get cited by AI search. "From £X" is enough.', placeholder:'e.g. From £45/hour · Standard clean £80 · Wedding cakes from £280' }, { id:'aiYears', label:'How many years have you been in business?', hint:'Years of experience is a strong E-E-A-T signal.', placeholder:'e.g. 12 years · since 2010 · established 2018' }, { id:'aiWhy', label:'Why do customers choose you over competitors?', hint:'Specific, verifiable reasons — not "we care about quality".', placeholder:'e.g. Only firm in Bristol accredited by X · 98% repeat customers · 4.9/5 from 400 reviews' }, { id:'aiArea', label:'What area / radius do you serve?', hint:'Location signals boost local AI search results.', placeholder:'e.g. All Bristol postcodes · within 10 miles of Leeds city centre' }, { id:'aiProcess', label:'Briefly describe what happens when someone uses you', hint:'Step-by-step process content gets quoted by AI overview answers.', placeholder:'e.g. 1. Free consultation call · 2. Bespoke quote within 24hrs · 3. Work starts within a week' }, { id:'aiSpecial', label:'Any awards, certifications or notable press mentions?', hint:'Named credentials get cited directly by Perplexity and ChatGPT.', placeholder:'e.g. Which? Trusted Trader · Gas Safe registered · Featured in The Guardian 2024' }, { id:'aiGuarantee', label:'Do you offer any guarantee or promise?', hint:'Guarantees increase trust signals and appear in AI-generated FAQs.', placeholder:'e.g. 100% satisfaction guarantee · free re-do if not happy · price match promise' }, ]; function rbBuildAIBoostQuestions(businessType) { const el = document.getElementById('ai-boost-questions'); if(!el) return; el.innerHTML = AI_BOOST_QUESTIONS.map(q=>`
${q.label}
${q.hint}
`).join(''); } // ── Build stage ────────────────────────────────────────────────────────── async function rbBuild() { const answers = {}; [...RB_QUESTIONS_SCHEMA, ...AI_BOOST_QUESTIONS].forEach(q=>{ const el = document.getElementById('rbq_'+q.id); answers[q.id] = el ? el.value.trim() : ''; }); answers._builder = _rbState._builder||''; answers._host = _rbState._host||''; answers._codeFormat = _rbState.codeFormat||'single'; _rbState.answers = answers; if(!answers.businessType && !answers.businessName && !_rbState.url){ alert('Please fill in at least your business name and type.'); return; } _rbState.allBuilds = []; _rbState.currentBuildIdx = 0; rbShowStage('rb-stage-building'); const STYLES = [ _rbState.style, 'warm friendly local inviting cream terracotta', 'minimal ultra-clean white professional bold typography', 'luxury premium dark gold high-end elegant', ]; const STYLE_NAMES = ['Modern','Professional','Premium','Bold']; // Build all 4 in sequence for(let i=0; i<4; i++){ _rbState.currentBuildIdx = i; document.getElementById('rb-build-title').textContent = `building the ${['Modern','Professional','Premium','Bold'][i]} version…`; // Update build dots for(let d=1;d<=4;d++){ const dot = document.getElementById('bdot'+d); if(!dot) continue; dot.className = d{ if(si

Build ${i+1} failed: ${e.message}

Try clicking rebuild.

`, style: STYLES[i], name: STYLE_NAMES[i]+'(failed)' }); } } document.getElementById('rb-build-prog').style.width='100%'; document.getElementById('rb-build-status').textContent='all 4 versions ready!'; for(let d=1;d<=4;d++){const dot=document.getElementById('bdot'+d);if(dot)dot.className='build-dot done';} await new Promise(r=>setTimeout(r,500)); _rbState.builtCode = _rbState.allBuilds[0]?.code||''; rbShowOutput(_rbState.allBuilds[0]?.code||'', answers); } // ── Claude API ────────────────────────────────────────────────────────── async function rbCallClaude(ans, styleOverride, styleName) { const pre = _rbState.prefilled||{}; const aiSig = _rbState.aiSignals||{}; const images = _rbState.images||[]; const url = _rbState.url; // ── APPROVED BUSINESS KNOWLEDGE: the only facts the builder may use ── const approved = _rbState.approvedFacts || []; const byCat = approved.reduce((a,f)=>{(a[f.category] ||= []).push(f.text); return a;},{}); const approvedBlock = approved.length ? ` APPROVED BUSINESS KNOWLEDGE — verified by the business owner. This is your ONLY source of truth about this business. ${Object.entries(byCat).map(([k,v])=>`${k.toUpperCase()}:\n${v.map(t=>' - '+t).join('\n')}`).join('\n\n')} ╔══════════════════════════════════════════════════════════════════╗ ║ ABSOLUTE RULE — YOU MUST NOT BREAK THIS ║ ║ ║ ║ Build the site using ONLY the approved facts above. ║ ║ ║ ║ You must NOT invent: ║ ║ - awards, certifications or accreditations ║ ║ - customer reviews, testimonials or quotes ║ ║ - statistics, numbers of customers, or years in business ║ ║ - prices, guarantees, or service areas ║ ║ - any claim of any kind that is not listed above ║ ║ ║ ║ If a fact is missing, WRITE AROUND IT or leave that section out. ║ ║ Never fill a gap with a plausible-sounding invention. ║ ║ A shorter honest website beats a longer fabricated one. ║ ╚══════════════════════════════════════════════════════════════════╝ ` : ''; const heroImgs = images.filter(i=>i.type==='hero'||i.score>=80).slice(0,3); const logoImg = images.find(i=>i.type==='logo'); const contentImgs = images.filter(i=>i.type==='content'||i.type==='product').slice(0,5); // Include uploaded images as base64 references const uploadedBlock = _rbState.uploadedImages.length ? `\nUPLOADED IMAGES (user supplied — use these in the HTML as with data URI src):\n` + _rbState.uploadedImages.map((img,i)=>` IMAGE ${i+1}: ${img.name} — ${img.dataUrl.substring(0,60)}...`).join('\n') : ''; const imageBlock = [ heroImgs[0] && `HERO IMAGE (use prominently above fold): ${heroImgs[0].url}`, logoImg && `LOGO: ${logoImg.url}`, ...contentImgs.map(i=>`CONTENT IMAGE: ${i.url} [alt: "${i.alt}"]`), ..._rbState.uploadedImages.map((img,idx)=>`UPLOADED IMAGE ${idx+1} (user's own photo — prioritise these): `), ].filter(Boolean).join('\n'); const psiBlock = _rbState.psiData?.mobile?.lighthouseResult ? (() => { const auds = _rbState.psiData.mobile.lighthouseResult.audits||{}; const cats = _rbState.psiData.mobile.lighthouseResult.categories||{}; const fails = Object.values(auds).filter(a=>a.score!=null&&a.score<0.8&&a.title) .sort((a,b)=>(a.score||1)-(b.score||1)).slice(0,6).map(a=>` FIX: ${a.title}`); return `LIGHTHOUSE: Perf ${Math.round((cats.performance?.score||0)*100)} · SEO ${Math.round((cats.seo?.score||0)*100)} · A11y ${Math.round((cats.accessibility?.score||0)*100)} · LCP ${pre._lcp||'?'}\nFIX THESE IN NEW SITE:\n${fails.join('\n')}`; })() : ''; const contentBlock = [ pre._h1 && `EXISTING H1: "${pre._h1}"`, ...(pre._h2s||[]).map(h=>`H2: "${h}"`), ...(pre._paragraphs||[]).slice(0,5).map(p=>`CONTENT: "${p.substring(0,250)}"`), ...(aiSig.serviceListItems||[]).slice(0,8).map(s=>`SERVICE: "${s}"`), ...(aiSig.quotes||[]).map(q=>`REAL CUSTOMER QUOTE: "${q.substring(0,200)}"`), ...(aiSig.statistics||[]).map(s=>`STAT: "${s}"`), ...(aiSig.prices||[]).map(p=>`PRICE: "${p}"`), ...(aiSig.awards||[]).map(a=>`AWARD: "${a}"`), ].filter(Boolean).join('\n'); // Format instructions based on code format preference const fmtInstructions = { single: 'Deliver as ONE complete HTML file. All CSS in one

Website Deployment Guide

Business: ${biz}
Generated by: fixmywebshite.com
Date: ${new Date().toLocaleDateString('en-GB',{year:'numeric',month:'long',day:'numeric'})}
Versions built: ${_rbState.allBuilds.length} (included in your download)

Step 1 — Download your website files

Use the "download .html" button on the fixmywebshite.com builder page to download each of your ${_rbState.allBuilds.length} website versions. Each file is named with the version number and style.

Step 2 — Choose your preferred version

You have ${_rbState.allBuilds.length} versions:

    ${_rbState.allBuilds.map((b,i)=>`
  • Version ${i+1}: ${b.name}
  • `).join('')}

Open each .html file in a browser to preview. Choose the one you like best — or mix elements from different versions with a developer's help.

Step 3 — Put it live

${ans._builder==='new'||!ans._builder ? `

Option A: Netlify Drop (easiest — free, takes 30 seconds)

1. Go to netlify.com/drop
2. Drag and drop your chosen .html file onto the page
3. Your site is live immediately on a free .netlify.app address
4. Add your own domain in Netlify's dashboard (Settings → Domain Management)
` : ''} ${ans._builder==='wordpress' ? `

WordPress

1. Install the free WPCode plugin (Plugins → Add New → search WPCode)
2. Code Snippets → Add Snippet → HTML Snippet
3. Paste the full HTML code from your downloaded file
4. Set location to "Homepage" and activate
` : ''} ${ans._builder==='squarespace' ? `

Squarespace

1. Settings → Advanced → Code Injection → Header
2. Paste everything between the opening <head> and </head> tags from your file
3. For the body content: add a Code Block to your homepage and paste the body HTML
` : ''}

Standard FTP / cPanel method (all hosts)

1. Rename your chosen file to index.html
2. Log into your hosting control panel
3. Open File Manager → public_html (or your domain folder)
4. Upload the file and overwrite the existing index.html
5. Clear your browser cache and visit your domain

Step 4 — After going live

1. Google Search Console — Go to search.google.com/search-console, add your site, verify ownership, and submit your sitemap. This tells Google to crawl your new site immediately.
2. Google Business Profile — Go to business.google.com and claim your listing if you haven't already. Required for appearing in local map results.
3. DMARC email security — Add this DNS TXT record to prevent email spoofing:
v=DMARC1; p=quarantine; rua=mailto:postmaster@${_rbState.url||'yourdomain.com'}
4. Enable HTTPS — In your hosting control panel, find SSL/HTTPS settings and enable free Let's Encrypt SSL. Most hosts do this automatically.
5. Re-audit in 28 days — Return to fixmywebshite.com and run a new audit. Google's real-user data (CrUX) updates every 28 days and your new score will reflect the improvements.

What was built into your site

  • LocalBusiness and FAQPage schema markup — eligible for rich results in Google
  • Optimised title tag (52–60 characters with your primary keyword)
  • Meta description with keyword, location and call to action
  • Open Graph and Twitter Card tags — social shares now show your image
  • 1,200+ words of real editorial content including FAQ section
  • AI search signals: entity clarity, specific stats, real customer quotes, complete FAQ answers
  • Mobile-first responsive design — works on all screen sizes
  • Performance optimised: critical CSS inline, images lazy-loaded, fonts preconnected
  • Security meta tags: CSP, X-Frame-Options, Referrer-Policy
  • Accessibility: skip link, semantic HTML5, ARIA landmarks, form labels
Tip: If you want to make small changes (phone number, address, opening hours), just open the .html file in any text editor (Notepad on Windows, TextEdit on Mac) and use Find & Replace (Ctrl+H / Cmd+H) to find and update the text.

Generated by fixmywebshite.com · ${new Date().getFullYear()} · Powered by Claude AI · Questions? hello@fixmywebshite.com

`; const blob = new Blob([guideHtml],{type:'text/html'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = (biz.replace(/[^a-z0-9]/gi,'-').toLowerCase()||'site') + '-deployment-guide.html'; a.click(); URL.revokeObjectURL(a.href); } // ── Buy more rebuilds ───────────────────────────────────────────────────── function rbBuyMore() { // Show a DCB payment for £3 → 10 more builds const confirmed = confirm('Pay £3 via your phone bill for 10 more website versions?'); if(!confirmed) return; alert('In production: Fonix DCB flow fires here for £3.\nFor demo: generating 3 bonus variations now…'); rbGenerateMoreBuilds(); } async function rbGenerateMoreBuilds() { const EXTRA_STYLES = [ 'corporate blue serious B2B professional', 'playful colourful creative youth brand', 'industrial dark moody photography-led', ]; const ans = _rbState.answers||{}; for(const style of EXTRA_STYLES){ try { const code = await rbCallClaude(ans, style, style); _rbState.allBuilds.push({ code, style, name: style }); const vsw = document.getElementById('version-switcher'); if(vsw) vsw.innerHTML = _rbState.allBuilds.map((b,i)=> `` ).join(''); } catch(e) { console.warn('Extra build failed:', e.message); } } } function rbReset() { _rbState = { url:'', psiData:null, fetchedHtml:'', prefilled:{}, aiSignals:{}, images:[], style:'modern professional dark', answers:{}, builtCode:'', scoreBefore:null, selectedTemplate:'modern', codeFormat:'single', allBuilds:[], currentBuildIdx:0, uploadedImages:[], paid:false }; document.getElementById('rb-scan-prog').style.width='5%'; rbShowStage('rb-stage-template'); // Reset template selection document.querySelectorAll('.tmpl-card').forEach((c,i)=>c.classList.toggle('selected',i===0)); } // ═══ DEMO / SAMPLE MODE ════════════════════════════════════════════════════ // Sample built website for demo (no API key needed to see the output stage) const DEMO_BUILT_HTML = (document.getElementById('_demoHtmlStore')||{textContent:''}).textContent // ── Demo mode for rebuild page ───────────────────────────────────────────── // If user clicks "scan my site" with mybakery.co.uk (or any demo url), // show simulated scan then jump straight to questions with pre-filled data, // and add a "show demo output" button so anyone can see the full output flow. const DEMO_URLS = ['mybakery.co.uk','mybakery','demo','test','example.com']; // Only the explicit demo URLs get the canned walkthrough. // EVERY other URL does a real fetch + real extraction. No pretending. const _origRbScanUrl = rbScanUrl; window.rbScanUrl = function() { const urlInput = document.getElementById('rbUrl'); const raw = (urlInput?.value || '').trim().toLowerCase() .replace(/^https?:\/\//,'').replace(/\/$/,''); if (!raw) { rbSkipScan(); return; } _rbState.url = raw; _rbState.codeFormat = document.querySelector('.code-fmt-opt.selected')?.dataset?.fmt || 'single'; _rbState._builder = document.getElementById('rbBuilder')?.value || ''; _rbState._host = document.getElementById('rbHost')?.value || ''; const isDemo = DEMO_URLS.some(d => raw === d || raw === 'www.'+d); if (isDemo) { rbRunDemoScan(raw); return; } rbShowStage('rb-stage-scanning'); const su = document.getElementById('rb-scanning-url'); if (su) su.textContent = raw; rbRunScan(raw); }; async function rbRunDemoScan(url) { rbShowStage('rb-stage-scanning'); document.getElementById('rb-scanning-url').textContent = url; const prog = document.getElementById('rb-scan-prog'); const stepsEl = document.getElementById('rb-scan-steps'); const demoSteps = [ 'fetching page HTML, images and structured data…', 'extracting AI-search signals (E-E-A-T, entities, FAQs)…', 'running Google Lighthouse mobile…', 'running Google Lighthouse desktop + SEO…', 'analysing images, social profiles and trust signals…', 'preparing smart questions…', ]; stepsEl.innerHTML = demoSteps.map((t,i) => `
${t}
` ).join(''); function setStep(i, state) { const el = document.getElementById('s' + i); if (!el) return; el.className = 'rb-scan-step ' + state; const ic = el.querySelector('i'); ic.className = state==='done'?'ti ti-check':state==='active'?'ti ti-loader-2':'ti ti-circle'; ic.style.animation = state==='active'?'spin .8s linear infinite':''; } // Simulate scan with delays for (let i = 1; i <= 6; i++) { setStep(i, 'active'); prog.style.width = (i * 16) + '%'; await new Promise(r => setTimeout(r, 600 + Math.random()*400)); setStep(i, 'done'); } prog.style.width = '100%'; await new Promise(r => setTimeout(r, 300)); // Inject demo prefilled data _rbState.prefilled = { businessName: 'My Bakery Bristol', businessType: 'Restaurant / café / bakery', phone: '0117 923 4567', email: 'hello@mybakery.co.uk', address: '12 Corn Street, Bristol BS1 1HT', openingHours: 'Mon-Fri 7am-5pm, Sat 7am-3pm', _title: 'My Bakery Bristol | Artisan Bread & Sourdough', _metaDesc: 'Fresh artisan bread and sourdough baked daily in Bristol.', _h1: 'Award-winning artisan bakery in Bristol', _h2s: ['Fresh sourdough bread baked every morning', 'Wedding cakes and celebration bakes', 'Order online for Bristol delivery'], _psiPerf: 41, _psiSEO: 71, _psiA11y: 92, _lcp: '6.1 s', _cls: '0.03', _tbt: '280 ms', _topOpps: ['Eliminate render-blocking resources', 'Serve images in next-gen formats', 'Properly size images'], _existingSchema: 'WebSite', _wordCount: 620, _socials: { facebook: 'https://facebook.com/mybakery', instagram: 'https://instagram.com/mybakery_bristol' }, _images: { hero: [ { url: 'https://images.unsplash.com/photo-1509440159596-0249088772ff?w=800&q=80', alt: 'artisan bakery bristol', type: 'hero', score: 90 }, { url: 'https://images.unsplash.com/photo-1586444248902-2f64eddc13df?w=800&q=80', alt: 'sourdough bread', type: 'hero', score: 85 }, ], logo: [], people: [{ url: 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=400&q=80', alt: 'baker at work', type: 'person', score: 72 }], content: [ { url: 'https://images.unsplash.com/photo-1535254973040-607b474cb50d?w=600&q=80', alt: 'wedding cake', type: 'product', score: 68 }, { url: 'https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?w=600&q=80', alt: 'pastries', type: 'content', score: 62 }, ], all: [] }, _imageCount: 14, _rating: '4.9/5 (127 reviews)', _existingFaqs: [], }; _rbState.prefilled._images.all = [ ..._rbState.prefilled._images.hero, ..._rbState.prefilled._images.people, ..._rbState.prefilled._images.content, ]; _rbState.images = _rbState.prefilled._images.all; _rbState.aiSignals = { socialProfiles: ['facebook', 'instagram'], statistics: ['6 years in business', '400+ weekly customers', '100-year-old starter'], quotes: ['"Best bread in Bristol — we drive 20 minutes just for their sourdough" — Sarah M.'], locations: ['Bristol', 'Corn Street'], serviceListItems: ['Sourdough bread', 'Wedding cakes', 'Coffee & pastries', 'Same-day delivery'], prices: ['From £4.50 a loaf', 'From £280 wedding cakes'], existingSchema: ['WebSite'], hasAboutPage: true, hasContactPage: true, }; _rbState.scoreBefore = 41; _rbState.psiData = null; _rbState.facts = addGapQuestions(factsFromExtractionOnly(_rbState.prefilled), _rbState.prefilled); _rbState.seoIssues = rbSeoRules(_rbState.prefilled, ''); rbShowStage('rb-stage-facts'); renderFactList(); // Add demo output button at top of questions stage const qs = document.getElementById('rb-stage-questions'); if (qs && !document.getElementById('rb-demo-btn')) { const demoBtn = document.createElement('div'); demoBtn.id = 'rb-demo-btn'; demoBtn.style.cssText = 'background:rgba(198,255,107,.06);border:1px solid rgba(198,255,107,.2);border-radius:12px;padding:.85rem 1.25rem;margin-bottom:1.5rem;display:flex;align-items:center;gap:12px;flex-wrap:wrap'; demoBtn.innerHTML = `
Demo mode — mybakery.co.uk sample data loaded
Questions pre-filled. Click build to generate real output, or skip ahead to see a sample result.
`; qs.insertBefore(demoBtn, qs.firstChild); } } function rbShowDemoOutput() { _rbState.builtCode = DEMO_BUILT_HTML; _rbState.answers = { businessName: 'My Bakery Bristol', businessType: 'Restaurant / café / bakery', location: 'Bristol, UK', targetKeywords: 'artisan bakery bristol, sourdough bread bristol', phone: '0117 923 4567', email: 'hello@mybakery.co.uk', address: '12 Corn Street, Bristol BS1 1HT', openingHours: 'Mon-Fri 7am-5pm, Sat 7am-3pm', uniqueSelling: 'Only Bristol bakery using a 100-year-old sourdough starter, open 7 days, award-winning', services: 'Sourdough bread\nWedding cakes\nCoffee & pastries\nLocal delivery', testimonial: '"Best bread in Bristol — we drive 20 minutes just for their sourdough." — Sarah M.', }; rbShowOutput(DEMO_BUILT_HTML, _rbState.answers); } // ── Fix: audit "destroy me" button runs demo immediately ────────────────── const _origRunAudit = runAudit; window.runAudit = function() { const url = document.getElementById('urlMain')?.value?.trim() || 'mybakery.co.uk'; document.getElementById('scanUrl') && (document.getElementById('scanUrl').textContent = url || 'mybakery.co.uk'); const loading = document.getElementById('loadingArea'); const results = document.getElementById('resultsArea'); if (loading) loading.style.display = 'block'; if (results) results.style.display = 'none'; loading?.scrollIntoView({ behavior:'smooth', block:'start' }); const fill = document.getElementById('progFill'); let pct = 0; const t = setInterval(() => { pct = Math.min(pct + Math.random()*12 + 4, 100); if (fill) fill.style.width = Math.round(pct) + '%'; if (pct >= 100) { clearInterval(t); setTimeout(() => { if (loading) loading.style.display = 'none'; showResults(); results?.scrollIntoView({ behavior:'smooth', block:'start' }); }, 400); } }, 280); }; // ── Fix: dials all draw correctly after page switch ─────────────────────── const _origGoPage = goPage; window.goPage = function(id) { _origGoPage(id); // Draw any dials that need it if (id === 'speed') setTimeout(() => { drawDial('speedDial1',41,140,sCol(41)); drawDial('speedDial2',74,140,sCol(74)); drawDial('speedDial3',92,110,sCol(92)); drawDial('speedDial4',95,110,sCol(95)); }, 80); if (id === 'checker') setTimeout(() => drawDial('checkerDial',67,260,'#60CFFF'), 80); if (id === 'report') setTimeout(() => { drawDial('rD0',67,130,sCol(67));[1,41,sCol(41)],[2,71,sCol(71)],[3,88,sCol(88)],[4,5,sCol(5)],[5,50,sCol(50)],[6,55,sCol(55)],[7,78,sCol(78)],[8,58,sCol(58)].forEach(([i,s,c]) => drawDial('rD'+i,s,80,c)); }, 80); if (id === 'backlinks')setTimeout(() => { drawDial('daClockBL',24,130,'#FFB347'); runOPRLookup(); }, 80); if (id === 'audit') setTimeout(() => { if (document.getElementById('resultsArea')?.style?.display !== 'none') { drawDial('bigDial',SCORES.overall,320,sCol(SCORES.overall)); buildMiniGrid(); } }, 80); if (id === 'directory')buildDir(document.querySelector('#catRow .cat-t.on')?.dataset?.cat || 'food'); if (id === 'beginners') { const b=document.getElementById('bstep1'); if(b)b.style.display='block'; } // Update back float const detail=['backlinks','compare','density','speed','checker','report','directory','beginners','about','rebuild','privacy']; const bf=document.getElementById('backFloat'); if(bf) bf.className = detail.includes(id) ? 'show' : ''; }; console.log('%c webshite demo ready ✓', 'color:#FF6B6B;font-weight:bold;font-size:14px');