diff --git a/cv-site b/cv-site deleted file mode 100755 index f44950a..0000000 Binary files a/cv-site and /dev/null differ diff --git a/static/js/main.js b/static/js/main.js index b5b8c05..b0995d2 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -577,8 +577,78 @@ initHTMXHandlers(); handleLandscapeAccordions(); // Auto-open sidebar accordions in landscape mode initZoomControlButtons(); + initScrollBehaviorJS(); // JavaScript fallback for scroll behavior }); + /** + * JavaScript-based scroll behavior (fallback for hyperscript) + * Desktop: Hide header on scroll down, show on scroll up + * Mobile (≤900px): Always show header (CSS handles this, but we skip adding class) + */ + function initScrollBehaviorJS() { + let lastScrollY = 0; + const scrollThreshold = 100; + const actionBar = document.querySelector('.action-bar'); + const navMenu = document.querySelector('.navigation-menu'); + + if (!actionBar) return; + + function isMobileViewport() { + return window.innerWidth <= 900; + } + + function handleScroll() { + const currentScrollY = window.scrollY || window.pageYOffset; + + // Skip header hiding logic on mobile - CSS override handles visibility + // but we still avoid adding the class for cleaner state + if (isMobileViewport()) { + // On mobile, always ensure header is visible + actionBar.classList.remove('header-hidden'); + if (navMenu) navMenu.classList.remove('header-hidden'); + lastScrollY = currentScrollY; + return; + } + + // At top of page - always show header + if (currentScrollY <= scrollThreshold) { + actionBar.classList.remove('header-hidden'); + if (navMenu && navMenu.classList.contains('menu-open')) { + navMenu.classList.remove('header-hidden'); + } + } + // Scrolling DOWN past threshold - hide header + else if (currentScrollY > lastScrollY && currentScrollY > scrollThreshold) { + actionBar.classList.add('header-hidden'); + if (navMenu && navMenu.classList.contains('menu-open')) { + navMenu.classList.add('header-hidden'); + } + } + // Scrolling UP - show header + else if (currentScrollY < lastScrollY) { + actionBar.classList.remove('header-hidden'); + if (navMenu && navMenu.classList.contains('menu-open')) { + navMenu.classList.remove('header-hidden'); + } + } + + lastScrollY = currentScrollY; + } + + // Use passive listener for better scroll performance + window.addEventListener('scroll', handleScroll, { passive: true }); + + // Also handle resize (viewport changes) + window.addEventListener('resize', function() { + if (isMobileViewport()) { + actionBar.classList.remove('header-hidden'); + if (navMenu) navMenu.classList.remove('header-hidden'); + } + }); + + console.log('Scroll behavior JS initialized'); + } + // ============================================================================= // HYPERSCRIPT-POWERED FEATURES (NO JS NEEDED) // ============================================================================= diff --git a/templates/index.html b/templates/index.html index bee13d1..fc2ec1f 100644 --- a/templates/index.html +++ b/templates/index.html @@ -141,9 +141,7 @@ { + if (msg.text().includes('Scroll behavior')) console.log('Console:', msg.text()); + }); + + console.log('1. Initial state:'); + let state = await page.evaluate(() => ({ + hidden: document.querySelector('.action-bar')?.classList.contains('header-hidden'), + scrollY: window.scrollY + })); + console.log(` ScrollY: ${state.scrollY}, Hidden: ${state.hidden}`); + + console.log('\n2. Scroll DOWN 500px:'); + await page.evaluate(() => window.scrollTo(0, 500)); + await page.waitForTimeout(300); + state = await page.evaluate(() => ({ + hidden: document.querySelector('.action-bar')?.classList.contains('header-hidden'), + scrollY: window.scrollY + })); + console.log(` ScrollY: ${state.scrollY}, Hidden: ${state.hidden}`); + console.log(state.hidden ? ' ✅ Bar hidden' : ' ❌ Bar should be hidden!'); + + console.log('\n3. Scroll UP to 300px:'); + await page.evaluate(() => window.scrollTo(0, 300)); + await page.waitForTimeout(300); + state = await page.evaluate(() => ({ + hidden: document.querySelector('.action-bar')?.classList.contains('header-hidden'), + scrollY: window.scrollY + })); + console.log(` ScrollY: ${state.scrollY}, Hidden: ${state.hidden}`); + console.log(!state.hidden ? ' ✅ Bar visible' : ' ❌ Bar should be visible!'); + + await ctx.close(); + + // Mobile test + console.log('\n4. Mobile (375px) - scroll down:'); + const mobileCtx = await browser.newContext({ + viewport: { width: 375, height: 667 }, + userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)', + hasTouch: true + }); + const mobilePage = await mobileCtx.newPage(); + await mobilePage.goto('http://localhost:1999/?lang=en'); + await mobilePage.waitForLoadState('networkidle'); + await mobilePage.waitForTimeout(500); + + await mobilePage.evaluate(() => window.scrollTo(0, 500)); + await mobilePage.waitForTimeout(300); + state = await mobilePage.evaluate(() => ({ + hidden: document.querySelector('.action-bar')?.classList.contains('header-hidden'), + transform: getComputedStyle(document.querySelector('.action-bar')).transform + })); + console.log(` Hidden class: ${state.hidden}, Transform: ${state.transform}`); + const isVisible = !state.hidden || state.transform === 'none' || state.transform.includes('0, 0)'); + console.log(isVisible ? ' ✅ Bar visible on mobile' : ' ❌ Bar should stay visible!'); + + await mobileCtx.close(); + await browser.close(); + console.log('\nDone.'); +} + +test().catch(console.error);