feat: iOS-specific blur bar and hide keyboard shortcuts on real mobile devices

Issue 1: Blur bar compatibility (Android doesn't always show at bottom)
 Solution: Wrap blur bar in @supports query for backdrop-filter
- Only shows on devices that support backdrop-filter (primarily iOS)
- Android devices without support won't see the bar
- Prevents layout issues on non-iOS devices

Issue 2: Keyboard shortcuts button on real mobile (no physical keyboard)
 Solution: Device detection + conditional hiding
- Added device-detection.js: Detects real mobile vs desktop browser
- Checks user agent (Android, iPhone, iPad, etc.) + touch support
- Adds 'is-mobile-device' or 'is-desktop' class to <html>
- CSS hides shortcuts button only on real mobile devices
- Desktop browser in mobile view: shortcuts button still visible (for testing)

Implementation Details:
1. Device Detection (static/js/device-detection.js):
   - User agent detection: /Android|iPhone|iPad|etc./
   - Touch support check: ontouchstart + maxTouchPoints
   - Class added to <html>: is-mobile-device or is-desktop

2. Blur Bar (@supports query):
   - Detects backdrop-filter support before applying
   - iOS: Shows blur bar with backdrop-filter
   - Android (most): No blur bar (no backdrop-filter support)
   - Prevents empty/broken bar on incompatible devices

3. CSS Hiding Rules:
   - .is-mobile-device .shortcuts-btn { display: none !important; }
   - Also hides zoom-toggle-btn and zoom-control on real mobile
   - Desktop mobile view: shortcuts button remains visible

Files Modified:
- static/js/device-detection.js: NEW - Device detection logic
- templates/index.html: Load device-detection.js early
- static/css/05-responsive/_breakpoints.css: @supports wrapper for blur bar
- static/css/04-interactive/_scroll-behavior.css: Hide shortcuts on real mobile
- tests/mjs/52-mobile-device-detection-test.mjs: Comprehensive device detection test

Test Results:
 iPhone (real mobile): is-mobile-device class, shortcuts hidden
 Desktop browser (mobile view): is-desktop class, shortcuts visible
 Blur bar: Only shows on devices with backdrop-filter support
This commit is contained in:
juanatsap
2025-11-24 20:48:12 +00:00
parent 8bf48a1dd7
commit da81a0b148
5 changed files with 129 additions and 20 deletions
@@ -109,9 +109,16 @@
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
}
/* Hide keyboard shortcuts button on real mobile devices (no keyboard) */
.is-mobile-device .shortcuts-btn,
.is-mobile-device .zoom-toggle-btn,
.is-mobile-device .zoom-control {
display: none !important;
}
/* Mobile adjustments - Flexbox button layout at bottom center */
@media (max-width: 900px) {
/* Hide zoom control on mobile (keyboard shortcuts now visible) */
/* Hide zoom control on mobile (keyboard shortcuts now visible on desktop mobile view) */
.zoom-toggle-btn,
.zoom-control {
display: none !important;
+21 -19
View File
@@ -670,27 +670,29 @@
Mobile: iOS-Style Button Bar with Blur
======================================== */
/* iOS-style blur bar - Only show on devices that support backdrop-filter (primarily iOS) */
@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 */
}
@supports (backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px)) {
.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);
/* 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);
}
}
}
+20
View File
@@ -0,0 +1,20 @@
/**
* DEVICE DETECTION
* Detects real mobile devices vs desktop browser in mobile view
* Adds 'is-mobile-device' class to body for styling differences
*/
(function() {
// Check if user agent indicates a real mobile device
const isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
// Also check for touch support (additional indicator)
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
// Consider it a real mobile device if both conditions are met
if (isMobileDevice && hasTouch) {
document.documentElement.classList.add('is-mobile-device');
} else {
document.documentElement.classList.add('is-desktop');
}
})();
+3
View File
@@ -57,6 +57,9 @@
})();
</script>
<!-- Device Detection - Detect real mobile devices vs desktop browser -->
<script src="/static/js/device-detection.js"></script>
<!-- HTMX with SRI (Subresource Integrity) -->
<script src="https://unpkg.com/htmx.org@1.9.10"
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env node
import { chromium } from 'playwright';
const MOBILE_VIEWPORT = { width: 375, height: 667 };
(async () => {
const browser = await chromium.launch({ headless: true });
console.log('🧪 Testing Mobile Device Detection and Button Visibility\n');
// TEST 1: Simulate mobile device (iPhone)
console.log('📱 TEST 1: iPhone User Agent (Real Mobile Device)\n');
const mobileContext = 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 mobilePage = await mobileContext.newPage();
await mobilePage.goto('http://localhost:1999/?lang=en&view=extended');
await mobilePage.waitForLoadState('networkidle');
const mobileDeviceClass = await mobilePage.evaluate(() => {
return {
hasClass: document.documentElement.classList.contains('is-mobile-device'),
allClasses: document.documentElement.className
};
});
const shortcutsVisible = await mobilePage.locator('.shortcuts-btn').isVisible().catch(() => false);
console.log('Device Detection:');
console.log(` • has 'is-mobile-device' class: ${mobileDeviceClass.hasClass ? '✅' : '❌'}`);
console.log(` • All classes: ${mobileDeviceClass.allClasses}`);
console.log(`\nShortcuts Button:`);
console.log(` • Visible: ${shortcutsVisible ? '❌ WRONG' : '✅ HIDDEN (correct)'}`);
await mobileContext.close();
// TEST 2: Desktop browser in mobile view
console.log('\n🖥️ TEST 2: Desktop User Agent in Mobile View\n');
const desktopContext = await browser.newContext({
viewport: MOBILE_VIEWPORT,
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
hasTouch: false
});
const desktopPage = await desktopContext.newPage();
await desktopPage.goto('http://localhost:1999/?lang=en&view=extended');
await desktopPage.waitForLoadState('networkidle');
const desktopDeviceClass = await desktopPage.evaluate(() => {
return {
hasClass: document.documentElement.classList.contains('is-desktop'),
allClasses: document.documentElement.className
};
});
const shortcutsVisibleDesktop = await desktopPage.locator('.shortcuts-btn').isVisible().catch(() => false);
console.log('Device Detection:');
console.log(` • has 'is-desktop' class: ${desktopDeviceClass.hasClass ? '✅' : '❌'}`);
console.log(` • All classes: ${desktopDeviceClass.allClasses}`);
console.log(`\nShortcuts Button:`);
console.log(` • Visible: ${shortcutsVisibleDesktop ? '✅ VISIBLE (correct for testing)' : '❌ HIDDEN'}`);
await desktopContext.close();
const allPassed = mobileDeviceClass.hasClass && !shortcutsVisible &&
desktopDeviceClass.hasClass && shortcutsVisibleDesktop;
console.log(`\n${allPassed ? '✅' : '❌'} Tests ${allPassed ? 'PASSED' : 'FAILED'}\n`);
await browser.close();
process.exit(allPassed ? 0 : 1);
})();