fix: Override .has-tooltip position for theme switcher button
The theme switcher button was hidden above the viewport on desktop because .has-tooltip's position: relative was overriding the button's position: fixed due to CSS cascade order (_tooltips.css loaded after _themes.css). Fixed by adding !important to .color-theme-switcher position: fixed rule. Changes: - static/css/01-foundation/_themes.css: Add !important to position: fixed to override .has-tooltip position: relative cascade Testing: - Added comprehensive tooltip tests for all buttons (desktop & mobile) - Verified theme switcher visible on desktop at correct position - Verified all tooltips working on hover (desktop only, hidden on mobile touch) - Verified button positioning in mobile bottom dock All buttons now display correctly: ✅ Desktop: All 6 buttons with working tooltips ✅ Mobile: All 5 buttons in bottom dock
This commit is contained in:
@@ -181,7 +181,7 @@
|
|||||||
THEME SWITCHER BUTTON STYLES - Dynamic colors based on theme mode
|
THEME SWITCHER BUTTON STYLES - Dynamic colors based on theme mode
|
||||||
============================================================================== */
|
============================================================================== */
|
||||||
.color-theme-switcher {
|
.color-theme-switcher {
|
||||||
position: fixed;
|
position: fixed !important; /* Override .has-tooltip position: relative */
|
||||||
bottom: 14rem; /* Middle position - between print (18rem) and shortcuts (10rem) */
|
bottom: 14rem; /* Middle position - between print (18rem) and shortcuts (10rem) */
|
||||||
left: 2rem;
|
left: 2rem;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
|
|||||||
Executable
+135
@@ -0,0 +1,135 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch({ headless: false });
|
||||||
|
|
||||||
|
console.log('========================================');
|
||||||
|
console.log(' COMPREHENSIVE TOOLTIP TEST');
|
||||||
|
console.log(' Testing ALL buttons on desktop & mobile');
|
||||||
|
console.log('========================================\n');
|
||||||
|
|
||||||
|
// ========== DESKTOP TEST ==========
|
||||||
|
console.log('📱 DESKTOP TEST (1920x1080)');
|
||||||
|
console.log('─────────────────────────────\n');
|
||||||
|
|
||||||
|
const desktopPage = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
|
||||||
|
await desktopPage.goto('http://localhost:1999/');
|
||||||
|
await desktopPage.waitForLoadState('networkidle');
|
||||||
|
await desktopPage.waitForTimeout(1000);
|
||||||
|
|
||||||
|
const desktopButtons = [
|
||||||
|
{ id: 'download-button', name: 'Download PDF', expectedPosition: 'right' },
|
||||||
|
{ id: 'print-friendly-button', name: 'Print Friendly', expectedPosition: 'right' },
|
||||||
|
{ id: 'zoom-toggle-button', name: 'Zoom Toggle', expectedPosition: 'right' },
|
||||||
|
{ id: 'shortcuts-button', name: 'Keyboard Shortcuts', expectedPosition: 'right' },
|
||||||
|
{ id: 'color-theme-switcher', name: 'Theme Switcher', expectedPosition: 'right' },
|
||||||
|
{ id: 'info-button', name: 'Info Button', expectedPosition: 'right' },
|
||||||
|
{ id: 'back-to-top', name: 'Back to Top', expectedPosition: 'left' }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const btn of desktopButtons) {
|
||||||
|
const button = desktopPage.locator(`#${btn.id}`);
|
||||||
|
const exists = await button.count() > 0;
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
console.log(`❌ ${btn.name}: NOT FOUND in DOM`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isVisible = await button.isVisible();
|
||||||
|
console.log(`${isVisible ? '✅' : '❌'} ${btn.name}: ${isVisible ? 'Visible' : 'Not visible'}`);
|
||||||
|
|
||||||
|
if (isVisible) {
|
||||||
|
// Test tooltip
|
||||||
|
await button.hover();
|
||||||
|
await desktopPage.waitForTimeout(300);
|
||||||
|
|
||||||
|
const tooltip = await desktopPage.evaluate((id) => {
|
||||||
|
const btn = document.getElementById(id);
|
||||||
|
if (!btn) return null;
|
||||||
|
|
||||||
|
const computed = window.getComputedStyle(btn, '::before');
|
||||||
|
return {
|
||||||
|
opacity: computed.opacity,
|
||||||
|
visibility: computed.visibility,
|
||||||
|
content: computed.content,
|
||||||
|
position: computed.position
|
||||||
|
};
|
||||||
|
}, btn.id);
|
||||||
|
|
||||||
|
if (tooltip && parseFloat(tooltip.opacity) > 0.5) {
|
||||||
|
console.log(` 💬 Tooltip: VISIBLE (opacity: ${tooltip.opacity})`);
|
||||||
|
} else {
|
||||||
|
console.log(` ⚠️ Tooltip: NOT visible or low opacity`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await desktopPage.screenshot({
|
||||||
|
path: '/Users/txeo/Git/yo/cv/tests/mjs/screenshots/all-tooltips-desktop.png',
|
||||||
|
fullPage: false
|
||||||
|
});
|
||||||
|
console.log('\n📸 Desktop screenshot saved\n');
|
||||||
|
|
||||||
|
await desktopPage.close();
|
||||||
|
|
||||||
|
// ========== MOBILE TEST ==========
|
||||||
|
console.log('📱 MOBILE TEST (375x667 - iPhone SE)');
|
||||||
|
console.log('─────────────────────────────\n');
|
||||||
|
|
||||||
|
const mobilePage = await browser.newPage({ viewport: { width: 375, height: 667 } });
|
||||||
|
await mobilePage.goto('http://localhost:1999/');
|
||||||
|
await mobilePage.waitForLoadState('networkidle');
|
||||||
|
await mobilePage.waitForTimeout(1000);
|
||||||
|
|
||||||
|
const mobileButtons = [
|
||||||
|
{ id: 'download-button', name: 'Download PDF' },
|
||||||
|
{ id: 'print-friendly-button', name: 'Print Friendly' },
|
||||||
|
{ id: 'shortcuts-button', name: 'Keyboard Shortcuts' },
|
||||||
|
{ id: 'color-theme-switcher', name: 'Theme Switcher' },
|
||||||
|
{ id: 'info-button', name: 'Info Button' }
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('Checking button visibility and positions on mobile...\n');
|
||||||
|
|
||||||
|
for (const btn of mobileButtons) {
|
||||||
|
const button = mobilePage.locator(`#${btn.id}`);
|
||||||
|
const exists = await button.count() > 0;
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
console.log(`❌ ${btn.name}: NOT FOUND`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isVisible = await button.isVisible();
|
||||||
|
const box = isVisible ? await button.boundingBox() : null;
|
||||||
|
|
||||||
|
if (isVisible && box) {
|
||||||
|
// Check if button is in bottom dock (y > 500 for iPhone SE height 667)
|
||||||
|
const inBottomDock = box.y > 500;
|
||||||
|
console.log(`${isVisible ? '✅' : '❌'} ${btn.name}: Visible at y=${Math.round(box.y)} ${inBottomDock ? '(bottom dock)' : ''}`);
|
||||||
|
|
||||||
|
// Tooltips are hidden on mobile touch devices, so we don't test them
|
||||||
|
} else {
|
||||||
|
console.log(`❌ ${btn.name}: Not visible or no bounding box`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await mobilePage.screenshot({
|
||||||
|
path: '/Users/txeo/Git/yo/cv/tests/mjs/screenshots/all-tooltips-mobile.png',
|
||||||
|
fullPage: true
|
||||||
|
});
|
||||||
|
console.log('\n📸 Mobile screenshot saved\n');
|
||||||
|
|
||||||
|
await mobilePage.close();
|
||||||
|
|
||||||
|
// ========== SUMMARY ==========
|
||||||
|
console.log('========================================');
|
||||||
|
console.log(' TEST COMPLETE');
|
||||||
|
console.log('========================================');
|
||||||
|
console.log('\n✅ All tests completed successfully!');
|
||||||
|
console.log('📸 Screenshots saved to tests/mjs/screenshots/\n');
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
Executable
+48
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch({ headless: false });
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
await page.goto('http://localhost:1999/');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
// Get all CSS rules affecting the theme button
|
||||||
|
const cssRules = await page.evaluate(() => {
|
||||||
|
const btn = document.getElementById('color-theme-switcher');
|
||||||
|
if (!btn) return null;
|
||||||
|
|
||||||
|
const matchedRules = [];
|
||||||
|
|
||||||
|
// Get all stylesheets
|
||||||
|
for (const sheet of document.styleSheets) {
|
||||||
|
try {
|
||||||
|
const rules = sheet.cssRules || sheet.rules;
|
||||||
|
for (const rule of rules) {
|
||||||
|
if (rule.selectorText && btn.matches(rule.selectorText)) {
|
||||||
|
matchedRules.push({
|
||||||
|
selector: rule.selectorText,
|
||||||
|
position: rule.style.position || 'not set',
|
||||||
|
bottom: rule.style.bottom || 'not set',
|
||||||
|
left: rule.style.left || 'not set',
|
||||||
|
source: sheet.href || 'inline',
|
||||||
|
cssText: rule.cssText
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Skip CORS blocked stylesheets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchedRules;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📋 CSS Rules matching #color-theme-switcher:');
|
||||||
|
console.log(JSON.stringify(cssRules, null, 2));
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
Executable
+69
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch({ headless: false });
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
// Navigate to page
|
||||||
|
await page.goto('http://localhost:1999/');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
// Check if theme button exists
|
||||||
|
const themeButton = page.locator('#color-theme-switcher');
|
||||||
|
const exists = await themeButton.count() > 0;
|
||||||
|
console.log('✅ Theme button exists in DOM:', exists);
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
// Get computed styles
|
||||||
|
const isVisible = await themeButton.isVisible();
|
||||||
|
console.log('🔍 Button isVisible():', isVisible);
|
||||||
|
|
||||||
|
const box = await themeButton.boundingBox();
|
||||||
|
console.log('📦 Bounding box:', box);
|
||||||
|
|
||||||
|
// Get computed styles manually
|
||||||
|
const styles = await page.evaluate(() => {
|
||||||
|
const btn = document.getElementById('color-theme-switcher');
|
||||||
|
if (!btn) return null;
|
||||||
|
|
||||||
|
const computed = window.getComputedStyle(btn);
|
||||||
|
return {
|
||||||
|
display: computed.display,
|
||||||
|
visibility: computed.visibility,
|
||||||
|
opacity: computed.opacity,
|
||||||
|
position: computed.position,
|
||||||
|
bottom: computed.bottom,
|
||||||
|
left: computed.left,
|
||||||
|
width: computed.width,
|
||||||
|
height: computed.height,
|
||||||
|
background: computed.background,
|
||||||
|
backgroundColor: computed.backgroundColor,
|
||||||
|
zIndex: computed.zIndex,
|
||||||
|
transform: computed.transform,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🎨 Computed styles:', JSON.stringify(styles, null, 2));
|
||||||
|
|
||||||
|
// Check --black-bar variable
|
||||||
|
const blackBar = await page.evaluate(() => {
|
||||||
|
return getComputedStyle(document.documentElement).getPropertyValue('--black-bar');
|
||||||
|
});
|
||||||
|
console.log('🔧 --black-bar variable:', blackBar || 'NOT DEFINED');
|
||||||
|
|
||||||
|
// Take screenshot
|
||||||
|
await page.screenshot({
|
||||||
|
path: '/Users/txeo/Git/yo/cv/tests/mjs/screenshots/theme-button-debug.png',
|
||||||
|
fullPage: false
|
||||||
|
});
|
||||||
|
console.log('📸 Screenshot saved');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep browser open for manual inspection
|
||||||
|
console.log('\n⏸️ Browser kept open for inspection. Press Ctrl+C to close.');
|
||||||
|
await page.waitForTimeout(60000);
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 385 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 385 KiB |
Executable
+86
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch({ headless: false });
|
||||||
|
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
|
||||||
|
|
||||||
|
console.log('🔍 Testing theme button visibility on DESKTOP...\n');
|
||||||
|
|
||||||
|
await page.goto('http://localhost:1999/');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
// Check theme button
|
||||||
|
const themeButton = page.locator('#color-theme-switcher');
|
||||||
|
const exists = await themeButton.count() > 0;
|
||||||
|
console.log(exists ? '✅ Theme button exists in DOM' : '❌ Theme button NOT in DOM');
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
const isVisible = await themeButton.isVisible();
|
||||||
|
console.log(isVisible ? '✅ Theme button is VISIBLE' : '❌ Theme button is NOT visible');
|
||||||
|
|
||||||
|
const box = await themeButton.boundingBox();
|
||||||
|
console.log('\n📦 Bounding box:', box);
|
||||||
|
|
||||||
|
// Check if Y position is positive (on screen)
|
||||||
|
if (box && box.y > 0) {
|
||||||
|
console.log('✅ Button is ON SCREEN (y > 0)');
|
||||||
|
} else if (box && box.y < 0) {
|
||||||
|
console.log('❌ Button is ABOVE SCREEN (y < 0) - STILL BROKEN');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get computed position
|
||||||
|
const position = await page.evaluate(() => {
|
||||||
|
const btn = document.getElementById('color-theme-switcher');
|
||||||
|
const computed = window.getComputedStyle(btn);
|
||||||
|
return {
|
||||||
|
position: computed.position,
|
||||||
|
bottom: computed.bottom,
|
||||||
|
left: computed.left,
|
||||||
|
top: computed.top
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n🎨 Computed position:', position);
|
||||||
|
|
||||||
|
if (position.position === 'fixed') {
|
||||||
|
console.log('✅ Position is FIXED (correct!)');
|
||||||
|
} else {
|
||||||
|
console.log(`❌ Position is ${position.position} (should be fixed)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take screenshot
|
||||||
|
await page.screenshot({
|
||||||
|
path: '/Users/txeo/Git/yo/cv/tests/mjs/screenshots/theme-button-fixed.png',
|
||||||
|
fullPage: false
|
||||||
|
});
|
||||||
|
console.log('\n📸 Screenshot saved to screenshots/theme-button-fixed.png');
|
||||||
|
|
||||||
|
// Test tooltip
|
||||||
|
console.log('\n🎯 Testing tooltip...');
|
||||||
|
await themeButton.hover();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
|
const tooltipVisible = await page.evaluate(() => {
|
||||||
|
const btn = document.getElementById('color-theme-switcher');
|
||||||
|
const computed = window.getComputedStyle(btn, '::before');
|
||||||
|
return {
|
||||||
|
opacity: computed.opacity,
|
||||||
|
visibility: computed.visibility,
|
||||||
|
content: computed.content
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('💬 Tooltip state:', tooltipVisible);
|
||||||
|
if (parseFloat(tooltipVisible.opacity) > 0.5) {
|
||||||
|
console.log('✅ Tooltip is VISIBLE on hover');
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ Tooltip opacity is low or hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ Test complete - browser will close in 5 seconds');
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
await browser.close();
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user