Files
cv-site/tests/mjs/58-modal-centering-test.mjs
T
juanatsap 75efeb1474 fix: Perfect modal centering on mobile (portrait and landscape)
Fixes info modal positioning to be perfectly centered on mobile devices
in all orientations.

ISSUE:
- Info modal was not centered on mobile viewports
- User reported "pop-up of information in mobile it is not centered"
- Modal positioning relied on inset:0 + margin:auto which doesn't
  work consistently on mobile devices

FIX:
- Added explicit mobile centering using transform translate(-50%, -50%)
- Position: top: 50%, left: 50% with transform centering
- Applied to all modals: info, keyboard shortcuts, and PDF download
- Added mobile-specific fade-in animation preserving centering
- Constrained modal to viewport with calc(100vw - 2rem) width/height

Files modified:
- static/css/04-interactive/_modals.css - Mobile centering for all modals
- tests/mjs/58-modal-centering-test.mjs - Validation test

Test results:
 Portrait (375×667): Perfect center - 0px offset
 Landscape (667×375): Perfect center - 0px offset
 Modal center matches viewport center exactly
2025-11-25 05:15:23 +00:00

152 lines
6.1 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
import { chromium } from 'playwright';
const MOBILE_VIEWPORT = { width: 375, height: 667 };
const LANDSCAPE_VIEWPORT = { width: 667, height: 375 };
(async () => {
const browser = await chromium.launch({ headless: true });
console.log('🧪 Testing Modal Centering on Mobile\n');
// TEST 1: Portrait Mode
console.log('📱 TEST 1: Portrait Mode (375×667)\n');
const portraitContext = await browser.newContext({
viewport: MOBILE_VIEWPORT,
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1',
hasTouch: true
});
const portraitPage = await portraitContext.newPage();
await portraitPage.goto('http://localhost:1999/?lang=en&view=extended');
await portraitPage.waitForLoadState('networkidle');
// Click info button to open modal
await portraitPage.click('.info-button');
await portraitPage.waitForTimeout(500); // Wait for animation
const portraitModal = await portraitPage.evaluate(() => {
const modal = document.querySelector('.info-modal');
if (!modal) return null;
const rect = modal.getBoundingClientRect();
const viewport = {
width: window.innerWidth,
height: window.innerHeight
};
const modalCenterX = rect.left + rect.width / 2;
const modalCenterY = rect.top + rect.height / 2;
const viewportCenterX = viewport.width / 2;
const viewportCenterY = viewport.height / 2;
return {
viewport,
modal: {
left: Math.round(rect.left),
top: Math.round(rect.top),
width: Math.round(rect.width),
height: Math.round(rect.height),
centerX: Math.round(modalCenterX),
centerY: Math.round(modalCenterY)
},
viewportCenter: {
x: Math.round(viewportCenterX),
y: Math.round(viewportCenterY)
},
offsetX: Math.round(Math.abs(modalCenterX - viewportCenterX)),
offsetY: Math.round(Math.abs(modalCenterY - viewportCenterY)),
isCentered: Math.abs(modalCenterX - viewportCenterX) < 5 && Math.abs(modalCenterY - viewportCenterY) < 5
};
});
console.log('Portrait Modal:');
console.log(` • Viewport: ${portraitModal.viewport.width}×${portraitModal.viewport.height}`);
console.log(` • Modal size: ${portraitModal.modal.width}×${portraitModal.modal.height}`);
console.log(` • Modal center: (${portraitModal.modal.centerX}, ${portraitModal.modal.centerY})`);
console.log(` • Viewport center: (${portraitModal.viewportCenter.x}, ${portraitModal.viewportCenter.y})`);
console.log(` • Offset: X=${portraitModal.offsetX}px, Y=${portraitModal.offsetY}px`);
console.log(` • Is centered: ${portraitModal.isCentered ? '✅' : '❌'}\n`);
await portraitPage.screenshot({
path: 'tests/screenshots/modal-portrait.png',
fullPage: true
});
await portraitContext.close();
// TEST 2: Landscape Mode
console.log('📱 TEST 2: Landscape Mode (667×375)\n');
const landscapeContext = await browser.newContext({
viewport: LANDSCAPE_VIEWPORT,
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1',
hasTouch: true
});
const landscapePage = await landscapeContext.newPage();
await landscapePage.goto('http://localhost:1999/?lang=en&view=extended');
await landscapePage.waitForLoadState('networkidle');
// Click info button to open modal
await landscapePage.click('.info-button');
await landscapePage.waitForTimeout(500); // Wait for animation
const landscapeModal = await landscapePage.evaluate(() => {
const modal = document.querySelector('.info-modal');
if (!modal) return null;
const rect = modal.getBoundingClientRect();
const viewport = {
width: window.innerWidth,
height: window.innerHeight
};
const modalCenterX = rect.left + rect.width / 2;
const modalCenterY = rect.top + rect.height / 2;
const viewportCenterX = viewport.width / 2;
const viewportCenterY = viewport.height / 2;
return {
viewport,
modal: {
left: Math.round(rect.left),
top: Math.round(rect.top),
width: Math.round(rect.width),
height: Math.round(rect.height),
centerX: Math.round(modalCenterX),
centerY: Math.round(modalCenterY)
},
viewportCenter: {
x: Math.round(viewportCenterX),
y: Math.round(viewportCenterY)
},
offsetX: Math.round(Math.abs(modalCenterX - viewportCenterX)),
offsetY: Math.round(Math.abs(modalCenterY - viewportCenterY)),
isCentered: Math.abs(modalCenterX - viewportCenterX) < 5 && Math.abs(modalCenterY - viewportCenterY) < 5
};
});
console.log('Landscape Modal:');
console.log(` • Viewport: ${landscapeModal.viewport.width}×${landscapeModal.viewport.height}`);
console.log(` • Modal size: ${landscapeModal.modal.width}×${landscapeModal.modal.height}`);
console.log(` • Modal center: (${landscapeModal.modal.centerX}, ${landscapeModal.modal.centerY})`);
console.log(` • Viewport center: (${landscapeModal.viewportCenter.x}, ${landscapeModal.viewportCenter.y})`);
console.log(` • Offset: X=${landscapeModal.offsetX}px, Y=${landscapeModal.offsetY}px`);
console.log(` • Is centered: ${landscapeModal.isCentered ? '✅' : '❌'}\n`);
await landscapePage.screenshot({
path: 'tests/screenshots/modal-landscape.png',
fullPage: true
});
await landscapeContext.close();
const allPassed = portraitModal.isCentered && landscapeModal.isCentered;
console.log(`${allPassed ? '✅' : '❌'} Tests ${allPassed ? 'PASSED' : 'FAILED'}\n`);
await browser.close();
process.exit(allPassed ? 0 : 1);
})();