2c372eee49
**Social Links in Footer (Page 2):** - Replace address/phone with LinkedIn, GitHub, and Behance links - Maintain email@ link - All links are clickable and open in new tabs - Footer displays social media profiles prominently **Company Logo Toggle Feature:** - Add "Show logos" toggle switch in top action bar - Toggle displays company logos (48x48px) to the left of each experience item - LinkedIn-style layout when logos are shown - Logos hidden by default, optional display via toggle - Graceful fallback: missing logos don't break layout (onerror handler) - Logos directory created at static/images/logos/ with README **Technical Implementation:** - New CSS file: logo-toggle.css for toggle switch and logo layout - JavaScript: toggleLogos() function for show/hide functionality - Template updates: experience items now support flex layout with logos - Action bar grid updated to accommodate 4 columns - Logo display uses CSS class `.show-logos` on `.cv-paper` - Print CSS: logos hidden in PDF exports by default **User Experience:** - Clean toggle switch UI with smooth animations - Mobile responsive design - Accessibility: proper ARIA labels for toggle - Optional feature that doesn't clutter default view - Professional LinkedIn-style appearance when enabled Logos can be added to static/images/logos/ directory using filenames from the companyLogo field in CV JSON data.
193 lines
6.0 KiB
JavaScript
193 lines
6.0 KiB
JavaScript
const { chromium } = require('playwright');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
async function compareRendered() {
|
|
const browser = await chromium.launch({ headless: true });
|
|
|
|
console.log('\n=== COMPARING RENDERED SITES ===\n');
|
|
|
|
// OLD SITE
|
|
const pageOld = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
|
|
console.log('Loading OLD site (React)...');
|
|
await pageOld.goto('http://localhost:3000', { waitUntil: 'networkidle', timeout: 30000 });
|
|
|
|
// Wait for React to render
|
|
await pageOld.waitForTimeout(2000);
|
|
|
|
// Take screenshot
|
|
await pageOld.screenshot({
|
|
path: './tests/screenshots/old-full-rendered.png',
|
|
fullPage: true
|
|
});
|
|
|
|
// Get actual rendered content
|
|
const oldContent = await pageOld.evaluate(() => {
|
|
const app = document.getElementById('app') || document.body;
|
|
return {
|
|
innerHTML: app.innerHTML.substring(0, 2000),
|
|
hasContent: app.innerHTML.length > 100,
|
|
classes: Array.from(document.querySelectorAll('[class]')).map(el => el.className).filter(c => c).slice(0, 50)
|
|
};
|
|
});
|
|
|
|
console.log('OLD site content loaded:', oldContent.hasContent);
|
|
console.log('OLD site classes found:', oldContent.classes.length);
|
|
if (oldContent.classes.length > 0) {
|
|
console.log('Sample classes:', oldContent.classes.slice(0, 10));
|
|
}
|
|
|
|
// NEW SITE
|
|
const pageNew = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
|
|
console.log('\nLoading NEW site (Go+HTMX)...');
|
|
await pageNew.goto('http://localhost:1999', { waitUntil: 'networkidle' });
|
|
|
|
await pageNew.screenshot({
|
|
path: './tests/screenshots/new-full-rendered.png',
|
|
fullPage: true
|
|
});
|
|
|
|
// SIDE-BY-SIDE COMPARISON
|
|
console.log('\n=== HEADER BADGES COMPARISON ===\n');
|
|
|
|
// Try multiple selectors for old site
|
|
const oldBadgeSelectors = [
|
|
'[class*="badge"]',
|
|
'[class*="title"]',
|
|
'div[class*="cv"]',
|
|
'.badge',
|
|
'.title-badge'
|
|
];
|
|
|
|
let oldBadges = null;
|
|
for (const selector of oldBadgeSelectors) {
|
|
try {
|
|
const count = await pageOld.locator(selector).count();
|
|
if (count > 0) {
|
|
console.log(`Found ${count} elements with selector: ${selector}`);
|
|
oldBadges = await pageOld.$$eval(selector, elements =>
|
|
elements.slice(0, 5).map(el => {
|
|
const computed = window.getComputedStyle(el);
|
|
return {
|
|
tag: el.tagName,
|
|
class: el.className,
|
|
text: el.textContent?.substring(0, 50),
|
|
styles: {
|
|
fontSize: computed.fontSize,
|
|
fontWeight: computed.fontWeight,
|
|
color: computed.color,
|
|
backgroundColor: computed.backgroundColor,
|
|
padding: computed.padding,
|
|
height: computed.height
|
|
}
|
|
};
|
|
})
|
|
);
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
// Try next selector
|
|
}
|
|
}
|
|
|
|
const newBadges = await pageNew.$$eval('.title-badge', elements =>
|
|
elements.slice(0, 5).map(el => {
|
|
const computed = window.getComputedStyle(el);
|
|
return {
|
|
tag: el.tagName,
|
|
class: el.className,
|
|
text: el.textContent?.trim(),
|
|
styles: {
|
|
fontSize: computed.fontSize,
|
|
fontWeight: computed.fontWeight,
|
|
color: computed.color,
|
|
backgroundColor: computed.backgroundColor,
|
|
padding: computed.padding,
|
|
height: computed.height
|
|
}
|
|
};
|
|
})
|
|
);
|
|
|
|
console.log('\nOLD site badges:');
|
|
console.log(JSON.stringify(oldBadges, null, 2));
|
|
|
|
console.log('\nNEW site badges:');
|
|
console.log(JSON.stringify(newBadges, null, 2));
|
|
|
|
// VISUAL PIXEL COMPARISON
|
|
console.log('\n=== VISUAL COMPARISON ===\n');
|
|
|
|
// Get dimensions
|
|
const oldDimensions = await pageOld.evaluate(() => ({
|
|
width: document.documentElement.scrollWidth,
|
|
height: document.documentElement.scrollHeight
|
|
}));
|
|
|
|
const newDimensions = await pageNew.evaluate(() => ({
|
|
width: document.documentElement.scrollWidth,
|
|
height: document.documentElement.scrollHeight
|
|
}));
|
|
|
|
console.log('OLD site dimensions:', oldDimensions);
|
|
console.log('NEW site dimensions:', newDimensions);
|
|
|
|
// Screenshot specific sections
|
|
try {
|
|
// Header comparison
|
|
const oldHeader = pageOld.locator('header, [class*="header"], div').first();
|
|
const newHeader = pageNew.locator('.cv-title-badges-header').first();
|
|
|
|
if (await oldHeader.count() > 0) {
|
|
await oldHeader.screenshot({ path: './tests/screenshots/old-header-section.png' });
|
|
}
|
|
await newHeader.screenshot({ path: './tests/screenshots/new-header-section.png' });
|
|
|
|
// Sidebar comparison
|
|
const oldSidebar = pageOld.locator('[class*="sidebar"], aside').first();
|
|
const newSidebar = pageNew.locator('.cv-sidebar').first();
|
|
|
|
if (await oldSidebar.count() > 0) {
|
|
await oldSidebar.screenshot({ path: './tests/screenshots/old-sidebar-section.png' });
|
|
}
|
|
await newSidebar.screenshot({ path: './tests/screenshots/new-sidebar-section.png' });
|
|
} catch (e) {
|
|
console.log('Error capturing sections:', e.message);
|
|
}
|
|
|
|
// CREATE COMPARISON REPORT
|
|
const report = {
|
|
timestamp: new Date().toISOString(),
|
|
oldSite: {
|
|
url: 'http://localhost:3000',
|
|
hasContent: oldContent.hasContent,
|
|
classesFound: oldContent.classes.length,
|
|
dimensions: oldDimensions,
|
|
badges: oldBadges
|
|
},
|
|
newSite: {
|
|
url: 'http://localhost:1999',
|
|
dimensions: newDimensions,
|
|
badges: newBadges
|
|
},
|
|
comparison: {
|
|
dimensionsMatch: Math.abs(oldDimensions.width - newDimensions.width) < 50 &&
|
|
Math.abs(oldDimensions.height - newDimensions.height) < 50,
|
|
pixelPerfect: null // To be determined by visual inspection
|
|
}
|
|
};
|
|
|
|
fs.writeFileSync(
|
|
'./tests/screenshots/comparison-report.json',
|
|
JSON.stringify(report, null, 2)
|
|
);
|
|
|
|
console.log('\n✓ Comparison complete!');
|
|
console.log('✓ Screenshots saved to tests/screenshots/');
|
|
console.log('✓ Report saved to comparison-report.json');
|
|
|
|
await browser.close();
|
|
}
|
|
|
|
compareRendered().catch(console.error);
|