ZipIt - Website to ZIP
| ZIPIT-20-LLQE-8KUM-L7YB\nZIPIT-20-KIHX-DZQQ-F26S\nZIPIT-20-R2PW-IKX9-G49S\nZIPIT-20-DJEU-F2GY-5A7P\nZIPIT-20-ZP73-YXH6-EMP7\nZIPIT-20-ELUJ-D4MR-C75X\nZIPIT-20-LTUV-95OZ-CWJ3\nZIPIT-20-ZTML-V7V8-IG14\nZIPIT-20-JQPG-SUGH-9Z5V\nZIPIT-20-Y0X1-P21S-CZII\nZIPIT-20-7O4G-UUU7-FTPA\nZIPIT-20-ICES-BB2X-4WMF\nZIPIT-20-QAM5-MLWV-SY6I\nZIPIT-20-UDMQ-6HHM-YU4Z\nZIPIT-20-JXAG-X81Y-XIM4\nZIPIT-20-5K93-P10P-0AUI\nZIPIT-20-TLGI-0G7O-C2JL\nZIPIT-20-R0CA-04NE-4SZ1\nZIPIT-20-HXA7-BUEJ-ASW8\nZIPIT-20-1JJG-Z8HF-PX4Z\nZIPIT-20-ZPLV-CDJK-WJGL\nZIPIT-20-Y54B-Q0GT-4CG6\nZIPIT-20-WCMC-P9H9-T3HV\nZIPIT-20-F30E-PKVH-FHGD\nZIPIT-20-H1HK-98EX-KSWW\nZIPIT-20-ISMY-R48E-MXDW\nZIPIT-20-54UD-RN5F-AK40\nZIPIT-20-SOHO-6OIO-V81E\nZIPIT-20-22VQ-78PT-R0RA\nZIPIT-20-PRWF-1B4L-N54Y\nZIPIT-20-ZKZ5-WRX1-JA5A\nZIPIT-20-EL2L-D09K-R5DD\nZIPIT-20-L9D6-EBDP-PQMZ\nZIPIT-20-CZLF-LARG-RX6P\nZIPIT-20-9TXH-70GT-DVH0\nZIPIT-20-9OT5-VDKY-VYY5\nZIPIT-20-KF5K-YBT3-VUTI\nZIPIT-20-QC8X-I9GW-HLSD\nZIPIT-20-7KIT-7IYZ-GFI1\nZIPIT-20-PTR8-LC9Q-KZUN\nZIPIT-20-5TDT-1OXR-DWN1\nZIPIT-20-9HL9-SMMI-QKC6\nZIPIT-20-LAHY-1OGO-6PBF\nZIPIT-20-USPH-1L70-TN7L\nZIPIT-20-K2O5-6IC2-8JCK\nZIPIT-20-NAH7-591V-PJDB\nZIPIT-20-SV1K-ZSHD-ZOB2\nZIPIT-20-NNFZ-L19A-1A8T\nZIPIT-20-CBTQ-O77Q-PINX\nZIPIT-20-1RIT-FYL9-JS04\nZIPIT-20-XA05-0B0M-LWFX\nZIPIT-20-VHWC-OGN4-PQGO\nZIPIT-20-JAFC-ZE39-SY43\nZIPIT-20-04MJ-9VM4-Y9OJ\nZIPIT-20-Q3U4-TUST-PBJ7\nZIPIT-20-O1VX-C6KR-WAA6\nZIPIT-20-GQG0-HDUM-RZTI\nZIPIT-20-Q98K-Z79C-D93T\nZIPIT-20-TMAW-JGIZ-FVBX\nZIPIT-20-OT2R-QF36-4PF1\nZIPIT-20-MGBH-3FCZ-XX0Z\nZIPIT-20-WEZT-ZF6W-FT21\nZIPIT-20-32I6-KYFU-84CN\nZIPIT-20-S1IM-FH8L-G9M9\nZIPIT-20-PXYN-CWWL-L5WG\nZIPIT-20-JDIM-DB0C-2Q1J\nZIPIT-20-7H5I-CR00-9CUU\nZIPIT-20-ZK7N-TFV9-KG3H\nZIPIT-20-Y8PY-SFYO-6I1A\nZIPIT-20-3AAF-YU71-QE45\nZIPIT-20-DFHA-SOKC-TF95\nZIPIT-20-1C7X-5EZU-A8NB\nZIPIT-20-0GHC-O20C-7ISK\nZIPIT-20-58H5-7AVQ-EZFJ\nZIPIT-20-CPLB-693W-GI3C\nZIPIT-20-W96Q-72W4-Q2LR\nZIPIT-20-5ISG-I06F-88MR\nZIPIT-20-X3R7-TTNR-2WC6\nZIPIT-20-YF2H-A6RR-GRV8\nZIPIT-20-K8GB-4F2J-FQXM\nZIPIT-20-UBQ5-4OTL-KAXM\nZIPIT-20-RF9Y-V5C5-F9P3\nZIPIT-20-71RE-ZWCJ-X2T5\nZIPIT-20-A2FT-91T0-ZWXB\nZIPIT-20-3DHF-4K8Y-81V8\nZIPIT-20-1466-M357-PVP3\nZIPIT-20-LB0P-ESSK-BBMQ\nZIPIT-20-C1ML-ZZMI-FRKU\nZIPIT-20-HAU4-EV1U-6HOL\nZIPIT-20-WHCZ-ZN3A-WFYJ\nZIPIT-20-MJLC-I5WL-0TO7\nZIPIT-20-80SA-Z2R9-7AH9\nZIPIT-20-2STP-A6JQ-LA4H\nZIPIT-20-7H1U-ZA0G-37BJ\nZIPIT-20-25TA-0Q97-K0XW\nZIPIT-20-1HHE-BP9M-DBMT\nZIPIT-20-E24H-MB4G-WR7U\nZIPIT-20-QCTN-CGYS-3VDG\nZIPIT-20-AHXX-74AF-T9SB\nZIPIT-20-RHDX-ECRR-08DK |
+20
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>SEO Audit Report</title> | ||
| <style> | ||
| body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; color: #111827; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f9fafb; } | ||
| .msg { text-align: center; padding: 60px; } | ||
| .msg h2 { font-size: 20px; margin-bottom: 8px; } | ||
| .msg p { color: #6b7280; font-size: 14px; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="msg"> | ||
| <h2>ZipIt Audit Report</h2> | ||
| <p>Please use the <strong>"Download Full Audit Report (PDF)"</strong> button in the ZipIt side panel to generate your report.</p> | ||
| </div> | ||
| </body> | ||
| </html> |
+262
| // ===== ZipIt PDF Report Generator ===== | ||
| // Generates a complete self-contained HTML report from seo + lighthouse data. | ||
| // Called from popup.js — returns an HTML string ready to open as blob URL. | ||
| function generateReportHTML(seo, lighthouse, pageUrl) { | ||
| const lh = lighthouse || {}; | ||
| const audits = lh.audits || {}; | ||
| const cats = lh.categories || {}; | ||
| // Score extraction | ||
| let pScore = '-', sScore = '-', bScore = '-', aScore = '-'; | ||
| if (cats.performance?.score != null) pScore = Math.round(cats.performance.score * 100); | ||
| if (cats.seo?.score != null) sScore = Math.round(cats.seo.score * 100); | ||
| if (cats['best-practices']?.score != null) bScore = Math.round(cats['best-practices'].score * 100); | ||
| if (cats.accessibility?.score != null) aScore = Math.round(cats.accessibility.score * 100); | ||
| function scoreColor(v) { | ||
| if (v === '-') return '#6b7280'; | ||
| return v >= 90 ? '#22c55e' : v >= 50 ? '#eab308' : '#ef4444'; | ||
| } | ||
| function icon(type) { | ||
| if (type === 'pass') return '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.5"><polyline points="20 6 9 17 4 12"></polyline></svg>'; | ||
| if (type === 'warn') return '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#eab308" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>'; | ||
| return '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>'; | ||
| } | ||
| function esc(s) { return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); } | ||
| function badge(st) { | ||
| const colors = { pass: 'background:#dcfce7;color:#166534', warn: 'background:#fef9c3;color:#854d0e', fail: 'background:#fee2e2;color:#991b1b' }; | ||
| const labels = { pass: 'Pass', warn: 'Warning', fail: 'Fail' }; | ||
| return `<span style="padding:3px 10px;border-radius:12px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;${colors[st]}">${labels[st]}</span>`; | ||
| } | ||
| // Build audit rows from seo data (always available) | ||
| const rows = []; | ||
| // --- BASIC INFORMATION --- | ||
| const titleStatus = (seo.titleLength >= 30 && seo.titleLength <= 60) ? 'pass' : seo.titleLength > 0 ? 'warn' : 'fail'; | ||
| rows.push({ section: 'Basic Information', title: 'Page Title', value: esc(seo.title || '(none)'), detail: `${seo.titleLength || 0} characters (ideal: 30–60)`, status: titleStatus }); | ||
| const descStatus = (seo.descriptionLength >= 120 && seo.descriptionLength <= 160) ? 'pass' : seo.descriptionLength > 0 ? 'warn' : 'fail'; | ||
| rows.push({ section: 'Basic Information', title: 'Meta Description', value: seo.descriptionLength ? `${seo.descriptionLength} chars` : 'Missing', detail: seo.description ? esc(seo.description.slice(0,160)) : 'No meta description found. Add one between 120–160 characters.', status: descStatus }); | ||
| rows.push({ section: 'Basic Information', title: 'Canonical URL', value: esc(seo.canonical || 'Not set'), detail: '', status: seo.canonical ? 'pass' : 'warn' }); | ||
| const h1Status = seo.h1Count === 1 ? 'pass' : 'fail'; | ||
| rows.push({ section: 'Basic Information', title: 'Heading Structure', value: `H1: ${seo.h1Count||0} · H2: ${seo.h2Count||0} · H3: ${seo.h3Count||0}`, detail: seo.h1Count !== 1 ? `Found ${seo.h1Count||0} H1 tag(s). Pages should have exactly 1.` : '', status: h1Status }); | ||
| const altStatus = seo.imgMissingAlt === 0 ? 'pass' : 'fail'; | ||
| rows.push({ section: 'Basic Information', title: 'Image Alt Attributes', value: seo.imgMissingAlt === 0 ? `All ${seo.imgCount||0} images have alt text` : `${seo.imgMissingAlt} of ${seo.imgCount||0} missing alt`, detail: seo.imgMissingAlt > 0 ? 'Add descriptive alt text to all images for accessibility and SEO.' : '', status: altStatus }); | ||
| const wcStatus = (seo.wordCount || 0) >= 300 ? 'pass' : 'warn'; | ||
| rows.push({ section: 'Basic Information', title: 'Word Count', value: `${seo.wordCount || 0} words`, detail: (seo.wordCount||0) < 300 ? 'Content may be too thin. Aim for 300+ words.' : '', status: wcStatus }); | ||
| // --- CONTENT & STRUCTURED DATA --- | ||
| const hasOg = !!(seo.ogTitle || seo.ogDescription || seo.ogImage); | ||
| rows.push({ section: 'Content & Structured Data', title: 'Open Graph Tags', value: hasOg ? 'Present' : 'Missing', detail: hasOg ? `og:title: "${esc(seo.ogTitle)}"` : 'Add Open Graph meta tags for better social sharing.', status: hasOg ? 'pass' : 'warn' }); | ||
| rows.push({ section: 'Content & Structured Data', title: 'Schema.org Markup', value: seo.schemaOrg ? 'Detected' : 'Not found', detail: !seo.schemaOrg ? 'Add structured data (JSON-LD) for rich search results.' : '', status: seo.schemaOrg ? 'pass' : 'warn' }); | ||
| if (seo.author) { | ||
| rows.push({ section: 'Content & Structured Data', title: 'Author Meta Tag', value: esc(seo.author), detail: '', status: 'pass' }); | ||
| } | ||
| // --- META TAGS --- | ||
| if (seo.metaTags && seo.metaTags.length > 0) { | ||
| seo.metaTags.slice(0, 10).forEach(m => { | ||
| rows.push({ section: 'Meta Tags', title: esc(m.name), value: esc(m.content.slice(0, 120)), detail: '', status: 'pass' }); | ||
| }); | ||
| } | ||
| // --- TECHNICAL SEO --- | ||
| const isHttps = (pageUrl || '').startsWith('https'); | ||
| rows.push({ section: 'Technical SEO', title: 'HTTPS', value: isHttps ? 'Secure connection' : 'Not secure', detail: !isHttps ? 'Migrate to HTTPS for security and SEO ranking benefit.' : '', status: isHttps ? 'pass' : 'fail' }); | ||
| // Add Lighthouse audits to Technical SEO if available | ||
| const techKeys = ['robots-txt', 'is-crawlable', 'http-status-code', 'plugins', 'valid-source-maps']; | ||
| techKeys.forEach(k => { | ||
| const a = audits[k]; | ||
| if (!a) return; | ||
| const st = a.score === 1 ? 'pass' : a.score === null ? 'warn' : a.score < 0.5 ? 'fail' : 'warn'; | ||
| rows.push({ section: 'Technical SEO', title: a.title, value: a.displayValue || (st === 'pass' ? 'OK' : ''), detail: st !== 'pass' ? (a.description||'').replace(/\[([^\]]*)\]\([^)]*\)/g,'$1') : '', status: st }); | ||
| }); | ||
| // --- MOBILE USABILITY --- | ||
| const mobileKeys = ['viewport', 'meta-viewport', 'content-width', 'tap-targets']; | ||
| mobileKeys.forEach(k => { | ||
| const a = audits[k]; | ||
| if (!a) return; | ||
| const st = a.score === 1 ? 'pass' : a.score === null ? 'warn' : a.score < 0.5 ? 'fail' : 'warn'; | ||
| rows.push({ section: 'Mobile Usability', title: a.title, value: a.displayValue || '', detail: st !== 'pass' ? (a.description||'').replace(/\[([^\]]*)\]\([^)]*\)/g,'$1') : '', status: st }); | ||
| }); | ||
| // --- LINK ANALYSIS --- | ||
| const linkKeys = ['crawlable-anchors', 'link-text']; | ||
| linkKeys.forEach(k => { | ||
| const a = audits[k]; | ||
| if (!a) return; | ||
| const st = a.score === 1 ? 'pass' : a.score === null ? 'warn' : a.score < 0.5 ? 'fail' : 'warn'; | ||
| rows.push({ section: 'Link Analysis', title: a.title, value: a.displayValue || '', detail: st !== 'pass' ? (a.description||'').replace(/\[([^\]]*)\]\([^)]*\)/g,'$1') : '', status: st }); | ||
| }); | ||
| // --- PERFORMANCE --- | ||
| if (seo.performance) { | ||
| const p = seo.performance; | ||
| const fcpVal = parseFloat(p.fcp); | ||
| rows.push({ section: 'Performance & Speed', title: 'First Contentful Paint', value: `${p.fcp}s`, detail: fcpVal > 1.8 ? 'Optimize server response and reduce render-blocking resources.' : '', status: fcpVal <= 1.8 ? 'pass' : fcpVal <= 3 ? 'warn' : 'fail' }); | ||
| const lcpVal = parseFloat(p.lcp); | ||
| rows.push({ section: 'Performance & Speed', title: 'Largest Contentful Paint', value: `${p.lcp}s`, detail: lcpVal > 2.5 ? 'Optimize the largest visible element for faster loading.' : '', status: lcpVal <= 2.5 ? 'pass' : lcpVal <= 4 ? 'warn' : 'fail' }); | ||
| const clsVal = parseFloat(p.cls); | ||
| rows.push({ section: 'Performance & Speed', title: 'Cumulative Layout Shift', value: p.cls, detail: clsVal > 0.1 ? 'Set explicit dimensions on images/ads to avoid layout shifts.' : '', status: clsVal <= 0.1 ? 'pass' : clsVal <= 0.25 ? 'warn' : 'fail' }); | ||
| rows.push({ section: 'Performance & Speed', title: 'Total Blocking Time', value: `${p.tbt} ms`, detail: p.tbt > 200 ? 'Reduce JavaScript execution time on the main thread.' : '', status: p.tbt <= 200 ? 'pass' : p.tbt <= 600 ? 'warn' : 'fail' }); | ||
| rows.push({ section: 'Performance & Speed', title: 'Server Response Time', value: `${p.responseTime} ms`, detail: p.responseTime > 600 ? 'Optimize server configuration or use a CDN.' : '', status: p.responseTime <= 200 ? 'pass' : p.responseTime <= 600 ? 'warn' : 'fail' }); | ||
| } | ||
| // Lighthouse performance audits with resource tables | ||
| const perfKeys = ['unused-css-rules', 'unused-javascript', 'render-blocking-resources', 'uses-optimized-images', 'modern-image-formats', 'server-response-time', 'uses-text-compression']; | ||
| perfKeys.forEach(k => { | ||
| const a = audits[k]; | ||
| if (!a) return; | ||
| const st = a.score === 1 ? 'pass' : a.score === null ? 'warn' : a.score < 0.5 ? 'fail' : 'warn'; | ||
| let detail = st !== 'pass' ? (a.description||'').replace(/\[([^\]]*)\]\([^)]*\)/g,'$1') : ''; | ||
| // Add resource table for failing items | ||
| if (st !== 'pass' && a.details && a.details.items && a.details.items.length > 0) { | ||
| detail += '<table style="width:100%;border-collapse:collapse;font-family:monospace;font-size:11px;margin-top:8px;background:#fff;border:1px solid rgba(0,0,0,0.05);border-radius:4px;"><thead><tr><th style="padding:6px 10px;text-align:left;background:rgba(0,0,0,0.02);color:#6b7280;font-weight:600;border-bottom:1px solid rgba(0,0,0,0.05);">Resource</th><th style="padding:6px 10px;text-align:left;background:rgba(0,0,0,0.02);color:#6b7280;font-weight:600;border-bottom:1px solid rgba(0,0,0,0.05);">Savings</th></tr></thead><tbody>'; | ||
| a.details.items.slice(0, 6).forEach(item => { | ||
| const url = esc(String(item.url || (item.node && item.node.snippet) || item.label || item.source || '')); | ||
| if (!url) return; | ||
| let savings = ''; | ||
| if (item.wastedBytes != null) savings = `${(item.wastedBytes/1024).toFixed(1)} KiB`; | ||
| else if (item.wastedMs != null) savings = `${Math.round(item.wastedMs)} ms`; | ||
| else if (item.totalBytes != null) savings = `${(item.totalBytes/1024).toFixed(1)} KiB`; | ||
| detail += `<tr><td style="padding:6px 10px;border-bottom:1px solid rgba(0,0,0,0.03);max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${url}</td><td style="padding:6px 10px;border-bottom:1px solid rgba(0,0,0,0.03);white-space:nowrap;">${savings}</td></tr>`; | ||
| }); | ||
| detail += '</tbody></table>'; | ||
| if (a.details.items.length > 6) detail += `<div style="font-size:11px;color:#6b7280;margin-top:4px;">+ ${a.details.items.length - 6} more</div>`; | ||
| } | ||
| rows.push({ section: 'Performance & Speed', title: a.title, value: a.displayValue || '', detail: detail, status: st }); | ||
| }); | ||
| // --- HEADING OUTLINE --- | ||
| let headingsSection = ''; | ||
| if (seo.headings && seo.headings.length > 0) { | ||
| let headingRows = seo.headings.slice(0, 25).map(h => { | ||
| const indent = parseInt(h.tag.replace('h','')) - 1; | ||
| return `<div style="padding:6px 12px 6px ${12 + indent * 20}px;font-size:12px;border-bottom:1px solid #f3f4f6;display:flex;align-items:center;gap:8px;"><span style="background:#f3f4f6;color:#6b7280;padding:2px 6px;border-radius:4px;font-size:10px;font-weight:700;text-transform:uppercase;">${h.tag}</span><span style="color:#111827;">${esc(h.text)}</span></div>`; | ||
| }).join(''); | ||
| headingsSection = `<div style="margin-bottom:32px;"><div style="display:flex;align-items:center;gap:8px;font-size:16px;font-weight:700;color:#111827;margin-bottom:12px;padding-bottom:8px;border-bottom:2px solid #e5e7eb;"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 12h8"></path><path d="M4 18V6"></path><path d="M12 18V6"></path><path d="M21 18h-4c-.6 0-1-.4-1-1v-4c0-.6.4-1 1-1h4"></path></svg>Heading Outline</div><div style="border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;">${headingRows}</div></div>`; | ||
| } | ||
| // Group rows by section | ||
| const sections = []; | ||
| const sectionOrder = ['Basic Information', 'Content & Structured Data', 'Meta Tags', 'Technical SEO', 'Mobile Usability', 'Link Analysis', 'Performance & Speed']; | ||
| const sectionIcons = { | ||
| 'Basic Information': '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>', | ||
| 'Content & Structured Data': '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path></svg>', | ||
| 'Meta Tags': '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>', | ||
| 'Technical SEO': '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>', | ||
| 'Mobile Usability': '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"></rect><line x1="12" y1="18" x2="12.01" y2="18"></line></svg>', | ||
| 'Link Analysis': '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>', | ||
| 'Performance & Speed': '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>' | ||
| }; | ||
| sectionOrder.forEach(secName => { | ||
| const secRows = rows.filter(r => r.section === secName); | ||
| if (secRows.length === 0) return; | ||
| let itemsHtml = secRows.map(r => { | ||
| let detailBlock = ''; | ||
| if (r.detail && r.status !== 'pass') { | ||
| const bg = r.status === 'fail' ? 'background:#fef2f2;border:1px solid #fecaca;' : 'background:#fffbeb;border:1px solid #fde68a;'; | ||
| detailBlock = `<div style="margin-top:12px;padding:12px 16px;border-radius:8px;font-size:13px;${bg}"><div style="font-weight:700;margin-bottom:4px;font-size:12px;">Fix Recommendation</div><div style="color:#6b7280;">${r.detail}</div></div>`; | ||
| } | ||
| return `<div style="padding:14px 16px;border:1px solid #e5e7eb;border-radius:8px;margin-bottom:10px;background:#fff;break-inside:avoid;"><div style="display:flex;justify-content:space-between;align-items:center;"><div style="display:flex;align-items:center;gap:10px;">${icon(r.status)}<div><span style="font-size:13px;font-weight:600;color:#111827;">${r.title}</span><span style="font-weight:400;color:#6b7280;font-size:12px;margin-left:6px;">${r.value ? '— ' + r.value : ''}</span></div></div>${badge(r.status)}</div>${detailBlock}</div>`; | ||
| }).join(''); | ||
| sections.push(`<div style="margin-bottom:28px;"><div style="display:flex;align-items:center;gap:8px;font-size:16px;font-weight:700;color:#111827;margin-bottom:14px;padding-bottom:8px;border-bottom:2px solid #e5e7eb;">${sectionIcons[secName] || ''}${secName}</div>${itemsHtml}</div>`); | ||
| }); | ||
| const reportDate = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); | ||
| return `<!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>SEO Audit Report — ${esc(pageUrl)}</title> | ||
| <style> | ||
| @media print { | ||
| body { background: #fff !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; } | ||
| .container { box-shadow: none !important; padding: 20px !important; max-width: 100% !important; } | ||
| .no-print { display: none !important; } | ||
| } | ||
| body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; color: #111827; line-height: 1.5; margin: 0; padding: 0; background: #f9fafb; } | ||
| .container { max-width: 900px; margin: 0 auto; padding: 40px; background: #fff; min-height: 100vh; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05); } | ||
| .save-btn { display:inline-flex;align-items:center;gap:8px;background:linear-gradient(135deg,#e8521a 0%,#d4451a 100%);color:#fff;border:none;padding:14px 32px;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;box-shadow:0 8px 24px rgba(232,82,26,0.3);transition:all 0.2s ease;font-family:inherit; } | ||
| .save-btn:hover { transform:translateY(-1px);box-shadow:0 12px 32px rgba(232,82,26,0.4); } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="container"> | ||
| <div class="no-print" style="text-align:center;padding:16px 0 24px;"> | ||
| <button class="save-btn" onclick="window.print()"> | ||
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg> | ||
| Save as PDF | ||
| </button> | ||
| </div> | ||
| <div style="text-align:center;margin-bottom:36px;"> | ||
| <div style="display:flex;align-items:center;justify-content:center;gap:12px;margin-bottom:16px;"> | ||
| <img src="data:image/png;base64,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" alt="ZipIt" width="44" height="44" style="border-radius:10px;"> | ||
| <span style="font-size:26px;font-weight:800;color:#e8521a;">zipit.</span> | ||
| </div> | ||
| <div style="font-size:22px;font-weight:800;color:#111827;margin-bottom:6px;">Comprehensive SEO & Performance Audit</div> | ||
| <div style="font-size:13px;color:#6b7280;margin-bottom:20px;">${reportDate}</div> | ||
| <div style="display:inline-flex;align-items:center;background:#f9fafb;border:1px solid #e5e7eb;border-radius:24px;padding:8px 18px;font-size:13px;font-weight:600;color:#111827;gap:8px;"> | ||
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg> | ||
| ${esc(pageUrl)} | ||
| </div> | ||
| <div style="display:flex;justify-content:center;gap:20px;margin-top:24px;border:1px solid #e5e7eb;border-radius:16px;padding:20px 32px;"> | ||
| <div style="text-align:center;padding:0 20px;border-right:1px solid #e5e7eb;"> | ||
| <div style="font-size:28px;font-weight:800;color:${scoreColor(pScore)}">${pScore}</div> | ||
| <div style="font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.5px;">Performance</div> | ||
| </div> | ||
| <div style="text-align:center;padding:0 20px;border-right:1px solid #e5e7eb;"> | ||
| <div style="font-size:28px;font-weight:800;color:${scoreColor(sScore)}">${sScore}</div> | ||
| <div style="font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.5px;">SEO</div> | ||
| </div> | ||
| <div style="text-align:center;padding:0 20px;border-right:1px solid #e5e7eb;"> | ||
| <div style="font-size:28px;font-weight:800;color:${scoreColor(bScore)}">${bScore}</div> | ||
| <div style="font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.5px;">Best Practices</div> | ||
| </div> | ||
| <div style="text-align:center;padding:0 20px;"> | ||
| <div style="font-size:28px;font-weight:800;color:${scoreColor(aScore)}">${aScore}</div> | ||
| <div style="font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.5px;">Accessibility</div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ${sections.join('')} | ||
| ${headingsSection} | ||
| <div style="text-align:center;padding:24px 0;border-top:1px solid #e5e7eb;margin-top:32px;font-size:11px;color:#9ca3af;"> | ||
| Generated by ZipIt · ${reportDate} · ${esc(pageUrl)} | ||
| </div> | ||
| <div class="no-print" style="text-align:center;padding:20px 0 40px;"> | ||
| <button class="save-btn" onclick="window.print()"> | ||
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg> | ||
| Save as PDF | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </body> | ||
| </html>`; | ||
| } |
| Add-Type -AssemblyName "System.Drawing" | ||
| $iconPath = "c:\Users\Universe\Desktop\extension Update\zipit_extension_firefox\icons\icon16.png" | ||
| # Load original image | ||
| $img = [System.Drawing.Image]::FromFile($iconPath) | ||
| # Create a new square 16x16 bitmap | ||
| $bmp = New-Object System.Drawing.Bitmap(16, 16) | ||
| $g = [System.Drawing.Graphics]::FromImage($bmp) | ||
| # Draw image scaled to 16x16 | ||
| $g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic | ||
| $g.DrawImage($img, 0, 0, 16, 16) | ||
| # Clean up drawer and old image | ||
| $g.Dispose() | ||
| $img.Dispose() | ||
| # Save new icon | ||
| $bmp.Save($iconPath, [System.Drawing.Imaging.ImageFormat]::Png) | ||
| $bmp.Dispose() | ||
| Write-Host "icon16.png resized to perfect 16x16 square successfully!" |
Sorry, the diff of this file is not supported yet
+10
-15
| chrome.runtime.onInstalled.addListener(() => { | ||
| if (typeof browser !== 'undefined' && browser.sidebarAction) { | ||
| browser.sidebarAction.setPanel({ panel: 'popup.html' }).catch(() => {}); | ||
| } | ||
| // Firefox handles sidebar setup via manifest.json | ||
| }); | ||
@@ -65,9 +63,13 @@ | ||
| if (typeof browser !== 'undefined' && browser.sidebarAction) { | ||
| browser.sidebarAction.toggle() | ||
| .then(() => sendResponse({ success: true })) | ||
| .catch(() => sendResponse({ error: true })); | ||
| browser.sidebarAction.open().then(() => { | ||
| sendResponse({ success: true, opened: true }); | ||
| }).catch((e) => { | ||
| console.warn('[ZipIt] sidebarAction.open:', e.message); | ||
| sendResponse({ error: true, hint: 'click_icon' }); | ||
| }); | ||
| return true; | ||
| } else { | ||
| sendResponse({ error: true, hint: 'click_icon' }); | ||
| } | ||
| sendResponse({ error: true }); | ||
| return false; | ||
| return true; | ||
| } | ||
@@ -79,8 +81,1 @@ | ||
| chrome.tabs.onRemoved.addListener((tabId) => panelOpen.delete(tabId)); | ||
| // Toggle Firefox sidebar on toolbar icon click | ||
| if (typeof browser !== 'undefined' && browser.sidebarAction) { | ||
| browser.action.onClicked.addListener(() => { | ||
| browser.sidebarAction.toggle().catch(() => {}); | ||
| }); | ||
| } |
+3
-2
| { | ||
| "manifest_version": 3, | ||
| "name": "__MSG_appName__", | ||
| "version": "1.5", | ||
| "version": "2.0", | ||
| "description": "__MSG_appDesc__", | ||
@@ -26,2 +26,3 @@ "icons": { | ||
| "default_panel": "popup.html", | ||
| "default_title": "Open ZipIt", | ||
| "default_icon": { | ||
@@ -34,3 +35,3 @@ "16": "icons/icon16.png", | ||
| "action": { | ||
| "default_title": "Open ZipIt Sidebar", | ||
| "default_title": "Open ZipIt", | ||
| "default_icon": { | ||
@@ -37,0 +38,0 @@ "16": "icons/icon16.png", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display