diff --git a/main.js b/main.js index e2ed0a7..0611af3 100644 --- a/main.js +++ b/main.js @@ -38,6 +38,7 @@ async function runTest() { console.log('Run Test triggered'); const urlInput = document.getElementById('test-url'); const url = urlInput.value.trim(); + const captureFilmstrip = document.getElementById('capture-filmstrip')?.checked || false; const errorMsg = document.getElementById('error-msg'); const resultsArea = document.getElementById('results-area'); @@ -67,7 +68,9 @@ async function runTest() { }, body: JSON.stringify({ url: url, - isMobile: currentDevice === 'mobile' + url: url, + isMobile: currentDevice === 'mobile', + captureFilmstrip: captureFilmstrip }) }); @@ -88,11 +91,53 @@ async function runTest() { function displayResults(data) { const resultsArea = document.getElementById('results-area'); - // Update Metrics - updateMetric('score-perf', Math.round(data.scores.performance), true); - updateMetric('metric-lcp', Math.round(data.metrics.lcp)); - updateMetric('metric-cls', data.metrics.cls.toFixed(3)); - updateMetric('metric-tbt', Math.round(data.metrics.tbt)); + // Calculate grades using grades.js if available, otherwise simplified logic + let overallGrade = 'F'; + let structureScore = 50; + + if (typeof calculateAllGrades === 'function') { + // Assume grades.js available + const grades = calculateAllGrades(data.metrics); + // Map average score to grade? Simplified: + // Use Performance Score as primary grade driver + } + + const perfScore = Math.round(data.scores.performance); + overallGrade = perfScore >= 90 ? 'A' : perfScore >= 80 ? 'B' : perfScore >= 70 ? 'C' : perfScore >= 60 ? 'D' : 'F'; + + // Structure Score (Average of non-perf categories) + structureScore = Math.round(( + (data.scores.seo || 0) + + (data.scores.bestPractices || data.scores['best-practices'] || 0) + + (data.scores.accessibility || 0) + ) / 3); + + // Update Dashboard UI + const gradeCircle = document.getElementById('overall-grade'); + const gradeLetter = gradeCircle.querySelector('.grade-letter'); + + // Animate Grade + gradeLetter.textContent = overallGrade; + gradeCircle.className = 'grade-circle grade-' + overallGrade.toLowerCase(); + + document.getElementById('performance-score').textContent = perfScore + '%'; + document.getElementById('structure-score').textContent = structureScore + '%'; + + // Web Vitals + const lcpVal = data.metrics.lcp < 1000 ? (data.metrics.lcp/1000).toFixed(2) + 's' : Math.round(data.metrics.lcp) + 'ms'; + const tbtVal = Math.round(data.metrics.tbt) + 'ms'; + const clsVal = data.metrics.cls.toFixed(2); + + document.getElementById('vital-lcp').textContent = lcpVal; + document.getElementById('vital-tbt').textContent = tbtVal; + document.getElementById('vital-cls').textContent = clsVal; + + // Display Filmstrip + if (data.filmstrip && data.filmstrip.length > 0) { + displayFilmstrip(data.filmstrip); + } else { + document.getElementById('filmstrip-section').style.display = 'none'; + } // Remove existing actions if any const existingActions = resultsArea.querySelector('.report-actions'); @@ -141,16 +186,20 @@ function displayResults(data) { resultsArea.scrollIntoView({ behavior: 'smooth' }); } -function updateMetric(id, value, isScore = false) { - const el = document.getElementById(id); - el.textContent = value; +function displayFilmstrip(items) { + const section = document.getElementById('filmstrip-section'); + const container = document.getElementById('filmstrip-container'); + section.style.display = 'block'; - if (isScore) { - el.className = 'metric-value'; // Reset - if (value >= 90) el.classList.add('score-good'); - else if (value >= 50) el.classList.add('score-average'); - else el.classList.add('score-poor'); - } + // Filter/Sample items if too many + const frames = items; + + container.innerHTML = frames.map(frame => ` +
+ Timestamp: ${frame.timing}ms +
${(frame.timing / 1000).toFixed(1)}s
+
+ `).join(''); } function showError(msg) { diff --git a/styles.css b/styles.css index 1e9f529..433eef9 100644 --- a/styles.css +++ b/styles.css @@ -116,6 +116,103 @@ body::before { z-index: 0; } +/* ========================================= + GTmetrix Dashboard + ========================================= */ +.gtmetrix-dashboard { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.dashboard-card { + background: var(--color-bg-tertiary); /* Darker internal card */ + padding: 1.5rem; + border-radius: 12px; + border: 1px solid var(--color-border); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 180px; +} + +.grade-card h3 { margin-bottom: 1rem; color: var(--color-text-secondary); font-size: 0.9rem; text-transform: uppercase; letter-spacing: 1px; } + +.grade-circle { + width: 100px; + height: 100px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 3.5rem; + font-weight: 800; + color: #fff; + background: #444; + transition: all 0.5s ease; + box-shadow: 0 4px 15px rgba(0,0,0,0.3); +} + +.grade-a { background: #0cce6b; box-shadow: 0 0 20px rgba(12, 206, 107, 0.4); } +.grade-b { background: #0ba759; } +.grade-c { background: #ffaa00; } +.grade-d { background: #ff4e00; } +.grade-f { background: #ff0000; } + +.scores-card { + flex-direction: row; + gap: 2rem; +} + +.score-item { text-align: center; } +.score-label { font-size: 0.9rem; color: var(--color-text-secondary); margin-bottom: 0.5rem; } +.score-value { font-size: 2.5rem; font-weight: 700; color: var(--color-text-primary); } +.score-divider { width: 1px; height: 60px; background: var(--color-border); } + +.vitals-card { align-items: stretch; } +.vitals-card h3 { text-align: center; margin-bottom: 1rem; font-size: 0.9rem; color: var(--color-text-secondary); text-transform: uppercase; } +.vitals-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem; + text-align: center; +} +.vital-label { font-size: 0.8rem; font-weight: 600; margin-bottom: 0.25rem; } +.vital-value { font-size: 1.2rem; font-weight: 500; color: var(--color-accent); } + +/* Filmstrip */ +.filmstrip-container { + display: flex; + overflow-x: auto; + gap: 0.5rem; + padding: 1rem 0; + scrollbar-width: thin; +} +.filmstrip-frame { + flex: 0 0 auto; + width: 100px; + text-align: center; +} +.filmstrip-frame img { + width: 100%; + height: auto; + border: 1px solid var(--color-border); + margin-bottom: 0.25rem; +} +.frame-time { font-size: 0.75rem; color: var(--color-text-secondary); } + +/* Checkbox */ +.checkbox-container { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; + font-size: 0.9rem; +} +.checkbox-container input { width: 16px; height: 16px; } + /* =================================== LAYOUT STRUCTURE =================================== */