From 1324975ec06b2ec34b92b764420e6baea69281c2 Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:09:32 -0800 Subject: [PATCH 1/4] made mobile UX a bit more better --- script.js | 137 +++++++++++++++++++++++++++++-------------------- styles.css | 146 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 179 insertions(+), 104 deletions(-) diff --git a/script.js b/script.js index ecc19f0..8e7b46c 100644 --- a/script.js +++ b/script.js @@ -1,6 +1,11 @@ -// Dark mode toggle +// Move these declarations to the very top of the file const darkModeToggle = document.getElementById('darkModeToggle'); +const menuToggle = document.getElementById('menuToggle'); +const sidebar = document.querySelector('.sidebar'); const body = document.body; +let touchStartX = 0; +let touchEndX = 0; +let touchStartY = 0; // Color theme definitions const colorThemes = { @@ -142,59 +147,82 @@ const colorThemes = { } }; -// Initialize theme from localStorage or system preference +// Initialize all UI controls document.addEventListener('DOMContentLoaded', () => { + // Dark mode initialization const savedTheme = localStorage.getItem('theme'); if (savedTheme) { body.dataset.theme = savedTheme; } else { - // Check system preference if no saved theme const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; body.dataset.theme = prefersDark ? 'dark' : 'light'; localStorage.setItem('theme', body.dataset.theme); } - // Update toggle button icon - darkModeToggle.innerHTML = body.dataset.theme === 'dark' - ? '' - : ''; + // Update dark mode toggle button icon + updateDarkModeIcon(); + // Dark mode toggle event listener + darkModeToggle.addEventListener('click', () => { + body.dataset.theme = body.dataset.theme === 'dark' ? 'light' : 'dark'; + localStorage.setItem('theme', body.dataset.theme); + updateDarkModeIcon(); + + // Reapply color theme when switching dark/light mode + const currentColorTheme = localStorage.getItem('colorTheme') || 'default'; + applyColorTheme(currentColorTheme); + }); + + // Color theme initialization const colorThemeSelect = document.getElementById('colorThemeSelect'); - - // Initialize theme from localStorage const savedColorTheme = localStorage.getItem('colorTheme') || 'default'; colorThemeSelect.value = savedColorTheme; applyColorTheme(savedColorTheme); - // Handle theme changes + // Color theme change event listener colorThemeSelect.addEventListener('change', (e) => { const selectedTheme = e.target.value; localStorage.setItem('colorTheme', selectedTheme); applyColorTheme(selectedTheme); }); + + // Test if marked is loaded + if (typeof marked === 'undefined') { + console.error('marked.js is not loaded!'); + document.getElementById('resources-container').innerHTML = ` +
+ Error: marked.js library is not loaded properly. +
`; + return; + } + + // If everything is working, proceed with main functionality + parseAndDisplayContent() + .then(() => console.log('Content successfully parsed and displayed')) + .catch(error => { + console.error('Error in main content processing:', error); + document.getElementById('resources-container').innerHTML = ` +
+ Error loading content: ${error.message} +
`; + }); }); -darkModeToggle.addEventListener('click', () => { - const newTheme = body.dataset.theme === 'dark' ? 'light' : 'dark'; - body.dataset.theme = newTheme; - localStorage.setItem('theme', newTheme); - - darkModeToggle.innerHTML = newTheme === 'dark' +// Helper function to update dark mode icon +function updateDarkModeIcon() { + darkModeToggle.innerHTML = body.dataset.theme === 'dark' ? '' : ''; +} + +function handleSwipe() { + const swipeThreshold = 100; + const swipeDistance = touchStartX - touchEndX; - // Reapply color theme with new dark/light mode - const currentColorTheme = localStorage.getItem('colorTheme') || 'default'; - applyColorTheme(currentColorTheme); -}); - -// Mobile menu toggle -const menuToggle = document.getElementById('menuToggle'); -const sidebar = document.querySelector('.sidebar'); - -menuToggle.addEventListener('click', () => { - sidebar.classList.toggle('active'); -}); + if (swipeDistance > swipeThreshold && sidebar.classList.contains('active')) { + sidebar.classList.remove('active'); + } +} // Search functionality const searchInput = document.getElementById('search'); @@ -655,33 +683,6 @@ function generateNavigation(sectionNames) { } } -// Remove the old fetch call and replace with this initialization -document.addEventListener('DOMContentLoaded', () => { - // Test if marked is loaded - if (typeof marked === 'undefined') { - console.error('marked.js is not loaded!'); - document.getElementById('resources-container').innerHTML = ` -
- Error: marked.js library is not loaded properly. -
`; - return; - } - - // Test marked with a simple markdown string - console.log('marked.js test:', marked.parse('# Test\nThis is a *test* of **marked.js**')); - - // If everything is working, proceed with main functionality - parseAndDisplayContent() - .then(() => console.log('Content successfully parsed and displayed')) - .catch(error => { - console.error('Error in main content processing:', error); - document.getElementById('resources-container').innerHTML = ` -
- Error loading content: ${error.message} -
`; - }); -}); - async function parseAndDisplayContent() { try { const response = await fetch('./README.md'); @@ -786,4 +787,28 @@ function applyColorTheme(themeName) { const cssVar = `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`; root.style.setProperty(cssVar, value); }); -} \ No newline at end of file +} + +// Mobile menu toggle functionality +menuToggle.addEventListener('click', () => { + sidebar.classList.toggle('active'); +}); + +// Optional: Close sidebar when clicking outside +document.addEventListener('click', (e) => { + if (window.innerWidth <= 768) { // Only on mobile + const isClickInsideSidebar = sidebar.contains(e.target); + const isClickOnMenuToggle = menuToggle.contains(e.target); + + if (!isClickInsideSidebar && !isClickOnMenuToggle && sidebar.classList.contains('active')) { + sidebar.classList.remove('active'); + } + } +}); + +// Optional: Close sidebar when pressing Escape key +document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && sidebar.classList.contains('active')) { + sidebar.classList.remove('active'); + } +}); \ No newline at end of file diff --git a/styles.css b/styles.css index 7e70744..327afec 100644 --- a/styles.css +++ b/styles.css @@ -51,6 +51,9 @@ body { border-right: 1px solid rgba(110, 84, 148, 0.15); box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05); top: 60px; /* Start below top nav */ + touch-action: pan-y; + will-change: transform; + -webkit-overflow-scrolling: touch; } .sidebar-header { @@ -254,12 +257,17 @@ body { /* Responsive Design */ @media (max-width: 768px) { .sidebar { - transform: translateX(-100%); + transform: translateX(-100%); /* Start off-screen */ + position: fixed; z-index: 1000; + touch-action: pan-y pinch-zoom; + will-change: transform; + transition: transform 0.3s ease; } - + .sidebar.active { transform: translateX(0); + box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); } .main-content { @@ -505,11 +513,11 @@ button:focus { margin-top: auto; } -/* Add top navigation styles */ +/* Update top navigation styles */ .top-nav { background-color: var(--primary-color); color: white; - padding: 1rem; + padding: 0.75rem; position: fixed; top: 0; left: 0; @@ -571,12 +579,10 @@ button:focus { } .logo-container { - height: 50px; - width: 200px; /* Adjust this value to your preferred width */ + height: 40px; + width: 160px; display: flex; align-items: center; - justify-content: center; - overflow: hidden; } .nav-logo { @@ -585,61 +591,99 @@ button:focus { object-fit: cover; /* This will maintain aspect ratio while fitting within the container */ } -/* Adjust container size for mobile */ +/* Update responsive styles */ @media (max-width: 768px) { + .nav-content { + padding: 0 3.5rem; /* Make space for menu toggle */ + } + + .menu-toggle { + top: 12px; /* Adjust position to align with nav */ + left: 12px; + } + + /* Adjust logo size for mobile */ .logo-container { - height: 40px; - width: 160px; /* Proportionally smaller for mobile */ + height: 32px; + width: 120px; } - - .nav-content h1 { - width: auto; - text-align: left; + + /* Make search more compact on mobile */ + .search-box { + max-width: 160px; + } + + .search-box input { + padding: 0.4rem 1.8rem 0.4rem 0.8rem; + font-size: 0.9rem; + } + + /* Adjust theme controls for mobile */ + .theme-controls { + display: flex; + gap: 0.5rem; + } + + .theme-select { + max-width: 100px; + font-size: 0.8rem; + padding: 0.3rem; + } + + .theme-toggle { + padding: 0.3rem; + font-size: 1rem; } } -.sidebar h2:first-of-type { - margin-top: 80px; /* Increased spacing from top nav */ - font-weight: 400; /* Reduced from default bold/600 to normal/400 */ -} +/* Extra small screens */ +@media (max-width: 480px) { + .nav-content { + flex-wrap: wrap; + gap: 0.5rem; + padding: 0 3rem; + } -/* Adjust for mobile if needed */ -@media (max-width: 768px) { - .sidebar h2:first-of-type { - margin-top: 70px; /* Slightly less space on mobile */ + .logo-container { + order: 1; + height: 28px; + width: 100px; + } + + .search-box { + order: 3; + width: 100%; + max-width: none; + margin-top: 0.3rem; + } + + .theme-controls { + order: 2; + } + + .theme-select { + max-width: 90px; } } -/* Update menu toggle button styles */ +/* Update menu toggle position */ .menu-toggle { display: none; - background: none; - border: none; - color: var(--text-color); - font-size: 1.2rem; /* Reduced from 1.5rem */ - cursor: pointer; - padding: 0.4rem 0.6rem; /* Adjusted padding */ position: fixed; - top: 15px; /* Align with top nav content */ - left: 15px; + top: 12px; + left: 12px; z-index: 1002; background-color: var(--primary-color); - border-radius: 4px; color: white; + border: none; + border-radius: 4px; + padding: 0.4rem 0.6rem; + cursor: pointer; + font-size: 1.1rem; box-shadow: 0 1px 3px rgba(0,0,0,0.2); transition: all 0.2s ease; } -.menu-toggle:hover { - background-color: var(--hover-color); - transform: scale(1.05); -} - -.menu-toggle:active { - transform: scale(0.95); -} - -/* Update mobile responsive styles */ @media (max-width: 768px) { .menu-toggle { display: flex; @@ -647,12 +691,18 @@ button:focus { justify-content: center; } - /* Adjust top nav padding to accommodate menu toggle */ - .nav-content { - padding-left: 3.5rem; /* Make room for menu toggle */ + .sidebar { + transform: translateX(-100%); } - /* Rest of your mobile styles... */ + .sidebar.active { + transform: translateX(0); + } + + .main-content { + margin-left: 0; + width: 100%; + } } /* Theme selector styles */ @@ -697,4 +747,4 @@ button:focus { font-size: 0.8rem; padding: 0.3rem 0.6rem; } -} \ No newline at end of file +} \ No newline at end of file From 82c34be52bad1a385b9826fc5c9d9a5dd1cfdc15 Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:12:52 -0800 Subject: [PATCH 2/4] more familiar mobile touch XP --- script.js | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ styles.css | 32 ++++++++++++++++++++- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/script.js b/script.js index 8e7b46c..9298b3a 100644 --- a/script.js +++ b/script.js @@ -6,6 +6,8 @@ const body = document.body; let touchStartX = 0; let touchEndX = 0; let touchStartY = 0; +let touchEndY = 0; +let isSwiping = false; // Color theme definitions const colorThemes = { @@ -206,6 +208,87 @@ document.addEventListener('DOMContentLoaded', () => { Error loading content: ${error.message} `; }); + + // Enhanced touch handling for sidebar + touchStartX = 0; + touchStartY = 0; + touchEndX = 0; + touchEndY = 0; + isSwiping = false; + + // Track touch movements + document.addEventListener('touchstart', (e) => { + touchStartX = e.touches[0].clientX; + touchStartY = e.touches[0].clientY; + isSwiping = true; + }, { passive: true }); + + document.addEventListener('touchmove', (e) => { + if (!isSwiping) return; + + touchEndX = e.touches[0].clientX; + touchEndY = e.touches[0].clientY; + + // Calculate vertical and horizontal distance + const deltaX = touchStartX - touchEndX; + const deltaY = Math.abs(touchStartY - touchEndY); + + // If vertical scrolling is more prominent, don't handle swipe + if (deltaY > Math.abs(deltaX)) { + isSwiping = false; + return; + } + + // Prevent default only if horizontal swipe is significant + if (Math.abs(deltaX) > 10) { + e.preventDefault(); + } + }, { passive: false }); + + document.addEventListener('touchend', () => { + if (!isSwiping) return; + + const deltaX = touchStartX - touchEndX; + const deltaY = Math.abs(touchStartY - touchEndY); + const swipeThreshold = 50; + + // Only handle horizontal swipes + if (Math.abs(deltaX) > swipeThreshold && deltaY < 100) { + if (deltaX > 0) { + // Swipe left - close sidebar + sidebar.classList.remove('active'); + } else { + // Swipe right - open sidebar + sidebar.classList.add('active'); + } + } + + isSwiping = false; + }, { passive: true }); + + // Update sidebar link click handling + document.querySelectorAll('.nav-links a').forEach(link => { + link.addEventListener('click', (e) => { + e.preventDefault(); + + // Remove active class from all links + document.querySelectorAll('.nav-links a').forEach(l => + l.classList.remove('active') + ); + + // Add active class to clicked link + link.classList.add('active'); + + // Get section name and display it + const section = link.getAttribute('data-section'); + displaySection(section, window.parsedResources); + + // Close sidebar on mobile + if (window.innerWidth <= 768) { + sidebar.classList.remove('active'); + } + }); + }); }); // Helper function to update dark mode icon diff --git a/styles.css b/styles.css index 327afec..49c9af4 100644 --- a/styles.css +++ b/styles.css @@ -51,9 +51,10 @@ body { border-right: 1px solid rgba(110, 84, 148, 0.15); box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05); top: 60px; /* Start below top nav */ - touch-action: pan-y; + touch-action: pan-y pinch-zoom; will-change: transform; -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; } .sidebar-header { @@ -161,6 +162,8 @@ body { min-height: 100vh; overflow: auto; margin-top: 0; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; } .content-header { @@ -299,6 +302,33 @@ body { max-width: none; margin-top: 0.5rem; } + + .nav-links a { + padding: 0.75rem 1rem; /* Larger touch target */ + min-height: 44px; /* iOS recommended minimum */ + } + + .theme-toggle, + .menu-toggle { + min-width: 44px; + min-height: 44px; + display: flex; + align-items: center; + justify-content: center; + } + + /* Improve search input touch target */ + .search-box input { + min-height: 44px; + } + + /* Add active state styles for touch feedback */ + .nav-links a:active, + .theme-toggle:active, + .menu-toggle:active { + opacity: 0.7; + transition: opacity 0.1s; + } } .markdown-content { From 0c5371521890fd9c96679873dd8d78339ba8247c Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:18:20 -0800 Subject: [PATCH 3/4] smoother touch functionality --- script.js | 157 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 87 insertions(+), 70 deletions(-) diff --git a/script.js b/script.js index 9298b3a..b309b32 100644 --- a/script.js +++ b/script.js @@ -209,85 +209,126 @@ document.addEventListener('DOMContentLoaded', () => { `; }); - // Enhanced touch handling for sidebar - touchStartX = 0; - touchStartY = 0; - touchEndX = 0; - touchEndY = 0; - isSwiping = false; + // Ensure we're working with valid elements + if (!sidebar || !menuToggle) { + console.error('Required elements not found'); + return; + } - // Track touch movements - document.addEventListener('touchstart', (e) => { + // Single function to handle sidebar state + const toggleSidebar = (show) => { + if (show === undefined) { + sidebar.classList.toggle('active'); + } else { + sidebar.classList[show ? 'add' : 'remove']('active'); + } + // Update aria-expanded state + menuToggle.setAttribute('aria-expanded', sidebar.classList.contains('active')); + }; + + // Menu toggle click handler + menuToggle.addEventListener('click', (e) => { + e.stopPropagation(); + toggleSidebar(); + }); + + // Delegate sidebar link clicks using event delegation + document.querySelector('.nav-links').addEventListener('click', (e) => { + const link = e.target.closest('a'); + if (!link) return; + + e.preventDefault(); + + // Remove active class from all links + document.querySelectorAll('.nav-links a').forEach(l => + l.classList.remove('active') + ); + + // Add active class to clicked link + link.classList.add('active'); + + // Get section name and display it + const section = link.getAttribute('data-section'); + displaySection(section, window.parsedResources); + + // Close sidebar on mobile + if (window.innerWidth <= 768) { + toggleSidebar(false); + } + }); + + // Touch handling + let touchStartX = 0; + let touchStartY = 0; + let touchEndX = 0; + let touchEndY = 0; + let isSwiping = false; + + const handleTouchStart = (e) => { touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; isSwiping = true; - }, { passive: true }); + }; - document.addEventListener('touchmove', (e) => { + const handleTouchMove = (e) => { if (!isSwiping) return; touchEndX = e.touches[0].clientX; touchEndY = e.touches[0].clientY; - // Calculate vertical and horizontal distance const deltaX = touchStartX - touchEndX; const deltaY = Math.abs(touchStartY - touchEndY); - // If vertical scrolling is more prominent, don't handle swipe if (deltaY > Math.abs(deltaX)) { isSwiping = false; return; } - // Prevent default only if horizontal swipe is significant if (Math.abs(deltaX) > 10) { e.preventDefault(); } - }, { passive: false }); + }; - document.addEventListener('touchend', () => { + const handleTouchEnd = () => { if (!isSwiping) return; const deltaX = touchStartX - touchEndX; const deltaY = Math.abs(touchStartY - touchEndY); const swipeThreshold = 50; - // Only handle horizontal swipes if (Math.abs(deltaX) > swipeThreshold && deltaY < 100) { - if (deltaX > 0) { - // Swipe left - close sidebar - sidebar.classList.remove('active'); - } else { - // Swipe right - open sidebar - sidebar.classList.add('active'); - } + toggleSidebar(deltaX < 0); } isSwiping = false; - }, { passive: true }); + }; - // Update sidebar link click handling - document.querySelectorAll('.nav-links a').forEach(link => { - link.addEventListener('click', (e) => { - e.preventDefault(); - - // Remove active class from all links - document.querySelectorAll('.nav-links a').forEach(l => - l.classList.remove('active') - ); - - // Add active class to clicked link - link.classList.add('active'); - - // Get section name and display it - const section = link.getAttribute('data-section'); - displaySection(section, window.parsedResources); - - // Close sidebar on mobile - if (window.innerWidth <= 768) { - sidebar.classList.remove('active'); + // Add touch event listeners + document.addEventListener('touchstart', handleTouchStart, { passive: true }); + document.addEventListener('touchmove', handleTouchMove, { passive: false }); + document.addEventListener('touchend', handleTouchEnd, { passive: true }); + + // Close sidebar when clicking outside + document.addEventListener('click', (e) => { + if (window.innerWidth <= 768 && + !sidebar.contains(e.target) && + !menuToggle.contains(e.target) && + sidebar.classList.contains('active')) { + toggleSidebar(false); + } + }); + + // Handle window resize + let resizeTimer; + window.addEventListener('resize', () => { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(() => { + if (window.innerWidth > 768) { + toggleSidebar(true); + } else { + toggleSidebar(false); } - }); + }, 250); }); }); @@ -870,28 +911,4 @@ function applyColorTheme(themeName) { const cssVar = `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`; root.style.setProperty(cssVar, value); }); -} - -// Mobile menu toggle functionality -menuToggle.addEventListener('click', () => { - sidebar.classList.toggle('active'); -}); - -// Optional: Close sidebar when clicking outside -document.addEventListener('click', (e) => { - if (window.innerWidth <= 768) { // Only on mobile - const isClickInsideSidebar = sidebar.contains(e.target); - const isClickOnMenuToggle = menuToggle.contains(e.target); - - if (!isClickInsideSidebar && !isClickOnMenuToggle && sidebar.classList.contains('active')) { - sidebar.classList.remove('active'); - } - } -}); - -// Optional: Close sidebar when pressing Escape key -document.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && sidebar.classList.contains('active')) { - sidebar.classList.remove('active'); - } -}); \ No newline at end of file +} \ No newline at end of file From e02ba3d6a4b520eae27e60f2dc112764fb836aeb Mon Sep 17 00:00:00 2001 From: Yeghro <130201060+Yeghro@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:36:54 -0800 Subject: [PATCH 4/4] fixed top nav bar overlapping with titles/headings --- styles.css | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/styles.css b/styles.css index 49c9af4..a538246 100644 --- a/styles.css +++ b/styles.css @@ -36,7 +36,7 @@ body { .container { display: flex; min-height: 100vh; - margin-top: 60px; /* Height of top nav */ + margin-top: 0; /* Remove this as we're using padding-top in main-content */ } /* Sidebar Styles */ @@ -50,11 +50,12 @@ body { transition: transform 0.3s ease; border-right: 1px solid rgba(110, 84, 148, 0.15); box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05); - top: 60px; /* Start below top nav */ + top: 60px; /* Align with top nav */ touch-action: pan-y pinch-zoom; will-change: transform; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; + padding-top: 1rem; /* Add some padding at the top */ } .sidebar-header { @@ -158,6 +159,7 @@ body { .main-content { margin-left: 280px; padding: 2rem; + padding-top: 80px; /* Increased padding-top to account for fixed nav */ flex: 1; min-height: 100vh; overflow: auto; @@ -275,6 +277,7 @@ body { .main-content { margin-left: 0; + padding-top: 100px; /* Increase padding-top for mobile to account for wrapped nav elements */ } .menu-toggle { @@ -288,6 +291,7 @@ body { .nav-content { flex-wrap: wrap; gap: 0.5rem; + padding: 0.5rem 3.5rem; } .nav-content h1 { @@ -301,6 +305,7 @@ body { width: 100%; max-width: none; margin-top: 0.5rem; + margin-bottom: 0.5rem; } .nav-links a { @@ -457,8 +462,13 @@ body { color: #f1c40f; } +/* Update resource container header styles */ #resources-container h2 { color: var(--primary-color); + margin-top: 1rem; /* Add top margin to push content down */ + padding-top: 1rem; /* Add padding to create more space */ + position: relative; /* Ensure it's positioned relative to its container */ + z-index: 1; /* Keep it above other content but below the nav */ } /* Main content scrollbar styles */ @@ -694,6 +704,10 @@ button:focus { .theme-select { max-width: 90px; } + + .main-content { + padding-top: 120px; /* Further increase padding-top for very small screens */ + } } /* Update menu toggle position */ @@ -777,4 +791,21 @@ button:focus { font-size: 0.8rem; padding: 0.3rem 0.6rem; } +} + +/* Adjust responsive spacing */ +@media (max-width: 768px) { + .main-content { + padding-top: 120px; /* Increase padding further */ + } + + #resources-container h2 { + margin-top: 0.5rem; /* Adjust margin for mobile */ + } +} + +@media (max-width: 480px) { + .main-content { + padding-top: 140px; /* Even more padding for smallest screens */ + } } \ No newline at end of file