/** * SEO/GEO Audit Report Generator v2 * Comprehensive standalone HTML reports with all audit sections */ export interface SeoAuditData { url: string; domain: string; protocol?: string; responseTime: number; server: string; htmlSize: number; title: string | null; titleLength: number; titleStatus: string; metaDescription: string | null; descLength: number; descStatus: string; metaKeywords: string | null; viewport: string | null; charset: string | null; robotsDirectives: string | null; canonical: string | null; hasCanonicalMismatch: boolean; xFrameOptions?: string; h1Count: number; h2Count: number; h3Count: number; h4Count: number; headingStatus: string; headings: { level: number; text: string }[]; links?: { total: number; internal: number; external: number; nofollow: number; sampleExternal?: { href: string; text: string; nofollow: boolean }[]; }; images?: { total: number; withAlt: number; withoutAlt: number; lazyLoaded: number; altCoverage: number; sampleWithoutAlt?: string[]; }; content?: { wordCount: number; sentenceCount: number; paragraphCount: number; avgWordsPerSentence: number; textPreview?: string; }; openGraph?: { title: string | null; description: string | null; image: string | null; type: string | null; url?: string | null }; twitterCard?: { card: string | null; title?: string | null; description?: string | null }; hreflang?: string[]; performance?: { inlineStyles: number; inlineScripts?: number; externalScripts: number; externalStylesheets: number; hasPreconnect: boolean; hasPreload: boolean; hasDnsPrefetch: boolean; usesAsyncScripts: boolean; usesDeferScripts: boolean; contentEncoding?: string; }; accessibility?: { hasLangAttr: boolean; hasAriaLabels: boolean; hasAltOnFirstImage: boolean }; structuredData?: { hasJsonLd: boolean; hasMicrodata: boolean; types: { type: string; found: boolean }[] }; scores?: { overall: number; technical: number; content: number; performance: number; social: number }; issues?: { severity: string; category: string; message: string }[]; } const sc = (s: number): string => s >= 80 ? "#22c55e" : s >= 60 ? "#f59e0b" : "#ef4444"; const svc = (s: string): string => s === "critical" ? "#ef4444" : s === "warning" ? "#f59e0b" : "#6b7280"; const badge = (label: string, color: string): string => '' + label + ''; const passFail = (val: boolean | null | undefined, yesLabel?: string, noLabel?: string): string => val ? badge(yesLabel || "PASS", "#22c55e") : badge(noLabel || "FAIL", "#ef4444"); const statusBadge = (status: string): string => { if (status === "good" || status === "ok" || status === "pass") return badge("GOOD", "#22c55e"); if (status === "missing" || status === "fail") return badge("MISSING", "#ef4444"); if (status === "too_long" || status === "warning" || status === "multiple_h1") return badge("WARNING", "#f59e0b"); if (status === "mismatch" || status === "missing_h1") return badge("CRITICAL", "#ef4444"); return badge(status.toUpperCase(), "#6b7280"); }; const t = (v: string | null | undefined, fallback: string): string => v ? v : '' + fallback + ''; const fixMap: Record = { Meta: "Ensure every page has a unique title tag (50-60 chars) with primary keyword near start. Write meta descriptions (150-160 chars) with a clear CTA. Add viewport meta tag for mobile compatibility. Include target keywords naturally without stuffing.", Content: "Maintain exactly one H1 per page containing the primary keyword. Build logical heading hierarchy (H1 > H2 > H3). Aim for 1000+ words on core pages. Ensure each paragraph adds value. Use short paragraphs (3-4 sentences) for readability.", Technical: "Set self-referencing canonical tags on all pages. Ensure valid SSL certificate with proper HTTPS redirect. Check robots.txt is not blocking important pages. Fix redirect chains (max 2 hops). Implement proper 301 redirects for moved pages.", Mobile: "Add viewport meta tag. Test with Google Mobile-Friendly test. Ensure tap targets are minimum 48x48px. Avoid horizontal scroll. Use responsive images with srcset.", Security: "Migrate to HTTPS with valid SSL certificate. Set up HTTP-to-HTTPS redirects. Configure HSTS headers. Set X-Frame-Options to prevent clickjacking. Implement Content-Security-Policy headers.", Performance: "Minimize inline styles. Add preconnect hints for third-party domains. Implement lazy loading for images. Use async/defer for non-critical scripts. Compress images (WebP/AVIF). Enable Brotli/gzip compression. Minimize render-blocking resources.", Social: "Add Open Graph tags (og:title, og:description, og:image, og:url, og:type). Implement Twitter Card meta tags. Ensure OG images are 1200x630px minimum. Validate with Facebook Sharing Debugger and Twitter Card Validator.", Accessibility: "Add lang attribute to tag. Implement ARIA labels for interactive elements. Add descriptive alt text to all images. Ensure keyboard navigation works. Use sufficient color contrast ratios (WCAG AA minimum).", Links: "Fix or remove broken internal links. Add external links to authoritative sources. Review nofollow attributes on external links. Ensure descriptive anchor text (avoid 'click here'). Implement logical internal linking structure.", "Structured Data": "Implement JSON-LD structured data for relevant content types. Validate with Google Rich Results Test. Add FAQ, Article, Product, or Organization schema as appropriate. Use schema.org markup for entities and facts.", }; export function generateSeoReportHtml(d: SeoAuditData): string { const now = new Date().toLocaleString(); // --- Issue Rows --- const criticalIssues = (d.issues || []).filter(i => i.severity === "critical"); const warningIssues = (d.issues || []).filter(i => i.severity === "warning"); const infoIssues = (d.issues || []).filter(i => i.severity === "info"); const issueRow = (issue: { severity: string; category: string; message: string }): string => "" + badge(issue.severity.toUpperCase(), svc(issue.severity)) + "" + issue.category + "" + issue.message + "" + (fixMap[issue.category] || "Review and address this issue based on SEO best practices.") + ""; const allIssueRows = (d.issues || []).map(issueRow).join(""); // --- Heading Rows --- const headingRows = (d.headings || []).slice(0, 30).map((h) => "" + h.level + "" + h.text + "" ).join(""); // --- Structured Data Rows --- const sdTypes = d.structuredData?.types || []; const sdRows = sdTypes.map((s) => "" + s.type + "" + passFail(s.found, "FOUND", "NOT FOUND") + "" ).join(""); // --- External Link Rows --- const extLinks = d.links?.sampleExternal || []; const extLinkRows = extLinks.slice(0, 15).map((l) => "" + l.href + "" + (l.text || "N/A") + "" + (l.nofollow ? badge("NOFOLLOW", "#f59e0b") : badge("FOLLOW", "#22c55e")) + "" ).join(""); // --- Missing Alt Image Rows --- const missingAlts = d.images?.sampleWithoutAlt || []; const altRows = missingAlts.slice(0, 10).map((src) => "" + src + "" ).join(""); // --- Hreflang Rows --- const hreflangTags = d.hreflang || []; const hreflangRows = hreflangTags.map((h) => "" + h + "" ).join(""); // --- GEO Score Calculation --- const geoScore = calculateGeoScore(d); const geoColor = sc(geoScore); // --- Action Plan --- const actionPlan = generateActionPlan(d); // --- Build HTML --- const css = "*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0f172a;color:#e2e8f0;line-height:1.6;padding:40px}@media print{body{background:#fff;color:#1e293b;padding:20px}}.container{max-width:960px;margin:0 auto}@media print{.container{max-width:100%}}.hero{text-align:center;padding:32px;background:linear-gradient(135deg,#1e293b 0%,#0f172a 100%);border-radius:20px;margin-bottom:32px;border:1px solid #334155}@media print{.hero{background:#f8fafc;border:1px solid #e2e8f0}}.hero h1{font-size:30px;margin-bottom:4px}.hero .url{color:#60a5fa;font-family:monospace;font-size:13px;word-break:break-all}.hero .meta{color:#94a3b8;margin-top:8px;font-size:12px}.scores{display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin:24px 0}.score-card{text-align:center;padding:18px 8px;background:#1e293b;border-radius:14px;border:1px solid #334155}@media print{.score-card{background:#f8fafc;border:1px solid #e2e8f0}}.score-value{font-size:34px;font-weight:800}.score-label{font-size:10px;text-transform:uppercase;letter-spacing:1px;color:#94a3b8;margin-top:4px}table{width:100%;border-collapse:collapse;margin:14px 0;background:#1e293b;border-radius:14px;overflow:hidden;border:1px solid #334155}@media print{table{background:#f8fafc;border:1px solid #e2e8f0}}th,td{padding:10px 14px;text-align:left;border-bottom:1px solid #334155;font-size:13px}@media print{th,td{border-bottom:1px solid #e2e8f0}}th{font-size:10px;text-transform:uppercase;letter-spacing:1px;color:#94a3b8;background:#162032}@media print{th{background:#f1f5f9;color:#64748b}}tr:hover{background:#1a2744}@media print{tr:hover{background:#f8fafc}}a{color:#60a5fa}.section{background:#1e293b;border-radius:16px;padding:24px;margin-bottom:20px;border:1px solid #334155}@media print{.section{background:#f8fafc;border:1px solid #e2e8f0}}h2{font-size:18px;margin-bottom:14px;padding-bottom:8px;border-bottom:2px solid #334155;display:flex;align-items:center;gap:8px}h2 .icon{font-size:20px}.stat-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin:12px 0}.stat-item{background:#162032;padding:12px;border-radius:10px;text-align:center}@media print{.stat-item{background:#f1f5f9}}.stat-value{font-size:22px;font-weight:800}.stat-label{font-size:10px;color:#94a3b8;text-transform:uppercase;letter-spacing:1px;margin-top:2px}.action-item{display:flex;gap:12px;padding:10px 0;border-bottom:1px solid #334155}.action-item:last-child{border:none}.action-priority{font-size:10px;font-weight:800;padding:2px 8px;border-radius:4px;text-transform:uppercase;white-space:nowrap}.action-priority.high{background:#ef444420;color:#ef4444}.action-priority.medium{background:#f59e0b20;color:#f59e0b}.action-priority.low{background:#22c55e20;color:#22c55e}.geo-bar{height:8px;background:#334155;border-radius:4px;overflow:hidden;margin:8px 0}.geo-fill{height:100%;border-radius:4px;transition:width 0.3s}.footer{text-align:center;padding:32px;color:#64748b;font-size:12px}"; let html = "SEO/GEO Audit - " + (d.domain || "report") + "
"; // === HERO === html += "

Comprehensive SEO/GEO Audit Report

" + "

" + (d.url || "N/A") + "

" + "

Generated by PromptArch Vibe Architect | " + now + "

"; // === EXECUTIVE SUMMARY === html += "

01 Executive Summary

" + "
" + "
" + (d.scores?.overall || 0) + "/100
Overall Score
" + "
" + (d.issues?.length || 0) + "
Issues Found
" + "
" + criticalIssues.length + "
Critical Issues
" + "
" + geoScore + "/100
GEO Readiness
" + "
"; // === SCORES === html += "

02 Scoring Breakdown

" + "
" + scoreCard("Overall", d.scores?.overall || 0) + scoreCard("Technical", d.scores?.technical || 0) + scoreCard("Content", d.scores?.content || 0) + scoreCard("Performance", d.scores?.performance || 0) + scoreCard("Social", d.scores?.social || 0) + "
" + geoScore + "
GEO
" + "
" + geoBar("Technical SEO", d.scores?.technical || 0) + geoBar("Content Quality", d.scores?.content || 0) + geoBar("Performance", d.scores?.performance || 0) + geoBar("Social/OG", d.scores?.social || 0) + geoBar("GEO Readiness", geoScore) + "
"; // === META TAGS === html += "

03 Meta Tags Analysis

" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
ElementValueStatus
Title" + t(d.title, "MISSING") + " (" + (d.titleLength || 0) + " chars)" + statusBadge(d.titleStatus) + "
Meta Description" + t(d.metaDescription, "MISSING") + " (" + (d.descLength || 0) + " chars)" + statusBadge(d.descStatus) + "
Meta Keywords" + t(d.metaKeywords, "Not set (modern SEO does not require)") + "Not a ranking factor
Viewport" + t(d.viewport, "MISSING") + "" + passFail(!!d.viewport) + "
Charset" + t(d.charset, "MISSING") + "" + passFail(!!d.charset) + "
Canonical" + (d.canonical || "None") + "" + (d.hasCanonicalMismatch ? badge("MISMATCH", "#ef4444") : d.canonical ? badge("OK", "#22c55e") : badge("MISSING", "#ef4444")) + "
Robots" + (d.robotsDirectives || "None") + "-
X-Frame-Options" + t(d.xFrameOptions, "Not set") + "" + passFail(!!d.xFrameOptions) + "
Protocol" + (d.protocol || "Unknown") + "" + (d.protocol === "HTTPS" ? badge("SECURE", "#22c55e") : badge("INSECURE", "#ef4444")) + "
"; // === SOCIAL / OPEN GRAPH === html += "

04 Social & Open Graph

" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
PropertyValueStatus
OG Title" + t(d.openGraph?.title, "Missing") + "" + passFail(!!d.openGraph?.title) + "
OG Description" + t(d.openGraph?.description, "Missing") + "" + passFail(!!d.openGraph?.description) + "
OG Image" + (d.openGraph?.image ? '' + d.openGraph.image.substring(0, 80) + '...' : "Missing") + "" + passFail(!!d.openGraph?.image) + "
OG Type" + t(d.openGraph?.type, "Missing") + "" + passFail(!!d.openGraph?.type) + "
OG URL" + t(d.openGraph?.url, "Missing") + "" + passFail(!!d.openGraph?.url) + "
Twitter Card" + t(d.twitterCard?.card, "Not set") + "" + passFail(!!d.twitterCard?.card) + "
Twitter Title" + t(d.twitterCard?.title, "Missing") + "" + passFail(!!d.twitterCard?.title) + "
Twitter Description" + t(d.twitterCard?.description, "Missing") + "" + passFail(!!d.twitterCard?.description) + "
"; // === HEADINGS === html += "

05 Heading Structure

" + "
" + "
" + d.h1Count + "
H1
" + "
" + d.h2Count + "
H2
" + "
" + d.h3Count + "
H3
" + "
" + d.h4Count + "
H4+
" + "
" + "

Status: " + statusBadge(d.headingStatus) + "

" + (headingRows ? "

Heading hierarchy (up to 30 shown):

" + headingRows + "
LevelText
" : "

No headings found.

") + "
"; // === LINKS === html += "

06 Links Analysis

" + "
" + "
" + (d.links?.total || 0) + "
Total Links
" + "
" + (d.links?.internal || 0) + "
Internal
" + "
" + (d.links?.external || 0) + "
External
" + "
" + (d.links?.nofollow || 0) + "
Nofollow
" + "
"; if (extLinkRows) { html += "

Sample external links:

" + "" + extLinkRows + "
URLAnchor TextRel
"; } html += "
"; // === IMAGES === html += "

07 Images & Alt Text

" + "
" + "
" + (d.images?.total || 0) + "
Total Images
" + "
" + (d.images?.withAlt || 0) + "
With Alt
" + "
" + (d.images?.withoutAlt || 0) + "
Missing Alt
" + "
" + (d.images?.altCoverage || 0) + "%
Alt Coverage
" + "
" + "

Lazy Loaded: " + passFail((d.images?.lazyLoaded || 0) > 0) + " (" + (d.images?.lazyLoaded || 0) + " images)

"; if (altRows) { html += "

Images missing alt text:

" + "" + altRows + "
Image Source
"; } html += "
"; // === CONTENT === html += "

08 Content Analysis

" + "
" + "
" + (d.content?.wordCount || 0) + "
Word Count
" + "
" + (d.content?.sentenceCount || 0) + "
Sentences
" + "
" + (d.content?.paragraphCount || 0) + "
Paragraphs
" + "
" + (d.content?.avgWordsPerSentence || 0) + "
Avg Words/Sentence
" + "
" + "" + "" + "" + "
MetricValueRecommendation
Word Count" + (d.content?.wordCount || 0) + "" + ((d.content?.wordCount || 0) >= 1000 ? badge("GOOD", "#22c55e") : badge("BELOW 1000", "#f59e0b")) + " Aim for 1000+ on core pages
Readability" + (d.content?.avgWordsPerSentence || 0) + " avg words/sentence" + ((d.content?.avgWordsPerSentence || 0) <= 20 ? badge("GOOD", "#22c55e") : badge("LONG", "#f59e0b")) + " Keep under 20 for readability
"; // === PERFORMANCE === html += "

09 Performance Signals

" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
MetricValueStatus
Server" + (d.server || "Unknown") + "-
Response Time" + d.responseTime + "ms" + (d.responseTime < 500 ? badge("FAST", "#22c55e") : d.responseTime < 1500 ? badge("OK", "#f59e0b") : badge("SLOW", "#ef4444")) + "
HTML Size" + (d.htmlSize || 0).toLocaleString() + " bytes-
Content Encoding" + t(d.performance?.contentEncoding, "None detected") + "" + passFail(!!d.performance?.contentEncoding) + "
External Scripts" + (d.performance?.externalScripts || 0) + "" + ((d.performance?.externalScripts || 0) <= 5 ? badge("OK", "#22c55e") : badge("HIGH", "#f59e0b")) + "
External Stylesheets" + (d.performance?.externalStylesheets || 0) + "-
Inline Styles" + (d.performance?.inlineStyles || 0) + "" + ((d.performance?.inlineStyles || 0) <= 10 ? badge("OK", "#22c55e") : badge("HIGH", "#f59e0b")) + "
Inline Scripts" + (d.performance?.inlineScripts || 0) + "" + ((d.performance?.inlineScripts || 0) === 0 ? badge("GOOD", "#22c55e") : badge("FOUND", "#f59e0b")) + "
Preconnect" + (d.performance?.hasPreconnect ? "Yes" : "No") + "" + passFail(d.performance?.hasPreconnect) + "
Preload" + (d.performance?.hasPreload ? "Yes" : "No") + "" + passFail(d.performance?.hasPreload) + "
DNS Prefetch" + (d.performance?.hasDnsPrefetch ? "Yes" : "No") + "" + passFail(d.performance?.hasDnsPrefetch) + "
Async/Defer Scripts" + (d.performance?.usesAsyncScripts || d.performance?.usesDeferScripts ? "Yes" : "No") + "" + passFail(d.performance?.usesAsyncScripts || d.performance?.usesDeferScripts) + "
"; // === ACCESSIBILITY === html += "

10 Accessibility

" + "" + "" + "" + "" + "" + "
CheckStatusImpact
HTML lang attribute" + passFail(d.accessibility?.hasLangAttr) + "Screen readers, SEO
ARIA labels present" + passFail(d.accessibility?.hasAriaLabels) + "Assistive technology
First image has alt text" + passFail(d.accessibility?.hasAltOnFirstImage) + "Screen readers
Alt text coverage" + (d.images?.altCoverage || 0) + "%" + ((d.images?.altCoverage || 0) >= 90 ? badge("GOOD", "#22c55e") : (d.images?.altCoverage || 0) >= 70 ? badge("FAIR", "#f59e0b") : badge("POOR", "#ef4444")) + "
"; // === STRUCTURED DATA === html += "

11 Structured Data / Schema

" + "" + "" + "" + "
FormatStatus
JSON-LD" + passFail(d.structuredData?.hasJsonLd) + "
Microdata" + passFail(d.structuredData?.hasMicrodata) + "
"; if (sdRows) { html += "

Detected schema types:

" + "" + sdRows + "
Schema TypeFound
"; } else if (!d.structuredData?.hasJsonLd && !d.structuredData?.hasMicrodata) { html += "

No structured data detected. Adding JSON-LD schema (FAQ, Article, Organization, Product) can significantly improve search visibility and enable rich results.

"; } html += "
"; // === HREFLANG === if (hreflangTags.length > 0) { html += "

12 Hreflang Tags

" + "" + hreflangRows + "
Language-Region
"; } // === GEO ANALYSIS === html += "

13 GEO (Generative Engine Optimization)

" + "
" + geoScore + "/100
GEO Readiness Score
" + "
" + (d.structuredData?.hasJsonLd ? "Yes" : "No") + "
Schema Markup
" + geoBar("Factual Content Structure", geoFactualScore(d)) + geoBar("Entity Schema", geoEntityScore(d)) + geoBar("Content Depth", geoContentDepthScore(d)) + geoBar("Citeability", geoCiteabilityScore(d)) + "

What is GEO?

" + "

GEO (Generative Engine Optimization) is the practice of optimizing content for AI-powered search engines like ChatGPT, Claude, Perplexity, and Google AI Overviews. Unlike traditional SEO which targets algorithmic rankings, GEO focuses on making your content authoritative, factual, and well-structured enough for AI models to cite as sources.

" + "

GEO Improvement Checklist

" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
FactorStatusAction
Structured Data / Schema" + passFail(d.structuredData?.hasJsonLd) + "Add JSON-LD for Organization, FAQ, Article, Product schemas
Heading Hierarchy" + statusBadge(d.headingStatus) + "One H1, logical H2-H4 hierarchy with keyword-rich headings
Content Depth (1000+ words)" + passFail((d.content?.wordCount || 0) >= 1000) + "Expand core pages with comprehensive, authoritative content
FAQ / Q&A Format" + passFail(d.structuredData?.types?.some((s) => s.type === "FAQ") && d.structuredData?.types?.some((s) => s.found)) + "Add FAQ schema with common questions about your topic
Clear Facts & Statistics" + passFail((d.content?.wordCount || 0) >= 500) + "Include verifiable data points, statistics, and citations
Lists & Tables" + passFail(d.h2Count >= 3) + "Use structured lists and comparison tables for scannability
Authoritative Tone" + passFail(d.h1Count === 1) + "Write expert-level content with clear author attribution
Meta Description CTA" + passFail(!!d.metaDescription && d.metaDescription.length >= 120) + "Include clear call-to-action in meta descriptions
"; // === ISSUES === html += "

14 Issues & How to Fix (" + (d.issues?.length || 0) + " found)

" + "
" + "" + badge("CRITICAL: " + criticalIssues.length, "#ef4444") + "" + "" + badge("WARNING: " + warningIssues.length, "#f59e0b") + "" + "" + badge("INFO: " + infoIssues.length, "#6b7280") + "" + "
" + "" + (allIssueRows || "") + "
SeverityCategoryIssueHow to Fix
No issues detected. Great job!
"; // === ACTION PLAN === html += "

15 Prioritized Action Plan

" + "

Recommended fixes ordered by impact and priority.

" + actionPlan + "
"; // === FAQ RECOMMENDATIONS === html += "

16 Recommended FAQ Schema Questions

" + "

Based on your content analysis, consider adding these FAQ items to improve featured snippet eligibility and GEO performance.

" + generateFaqRecommendations(d) + "
"; // === GENERAL RECOMMENDATIONS === html += "

17 SEO/GEO Best Practices Checklist

    " + "
  1. Title Tags: Keep under 60 characters. Place primary keyword near the beginning. Make each page title unique.
  2. " + "
  3. Meta Descriptions: Write 150-160 characters with a clear call-to-action. Include target keywords naturally.
  4. " + "
  5. Heading Structure: Use exactly one H1 per page. Create a logical hierarchy. Include keywords in headings.
  6. " + "
  7. Content Quality: Aim for 1000+ words on core pages. Write for users first, search engines second. Update content regularly.
  8. " + "
  9. Image Optimization: Add descriptive alt text to every image. Use WebP/AVIF format. Implement lazy loading. Compress file sizes.
  10. " + "
  11. Page Speed: Minimize render-blocking resources. Enable compression (Brotli/gzip). Use CDN. Optimize images and fonts.
  12. " + "
  13. Mobile Experience: Ensure responsive design. Test tap targets. Avoid horizontal scrolling. Optimize font sizes.
  14. " + "
  15. Structured Data: Implement JSON-LD schema markup. Validate with Google Rich Results Test. Add FAQ, Article, or Product schema.
  16. " + "
  17. Internal Linking: Create logical site architecture. Use descriptive anchor text. Ensure important pages are within 3 clicks of homepage.
  18. " + "
  19. Technical SEO: Submit XML sitemap. Configure robots.txt properly. Fix broken links. Implement canonical tags. Monitor crawl errors.
  20. " + "
  21. GEO Optimization: Structure content with clear facts, lists, and tables. Use schema markup for entities. Optimize for featured snippets. Create comprehensive, authoritative content that AI models can cite as sources.
  22. " + "
  23. Security: Enforce HTTPS. Set HSTS headers. Configure X-Frame-Options. Implement Content-Security-Policy.
  24. " + "
"; // === FOOTER === html += "

PromptArch Vibe Architect | " + now + "

" + "

rommark.dev/tools/promptarch

"; html += "
"; return html; } // --- Helper functions --- function scoreCard(label: string, value: number): string { return "
" + value + "
" + label + "
"; } function geoBar(label: string, value: number): string { return "
" + label + "" + value + "%
" + "
"; } function calculateGeoScore(d: SeoAuditData): number { let score = 0; if (d.structuredData?.hasJsonLd) score += 20; if (d.structuredData?.hasMicrodata) score += 5; if (d.structuredData?.types?.some((s) => s.type === "FAQ" && s.found)) score += 15; if (d.structuredData?.types?.some((s) => s.type === "Organization" && s.found)) score += 10; if (d.structuredData?.types?.some((s) => s.type === "Article" && s.found)) score += 5; if (d.h1Count === 1) score += 10; if (d.h2Count >= 3) score += 5; if ((d.content?.wordCount || 0) >= 1000) score += 15; else if ((d.content?.wordCount || 0) >= 500) score += 8; if (d.openGraph?.title && d.openGraph?.description) score += 5; if (d.metaDescription && d.metaDescription.length >= 120) score += 5; if (d.accessibility?.hasLangAttr) score += 5; return Math.min(score, 100); } function geoFactualScore(d: SeoAuditData): number { let s = 0; if ((d.content?.wordCount || 0) >= 1000) s += 40; else if ((d.content?.wordCount || 0) >= 500) s += 25; else if ((d.content?.wordCount || 0) >= 200) s += 10; if (d.h2Count >= 3) s += 30; if ((d.content?.paragraphCount || 0) >= 5) s += 30; return Math.min(s, 100); } function geoEntityScore(d: SeoAuditData): number { if (d.structuredData?.hasJsonLd) return 80; if (d.structuredData?.hasMicrodata) return 50; return 10; } function geoContentDepthScore(d: SeoAuditData): number { let s = 0; const wc = d.content?.wordCount || 0; if (wc >= 2000) s += 40; else if (wc >= 1000) s += 30; else if (wc >= 500) s += 15; if (d.h3Count >= 3) s += 20; if (d.h4Count >= 2) s += 10; if ((d.content?.paragraphCount || 0) >= 8) s += 15; if ((d.links?.external || 0) >= 3) s += 15; return Math.min(s, 100); } function geoCiteabilityScore(d: SeoAuditData): number { let s = 0; if (d.title && d.title.length >= 30) s += 15; if (d.metaDescription && d.metaDescription.length >= 120) s += 15; if (d.openGraph?.title) s += 10; if (d.structuredData?.types?.some((t) => t.type === "Article" && t.found)) s += 20; if (d.accessibility?.hasLangAttr) s += 10; if (d.h1Count === 1) s += 10; if (d.canonical) s += 10; if ((d.content?.wordCount || 0) >= 1000) s += 10; return Math.min(s, 100); } function generateActionPlan(d: SeoAuditData): string { const items: { priority: string; action: string; impact: string }[] = []; // Critical issues first (d.issues || []).filter(i => i.severity === "critical").forEach(issue => { items.push({ priority: "high", action: issue.message, impact: issue.category }); }); // Data-driven actions if (!d.title) items.push({ priority: "high", action: "Add a unique title tag (50-60 chars) with primary keyword", impact: "Meta / SEO" }); if (!d.metaDescription) items.push({ priority: "high", action: "Add meta description (150-160 chars) with target keyword and CTA", impact: "Meta / CTR" }); if (!d.canonical) items.push({ priority: "high", action: "Add self-referencing canonical tag to prevent duplicate content issues", impact: "Technical" }); if (!d.viewport) items.push({ priority: "high", action: "Add viewport meta tag for proper mobile rendering", impact: "Mobile" }); if (d.protocol !== "HTTPS") items.push({ priority: "high", action: "Migrate to HTTPS with valid SSL certificate and proper redirects", impact: "Security" }); if (d.h1Count === 0) items.push({ priority: "high", action: "Add a single H1 heading with the primary keyword", impact: "Content" }); if (d.h1Count > 1) items.push({ priority: "medium", action: "Consolidate to exactly one H1 heading per page", impact: "Content" }); if (!d.structuredData?.hasJsonLd) items.push({ priority: "medium", action: "Implement JSON-LD structured data (FAQ, Organization, Article schemas)", impact: "Structured Data / GEO" }); if (!d.openGraph?.title) items.push({ priority: "medium", action: "Add Open Graph tags (og:title, og:description, og:image) for social sharing", impact: "Social" }); if (!d.twitterCard?.card) items.push({ priority: "medium", action: "Add Twitter Card meta tags for better Twitter previews", impact: "Social" }); if (!d.accessibility?.hasLangAttr) items.push({ priority: "medium", action: 'Add lang attribute to tag (e.g. lang="en")', impact: "Accessibility" }); if ((d.images?.altCoverage || 0) < 90) items.push({ priority: "medium", action: "Add descriptive alt text to all images (currently " + (d.images?.altCoverage || 0) + "% coverage)", impact: "Accessibility / SEO" }); if (!d.performance?.hasPreconnect) items.push({ priority: "medium", action: "Add preconnect hints for third-party domains to improve load time", impact: "Performance" }); if (!d.performance?.usesAsyncScripts && !d.performance?.usesDeferScripts) items.push({ priority: "medium", action: "Use async or defer attributes on non-critical scripts", impact: "Performance" }); if ((d.images?.lazyLoaded || 0) === 0 && (d.images?.total || 0) > 0) items.push({ priority: "medium", action: "Implement lazy loading for images to improve initial page load", impact: "Performance" }); if (!d.structuredData?.types?.some((t) => t.type === "FAQ" && t.found)) items.push({ priority: "low", action: "Add FAQ schema with common questions about your topic for featured snippet eligibility", impact: "GEO" }); if ((d.content?.wordCount || 0) < 1000) items.push({ priority: "low", action: "Expand content to 1000+ words on core pages for better depth and authority", impact: "Content / GEO" }); if ((d.images?.total || 0) > 0 && !d.images?.lazyLoaded) items.push({ priority: "low", action: "Add loading=\"lazy\" to below-the-fold images", impact: "Performance" }); // Warning issues (d.issues || []).filter(i => i.severity === "warning").forEach(issue => { if (!items.some(it => it.action === issue.message)) { items.push({ priority: "medium", action: issue.message, impact: issue.category }); } }); // Deduplicate and limit const seen = new Set(); const unique = items.filter(item => { if (seen.has(item.action)) return false; seen.add(item.action); return true; }).slice(0, 20); return unique.map(item => "
" + item.priority + "
" + "
" + item.action + "
" + "
Impact: " + item.impact + "
" ).join(""); } function generateFaqRecommendations(d: SeoAuditData): string { const domain = d.domain || "this site"; const title = d.title || domain; const questions: string[] = []; questions.push("What is " + title + "?
Provide a clear, concise answer (40-60 words) explaining what " + domain + " offers and its primary value proposition."); questions.push("How does " + domain + " work?
Explain the core functionality, process, or service in simple terms."); questions.push("What are the main benefits of using " + domain + "?
List 3-5 key benefits with brief explanations."); questions.push("Is " + domain + " free or paid?
Provide clear pricing information or state if the service is free."); questions.push("How do I get started with " + domain + "?
Outline the steps a new user should take to begin."); if (d.protocol === "HTTPS") { questions.push("Is " + domain + " secure?
Confirm security measures in place (SSL, data protection, etc.)."); } if ((d.content?.wordCount || 0) >= 300) { questions.push("What makes " + domain + " different from competitors?
Highlight unique selling points and differentiators."); } return questions.map((q, i) => { const parts = q.split("
"); return "
" + (i + 1) + ". " + parts[0] + "
" + "
" + parts[1] + "
"; }).join(""); } export function downloadSeoReport(d: SeoAuditData, format: "html" | "pdf") { const html = generateSeoReportHtml(d); if (format === "html") { const blob = new Blob([html], { type: "text/html" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "seo-geo-audit-" + (d.domain || "report") + "-" + new Date().toISOString().slice(0, 10) + ".html"; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } else { const iframe = document.createElement("iframe"); iframe.style.position = "fixed"; iframe.style.right = "0"; iframe.style.bottom = "0"; iframe.style.width = "0"; iframe.style.height = "0"; iframe.style.border = "none"; document.body.appendChild(iframe); const doc = iframe.contentDocument || iframe.contentWindow?.document; if (doc) { doc.open(); doc.write(html); doc.close(); iframe.onload = () => { setTimeout(() => { iframe.contentWindow?.focus(); iframe.contentWindow?.print(); setTimeout(() => document.body.removeChild(iframe), 5000); }, 600); }; } } }