%3C/text%3E%3C/svg%3E" type="image/svg+xml">

PageSpeed Catalyst: Code Optimization Assistant

Analyze Your Website Performance

PageSpeed Catalyst transforms raw web performance data into actionable code improvements. Upload your Lighthouse JSON report or HAR (HTTP Archive) file to get started.

Drag & drop your Lighthouse JSON or HAR file here, or click to browse.

Optimization Results

Here are your personalized code snippets and suggestions to boost your Core Web Vitals and loading speed:

Critical CSS Optimization

Tip: Embed this CSS directly in your HTML head for instant styling of above-the-fold content.

Image Optimization & Lazy Loading

Tip: Ensure all images have `width` and `height` attributes to prevent layout shifts. Use WebP/AVIF formats.

Font Optimization

Tip: Preload critical fonts with `<link rel="preload" as="font" ...>` for faster rendering.

Eliminate Render-Blocking Resources

Tip: For non-critical JS, use `defer` or `async`. For CSS, split into critical and non-critical blocks.

`; return { website: website, score: score, issues: issues.join(', '), criticalCss: criticalCss, imageOptimization: imageOptimization, fontOptimization: fontOptimization, renderBlocking: renderBlocking, fullReport: `This is a mock full report generated from ${uploadedFile.name} with performance score ${score}. It would contain detailed metrics, diagnostics, and opportunities found in a real Lighthouse/HAR analysis. This would be a large JSON or PDF file.` }; } function displayResults(data) { document.getElementById('criticalCssSnippet').textContent = data.criticalCss; document.getElementById('imageOptimizationSnippet').textContent = data.imageOptimization; document.getElementById('fontOptimizationSnippet').textContent = data.fontOptimization; document.getElementById('renderBlockingSnippet').textContent = data.renderBlocking; } function showMessage(msg, type = 'info') { messageArea.textContent = msg; messageArea.className = `message ${type}`; messageArea.style.display = 'block'; } function hideMessage() { messageArea.style.display = 'none'; } // --- Copy to Clipboard --- document.querySelectorAll('.copy-button').forEach(button => { button.addEventListener('click', (e) => { const targetId = e.target.dataset.copyTarget; const snippetElement = document.getElementById(targetId); if (snippetElement) { const textToCopy = snippetElement.textContent; navigator.clipboard.writeText(textToCopy).then(() => { const originalText = e.target.textContent; e.target.textContent = 'Copied!'; setTimeout(() => { e.target.textContent = originalText; translatePage(); // Re-translate in case it was a specific copy message }, 1500); }).catch(err => { console.error('Failed to copy: ', err); }); } }); }); // --- Export & WhatsApp Integration --- document.getElementById('exportReportButton').addEventListener('click', () => { if (!isUnlocked) { showMessage(i18n[currentLang].exportFailedLocked, 'error'); showPaymentOverlay(); return; } if (analysisData && analysisData.fullReport) { const blob = new Blob([analysisData.fullReport], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'PageSpeed_Catalyst_Report.txt'; // Could be .json or .pdf document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } else { showMessage("No report data to export. Please run an analysis first.", 'error'); } }); document.getElementById('shareWhatsAppButton').addEventListener('click', () => { if (!isUnlocked) { showMessage(i18n[currentLang].exportFailedLocked, 'error'); // Using same message as export for simplicity showPaymentOverlay(); return; } if (analysisData) { const message = i18n[currentLang].shareWhatsappMessage .replace('{website}', analysisData.website || 'N/A') .replace('{score}', analysisData.score || 'N/A') .replace('{issues}', analysisData.issues || 'No specific issues identified.'); const whatsappUrl = `https://wa.me/${WHATSAPP_NUMBER}?text=${encodeURIComponent(message)}`; window.open(whatsappUrl, '_blank'); } else { showMessage("No analysis data to share. Please run an analysis first.", 'error'); } }); // --- Monetization Logic --- const paymentOverlay = document.getElementById('paymentOverlay'); const subscribeHubButton = document.getElementById('subscribeHubButton'); const buyOneTimeButton = document.getElementById('buyOneTimeButton'); const payCryptoButton = document.getElementById('payCryptoButton'); const firebaseAuthModal = document.getElementById('firebaseAuthModal'); const authForm = document.getElementById('authForm'); const authTitle = document.getElementById('authTitle'); const authEmail = document.getElementById('authEmail'); const authPassword = document.getElementById('authPassword'); const authActionButton = document.getElementById('authActionButton'); const googleSignInButton = document.getElementById('googleSignInButton'); const toggleAuthModeButton = document.getElementById('toggleAuthMode'); const authMessage = document.getElementById('authMessage'); const closeAuthModalButton = document.getElementById('closeAuthModal'); const cryptoPaymentModal = document.getElementById('cryptoPaymentModal'); const cryptoQrCode = document.getElementById('cryptoQrCode'); const cryptoAmountDisplay = document.getElementById('cryptoAmountDisplay'); const cryptoCurrencyDisplay = document.getElementById('cryptoCurrencyDisplay'); const cryptoAddressDisplay = document.getElementById('cryptoAddressDisplay'); const cryptoNetworkDisplay = document.getElementById('cryptoNetworkDisplay'); const cryptoPaymentStatus = document.getElementById('cryptoPaymentStatus'); const closeCryptoModal = document.getElementById('closeCryptoModal'); let isRegisterMode = false; let cryptoPaymentPollInterval; let currentAuthUser = null; function updateFreeUsesCounter() { if (isUnlocked) { freeUsesCounter.textContent = i18n[currentLang].unlimitedAccess; freeUsesCounter.style.display = 'block'; } else { const uses = getUsageCount(); const remaining = 3 - uses; if (remaining >= 0) { freeUsesCounter.textContent = i18n[currentLang].usesRemaining.replace('{count}', remaining); freeUsesCounter.style.display = 'block'; } else { freeUsesCounter.style.display = 'none'; // Hide if limit exceeded and overlay is shown } } } function getUsageCount() { return parseInt(localStorage.getItem(`pv_actions_${WIDGET_SLUG}`) || '0'); } function incrementUsageCounter() { if (isUnlocked) return; let uses = getUsageCount(); uses++; localStorage.setItem(`pv_actions_${WIDGET_SLUG}`, uses); updateFreeUsesCounter(); } function unlockWidgetLocally() { isUnlocked = true; localStorage.setItem(`pv_unlocked_${WIDGET_SLUG}`, 'true'); paymentOverlay.style.display = 'none'; paymentOverlay.classList.remove('active'); freeUsesCounter.textContent = i18n[currentLang].unlimitedAccess; freeUsesCounter.style.display = 'block'; console.log("Widget unlocked!"); } function showPaymentOverlay() { if (isUnlocked) return; paymentOverlay.style.display = 'flex'; // Trigger reflow to ensure transition works void paymentOverlay.offsetWidth; paymentOverlay.classList.add('active'); updatePaymentButtons(); } function updatePaymentButtons() { if (auth.currentUser) { subscribeHubButton.textContent = i18n[currentLang].subscribeHubButtonLoggedIn; } else { subscribeHubButton.textContent = i18n[currentLang].subscribeHubButton; } translatePage(); // Ensure all modal texts are current } async function checkAndUnlockWidget() { // 1. Check URL for Stripe success redirect const urlParams = new URLSearchParams(window.location.search); const stripeStatus = urlParams.get('status'); const stripeSessionId = urlParams.get('session_id'); const subscriptionStatus = urlParams.get('subscription_status'); if ((stripeStatus === 'success' || subscriptionStatus === 'success') && stripeSessionId) { loadingSpinner.style.display = 'block'; const verifyUrl = stripeStatus === 'success' ? `${PIXELOFFICE_API_BASE}verify-session?session_id=${stripeSessionId}` : `${PIXELOFFICE_API_BASE}verify-hub-subscription?session_id=${stripeSessionId}`; // Assuming a verify-hub-subscription endpoint try { const response = await fetch(verifyUrl); const data = await response.json(); if (data.success || data.active) { // Check both for one-time and subscription unlockWidgetLocally(); showMessage(i18n[currentLang].unlimitedAccess, 'success'); console.log("Payment verified, widget unlocked."); } else { showMessage("Payment verification failed. Please contact support.", 'error'); console.error("Payment verification failed:", data.message); } } catch (error) { console.error("Error during payment verification:", error); showMessage("Error verifying payment. Please contact support.", 'error'); } finally { loadingSpinner.style.display = 'none'; // Clean URL const newUrl = new URL(window.location.href); newUrl.searchParams.delete('status'); newUrl.searchParams.delete('session_id'); newUrl.searchParams.delete('subscription_status'); window.history.replaceState({}, document.title, newUrl.toString()); } } // 2. Check localStorage for existing unlock if (localStorage.getItem(`pv_unlocked_${WIDGET_SLUG}`) === 'true') { unlockWidgetLocally(); console.log("Widget unlocked from localStorage."); return; // No need to check Firebase if already unlocked } // 3. Check Firebase Auth for subscription (if not unlocked locally) if (firebaseAuthInitialized) { auth.onAuthStateChanged(async user => { currentAuthUser = user; updatePaymentButtons(); // Update button text based on login status if (user) { console.log("User logged in:", user.email); try { const response = await fetch(`${PIXELOFFICE_API_BASE}check-subscription?email=${encodeURIComponent(user.email)}`); const data = await response.json(); if (data.active) { unlockWidgetLocally(); showMessage(i18n[currentLang].unlimitedAccess, 'success'); console.log("User has active subscription, widget unlocked."); } else { console.log("User logged in but no active subscription."); } } catch (error) { console.error("Error checking subscription:", error); } } else { console.log("User not logged in."); } }); } // Finally, check usage limit after initial checks if (!isUnlocked && getUsageCount() >= 3) { showPaymentOverlay(); } else { paymentOverlay.style.display = 'none'; paymentOverlay.classList.remove('active'); } updateFreeUsesCounter(); } function setupPaymentListeners() { subscribeHubButton.addEventListener('click', async () => { if (!auth.currentUser) { showFirebaseAuthModal(); } else { // User is logged in, initiate Hub subscription loadingSpinner.style.display = 'block'; try { const response = await fetch(`${PIXELOFFICE_API_BASE}create-hub-subscription`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: auth.currentUser.email, userId: auth.currentUser.uid }) }); const data = await response.json(); if (data.url) { window.location.href = data.url; } else { showMessage("Failed to create Hub subscription. Please try again.", 'error'); console.error("Hub subscription error:", data.message); } } catch (error) { console.error("Error creating Hub subscription:", error); showMessage("Error creating Hub subscription. Please try again.", 'error'); } finally { loadingSpinner.style.display = 'none'; } } }); buyOneTimeButton.addEventListener('click', async () => { loadingSpinner.style.display = 'block'; try { const response = await fetch(`${PIXELOFFICE_API_BASE}create-session`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ slug: WIDGET_SLUG, amount: 199, // $1.99 widgetName: i18n[currentLang].widgetTitle }) }); const data = await response.json(); if (data.url) { window.location.href = data.url; } else { showMessage("Failed to create Stripe session. Please try again.", 'error'); console.error("Stripe session error:", data.message); } } catch (error) { console.error("Error creating Stripe session:", error); showMessage("Error creating Stripe session. Please try again.", 'error'); } finally { loadingSpinner.style.display = 'none'; } }); payCryptoButton.addEventListener('click', async () => { loadingSpinner.style.display = 'block'; try { const response = await fetch(`${PIXELOFFICE_API_BASE}request-crypto`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ slug: WIDGET_SLUG, amount: 199, // $1.99 widgetName: i18n[currentLang].widgetTitle }) }); const data = await response.json(); if (data.qrCodeUrl && data.amount && data.currency && data.address && data.network && data.paymentId) { showCryptoPaymentModal(data); startCryptoPolling(data.paymentId); } else { showMessage("Failed to initiate crypto payment. Please try again.", 'error'); console.error("Crypto payment initiation error:", data.message); } } catch (error) { console.error("Error initiating crypto payment:", error); showMessage("Error initiating crypto payment. Please try again.", 'error'); } finally { loadingSpinner.style.display = 'none'; } }); } // --- Firebase Auth Modal Logic --- function showFirebaseAuthModal() { firebaseAuthModal.style.display = 'flex'; void firebaseAuthModal.offsetWidth; // Trigger reflow firebaseAuthModal.classList.add('active'); isRegisterMode = false; updateAuthModalText(); authMessage.style.display = 'none'; } function hideFirebaseAuthModal() { firebaseAuthModal.classList.remove('active'); setTimeout(() => { firebaseAuthModal.style.display = 'none'; }, 300); // Wait for transition authEmail.value = ''; authPassword.value = ''; authMessage.style.display = 'none'; } function updateAuthModalText() { authTitle.textContent = isRegisterMode ? i18n[currentLang].registerTitle : i18n[currentLang].signInTitle; authActionButton.textContent = isRegisterMode ? i18n[currentLang].registerButton : i18n[currentLang].signInButton; toggleAuthModeButton.textContent = isRegisterMode ? i18n[currentLang].haveAccountSignIn : i18n[currentLang].noAccountRegister; authEmail.placeholder = i18n[currentLang].emailPlaceholder; authPassword.placeholder = i18n[currentLang].passwordPlaceholder; googleSignInButton.querySelector('span:last-child').textContent = i18n[currentLang].signInWithGoogle; closeAuthModalButton.textContent = i18n[currentLang].closeButton; } toggleAuthModeButton.addEventListener('click', () => { isRegisterMode = !isRegisterMode; updateAuthModalText(); authMessage.style.display = 'none'; // Clear messages on mode switch authEmail.value = ''; authPassword.value = ''; }); closeAuthModalButton.addEventListener('click', hideFirebaseAuthModal); authForm.addEventListener('submit', async (e) => { e.preventDefault(); const email = authEmail.value; const password = authPassword.value; if (!email || !password) { authMessage.textContent = i18n[currentLang].authError.replace('{message}', 'Email and password are required.'); authMessage.style.display = 'block'; return; } authActionButton.disabled = true; loadingSpinner.style.display = 'block'; // Use main spinner authMessage.style.display = 'none'; try { if (isRegisterMode) { await auth.createUserWithEmailAndPassword(email, password); console.log("Registered successfully!"); // After registration, directly attempt to subscribe to Hub await handleHubSubscriptionAfterAuth(email); } else { await auth.signInWithEmailAndPassword(email, password); console.log("Signed in successfully!"); // After sign-in, check subscription await checkSubscriptionAndUnlock(email); } hideFirebaseAuthModal(); } catch (error) { console.error("Auth error:", error); authMessage.textContent = i18n[currentLang].authError.replace('{message}', error.message); authMessage.style.display = 'block'; } finally { authActionButton.disabled = false; loadingSpinner.style.display = 'none'; } }); googleSignInButton.addEventListener('click', async () => { loadingSpinner.style.display = 'block'; authMessage.style.display = 'none'; try { const provider = new firebase.auth.GoogleAuthProvider(); await auth.signInWithPopup(provider); console.log("Google signed in successfully!"); await checkSubscriptionAndUnlock(auth.currentUser.email); // Check subscription immediately hideFirebaseAuthModal(); } catch (error) { console.error("Google Auth error:", error); authMessage.textContent = i18n[currentLang].authError.replace('{message}', error.message); authMessage.style.display = 'block'; } finally { loadingSpinner.style.display = 'none'; } }); async function checkSubscriptionAndUnlock(email) { try { const response = await fetch(`${PIXELOFFICE_API_BASE}check-subscription?email=${encodeURIComponent(email)}`); const data = await response.json(); if (data.active) { unlockWidgetLocally(); showMessage(i18n[currentLang].unlimitedAccess, 'success'); console.log("User has active subscription, widget unlocked."); } else { showMessage("You are signed in, but don't have an active Hub subscription. Please subscribe to unlock all features.", 'info'); // Optionally, show payment overlay with "Subscribe" button active showPaymentOverlay(); } } catch (error) { console.error("Error checking subscription:", error); showMessage("Error checking subscription. Please try again.", 'error'); } } async function handleHubSubscriptionAfterAuth(email) { // This is called after a successful registration or if a logged-in user clicks 'Subscribe Hub' loadingSpinner.style.display = 'block'; try { const response = await fetch(`${PIXELOFFICE_API_BASE}create-hub-subscription`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, userId: auth.currentUser.uid }) }); const data = await response.json(); if (data.url) { window.location.href = data.url; } else { showMessage("Failed to create Hub subscription. Please try again.", 'error'); console.error("Hub subscription error:", data.message); } } catch (error) { console.error("Error creating Hub subscription:", error); showMessage("Error creating Hub subscription. Please try again.", 'error'); } finally { loadingSpinner.style.display = 'none'; } } // --- Crypto Payment Modal Logic --- function showCryptoPaymentModal(data) { cryptoQrCode.src = data.qrCodeUrl; cryptoAmountDisplay.textContent = (data.amount / 100).toFixed(2); // Convert cents to dollars cryptoCurrencyDisplay.textContent = data.currency; cryptoAddressDisplay.textContent = data.address; cryptoNetworkDisplay.textContent = data.network; cryptoPaymentStatus.textContent = i18n[currentLang].cryptoPaymentStatusText; cryptoPaymentStatus.className = 'message info'; cryptoPaymentModal.style.display = 'flex'; void cryptoPaymentModal.offsetWidth; // Trigger reflow cryptoPaymentModal.classList.add('active'); paymentOverlay.style.display = 'none'; // Hide main payment overlay } function hideCryptoPaymentModal() { clearInterval(cryptoPaymentPollInterval); cryptoPaymentModal.classList.remove('active'); setTimeout(() => { cryptoPaymentModal.style.display = 'none'; }, 300); } closeCryptoModal.addEventListener('click', hideCryptoPaymentModal); // Copy crypto address document.querySelector('.crypto-info .copy-btn').addEventListener('click', (e) => { const targetId = e.target.dataset.copyTarget; const addressElement = document.getElementById(targetId); if (addressElement) { navigator.clipboard.writeText(addressElement.textContent).then(() => { const originalText = e.target.textContent; e.target.textContent = 'Copied!'; setTimeout(() => { e.target.textContent = originalText; translatePage(); }, 1500); }).catch(err => { console.error('Failed to copy crypto address: ', err); }); } }); function startCryptoPolling(paymentId) { // Poll every 5 seconds for payment status cryptoPaymentPollInterval = setInterval(async () => { try { const response = await fetch(`${PIXELOFFICE_API_BASE}verify-crypto?paymentId=${paymentId}`); const data = await response.json(); if (data.status === 'paid') { clearInterval(cryptoPaymentPollInterval); unlockWidgetLocally(); cryptoPaymentStatus.textContent = "Payment successful! Widget unlocked."; cryptoPaymentStatus.className = 'message success'; setTimeout(hideCryptoPaymentModal, 2000); // Hide after a short delay } else if (data.status === 'pending') { // Still pending, update message if needed cryptoPaymentStatus.textContent = i18n[currentLang].cryptoPaymentStatusText; cryptoPaymentStatus.className = 'message info'; } else if (data.status === 'expired' || data.status === 'failed') { clearInterval(cryptoPaymentPollInterval); cryptoPaymentStatus.textContent = "Payment failed or expired. Please try again."; cryptoPaymentStatus.className = 'message error'; } } catch (error) { console.error("Error polling crypto payment status:", error); // Handle network errors, but don't stop polling unless explicitly failed } }, 5000); }