'; } function wsDownloadReport(baseName,title,text,fmt){ fmt=String(fmt||'TXT').toUpperCase(); var blob,ext; if(fmt==='PDF'){ blob=new Blob([wsBuildPdf(title,text)],{type:'application/pdf'}); ext='pdf'; } else if(fmt==='DOCX'||fmt==='DOC'){ blob=new Blob([''+wsBuildDoc(title,text)],{type:'application/msword'}); ext='doc'; } else { blob=new Blob([(title?title+'\n\n':'')+String(text==null?'':text)],{type:'text/plain;charset=utf-8'}); ext='txt'; } try{ var url=URL.createObjectURL(blob); var a=document.createElement('a'); a.href=url; a.download=baseName+'.'+ext; document.body.appendChild(a); a.click(); a.remove(); setTimeout(function(){ URL.revokeObjectURL(url); },1500); }catch(e){} } function topbar(){ return `
${state.account.loggedIn?'':''}
${state.account.loggedIn?'':''}${state.account.admin?'Admin panel':''}${state.account.premium?'':'' + icon('crown') + 'Upgrade to Pro'}${accountMenu()}
`; } function drawer(active){ const links = ORDER.map(key => { const t = ROUTES[key]; return `${icon(t.icon)}${t.short}`; }).join(''); const docLink = `${icon('folder')}Documents`; const chatLink = `${icon('chat')}Chat`; return `
`; } function footer(){ return ``; } function homePage(){ const ready = state.homeText.trim().length > 0; const TOOL_CTA = {humanizer:'Humanize text', detector:'Detect AI', rewriter:'Rewrite text', grammar:'Check grammar', email:'Generate email'}; const sel = state.homeTool || null; const ctaLabel = sel ? (TOOL_CTA[sel] || 'Run') : 'Ask AI'; const chips = [['grammar','Grammar Checker'],['humanizer','AI Humanizer'],['detector','AI Detector'],['rewriter','Advanced Rewriter']].map(([key,label]) => ``).join(''); const chipRow = sel ? `
${icon(ROUTES[sel].icon)}${ROUTES[sel].label}
` : `${chips}`; return `
${icon('crown')}Enjoy unlimited access starting at $8/month. Cancel anytime.Explore Pro

Write, rewrite, humanize, and check AI content in one place

Use Word Spinner's writing tools for free. Paste your text, pick an action, and get a real result in seconds.

${state.homeMinWarn?'
Please enter at least 5 words to ask the AI.
':''}${(state.homeText && wordMatches(state.homeText).length>=2000)?'
Pasted text trimmed to the maximum of 2000 words
':''}
${chipRow}
${popularTools()}${seoSections()}${fullSuite()}${seoAudience()}${footer()}
`; } function popularTools(){ const cards = [ ['sparkles','AI Humanizer','Paste AI text and humanize it in one click.','/ai-humanizer','#00A3FF'], ['shield','AI Detector','Paste text and get a clear human vs AI signal.','/ai-detector','#00A3FF'], ['rewrite','Advanced Rewriter','Paste text and choose a rewrite mode.','/advanced-rewriter','#00A3FF'], ['grammar','Grammar Checker','Paste rough text and clean it up instantly.','/grammar-checker','#1DC894'], ['mail','Email Generator','Add a short brief and Word Spinner drafts the email, subject lines, and a clear call to action.','/email','#00A3FF'], ['chat','AI Chat','Brainstorm, draft, and get answers in a conversation.','/chat','#00A3FF'] ].map(([ic,label,desc,href,color]) => `
${icon(ic)}

${label}

${esc(desc)}

`).join(''); return `

Popular writing tools

Everything you need to write, fix, and check. Free to try.

${cards}
`; } function seoSections(){ return `

Write like a pro, and still sound like you

Turn rough notes into clear, polished, professional writing in a fraction of the time. Word Spinner fixes grammar, spelling, and clarity while keeping your natural voice, so every message sounds like the best version of you.

Fix grammar, spelling and clarity Make AI text sound human Check originality with AI detection
Word Spinner voice and grammar check scoring a piece of writing for grammar, fluency, clarity and engagement
Word Spinner AI chat explaining what it is and helping draft a quick overview document

An AI writing partner that actually gets it

Brainstorm, research, and draft through a natural conversation, then move straight into a finished document. Word Spinner understands what you want to say and helps you say it clearly, faster.

Think and write with AI chat Rewrite and paraphrase instantly Draft emails in seconds

One assistant for everything you write

Rewrite a paragraph, tighten an email, or polish a full document from a single place. Word Spinner keeps your writing consistent across every task, so you never have to start from a blank page again.

Paraphrase and rewrite any text Generate professional emails See what Pro unlocks
Word Spinner rewriting text on a laptop alongside the tools people use every day
`; } function seoAudience(){ return `

Made for the way you write

Whatever you are working on, Word Spinner helps you get it written and get it right.

Business owner reviewing a report drafted with Word Spinner

Business owners

Write proposals, reports, and updates that sound sharp, without hiring a copywriter.

Job seeker refining a resume and cover letter with Word Spinner

Job seekers

Refine your resume and tailor every cover letter to the role in minutes.

Professional writing a clear, polished message with Word Spinner

Professionals and students

Turn research and rough notes into clear, original writing you can trust.

Write with confidence, starting today

Join the writers who work faster and sound better with Word Spinner. Free to start, no signup required.

Person writing confidently with Word Spinner open on a laptop
`; } function fullSuite(){ const cats = Object.keys(WS_TOOLS); const active = (state.suiteTab && WS_TOOLS[state.suiteTab]) ? state.suiteTab : cats[0]; const tabs = cats.map(c => ``).join(''); const ext = ''; const cards = WS_TOOLS[active].tools.map(([name,slug,desc]) => `

${esc(name)}

${ext}

${esc(desc)}

`).join(''); // Restored on owner request (2026-07-10): the free-tools cross-promo to tools.word-spinner.com is wanted on the homepage. return `

Word Spinner full suite of free tools

Free, no-signup tools on Word Spinner.

${tabs}
${cards}
Browse all free tools ${icon('arrow')}
`; } function howItWorks(){ return `

How Word Spinner works

Start in the Smart AI box or pick a tool from the side menu.

1

Add your text

Write, paste, or upload a document into the editor.

2

Pick an action

Humanize, detect, rewrite, fix, summarize, or translate.

3

Get your result

Copy it instantly. Sign up free for higher limits and history.

`; } function detectHumanizer(text){ const value = (text||'').toLowerCase(); const hasCitation = /\([a-z][a-z\s.-]+,\s*\d{4}\)|\[\d+\]/i.test(text||''); const hasEmail = /^(hi|hello|dear)\b|regards|sincerely|subject:/i.test((text||'').trim()); const hasSocial = /#\w+|\b(link in bio|follow|subscribe)\b/i.test(text||''); const hasBusiness = /\b(organization|proposal|client|stakeholder|operational|business|revenue)\b/.test(value); const hasMarketing = /\b(unlock|game-changing|elevate|brand|campaign|conversion)\b/.test(value); const academic = hasCitation || /\b(study|research|therefore|implementation|sustainability|policy|evidence)\b/.test(value); const wordCount = words(text); let template = 'University essay'; let level = wordCount > 80 ? 'University' : 'College'; let tone = 'Natural'; let confidence = 72; if (hasEmail){ template = 'Email'; level = 'Professional'; tone = 'Business'; confidence = 88; } else if (hasSocial){ template = 'Social post'; level = 'Creator'; tone = 'Creator'; confidence = 84; } else if (hasMarketing){ template = 'Marketing copy'; level = 'Professional'; tone = 'Marketing'; confidence = 81; } else if (hasBusiness){ template = 'Business'; level = 'Professional'; tone = 'Business'; confidence = 82; } else if (academic){ template = 'Research'; level = 'University'; tone = 'Academic'; confidence = 86; } if (!text.trim()){ template = 'Auto'; level = 'Auto'; tone = 'Natural'; confidence = 0; } const warnings = []; if (hasCitation) warnings.push('citations found'); if (/\d/.test(text||'')) warnings.push('numbers found'); if (/"[^"\n]+"|'[^'\n]+'/.test(text||'')) warnings.push('quotes found'); return {template, level, tone, confidence, warnings}; } function selectControl(field, values, locked){ const current = state.humanizer[field]; return ``; } function labelFor(field){ const labels = {template:'Template', level:'Level', goal:'Goal', mode:'Mode', region:'Language / region', readability:'Readability', outputLength:'Output length', strength:'Humanization strength', preserveMeaning:'Preserve meaning', sentenceVariation:'Sentence variation', vocabulary:'Vocabulary complexity'}; return labels[field] || field; } function toggleRow(label, field, value, locked){ const action = locked ? 'humanizer-upgrade' : 'humanizer-toggle'; return ``; } function humanizerControls(){ return `
${selectControl('template', HUMANIZER_OPTIONS.template)}${selectControl('level', HUMANIZER_OPTIONS.level)}${selectControl('goal', HUMANIZER_OPTIONS.goal)}${selectControl('mode', HUMANIZER_OPTIONS.mode)}
`; } function humanizerEditor(){ const wc = words(state.toolText); const over = isToolOverLimit('humanizer', wc); const daily = getDailyRuns(); const cta = over ? 'Upgrade' : 'Humanize'; return `

Input

Paste AI-generated text. The preview uses mock output and real UI rules.

${wordCounterText('humanizer', wc)}
`; } function humanizerSettings(){ const h = state.humanizer; return `
`; } function processingView(){ const steps = ['Analyzing your text','Finding robotic phrases','Adjusting rhythm and tone','Checking meaning preservation','Finalizing your result']; return `

Humanizing your text

This preview shows the guided processing flow.

${steps.map((step, index) => `
${index+1}${esc(step)}
`).join('')}
`; } function humanizerResult(){ const data = state.humanizer.resultData; if (state.loading) return processingView(); if (!data) return `
${icon('sparkles')}

Humanized result appears here

Run the cockpit to see the output and what changed.

`; return `
${humanizedView(data)}
`; } function humanizedView(data){ return `
${esc(data.output)}
`; } function compareView(data){ return `

Original

${highlightCompareText(data.original, data.changes, 'before')}

Humanized

${highlightCompareText(data.output, data.changes, 'after')}

${data.changes.map(change => `
${esc(change.label)}
${esc(change.before)}

${esc(change.after)}

${esc(change.reason)}
`).join('')}
`; } function highlightCompareText(text, changes, side){ let output = esc(text); changes.forEach(change => { const raw = side === 'before' ? change.before : change.after; const token = esc(raw); if (token && output.includes(token)) output = output.replace(token, `${token}`); }); return output; } function humanizerPage(){ const h = state.humanizer; const wc = words(state.toolText); const over = isToolOverLimit('humanizer', wc); const modes = ['Standard','Academic','Professional','Casual']; const seg = modes.map(m => ``).join(''); const LANGS = ['Auto detect','English (US)','English (UK)','Dutch','Spanish','French','German','Portuguese']; const langCtrl = isRemover ? '' : `
${h.langOpen?`
${LANGS.map(l => ``).join('')}
`:''}
`; let body; if (freeOver){ body = humanizerLimitOverlay(wc); } else if (state.loading){ body = `

Humanizing your text…

`; } else if (h.resultData && h.showStats){ body = humanizerStatsView(h.resultData); } else if (h.resultData){ body = `

${highlightHumanizedChanges(h.resultData.output, h.resultData.changes, h.openVariant, h.highlightChanges)}

`; } else { body = `
${icon('sparkles')}

Does your writing sound too robotic?

Paste AI-generated text and turn it into clear, natural writing in one click.

`; } const outRight = h.resultData && !state.loading && !freeOver ? `
${icon('check')} Humanized
` : ''; const cta = over ? 'Upgrade' : 'Humanize'; const cnt = ((h.resultData && h.resultData.changes) || []).length; const dlMenu = h.downloadOpen ? `` : ''; const outFootbar = (h.resultData && !state.loading && !freeOver) ? `
${icon('sparkles')} ${esc(h.mode)}${words(h.resultData.output)} words${h.highlightChanges?`${cnt} ${cnt===1?'change':'changes'}`:''}
${dlMenu}Pro - run the AI Detector on your humanized text to confirm it reads as human before you publish.
` : ''; const lsel = (label, val) => `
${icon('lock')} ${label}
${val}${icon('chevron')}
`; const ltog = (label, on) => `
${icon('lock')} ${label}
`; const settings = h.settingsOpen ? `
` : ''; return `
${icon('sparkles')}

AI Humanizer

Make AI-generated text sound natural and human

${quotaNotice()}
${seg}
${langCtrl}
${settings}
Your text
${wordCounterText('humanizer', wc)}
Result${outRight}
${body}
${outFootbar}
${humanizerContent()}${footer()}
`; } function detectorSelect(field, values){ const current = state.detector[field]; const labels = {scanType:'Scan type', contentType:'Content type', sensitivity:'Sensitivity', language:'Language'}; return ``; } function detectorSampleButtons(){ return Object.keys(DETECTOR_SAMPLES).map(name => ``).join(''); } function detectorToolbar(){ const d = state.detector; return `
${icon('shield')}AI DetectorEvidence-based scan workspace
${detectorSelect('language', DETECTOR_OPTIONS.language)}
${detectorSelect('scanType', DETECTOR_OPTIONS.scanType)}${detectorSelect('contentType', DETECTOR_OPTIONS.contentType)}${detectorSelect('sensitivity', DETECTOR_OPTIONS.sensitivity)}
Samples${detectorSampleButtons()}
`; } function detectorEditor(){ const wc = words(state.toolText); const over = isToolOverLimit('detector', wc); const tooShort = wc > 0 && wc < 40; const canRun = wc >= 40 && !state.loading; const data = state.detector.resultData; const status = !wc ? 'To analyze text, add at least 40 words.' : tooShort ? 'To analyze text, add at least 40 words.' : wc < 80 ? 'For better accuracy, scan at least 80 words.' : data ? 'Analysis complete' : 'Ready to scan'; const canvas = data ? detectorDocumentCanvas(data) : `
To analyze text, add at least 40 words.Paste a draft, try a sample, or upload a document.
`; return `

Document

Paste text, run a scan, then review highlighted evidence.

${canvas}
${wordCounterChip('ws-detector-count', 'detector', wc)}${esc(status)}
`; } function detectorDocumentCanvas(data){ return `
${data.sentences.map((sentence, index) => ``).join(' ')}
`; } function detectorAnalysisPanel(){ const data = state.detector.resultData; return ``; } function detectorEmptyPanel(){ return `
Word Spinner AI Detector
--%

Run a scan to see AI likelihood, highlighted sections, and next steps.

${detectorBreakdownRows({aiGenerated:'--', aiRefined:'--', humanWritten:'--'})}

Before you scan

Will my text be shared with anyone?

This static preview does not send text to a backend.

What are false positives and false negatives?

AI detection is probabilistic. Treat results as signals, not proof.

How do I maintain my unique voice?

Review flagged sections, add specific details, and keep phrasing that sounds like you.

Our AI detector flags text that may be AI-generated. Use your best judgment when reviewing results. Never rely on AI detection alone to make decisions that could impact someone's career or academic standing.
`; } function detectorResultPanel(data){ const selected = data.sentences[state.detector.selectedSentence] || data.sentences[0]; return `
Word Spinner AI Detector
${data.aiLikely}%of text is likely AI

${esc(data.prediction)}. ${esc(data.confidence)}.

${detectorBreakdownRows(data.breakdown)}
AI detection is probabilistic. Use these results as one signal, not as the only basis for academic, hiring, or professional decisions.
Want your text to sound more authentic?

Send flagged sections to the AI Humanizer and then re-scan your draft.

Flagged section

${esc(selected.label)}

${esc(selected.text)}

How strong is this signal?${esc(selected.probability)}Why it triggers AI detection?
${selected.reasons.map(reason => `${esc(reason)}`).join('')}

Stats

Words${data.stats.words}
Sentences${data.stats.sentences}
Average sentence${data.stats.avgSentenceLength} words
Likely AI${data.counts.ai}
Mixed${data.counts.mixed}
Likely human${data.counts.human}
`; } function detectorBreakdownRows(breakdown){ return `
AI-generated${breakdown.aiGenerated}%
AI-refined${breakdown.aiRefined}%
Human-written${breakdown.humanWritten}%
`; } function detectorLoadingView(){ const steps = ['Analyzing text','Checking sentence patterns','Finding AI-like sections','Building report','Analysis complete']; return `

Building your AI detection report

${steps.map((step,index) => `
${index+1}${esc(step)}
`).join('')}
`; } function variantsFor(after){ return HUMANIZER_VARIANTS[after] || []; } function variantPopover(idx, change){ const values = change && Array.isArray(change.variants) ? change.variants.map(v => String(v || '').replace(/\s+/g, ' ').trim()).filter(Boolean).slice(0, 5) : []; if (!values.length) return ''; const items = values.map(v => ``).join(''); return `${items}`; } function highlightHumanizedChanges(text, changes, openIndex, enabled){ if (!enabled) return esc(text); // Zelfde robuuste aanpak als de humanizer-variant: backend-positie + cursor over de rauwe // tekst; de oude versie deed indexOf vanaf 0 in de escaped string en in-place slices, // waardoor herhaalde tokens botsten en de helft van de wijzigingen geen highlight kreeg. const raw = String(text || ''); let html = ''; let pos = 0; (changes || []).forEach((c, idx) => { const t = c.selected || c.after; if (!t) return; let i = (typeof c.position === 'number' && raw.substr(c.position, t.length) === t) ? c.position : raw.indexOf(t, pos); if (i < 0) i = raw.indexOf(t, pos); if (i < 0 || i < pos) return; html += esc(raw.slice(pos, i)); const pop = idx === openIndex ? variantPopover(idx, c) : ''; html += `${esc(t)}${pop}`; pos = i + t.length; }); return html + esc(raw.slice(pos)); } function humanizerStatsView(data){ const out = data.output || ''; const w = words(out); const chars = out.length; const sentences = (out.match(/[^.!?]+[.!?]+/g) || [out]).filter(s => s.trim()).length || 1; const readMin = Math.max(1, Math.round(w / 200)); const avgLen = Math.round(w / sentences); return `
${icon('check')} HumanizedStatistics
RewrittenEdited for clearer, more natural phrasing.
Words${w}
Characters${chars}
Sentences${sentences}
Reading time${readMin} min
Avg sentence${avgLen} words
ReadabilityClear
`; } function humanizerLimitOverlay(wc){ const lines = [92,86,74,96,68,88,72,90,64].map(p => `
`).join(''); return `
${lines}
${icon('lock')}
${wc}/125 words · limit exceeded

To view your text and humanize without limits, upgrade to Pro.

`; } function detectorPage(){ const d = state.detector; const wc = words(state.toolText); const over = isToolOverLimit('detector', wc); const canRun = wc >= 40 && !state.loading; const data = d.resultData; const canvas = data ? `
${data.sentences.map((s,i) => ``).join(' ')}
` : ``; const status = (wc > 0 && wc < 40) ? 'Add at least 40 words to scan' : ''; let body; if (state.loading && state.route === 'detector'){ body = `

Scanning your text…

`; } else if (data){ body = detectorCleanResult(data); } else { body = `
${icon('shield')}

Check if text sounds AI-generated

Paste text and get a clear human vs AI signal with highlighted evidence.

`; } const outTools = data && !state.loading ? `
` : ''; return `
${icon('shield')}

AI Detector

Check whether text looks human, AI-generated, or mixed

Document
${canvas}
${wordCounterChip('ws-detector-count', 'detector', wc)}${esc(status)}
Analysis${outTools}
${body}
${detectorContent()}${footer()}
`; } function detectorCleanResult(data){ const sel = data.sentences[state.detector.selectedSentence] || data.sentences[0]; const pct = data.aiLikely; const tone = pct >= 75 ? 'ai' : pct >= 45 ? 'mixed' : 'human'; const C = 327; const off = (C * (1 - pct / 100)).toFixed(1); const b = data.breakdown; const bar = (key, label, val) => `
${label}${val}%
`; return `
${pct}%
of text is likely AI
AI-generated${b.aiGenerated}%
AI-refined${b.aiRefined}%
Human-written${b.humanWritten}%
Want your text to sound more authentic?
${data.sentences.map((s,i) => ``).join('')}

AI detection is probabilistic - use it as one signal, not proof. Don't rely on it alone for decisions that affect someone's academic or professional standing.

`; } function sentenceReason(text, pAi){ const value = text.toLowerCase(); const reasons = []; if (/in today|rapidly evolving|leverag|cutting-edge|paramount|transformative|unlock|streamline|elevate|comprehensive/.test(value)) reasons.push('Common AI phrase'); if (/\b(therefore|furthermore|moreover|additionally|ultimately)\b/.test(value)) reasons.push('Generic transition'); if (text.split(/\s+/).length > 24) reasons.push('Long polished sentence'); if (/optimize|efficiency|sustainable growth|innovative solutions/.test(value)) reasons.push('Corporate phrasing'); if (!reasons.length) reasons.push(pAi >= 50 ? 'Predictable phrasing' : 'Natural variation'); return reasons.slice(0, 3); } function generateDetectorResult(text){ const raw = text.trim().match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [text.trim()]; const sentences = raw.map((part, index) => { const clean = part.trim(); const value = clean.toLowerCase(); let score = 24 + (index % 3) * 12; if (/in today|rapidly evolving|leverag|cutting-edge|paramount|transformative|unlock|streamline|elevate|comprehensive/.test(value)) score += 42; if (/optimize|efficiency|sustainable growth|innovative solutions|game-changing/.test(value)) score += 28; if (/i | we | our | messy|remember|notes|interviews|rough/.test(value)) score -= 18; if (clean.split(/\s+/).length > 24) score += 12; score = Math.max(6, Math.min(96, score)); const labelKey = score >= 80 ? 'ai' : score >= 45 ? 'mixed' : 'human'; const label = labelKey === 'ai' ? 'AI-generated' : labelKey === 'mixed' ? 'AI-refined' : 'Human-written'; const probability = score >= 80 ? 'Strong signal' : score >= 45 ? 'Moderate signal' : 'Low signal'; return {text:clean, pAi:score, labelKey, label, probability, reasons:sentenceReason(clean, score)}; }); const avg = Math.round(sentences.reduce((sum, s) => sum + s.pAi, 0) / sentences.length); const counts = { ai:sentences.filter(s => s.labelKey === 'ai').length, mixed:sentences.filter(s => s.labelKey === 'mixed').length, human:sentences.filter(s => s.labelKey === 'human').length }; const total = Math.max(1, sentences.length); const breakdown = { aiGenerated:Math.round(counts.ai / total * 100), aiRefined:Math.round(counts.mixed / total * 100), humanWritten:Math.max(0, 100 - Math.round(counts.ai / total * 100) - Math.round(counts.mixed / total * 100)) }; const wc = words(text); return { aiLikely:avg, prediction:avg >= 75 ? 'Likely AI-generated' : avg >= 45 ? 'Mixed or AI-refined' : 'Likely human-written', confidence:wc >= 120 ? 'Stronger scan signal' : wc >= 80 ? 'Moderate scan signal' : 'Limited scan signal', breakdown, counts, sentences, stats:{words:wc, sentences:sentences.length, avgSentenceLength:Math.round(wc / total)} }; } function runDetector(){ const wc = words(state.toolText); if (!wc || wc < 40) return; if (isToolOverLimit('detector', wc)){ state.modal = limitModalFor('detector'); render(); return; } if (getDetectorRuns() >= 5){ state.modal = 'signup'; render(); return; } state.loading = true; state.copied = false; state.detector.processingStep = 0; render(); let step = 0; const timer = setInterval(() => { step += 1; state.detector.processingStep = Math.min(4, step); if (step >= 5){ clearInterval(timer); setDetectorRuns(getDetectorRuns() + 1); state.loading = false; state.detector.mobileTab = 'results'; state.detector.selectedSentence = 0; state.modal = 'tool-error'; persistToolHistory('detector', state.toolText, 'AI Detector report: ' + state.detector.resultData.aiLikely + '% likely AI'); render(); } }, 220); } function rewriterContent(){ const rows = [ {tag:'Rewrite Studio', t:'Rewrite text with real control', b:'Choose what to improve, clarity, tone, length, or structure, then rewrite without losing your original meaning.', cta:'Try a sample', act:'rewriter-sample', scroll:1, accent:'blue'}, {tag:'Shorten or expand', t:'Make it tighter or more detailed', b:'Cut a paragraph down to the essentials, or expand a thin draft into something fuller, in one click.', cta:'Rewrite text', act:'rewriter-sample', scroll:1, accent:'green'}, {tag:'Tone control', t:'Shift tone without starting over', b:'Move the same text between standard, formal, friendly, and confident so it fits the reader and the moment.', cta:'See tones', act:'tool-scroll-top', accent:'purple'}, {tag:'Compare versions', t:'Generate and compare rewrite versions', b:'Pro creates several versions side by side, clearer, more formal, shorter, so you can pick the best one.', cta:'Unlock versions', act:'rewriter-upgrade', accent:'pink'} ]; const shot = (accent) => `
${icon('rewrite')} Rewritten
`; const featureRows = rows.map((r, i) => `
${r.tag}

${r.t}

${r.b}

${shot(r.accent)}
`).join(''); const steps = [['Paste or upload text','Drop in a sentence, paragraph, or full draft.'],['Choose a rewrite goal','Pick clearer, shorter, expand, or a different tone.'],['Review and apply','Compare what changed, then copy or keep editing.']].map((s,i)=>`
${i+1}

${s[0]}

${s[1]}

`).join(''); const compare = [['Goal-based rewriting','Random synonym swaps'],['Keeps your meaning intact','Drifts off the original point'],['Tone, length, and clarity control','One generic output'],['Compare multiple versions','Single take only'],['One-click send to Humanizer','No next step offered']].map(c=>`
${icon('check')} ${c[0]}
${c[1]}
`).join(''); const personas = [['Students','Reshape rough drafts into clear, readable writing in your own voice.'],['Professionals','Tighten emails, reports, and proposals so they land the first time.'],['Content teams','Adjust tone and length across articles without rewriting from scratch.']].map(p=>`

${p[0]}

${p[1]}

`).join(''); const quotes = [['Saves me hours','I draft fast and let the rewriter sharpen clarity and tone.','S'],['Right tone, every time','Switching between formal and friendly is one click now.','P'],['Keeps the meaning','It improves the wording without changing what I actually said.','C']].map(q=>`
\u201C

${q[0]}

${q[1]}

${q[2]}Verified user
`).join(''); const guides = ['How to rewrite for clarity without losing meaning','Rewriter vs paraphraser vs humanizer','Choosing the right tone for your reader','When to shorten and when to expand'].map(g=>`Guide${g}Read more ${icon('arrow')}`).join(''); const tools = ORDER.map(k=>`${icon(ROUTES[k].icon)}${ROUTES[k].label}`).join(''); const faqs = [['Is the Advanced Rewriter free?','Yes. You can rewrite up to 125 words at a time with no account. Pro unlocks longer text, more modes, and multiple versions.'],['How is it different from the AI Humanizer?','The rewriter reshapes clarity, tone, length, and structure. The AI Humanizer focuses on making AI-generated text sound more natural.'],['Will it keep my meaning?','Yes. It rewrites the wording while preserving your original message.'],['Can it shorten or expand text?','Yes. Choose Shorter to tighten text or Expand to add detail.'],['Does it support other languages?','Yes, English plus 30+ other languages.']].map(f=>`
${f[0]}${icon('chevron')}

${f[1]}

`).join(''); return `
Free Advanced Rewriter

Rewrite text with control

Improve clarity, tone, structure, and length while keeping your meaning intact.

No signup to tryGoal-based rewritingKeeps your meaning
${featureRows}
Working with AI text?

Need it to sound more natural?

The Advanced Rewriter reshapes clarity, tone, and structure. If you are starting from AI-generated text and want it to read like a person wrote it, the AI Humanizer is built for exactly that.

Open the AI Humanizer

How to rewrite text in 3 steps

${steps}
${icon('rewrite')} Word Spinner
Basic spinners
${compare}

Unlock everything the Advanced Rewriter offers

Free
$0
  • ${icon('check')} Rewrite up to 125 words
  • ${icon('check')} Standard, Clearer, Shorter, Expand
  • ${icon('check')} Tone and length controls
  • ${icon('check')} Change summary

Who uses the Advanced Rewriter?

${personas}

Why writers love the rewriter

${quotes}
Browser extension

Rewrite anywhere with the Word Spinner extension

Rewrite for clarity and tone inside Google Docs, email, your CMS, and any web page, without copy-pasting.

${icon('rewrite')} Rewrite

Do it all with Word Spinner

${tools}

Advanced Rewriter FAQs

${faqs}
`; } function rewriterVariantPopover(idx, change){ var values = change && Array.isArray(change.variants) ? change.variants.map(function(v){ return String(v || '').replace(/\s+/g, ' ').trim(); }).filter(Boolean).slice(0, 5) : []; if (!values.length) return ''; var items = values.map(function(v){ return ``; }).join(''); return `${items}`; } function rewriterHighlight(text, changes, openIndex, enabled){ if (!enabled) return esc(text); var html = esc(text); (changes || []).forEach(function(c, idx){ if (!c.after) return; var token = esc(c.after); var i = html.indexOf(token); if (i < 0 || !token) return; var pop = idx === openIndex ? rewriterVariantPopover(idx, c) : ''; var mark = `${token}${pop}`; html = html.slice(0, i) + mark + html.slice(i + token.length); }); return html; } function rewriterStatsView(data){ var out = data.output || ''; var w = words(out); var chars = out.length; var sentences = (out.match(/[^.!?]+[.!?]+/g) || [out]).filter(function(s){return s.trim();}).length || 1; var readMin = Math.max(1, Math.round(w / 200)); var avgLen = Math.round(w / sentences); var cnt = (data.changes || []).length; return `
${icon('check')} RewrittenStatistics
${data.wordsBefore} → ${data.wordsAfter}
Words before and after${cnt} ${cnt===1?'change':'changes'} applied, meaning intact.
Words${w}
Characters${chars}
Sentences${sentences}
Reading time${readMin} min
Avg sentence${avgLen} words
ReadabilityClear
`; } function rewriterPage(){ const r = state.rewriter; const wc = words(state.toolText); const limit = ROUTES.rewriter.limit; const over = isToolOverLimit('rewriter', wc); const freeOver = !isPremiumUser() && over; const data = r.resultData; const freeModes = ['Standard','Clearer','Shorter','Expand','More formal']; const lockedModes = ['Academic','Creative']; const seg = freeModes.map(m => ``).join('') + lockedModes.map(m => ``).join(''); const LANGS = ['Auto detect','English (US)','English (UK)','Dutch','Spanish','French','German','Portuguese']; const langCtrl = `
${r.langOpen?`
${LANGS.map(l => ``).join('')}
`:''}
`; const rwSel = (field, label, values) => ``; const controls = `
${rwSel('tone','Tone',['Standard','Formal','Friendly','Confident'])}${rwSel('length','Length',['Shorter','Same length','More detailed'])}
Keep meaning
${icon('lock')} Strict ${icon('chevron')}
`; let body; if (freeOver){ body = humanizerLimitOverlay(wc); } else if (state.loading){ body = `

Rewriting your text…

`; } else if (data && r.showStats){ body = rewriterStatsView(data); } else if (data){ let html = rewriterHighlight(data.output, data.changes, r.openVariant, r.highlightChanges); const versions = `
${icon('lock')} Compare rewrite versions Pro
`; body = `

${html}

${versions}
`; } else { body = `
${icon('rewrite')}

Rewrite text with control

Paste text to rewrite it for better clarity, tone, flow, and length while keeping your meaning intact.

`; } const cnt = ((data && data.changes) || []).length; const dlMenu = r.downloadOpen ? `` : ''; const outRight = data && !state.loading && !freeOver ? `
${icon('check')} Rewritten
` : ''; const outFootbar = (data && !state.loading && !freeOver) ? `
${icon('rewrite')} ${esc(r.mode)}${data.wordsBefore} → ${data.wordsAfter} words${r.highlightChanges?`${cnt} ${cnt===1?'change':'changes'}`:''}
${dlMenu}
` : ''; const lsel = (label, val) => `
${icon('lock')} ${label}
${val}${icon('chevron')}
`; const ltog = (label, on) => `
${icon('lock')} ${label}
`; const settings = r.settingsOpen ? `
` : ''; const cta = (freeOver && wc <= absoluteLimitFor('rewriter')) ? 'Upgrade to rewrite full text' : 'Rewrite'; return `
${icon('rewrite')}

Advanced Rewriter

Rewrite for clarity, tone, length, and structure with meaning intact

${quotaNotice()}
${seg}
${langCtrl}
${settings} ${controls}
Your text
${wordCounterText('rewriter', wc)}
Result${outRight}
${body}
${outFootbar}
${rewriterContent()}${footer()}
`; } function toolPage(key){ const t = ROUTES[key]; const wc = words(state.toolText); const over = wc > t.limit; const hasText = state.toolText.trim().length > 0; return `
${icon(t.icon)}

${t.label}

${!hasText?`
`:''}
${wc} / ${t.limit} words
${resultHtml(key)}
${toolContent(key)}${footer()}
`; } function resultHtml(key){ const t = ROUTES[key]; if (state.loading) return `

Working on it...

${esc(t.action)} is running in this frontend preview.

`; if (!state.result) return `
${icon(t.icon)}

${esc(t.emptyTitle)}

${esc(t.emptyBody)}

`; if (t.resultType === 'score') return `
88%
Likely human-written
Prediction across 1 scan

Human 88%AI 12%

Humanize flagged text →
`; return `
✓ ${esc(t.action.replace('Check Grammar','Grammar checked'))}
${esc(state.result)}
Open AI Detector
`; } function humanizerContent(){ const rows = [ {tag:'Browser extension', t:'Humanize AI text right where you write', b:'Add the Word Spinner extension and humanize AI output the moment it appears - inside ChatGPT, Gmail, Google Docs, and anywhere you type.', cta:'Add to Chrome', href:'https://chromewebstore.google.com/detail/word-spinner-extension/cpanhcdlkmibhbgpajhomahnflagaopa', accent:'blue'}, {tag:'AI signal', t:'Check how human your text sounds', b:'Run your draft through the built-in AI Detector to see a clear human-vs-AI signal before you publish, submit, or send.', cta:'Try the AI Detector', href:'/ai-detector', accent:'pink'}, {tag:'Natural rewrite', t:'A best-in-class humanizing engine', b:'Word Spinner reworks robotic phrasing into natural rhythm and flow while keeping your meaning intact - not just swapping synonyms.', cta:'Humanize free', act:'humanizer-clear', scroll:1, accent:'green'}, {tag:'Multilingual', t:'Humanize AI text in 30+ languages', b:'From English and Dutch to Spanish, French, and German, get fluent, human writing that still sounds like you.', cta:'Try Humanizer', act:'tool-scroll-top', accent:'purple'} ]; const shot = (accent) => `
${icon('sparkles')} Humanized
`; const featureRows = rows.map((r, i) => `
${r.tag}

${r.t}

${r.b}

${r.href?`${r.cta}`:``}
${shot(r.accent)}
`).join(''); const steps = [['Paste your AI text','Drop in content from ChatGPT, Claude, Gemini, or any AI tool.'],['Humanize instantly','Pick a tone, press Humanize, and watch the changes highlight live.'],['Make it your own','Click any highlighted word to swap in a better fit, then copy or download.']].map((s,i)=>`
${i+1}

${s[0]}

${s[1]}

`).join(''); const compare = [['Reads naturally, keeps your meaning','Often garbles meaning or adds errors'],['Live highlights of every change','No visibility into what changed'],['Built-in AI detection signal','No way to check the result'],['30+ languages, 4 writing tones','English-only, one generic style'],['Works inside your browser','Copy-paste only']].map(c=>`
${icon('check')} ${c[0]}
${c[1]}
`).join(''); const personas = [['Students','Make essays and assignments read in your own natural voice.'],['Professionals','Turn AI drafts of emails and reports into polished, human writing.'],['Content creators','Keep blogs and posts on-brand, engaging, and clearly human.']].map(p=>`

${p[0]}

${p[1]}

`).join(''); const quotes = [['It\u2019s an awesome tool','Word Spinner turns my AI drafts into writing that finally sounds like me.','M'],['Beats every alternative','The live highlights make it obvious what changed. Nothing else does this.','S'],['Simple and powerful','Paste, humanize, copy. It just works, every single time.','J']].map(q=>`
\u201C

${q[0]}

${q[1]}

${q[2]}Verified user
`).join(''); const guides = ['How to make AI writing sound human','AI Humanizer vs paraphraser: what\u2019s the difference?','Using AI responsibly in school and work','10 phrases that make text sound AI-generated'].map(g=>`Guide${g}Read more ${icon('arrow')}`).join(''); const tools = ORDER.map(k=>`${icon(ROUTES[k].icon)}${ROUTES[k].label}`).join(''); const faqs = [['Is the AI Humanizer free?','Yes. You can humanize up to 125 words at a time with no account. Sign up free for higher limits and saved history.'],['Will it change my meaning?','No. It rewrites phrasing and tone while keeping what your text actually says intact.'],['Will my text read naturally?','Yes - it rewrites robotic phrasing into natural rhythm and flow while keeping your meaning intact. It is not a way to misrepresent authorship, so please use it responsibly.'],['Which languages are supported?','English, Dutch, Spanish, French, German, Portuguese, and 25+ more.'],['Is there a browser extension?','Yes - humanize text directly inside ChatGPT, Gmail, Docs, and more with the Word Spinner extension.']].map(f=>`
${f[0]}${icon('chevron')}

${f[1]}

`).join(''); return `
Free AI Humanizer

The free Humanize AI tool that actually sounds human

Turn AI-generated text into clear, natural writing - in seconds, in your browser, on brand.

No signup to try30+ languagesKeeps your meaning
${featureRows}
Responsible AI

Write naturally, write responsibly

Word Spinner helps your writing read clearly and naturally. It won\u2019t guarantee any detector result, and it\u2019s not a way to pass off others\u2019 work as your own. Always follow your school or workplace policies on AI use.

Read our responsible-AI guide

How to humanize AI text in 3 steps

${steps}
${icon('sparkles')} Word Spinner
Basic "make it natural" tools
${compare}

Unlock everything the AI Humanizer offers

Free
$0
  • ${icon('check')} Humanize up to 125 words
  • ${icon('check')} Standard & Casual tones
  • ${icon('check')} AI detection signal
  • ${icon('check')} 6 languages

How can the AI Humanizer help you?

${personas}

Why writers love the AI Humanizer

${quotes}
Browser extension

Humanize anywhere with the Word Spinner extension

Humanize, detect, and rewrite without leaving the page - inside ChatGPT, Gmail, Google Docs, LinkedIn, and any text box.

${icon('sparkles')} Humanize

Do it all with Word Spinner

${tools}

AI Humanizer FAQs

${faqs}
`; } function detectorContent(){ const rows = [ {tag:'Browser extension', t:'Detect AI text anywhere you read', b:'Add the Word Spinner extension and scan for AI right inside Google Docs, email, your LMS, and any web page - no copy-paste.', cta:'Add to Chrome', href:'https://chromewebstore.google.com/detail/word-spinner-extension/cpanhcdlkmibhbgpajhomahnflagaopa', accent:'pink'}, {tag:'Sentence-level', t:'See exactly which sentences look AI', b:'Every sentence is scored and highlighted, so you know precisely where AI-generated phrasing appears - not just one vague number.', cta:'Scan a sample', act:'detector-sample', scroll:1, accent:'blue'}, {tag:'Clear signal', t:'A breakdown you can actually explain', b:'Get an at-a-glance percentage plus a human / AI-refined / AI-generated split you can show a student or client with confidence.', cta:'Try the detector', act:'detector-sample', scroll:1, accent:'green'}, {tag:'Model coverage', t:'Built for today\u2019s AI models', b:'Tuned for content from ChatGPT, Claude, Gemini, and DeepSeek - including lightly edited, AI-refined writing.', cta:'See how it works', act:'tool-scroll-top', accent:'purple'} ]; const shot = (accent) => `
${icon('shield')} Scanned
`; const featureRows = rows.map((r, i) => `
${r.tag}

${r.t}

${r.b}

${shot(r.accent)}
`).join(''); const steps = [['Paste or upload text','Drop in a draft, essay, or article - or scan a page with the extension.'],['Run the scan','Get an overall AI likelihood in just a few seconds.'],['Review the evidence','Read the highlighted sentences and decide with full context.']].map((s,i)=>`
${i+1}

${s[0]}

${s[1]}

`).join(''); const compare = [['Sentence-level highlighted evidence','One overall number only'],['Human / AI-refined / AI split','Binary AI-or-not guess'],['Tuned for the newest AI models','Outdated on current tools'],['One-click send to the Rewriter','No next step offered'],['Scans inside your browser','Copy-paste only']].map(c=>`
${icon('check')} ${c[0]}
${c[1]}
`).join(''); const personas = [['Educators','Review student work for AI with clear, explainable evidence.'],['Editors & content teams','QA drafts and freelance submissions before they go live.'],['Recruiters & publishers','Check applications and submissions for authenticity.']].map(p=>`

${p[0]}

${p[1]}

`).join(''); const quotes = [['Finally, real evidence','The sentence highlights let me show exactly what got flagged and why.','E'],['Fast and clear','One scan and I know which drafts need a closer human look.','R'],['Part of our QA now','We run every submission through it before anything is published.','P']].map(q=>`
\u201C

${q[0]}

${q[1]}

${q[2]}Verified user
`).join(''); const guides = ['How AI detectors actually work','Understanding false positives & false negatives','Using AI detection fairly in the classroom','Human, AI, or AI-refined: what\u2019s the difference?'].map(g=>`Guide${g}Read more ${icon('arrow')}`).join(''); const tools = ORDER.map(k=>`${icon(ROUTES[k].icon)}${ROUTES[k].label}`).join(''); const faqs = [['Is the AI Detector free?','Yes - scan up to 125 words per check with no account. Pro unlocks unlimited words, Deep Scan, and document uploads.'],['How accurate is it?','It gives a strong probabilistic signal across current AI models, but no detector is 100% accurate. Always use your judgment.'],['What do the categories mean?','Human-written, AI-refined (human text edited by AI), and AI-generated.'],['Does it support multiple languages?','Yes - English plus 30+ other languages.'],['Should I rely on it alone?','No. Treat results as one signal, never the only basis for academic or hiring decisions.']].map(f=>`
${f[0]}${icon('chevron')}

${f[1]}

`).join(''); return `
Free AI Detector

Catch AI-generated text with confidence

Check whether writing is human, AI-generated, or somewhere in between - with sentence-level evidence, in your browser.

No signup to trySentence-level evidenceLatest AI models
${featureRows}
Responsible AI

Detection is a signal, not a verdict

Word Spinner flags text that may be AI-generated and shows you the evidence. AI detection is probabilistic, so never rely on it alone to make decisions that affect someone\u2019s career or academic standing.

Read our responsible-AI guide

How to detect AI text in 3 steps

${steps}
${icon('shield')} Word Spinner
Basic AI detectors
${compare}

Unlock everything the AI Detector offers

Free
$0
  • ${icon('check')} Scan up to 125 words
  • ${icon('check')} Quick Scan
  • ${icon('check')} Human-vs-AI signal
  • ${icon('check')} Sentence highlights

Who uses the AI Detector?

${personas}

Why teams trust the AI Detector

${quotes}
Browser extension

Detect AI on any page with the Word Spinner extension

Scan text for AI inside Google Docs, your LMS, email, and any web page - without copy-pasting a thing.

${icon('shield')} Scan

Do it all with Word Spinner

${tools}

AI Detector FAQs

${faqs}
`; } function toolContent(key){ const t = ROUTES[key]; const c = CONTENT[key]; const featureItems = c.features.map(([title, body], index) => `
${index + 1}

${esc(title)}

${esc(body)}

`).join(''); const geoItems = c.geo.map(([title, body]) => `

${esc(title)}

${esc(body)}

`).join(''); return `

${esc(t.label)}

${esc(c.lead)}

${featureItems}
${toolVisual(key)}

How to use ${esc(t.label)}

Three clear steps, built into the same Word Spinner editor.

1

Paste your text

Drop content into the editor above or start from sample text.

2

Choose your settings

Use modes, language controls, and limits that match the task.

3

Run ${esc(t.action)}

Review the result, copy it, or continue with another Word Spinner tool.

${esc(c.geoTitle)}

These answer-style sections help both search engines and AI answer engines understand what this tool does, who it helps, and when to use it.

${geoItems}

Related tools

Keep working without leaving the shared shell.

${ORDER.filter(x=>x!==key).slice(0,3).map(other=>{const ot=ROUTES[other];return `
${icon(ot.icon)}

${ot.label}

${esc(ot.emptyBody)}

`}).join('')}
`; } function toolVisual(key){ const t = ROUTES[key]; const c = CONTENT[key]; return `
${icon(t.icon)} ${esc(c.visual)}
`; } function modal(){ if (!state.modal) return ''; let copy = (state.account && state.account.loggedIn) ? ['Upgrade to full access','Your free account includes up to 125 words per run. Upgrade for higher limits and premium tools.'] : ['Log in or create your free account','Use a real Word Spinner account to save history and unlock higher limits.']; let extra = (state.account && state.account.loggedIn) ? `
Upgrade to full access
` : `
Log inCreate free account
`; if (state.modal === 'limit') copy = (state.account && state.account.loggedIn) ? ['You reached the free word limit','Upgrade to full access to process longer text with higher limits.'] : ['You reached the free word limit','Log in or create a free account to process longer text with higher limits.']; if (state.modal === 'premium') copy = (state.account && state.account.loggedIn) ? ['Unlock premium modes','Upgrade to full access to use Formal, Academic, and advanced controls.'] : ['Unlock premium modes','Log in or create an account to use Formal, Academic, and advanced controls.']; if (state.modal === 'premium-limit') { const max = premiumMaxWords(); copy = ['Maximum words reached', max === null ? ('Input is limited to ' + ((typeof absoluteLimitFor==='function')?absoluteLimitFor(state.route):2000) + ' words per run. Shorten the input or continue with the first part.') : 'Your account has ' + max + ' words remaining. Shorten the text and try again.']; extra = '
'; } if (state.modal === 'humanizer-limit') { copy = ['Humanize longer text','Free users can humanize up to 125 words at a time. Upgrade to process longer essays, emails, articles, and business documents.']; extra = ``; } return `

${copy[0]}

${copy[1]}

${extra}${/close-modal|tool-keep-editing/.test(extra)?'':'
'}
`; } function generateHumanizerResult(text){ const detected = detectHumanizer(text); const original = text.trim(); const preview = buildHumanizerPreview(original); const output = preview.output; return { original, output, detected, changes:preview.changes }; } function rewritePreview(text){ return buildHumanizerPreview(text).output; } function buildHumanizerPreview(text){ if (!text.trim()) return {output:'', changes:[]}; let out = text.trim(); const changes = []; const replacements = [ [/In today\'s fast-paced digital landscape,?/i, 'These days,', 'Less robotic', 'Replaced a generic AI opener with a more natural start.'], [/rapidly evolving technological landscape/i, 'changing tech world', 'Simplified phrasing', 'Made an inflated phrase easier to read.'], [/leveraging cutting-edge solutions/i, 'using practical tools', 'More natural wording', 'Changed corporate phrasing into everyday language.'], [/is paramount/i, 'matters', 'Cleaner rhythm', 'Made a formal phrase more direct.'], [/optimize operational efficiency/i, 'make work easier', 'Clearer meaning', 'Kept the idea while making it more direct.'], [/drive sustainable growth/i, 'build growth that lasts', 'Meaning preserved', 'Kept the business meaning in simpler words.'], [/transformative force/i, 'major influence', 'Less inflated', 'Reduced over-polished wording.'], [/enhance/i, 'improve', 'Simpler word choice', 'Used a more common word.'], [/utilize/i, 'use', 'Simpler word choice', 'Used a more direct verb.'] ]; replacements.forEach(([pattern, value, label, reason]) => { const match = out.match(pattern); if (!match) return; out = out.replace(pattern, value); changes.push({label, before:match[0], after:value, reason}); }); if (!changes.length){ const first = text.trim().split(/\s+/).slice(0, Math.min(8, words(text))).join(' '); out = 'Here is a more natural version: ' + out.replace(/\s+/g, ' '); changes.push({label:'More natural rhythm', before:first, after:'Here is a more natural version', reason:'Added a clearer, more conversational lead-in for the preview.'}); } return {output:out, changes}; } function runHumanizer(){ const wc = words(state.toolText); if (!wc) return; if (isToolOverLimit('humanizer', wc)){ state.modal = limitModalFor('humanizer'); render(); return; } state.loading = true; state.copied = false; state.humanizer.processingStep = 0; state.humanizer.resultData = null; render(); let step = 0; const timer = setInterval(() => { step += 1; state.humanizer.processingStep = Math.min(4, step); if (step >= 5){ clearInterval(timer); setDailyRuns(getDailyRuns(state.route) + 1, state.route); state.loading = false; state.humanizer.resultTab = 'humanized'; state.humanizer.mobileTab = 'result'; state.modal = 'tool-error'; persistToolHistory('humanizer', state.toolText, state.humanizer.resultData && state.humanizer.resultData.output); render(); } }, 260); } var REWRITER_SAMPLE = 'In today\u2019s rapidly evolving digital landscape, organizations must leverage innovative solutions to streamline their workflows and maximize operational efficiency. By adopting a comprehensive strategy, teams can unlock new opportunities and position themselves for sustained long-term growth.'; function generateRewriterResult(text){ var original = (text || '').trim(); var preview = buildHumanizerPreview(original); var output = preview.output || original; var changes = preview.changes || []; var mode = state.rewriter.mode; if (mode === 'Shorter'){ var parts = output.split(/(?<=[.!?])\s+/).filter(Boolean); if (parts.length > 1) output = parts.slice(0, Math.max(1, Math.ceil(parts.length * 0.6))).join(' '); } var wb = words(original); var wa = words(output); return { original:original, output:output, changes:changes, wordsBefore:wb, wordsAfter:wa }; } function runRewriter(){ var wc = words(state.toolText); if (!wc) return; if (isToolOverLimit('rewriter', wc)){ state.modal = limitModalFor('rewriter'); render(); return; } state.loading = true; state.copied = false; state.rewriter.processingStep = 0; state.rewriter.resultData = null; state.rewriter.openVariant = null; render(); var step = 0; var timer = setInterval(function(){ step += 1; state.rewriter.processingStep = Math.min(4, step); if (step >= 5){ clearInterval(timer); setDailyRuns(getDailyRuns(state.route) + 1, state.route); state.loading = false; state.modal = 'tool-error'; persistToolHistory('rewriter', state.toolText, state.rewriter.resultData && state.rewriter.resultData.output); render(); } }, 240); } function runTool(){ if (state.route === 'humanizer'){ runHumanizer(); return; } if (state.route === 'detector'){ runDetector(); return; } if (state.route === 'rewriter'){ runRewriter(); return; } const key = state.route; const t = ROUTES[key]; const wc = words(state.toolText); if (!wc) return; if (isToolOverLimit(key, wc)){ state.modal = limitModalFor(key); render(); return; } state.loading = true; state.result = null; render(); setTimeout(() => { state.loading = false; state.modal = 'tool-error'; state.result = null; setDailyRuns(getDailyRuns(key) + 1, key); render(); }, 850); } function hydrateSeed(){ const seed = sessionStorage.getItem('ws-seed-text'); const seedTool = sessionStorage.getItem('ws-seed-tool'); if (seed && state.route !== 'home' && !state.toolText && (function(st){if(!st)return true;var k=function(v){v=String(v||'');return /rewriter/.test(v)?'rewriter':/grammar/.test(v)?'grammar':/humaniz|remover/.test(v)?'humanizer':/detector/.test(v)?'detector':/summ/.test(v)?'summarizer':/translate/.test(v)?'translate':/chat/.test(v)?'chat':/email/.test(v)?'email':v;};return k(st)===k(location.pathname);})(seedTool)){ state.toolText = seed; sessionStorage.removeItem('ws-seed-text'); sessionStorage.removeItem('ws-seed-tool'); } } function relTime(ts){ var s=(Date.now()-ts)/1000; if(s<60)return 'just now'; if(s<3600)return Math.floor(s/60)+'m ago'; if(s<86400)return Math.floor(s/3600)+'h ago'; return Math.floor(s/86400)+'d ago'; } function historyPanel(){ var items=getHistory(state.route); var list = state.historyLoading ? '
'+icon('history')+'

Loading history

Recent ' + esc(historyToolLabel(state.route)) + ' runs will show here.

' : (items.length ? items.map(function(h){ var r=ROUTES[h.tool]; var col=r?r.color:'#00A3FF'; var lbl=historyToolLabel(h.tool); return ''; }).join('') : '
'+icon('history')+'

No history yet

Your recent ' + esc(historyToolLabel(state.route)) + ' runs will show up here.

'); return '
'; } function render(){ state.route = routeFromPath(); const compact = window.matchMedia && window.matchMedia('(max-width: 980px)').matches; const animateDrawer = state._animateDrawer; state._animateDrawer = false; if (state._lastRoute !== state.route){ state._lastRoute = state.route; var _sp = null; try { _sp = localStorage.getItem('ws-sidebar'); } catch(e){} state.sidebarOpen = compact ? false : (_sp === null ? true : _sp === '1'); } const active = state.route === 'home' ? null : state.route; const page = state.route === 'home' ? homePage() : state.route === 'humanizer' ? humanizerPage() : state.route === 'detector' ? detectorPage() : state.route === 'rewriter' ? rewriterPage() : toolPage(state.route); document.body.innerHTML = `
${topbar()}${drawer(active)}${page}${modal()}
`; if (state.historyOpen) document.body.insertAdjacentHTML('beforeend', historyPanel()); const homeInput = document.querySelector('#home-smart-input'); if (homeInput && document.activeElement !== homeInput) homeInput.selectionStart = homeInput.selectionEnd = homeInput.value.length; updateHumanizerLive(); updateDetectorLive(); } function navigateTool(key){ if (state.homeText.trim()) { sessionStorage.setItem('ws-seed-text', state.homeText.trim()); sessionStorage.setItem('ws-seed-tool', hrefFor(key)); } location.href = hrefFor(key); } function updateHumanizerLive(){ if (state.route !== 'humanizer') return; const wc = words(state.toolText); const over = isToolOverLimit('humanizer', wc); const wordCounter = document.querySelector('.ws-humanizer-word-count'); const firstFree = document.querySelector('.ws-first-free'); const primary = document.querySelector('[data-action="humanizer-run"]'); const summary = document.querySelector('.ws-detected-summary'); const freeOver = !isPremiumUser() && over; if (wordCounter){ wordCounter.textContent = wordCounterText('humanizer', wc); wordCounter.classList.toggle('over', over); var _fp=wordCounter.parentElement; if(_fp){var _fx=_fp.querySelector('.ws-limit-flag'); if(_fx)_fx.remove(); var _fh=limitFlag('humanizer', wc); if(_fh)wordCounter.insertAdjacentHTML('afterend', _fh);} } if (firstFree) firstFree.hidden = !freeOver; if (primary && !state.loading) primary.textContent = over ? 'Upgrade' : 'Humanize'; if (summary){ const d = detectHumanizer(state.toolText); summary.textContent = `Detected: ${state.humanizer.autoDetect ? `${d.template} · ${d.level} · ${d.tone}` : 'Manual settings'}`; } } function updateDetectorLive(){ if (state.route !== 'detector') return; const wc = words(state.toolText); const over = isToolOverLimit('detector', wc); const count = document.querySelector('.ws-detector-count'); const status = document.querySelector('.ws-detector-status'); const primary = document.querySelector('[data-action="detector-run"]'); if (count){ count.textContent = wordCounterText('detector', wc); count.classList.toggle('over', over); var _fp=count.parentElement; if(_fp){var _fx=_fp.querySelector('.ws-limit-flag'); if(_fx)_fx.remove(); var _fh=limitFlag('detector', wc); if(_fh)count.insertAdjacentHTML('afterend', _fh);} } if (status){ const msg = (wc > 0 && wc < 40) ? 'Add at least 40 words to scan' : ''; status.textContent = msg; status.classList.toggle('warn', wc > 0 && wc < 40); } if (primary){ primary.disabled = wc < 40 || (!isPremiumUser() && wc > ROUTES.detector.limit) || state.loading; } } document.addEventListener('click', e => { const actionEl = e.target.closest('[data-action]'); const action = actionEl ? actionEl.dataset.action : ''; if (action === 'close-modal'){ state.modal = null; render(); return; } if (e.target.closest('[data-stop]') && !actionEl) return; if (state.account.open && !e.target.closest('.ws-acct')){ state.account.open = false; render(); } if (state.humanizer.openVariant != null && !e.target.closest('.wsc-variants') && !e.target.closest('.wsc-hl')){ state.humanizer.openVariant = null; render(); } if (state.rewriter && state.rewriter.openVariant != null && !e.target.closest('.wsc-variants') && !e.target.closest('.wsc-hl')){ state.rewriter.openVariant = null; render(); } if (state.humanizer.downloadOpen && !e.target.closest('.wsc-dl')){ state.humanizer.downloadOpen = false; render(); } if (state.detector.downloadOpen && !e.target.closest('.wsc-dl')){ state.detector.downloadOpen = false; render(); } if (state.humanizer.langOpen && !e.target.closest('.wsc-lang')){ state.humanizer.langOpen = false; render(); } if (!actionEl) return; if (action && !(state.account && state.account.loggedIn) && /-run$/.test(action)) { var _histKey = action === 'tool-run' ? state.route : action.slice(0, -4); if (_histKey && state.toolText && String(state.toolText).trim()) { try { recordHistory(_histKey, state.toolText); } catch (_e) {} } } if (action === 'tool-scroll-top'){ e.preventDefault(); window.scrollTo({top:0,behavior:'smooth'}); return; } if (action === 'tool-shorten-run'){ e.preventDefault(); (function(){ var key = (state.route==='humanizer'||state.route==='rewriter'||state.route==='detector'||state.route==='grammar') ? state.route : null; if(!key){ state.modal=null; render(); return; } var lim = (typeof absoluteLimitFor==='function') ? absoluteLimitFor(key) : 2000; var keep = lim; var m = wordMatches(state.toolText); if (m.length > keep){ var cut = m[keep-1]; state.toolText = state.toolText.slice(0, cut.index + cut[0].length); } state.modal = null; render(); if (key==='rewriter' && typeof runRewriter==='function') runRewriter(); else if (key==='humanizer' && typeof runHumanizer==='function') runHumanizer(); else if (key==='detector' && typeof runDetector==='function') runDetector(); else if (key==='grammar' && typeof runGrammar==='function') runGrammar(); })(); return; } if (action === 'tool-keep-editing'){ e.preventDefault(); state.modal = null; render(); var __ed=document.querySelector('.wsc-editor, .ws-editor, .ws-humanizer-editor, textarea'); if(__ed) __ed.focus(); window.scrollTo({top:0,behavior:'smooth'}); return; } if (actionEl.dataset && actionEl.dataset.scrolltop){ setTimeout(function(){ window.scrollTo({top:0,behavior:'smooth'}); }, 30); } if (action === 'toggle-theme'){ const dark = !document.documentElement.classList.contains('ws-dark'); try{localStorage.setItem('ws-theme', dark?'dark':'light');}catch(e){} if(window.__WS_APPLY_THEME) window.__WS_APPLY_THEME(); else document.documentElement.classList.toggle('ws-dark', dark); if(state.acct) state.acct.theme = dark?'dark':'light'; if(state.account) state.account.open = state.account.open; render(); } if (action === 'toggle-sidebar'){ state.sidebarOpen = !state.sidebarOpen; state._animateDrawer = true; try{localStorage.setItem('ws-sidebar', state.sidebarOpen?'1':'0');}catch(e){} render(); } if (action === 'open-sidebar'){ state.sidebarOpen = true; state._animateDrawer = true; try{localStorage.setItem('ws-sidebar','1');}catch(e){} render(); } if (action === 'close-sidebar'){ state.sidebarOpen = false; state._animateDrawer = true; try{localStorage.setItem('ws-sidebar','0');}catch(e){} render(); } if (action === 'open-history'){ e.preventDefault(); state.historyOpen = true; var _os=document.querySelector('.ws-hist-scrim'); if(_os)_os.remove(); var _od=document.querySelector('.ws-hist-drawer'); if(_od)_od.remove(); document.body.insertAdjacentHTML('beforeend', historyPanel()); loadServerHistory(state.route); return; } if (action === 'close-history'){ state.historyOpen = false; render(); } if (action === 'hist-open'){ var _h=getHistory().find(function(x){return x.id===actionEl.dataset.id;}); if(_h){ sessionStorage.setItem('ws-seed-text', _h.source || _h.text || _h.output); sessionStorage.setItem('ws-seed-tool', hrefFor(_h.tool)); location.href = hrefFor(_h.tool); } return; } if (action === 'login'){ e.preventDefault(); location.href = '/login'; } if (action === 'detector-refine'){ e.preventDefault(); try{ sessionStorage.setItem('ws-seed-text', state.toolText || ''); sessionStorage.setItem('ws-autorun', 'rewriter'); }catch(_e){} location.href = '/advanced-rewriter'; return; } if (action === 'humanizer-upgrade'){ e.preventDefault(); if ((state.account && state.account.loggedIn)){ location.href = '/pricing'; } else { location.href = '/login'; } } if (action === 'premium-mode'){ state.modal = 'premium'; render(); } if (action === 'close-modal'){ state.modal = null; render(); } if (action === 'select-home-tool'){ state.homeTool = actionEl.dataset.tool; state.selectedTool = actionEl.dataset.tool; render(); } if (action === 'home-deselect'){ state.homeTool = null; render(); } if (action === 'home-sample'){ state.homeText = ROUTES[state.selectedTool].sample; state.homeExpanded = true; render(); } if (action === 'home-run'){ if (state.homeTool){ try{ if(state.homeText && state.homeText.trim()) sessionStorage.setItem('ws-autorun', state.homeTool); }catch(_e){} navigateTool(state.homeTool); } else { var hw = state.homeText.trim() ? state.homeText.trim().split(/\s+/).filter(Boolean).length : 0; if (hw < 5){ state.homeMinWarn = true; render(); document.querySelector('#home-smart-input')?.focus(); return; } state.homeMinWarn = false; if (state.homeText.trim()) sessionStorage.setItem('ws-chat-seed', state.homeText.trim()); location.href = '/chat'; } } if (action === 'prompt'){ state.homeText = actionEl.dataset.text || ''; state.homeExpanded = true; render(); } if (action === 'suite-tab'){ state.suiteTab = actionEl.dataset.cat; const _sec=document.querySelector('.ws-suite'); if(_sec){ _sec.outerHTML = fullSuite(); } else { render(); } } if (action === 'tool-sample'){ state.toolText = ROUTES[state.route].sample; state.result = null; render(); } if (action === 'tool-run'){ runTool(); } if (action === 'detector-run'){ runDetector(); } if (action === 'detector-sample'){ state.toolText = DETECTOR_SAMPLES[actionEl.dataset.sample] || DETECTOR_SAMPLES.ChatGPT; state.detector.resultData = null; state.detector.mobileTab = 'input'; render(); } if (action === 'detector-clear'){ state.toolText = ''; state.detector.resultData = null; state.detector.mobileTab = 'input'; render(); } if (action === 'detector-highlight'){ state.detector.selectedSentence = parseInt(actionEl.dataset.index || '0', 10) || 0; state.detector.mobileTab = 'highlights'; render(); } if (action === 'detector-mobile-tab'){ state.detector.mobileTab = actionEl.dataset.tab; render(); } if (action === 'detector-rescan'){ runDetector(); } if (action === 'detector-download-toggle'){ state.detector.downloadOpen = !state.detector.downloadOpen; render(); } if (action === 'detector-download'){ var fmt=(actionEl.dataset.format||'TXT').toLowerCase(); var d=state.detector.resultData; var rep=(d?('AI Detector report: '+d.aiLikely+'% likely AI'):'AI Detector report')+String.fromCharCode(10)+String.fromCharCode(10)+(state.toolText||''); try{ wsDownloadReport('ai-detector-report', (d ? ('AI Detector report: ' + d.aiLikely + '% likely AI') : 'AI Detector report'), (state.toolText || ''), (actionEl.dataset.format || 'TXT')); }catch(err){} state.detector.downloadOpen=false; render(); } if (action === 'detector-premium'){ if ((state.account && state.account.loggedIn)){ location.href = '/pricing'; } else { location.href = '/login'; } } if (action === 'detector-copy-report'){ flashCopied(actionEl); } if (action === 'detector-humanize'){ const flagged = state.detector.resultData ? state.detector.resultData.sentences.filter(s => s.labelKey !== 'human').map(s => s.text).join(' ') : state.toolText; sessionStorage.setItem('ws-seed-text', flagged || state.toolText); sessionStorage.setItem('ws-seed-tool', hrefFor('humanizer')); location.href = hrefFor('humanizer'); } if (action === 'detector-paste'){ const input = document.querySelector('.ws-detector-editor'); if (navigator.clipboard && navigator.clipboard.readText){ navigator.clipboard.readText().then(value => { state.toolText = (function(v){ try { if (typeof effectiveLimitFor==='function'){ var c=effectiveLimitFor(state.route); if (c!==Infinity){ var m=wordMatches(v); if (m.length>c){ var q=m[c-1]; v=v.slice(0,q.index+q[0].length); if(!state._capWarned){ state._capWarned=true; if (state.route==='detector' || (typeof isPremiumUser==='function'&&isPremiumUser())){ setTimeout(function(){ if(window.WSV2ToolBridge&&WSV2ToolBridge.showToolToast) WSV2ToolBridge.showToolToast('Pasted text trimmed to the maximum of '+c+' words', true); }, 150); } else if(!state.modal){ state.modal=(typeof limitModalFor==='function')?limitModalFor(state.route):'limit'; } } } else if(state._capWarned&&m.length { if (input) input.focus(); }); } else if (input) input.focus(); } if (action === 'rewriter-run'){ runRewriter(); } if (action === 'rewriter-mode'){ state.rewriter.mode = actionEl.dataset.mode || 'Standard'; render(); } if (action === 'rewriter-upgrade'){ e.preventDefault(); if ((state.account && state.account.loggedIn)){ location.href = '/pricing'; } else { location.href = '/login'; } } if (action === 'rewriter-highlight-toggle'){ state.rewriter.highlightChanges = !state.rewriter.highlightChanges; state.rewriter.openVariant = null; render(); } if (action === 'rewriter-stats-toggle'){ state.rewriter.showStats = !state.rewriter.showStats; render(); } if (action === 'rewriter-settings-toggle'){ state.rewriter.settingsOpen = !state.rewriter.settingsOpen; render(); } if (action === 'rewriter-settings-close'){ state.rewriter.settingsOpen = false; render(); } if (action === 'rewriter-variant'){ const idx = parseInt(actionEl.dataset.index, 10); state.rewriter.openVariant = state.rewriter.openVariant === idx ? null : idx; render(); } if (action === 'rewriter-pick-variant'){ const idx = parseInt(actionEl.dataset.index, 10); const val = actionEl.dataset.value; const data = state.rewriter.resultData; if (data && data.changes[idx]){ const old = String(data.changes[idx].selected || data.changes[idx].after || ''); let pos = -1; { const out = String(data.output || ''); let cursor = 0; for (let j = 0; j <= idx; j++){ const t = data.changes[j] && String(data.changes[j].selected || data.changes[j].after || ''); if (!t){ if (j === idx){ pos = -1; } continue; } let k = out.indexOf(t, cursor); if (k < 0){ if (j === idx){ pos = out.indexOf(t); } continue; } if (j === idx){ pos = k; break; } cursor = k + t.length; } } if (pos >= 0 && old) data.output = data.output.slice(0, pos) + val + data.output.slice(pos + old.length); data.changes[idx].after = val; data.changes[idx].selected = val; } state.rewriter.openVariant = null; render(); } if (action === 'rewriter-lang-toggle'){ state.rewriter.langOpen = !state.rewriter.langOpen; render(); } if (action === 'rewriter-lang-pick'){ saveToolLanguage('rewriter', actionEl.dataset.lang || state.rewriter.language); state.rewriter.langOpen = false; render(); } if (action === 'rewriter-sample'){ state.toolText = REWRITER_SAMPLE; state.rewriter.resultData = null; render(); } if (action === 'rewriter-clear'){ state.toolText = ''; state.rewriter.resultData = null; render(); } if (action === 'rewriter-copy'){ const text = (state.rewriter.resultData && state.rewriter.resultData.output) || ''; if (navigator.clipboard && text) navigator.clipboard.writeText(text).catch(()=>{}); flashCopied(actionEl); } if (action === 'rewriter-download-toggle'){ state.rewriter.downloadOpen = !state.rewriter.downloadOpen; render(); } if (action === 'rewriter-download'){ const fmt = (actionEl.dataset.format || 'TXT').toLowerCase(); const text = (state.rewriter.resultData && state.rewriter.resultData.output) || ''; try { wsDownloadReport('rewritten-text', 'Rewritten text', text, (actionEl.dataset.format || 'TXT')); } catch(err){} state.rewriter.downloadOpen = false; render(); } if (action === 'rewriter-humanize'){ const text = (state.rewriter.resultData && state.rewriter.resultData.output) || state.toolText; sessionStorage.setItem('ws-seed-text', text); sessionStorage.setItem('ws-seed-tool', hrefFor('humanizer')); location.href = hrefFor('humanizer'); } if (action === 'rewriter-paste'){ const input = document.querySelector('.wsc-editor'); if (navigator.clipboard && navigator.clipboard.readText){ navigator.clipboard.readText().then(value => { state.toolText = (function(v){ try { if (typeof effectiveLimitFor==='function'){ var c=effectiveLimitFor(state.route); if (c!==Infinity){ var m=wordMatches(v); if (m.length>c){ var q=m[c-1]; v=v.slice(0,q.index+q[0].length); if(!state._capWarned){ state._capWarned=true; if (state.route==='detector' || (typeof isPremiumUser==='function'&&isPremiumUser())){ setTimeout(function(){ if(window.WSV2ToolBridge&&WSV2ToolBridge.showToolToast) WSV2ToolBridge.showToolToast('Pasted text trimmed to the maximum of '+c+' words', true); }, 150); } else if(!state.modal){ state.modal=(typeof limitModalFor==='function')?limitModalFor(state.route):'limit'; } } } else if(state._capWarned&&m.length { if (input) input.focus(); }); } else if (input) input.focus(); } if (action === 'humanizer-run'){ runHumanizer(); } if (action === 'acct-toggle'){ state.account.open = !state.account.open; render(); } if (action === 'acct-login'){ location.href = '/login'; } if (action === 'acct-logout'){ e.preventDefault(); var _wsLogoutPost=function(){ var f=document.createElement('form'); f.method='POST'; f.action='/logout'; var t=document.createElement('input'); t.type='hidden'; t.name='_token'; t.value=(window.__WS_AUTH_STATE&&window.__WS_AUTH_STATE.csrf)||''; f.appendChild(t); document.body.appendChild(f); f.submit(); }; if (window.WSV2AuthBridge && window.WSV2AuthBridge.logout){ window.WSV2AuthBridge.logout().catch(_wsLogoutPost); } else { _wsLogoutPost(); } return; } if (action === 'acct-contact'){ state.account.open = false; render(); if (window.WSOpenSupportChat) window.WSOpenSupportChat(); } if (action === 'acct-soon'){ state.account.open = false; render(); } if (action === 'acct-account'){ state.account.open = false; location.href = '/account'; } if (action === 'humanizer-mode'){ state.humanizer.mode = actionEl.dataset.mode || 'Standard'; render(); } if (action === 'humanizer-stats-toggle'){ state.humanizer.showStats = !state.humanizer.showStats; render(); } if (action === 'humanizer-highlight-toggle'){ state.humanizer.highlightChanges = !state.humanizer.highlightChanges; state.humanizer.openVariant = null; render(); } if (action === 'humanizer-variant'){ const idx = parseInt(actionEl.dataset.index, 10); state.humanizer.openVariant = state.humanizer.openVariant === idx ? null : idx; render(); } if (action === 'humanizer-variant-break'){ state.humanizer.openVariant = null; render(); } if (action === 'humanizer-pick-variant'){ const idx = parseInt(actionEl.dataset.index, 10); const val = actionEl.dataset.value; const data = state.humanizer.resultData; if (data && data.changes[idx]){ const old = String(data.changes[idx].selected || data.changes[idx].after || ''); let pos = -1; { const out = String(data.output || ''); let cursor = 0; for (let j = 0; j <= idx; j++){ const t = data.changes[j] && String(data.changes[j].selected || data.changes[j].after || ''); if (!t){ if (j === idx){ pos = -1; } continue; } let k = out.indexOf(t, cursor); if (k < 0){ if (j === idx){ pos = out.indexOf(t); } continue; } if (j === idx){ pos = k; break; } cursor = k + t.length; } } if (pos >= 0 && old) data.output = data.output.slice(0, pos) + val + data.output.slice(pos + old.length); data.changes[idx].after = val; data.changes[idx].selected = val; } state.humanizer.openVariant = null; render(); } if (action === 'humanizer-download-toggle'){ state.humanizer.downloadOpen = !state.humanizer.downloadOpen; render(); } if (action === 'humanizer-download'){ const fmt = (actionEl.dataset.format || 'TXT').toLowerCase(); const text = (state.humanizer.resultData && state.humanizer.resultData.output) || ''; try { wsDownloadReport('humanized-text', 'Humanized text', text, (actionEl.dataset.format || 'TXT')); } catch(err){} state.humanizer.downloadOpen = false; render(); } if (action === 'humanizer-lang-toggle'){ state.humanizer.langOpen = !state.humanizer.langOpen; render(); } if (action === 'humanizer-lang-pick'){ saveToolLanguage('humanizer', actionEl.dataset.lang || state.humanizer.language); state.humanizer.langOpen = false; render(); } if (action === 'humanizer-sample'){ state.toolText = HUMANIZER_SAMPLES[state.humanizer.template] || HUMANIZER_SAMPLES.Auto; state.humanizer.resultData = null; render(); } if (action === 'humanizer-clear'){ state.toolText = ''; state.humanizer.resultData = null; render(); } if (action === 'humanizer-first-free'){ state.modal = null; state.toolText = firstWords(state.toolText, 125); render(); setTimeout(runHumanizer, 20); } if (action === 'humanizer-settings-toggle'){ state.humanizer.settingsOpen = !state.humanizer.settingsOpen; render(); } if (action === 'humanizer-settings-close'){ state.humanizer.settingsOpen = false; render(); } if (action === 'humanizer-toggle'){ const field = actionEl.dataset.field; const value = actionEl.dataset.value; if (field === 'protected') state.humanizer.protected[value] = !state.humanizer.protected[value]; else state.humanizer[field] = !state.humanizer[field]; render(); } if (action === 'humanizer-result-tab'){ state.humanizer.resultTab = actionEl.dataset.tab; render(); } if (action === 'humanizer-mobile-tab'){ state.humanizer.mobileTab = actionEl.dataset.tab; render(); } if (action === 'humanizer-refine'){ state.humanizer.resultTab = 'humanized'; state.humanizer.mode = actionEl.dataset.refine === 'academic' ? 'Academic' : actionEl.dataset.refine === 'professional' ? 'Business' : 'Natural'; runHumanizer(); } if (action === 'humanizer-copy'){ const text = state.humanizer.resultData?.output || ''; if (navigator.clipboard && text) navigator.clipboard.writeText(text).catch(()=>{}); flashCopied(actionEl); } if (action === 'humanizer-paste'){ const input = document.querySelector('.ws-humanizer-editor'); if (navigator.clipboard && navigator.clipboard.readText){ navigator.clipboard.readText().then(text => { state.toolText = (function(v){ try { if (typeof effectiveLimitFor==='function'){ var c=effectiveLimitFor(state.route); if (c!==Infinity){ var m=wordMatches(v); if (m.length>c){ var q=m[c-1]; v=v.slice(0,q.index+q[0].length); if(!state._capWarned){ state._capWarned=true; if (state.route==='detector' || (typeof isPremiumUser==='function'&&isPremiumUser())){ setTimeout(function(){ if(window.WSV2ToolBridge&&WSV2ToolBridge.showToolToast) WSV2ToolBridge.showToolToast('Pasted text trimmed to the maximum of '+c+' words', true); }, 150); } else if(!state.modal){ state.modal=(typeof limitModalFor==='function')?limitModalFor(state.route):'limit'; } } } else if(state._capWarned&&m.length { if (input) input.focus(); }); } else if (input) input.focus(); } }); document.addEventListener('change', e => { const detectorField = e.target.dataset && e.target.dataset.detectorField; if (detectorField){ state.detector[detectorField] = e.target.value; render(); return; } const rewriterField = e.target.dataset && e.target.dataset.rewriterField; if (rewriterField){ state.rewriter[rewriterField] = e.target.value; render(); return; } const field = e.target.dataset && e.target.dataset.humanizerField; if (!field) return; state.humanizer[field] = e.target.value; if (['template','level','goal','mode'].includes(field)) state.humanizer.autoDetect = field === 'template' && e.target.value === 'Auto' ? state.humanizer.autoDetect : false; render(); }); function refocusToolInput(){ try{ var ed=document.querySelector('textarea[data-action="tool-input"]'); if(ed && document.activeElement!==ed){ ed.focus(); var L=ed.value.length; ed.setSelectionRange(L,L); } }catch(_e){} } document.addEventListener('input', e => { if (e.target.matches('[data-action="humanizer-strength"]')){ const levels = ['Light','Balanced','Strong']; state.humanizer.strength = levels[parseInt(e.target.value, 10) || 0] || 'Balanced'; const badge = document.querySelector('.wsc-strength-badge'); if (badge) badge.textContent = state.humanizer.strength; return; } if (e.target.matches('[data-action="home-input"]')){ // Harde cap 2000 woorden op de homepage smart-box (owner); tools vangen daarna hun eigen limiet. (function(){ var m = wordMatches(e.target.value); if (m.length > 2000){ var q = m[1999]; e.target.value = e.target.value.slice(0, q.index + q[0].length); } // Inline, persistente melding onder de balk (geen pop-up): blijft staan zolang de tekst op de 2000-cap zit. var _box = document.getElementById('smart-box'); var _inner = _box && _box.querySelector('.ws-smart-inner'); if (_box && _inner){ var _atCap = wordMatches(e.target.value).length >= 2000; var _h = _box.querySelector('.ws-home-cap-hint'); if (_atCap && !_h){ _h = document.createElement('div'); _h.className = 'ws-smart-hint ws-home-cap-hint'; _h.textContent = 'Pasted text trimmed to the maximum of 2000 words'; _inner.insertAdjacentElement('afterend', _h); } else if (!_atCap && _h){ _h.remove(); } } })(); state.homeText = e.target.value; state.homeExpanded = true; document.querySelector('#smart-box')?.classList.add('is-expanded'); document.querySelector('.ws-run')?.classList.toggle('ready', state.homeText.trim().length > 0); if (state.homeMinWarn && state.homeText.trim().split(/\s+/).filter(Boolean).length >= 5){ state.homeMinWarn = false; document.querySelector('.ws-smart-hint')?.remove(); } } if (e.target.matches('[data-action="tool-input"]')){ // Harde invoer-cap per accountstatus (owner-spec): meer dan de effectieve limiet kan // fysiek niet geplakt/getypt worden. Overflow wordt direct afgekapt; gratis gebruikers // krijgen eenmalig de upsell-modal, betaalde een toast. Reset zodra weer onder de cap. if (typeof effectiveLimitFor === 'function' && (state.route==='humanizer'||state.route==='rewriter'||state.route==='grammar'||state.route==='detector')){ var __cap = effectiveLimitFor(state.route); if (__cap !== Infinity){ var __m = wordMatches(e.target.value); if (__m.length > __cap){ var __cut = __m[__cap-1]; var __trimmed = e.target.value.slice(0, __cut.index + __cut[0].length); e.target.value = __trimmed; state.toolText = __trimmed; if (!state._capWarned){ state._capWarned = true; if (state.route === 'detector' || (typeof isPremiumUser==='function' && isPremiumUser())){ var __capMsg = 'Pasted text trimmed to the maximum of ' + __cap + ' words'; setTimeout(function(){ if (window.WSV2ToolBridge && WSV2ToolBridge.showToolToast) WSV2ToolBridge.showToolToast(__capMsg, true); }, 150); } else if (!state.modal){ state.modal = (typeof limitModalFor==='function') ? limitModalFor(state.route) : 'limit'; render(); return; } } } else if (state._capWarned && __m.length < __cap){ state._capWarned = false; } } } state.toolText = e.target.value; if (state.route === 'humanizer'){ const nowOver = isToolOverLimit('humanizer', words(state.toolText)); if (nowOver !== !!state._humOver){ state._humOver = nowOver; render(); refocusToolInput(); return; } } const t = ROUTES[state.route]; const wc = words(state.toolText); const count = document.querySelector('#tool-count'); if (count && t){ count.textContent = wordCounterText(state.route, wc); count.classList.toggle('over', isToolOverLimit(state.route, wc)); var _fp=count.parentElement; if(_fp){var _fx=_fp.querySelector('.ws-limit-flag'); if(_fx)_fx.remove(); var _fh=limitFlag(state.route, wc); if(_fh)count.insertAdjacentHTML('afterend', _fh);} } updateHumanizerLive(); if (state.route === 'detector'){ state.detector.resultData = null; updateDetectorLive(); } if (state.route === 'rewriter'){ const nowOver = isToolOverLimit('rewriter', wc); state.rewriter.resultData = null; const rc = document.querySelector('.ws-rw-count'); if (rc){ rc.textContent = wordCounterText('rewriter', wc); rc.classList.toggle('over', nowOver); var _fp=rc.parentElement; if(_fp){var _fx=_fp.querySelector('.ws-limit-flag'); if(_fx)_fx.remove(); var _fh=limitFlag('rewriter', wc); if(_fh)rc.insertAdjacentHTML('afterend', _fh);} } if (nowOver !== !!state._rwOver){ state._rwOver = nowOver; render(); refocusToolInput(); return; } } } }); document.addEventListener('focusin', e => { if (e.target.matches('#home-smart-input')){ state.homeExpanded = true; document.querySelector('#smart-box')?.classList.add('is-expanded'); } }); document.addEventListener('DOMContentLoaded', () => { state.route = routeFromPath(); hydrateSeed(); syncServerAuthState(); hydrateToolLanguages(); render(); (function(){ try{ var _ar=sessionStorage.getItem('ws-autorun'); if(!_ar) return; sessionStorage.removeItem('ws-autorun'); if(_ar!==state.route) return; if(!(state.toolText && String(state.toolText).trim())) return; setTimeout(function(){ try{ if(_ar==='rewriter' && typeof runRewriter==='function') runRewriter(); else if(_ar==='humanizer' && typeof runHumanizer==='function') runHumanizer(); else if(_ar==='detector' && typeof runDetector==='function') runDetector(); else if(_ar==='grammar' && typeof runGrammar==='function') runGrammar(); }catch(_e){} }, 80); }catch(_e){} })(); }); })();