diff --git a/static/css/05-responsive/_breakpoints.css b/static/css/05-responsive/_breakpoints.css index 50218d9..da10b1b 100644 --- a/static/css/05-responsive/_breakpoints.css +++ b/static/css/05-responsive/_breakpoints.css @@ -665,3 +665,107 @@ } } + +/* ======================================== + Mobile: iOS-Style Button Bar with Blur + ======================================== */ + +@media (max-width: 540px) { + /* iOS-style blur bar behind fixed buttons */ + .fixed-buttons-backdrop { + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 90px; /* Enough to cover button area */ + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(20px) saturate(180%); + -webkit-backdrop-filter: blur(20px) saturate(180%); + border-top: 0.5px solid rgba(0, 0, 0, 0.1); + z-index: 98; /* Below buttons (99) but above content */ + pointer-events: none; /* Don't block button clicks */ + } + + /* Dark mode blur bar */ + [data-color-theme="dark"] .fixed-buttons-backdrop, + [data-color-theme="auto"] .fixed-buttons-backdrop { + background: rgba(30, 30, 30, 0.8); + border-top: 0.5px solid rgba(255, 255, 255, 0.1); + } +} + +/* ======================================== + Landscape Orientation Fixes + ======================================== */ + +@media (max-width: 915px) and (orientation: landscape) { + /* Reduce header size in landscape */ + .cv-header { + margin-bottom: 1rem !important; + } + + .cv-name { + font-size: 1.2rem !important; + } + + .years-experience { + font-size: 0.9em !important; + } + + /* Hide photo in landscape to save vertical space */ + .cv-photo { + display: none !important; + } + + /* Compact action bar */ + .action-bar { + padding: 0.5rem 1rem !important; + } + + /* Reduce sidebar padding */ + .cv-sidebar { + padding: 1rem !important; + } + + /* Compact sections */ + .section-title { + font-size: 1rem !important; + margin-bottom: 0.5rem !important; + } + + /* Reduce margins */ + .experience-item, + .project-item, + .course-item { + margin-bottom: 1rem !important; + } + + /* Make hamburger menu more accessible in landscape */ + .hamburger-menu { + position: fixed !important; + top: 10px !important; + left: 10px !important; + z-index: 1001 !important; + } + + /* Adjust fixed buttons for landscape */ + .fixed-btn { + bottom: 1rem !important; + width: 40px !important; + height: 40px !important; + } + + .back-to-top-btn { + right: 1rem !important; + } + + .shortcuts-btn { + left: 1rem !important; + bottom: 5rem !important; + } + + .zoom-toggle-btn { + left: 1rem !important; + bottom: 9rem !important; + } +} diff --git a/templates/index.html b/templates/index.html index 9cca6a7..14b2e33 100644 --- a/templates/index.html +++ b/templates/index.html @@ -168,6 +168,10 @@ {{template "error-toast" .}} {{template "pdf-toast" .}} + + +
+ {{template "back-to-top" .}} {{template "info-button" .}} {{template "download-button" .}} diff --git a/tests/mjs/48-mobile-landscape-and-blur-test.mjs b/tests/mjs/48-mobile-landscape-and-blur-test.mjs new file mode 100644 index 0000000..f855e47 --- /dev/null +++ b/tests/mjs/48-mobile-landscape-and-blur-test.mjs @@ -0,0 +1,117 @@ +#!/usr/bin/env node + +import { chromium } from 'playwright'; + +const PORTRAIT = { width: 375, height: 667 }; // iPhone SE portrait +const LANDSCAPE = { width: 667, height: 375 }; // iPhone SE landscape + +(async () => { + const browser = await chromium.launch({ headless: true }); + + console.log('๐Ÿงช Testing Mobile Portrait & Landscape Views\n'); + + // TEST 1: Portrait with Blur Bar + console.log('๐Ÿ“ฑ TEST 1: Portrait Mode with iOS Blur Bar\n'); + const portraitContext = await browser.newContext({ viewport: PORTRAIT }); + const portraitPage = await portraitContext.newPage(); + + await portraitPage.goto('http://localhost:1999/?lang=en&view=extended'); + await portraitPage.waitForLoadState('networkidle'); + + // Check for blur backdrop + const blurBackdrop = await portraitPage.locator('.fixed-buttons-backdrop'); + const backdropExists = await blurBackdrop.count() > 0; + + console.log(`๐Ÿ“Š Blur Backdrop:`); + console.log(` โ€ข Element exists: ${backdropExists ? 'โœ…' : 'โŒ'}`); + + if (backdropExists) { + const backdropStyles = await blurBackdrop.evaluate(el => { + const computed = window.getComputedStyle(el); + return { + position: computed.position, + bottom: computed.bottom, + height: computed.height, + backdropFilter: computed.backdropFilter, + background: computed.background, + borderTop: computed.borderTop, + zIndex: computed.zIndex + }; + }); + + console.log(` โ€ข Position: ${backdropStyles.position}`); + console.log(` โ€ข Bottom: ${backdropStyles.bottom}`); + console.log(` โ€ข Height: ${backdropStyles.height}`); + console.log(` โ€ข Backdrop filter: ${backdropStyles.backdropFilter}`); + console.log(` โ€ข Border top: ${backdropStyles.borderTop}`); + console.log(` โ€ข Z-index: ${backdropStyles.zIndex}`); + + const hasBlur = backdropStyles.backdropFilter.includes('blur'); + const isFixed = backdropStyles.position === 'fixed'; + const atBottom = backdropStyles.bottom === '0px'; + + console.log(`\nโœ… Blur Bar Check:`); + console.log(` โ€ข Fixed position: ${isFixed ? 'โœ…' : 'โŒ'}`); + console.log(` โ€ข At bottom: ${atBottom ? 'โœ…' : 'โŒ'}`); + console.log(` โ€ข Has blur effect: ${hasBlur ? 'โœ…' : 'โŒ'}`); + } + + await portraitPage.screenshot({ + path: 'tests/screenshots/mobile-portrait-blur-bar.png', + fullPage: true + }); + console.log('\n๐Ÿ“ธ Screenshot: tests/screenshots/mobile-portrait-blur-bar.png'); + + await portraitContext.close(); + + // TEST 2: Landscape Orientation + console.log('\n๐Ÿ“ฑ TEST 2: Landscape Mode\n'); + const landscapeContext = await browser.newContext({ viewport: LANDSCAPE }); + const landscapePage = await landscapeContext.newPage(); + + await landscapePage.goto('http://localhost:1999/?lang=en&view=extended'); + await landscapePage.waitForLoadState('networkidle'); + + // Check landscape-specific styles + const cvName = await landscapePage.locator('.cv-name'); + const cvPhoto = await landscapePage.locator('.cv-photo'); + const actionBar = await landscapePage.locator('.action-bar'); + + const nameStyles = await cvName.evaluate(el => { + const computed = window.getComputedStyle(el); + return { + fontSize: computed.fontSize, + marginBottom: computed.marginBottom + }; + }); + + const photoDisplay = await cvPhoto.evaluate(el => { + return window.getComputedStyle(el).display; + }); + + const actionBarPadding = await actionBar.evaluate(el => { + return window.getComputedStyle(el).padding; + }); + + console.log(`๐Ÿ“Š Landscape Optimizations:`); + console.log(` โ€ข Name font size: ${nameStyles.fontSize} (should be ~1.2rem)`); + console.log(` โ€ข Photo hidden: ${photoDisplay === 'none' ? 'โœ… YES' : 'โŒ NO'}`); + console.log(` โ€ข Action bar compact: ${actionBarPadding}`); + + const isOptimized = photoDisplay === 'none' && + parseFloat(nameStyles.fontSize) < 20; + + console.log(`\n${isOptimized ? 'โœ…' : 'โŒ'} Landscape mode ${isOptimized ? 'IS' : 'is NOT'} optimized`); + + await landscapePage.screenshot({ + path: 'tests/screenshots/mobile-landscape-optimized.png', + fullPage: true + }); + console.log('\n๐Ÿ“ธ Screenshot: tests/screenshots/mobile-landscape-optimized.png'); + + await landscapeContext.close(); + + console.log('\nโœ… All tests completed!\n'); + await browser.close(); + +})();