1/** 2 * Dynamic Header Height Calculation and Spacing Adjustment 3 * Ensures proper spacing for fixed header across all viewport sizes 4 */ 5 6(function() { 7 'use strict'; 8 9 // Configuration 10 const HEADER_SELECTOR = 'header'; 11 const MAIN_SELECTOR = 'main#primary'; 12 const STORY_HERO_SELECTOR = '.story-hero-no-image, .story-hero-with-image'; 13 const DEBOUNCE_DELAY = 100; 14 15 // State 16 let headerHeight = 0; 17 let resizeTimeout = null; 18 19 /** 20 * Debounce function to limit resize event calls 21 */ 22 function debounce(func, wait) { 23 return function executedFunction(...args) { 24 const later = () => { 25 clearTimeout(resizeTimeout); 26 func(...args); 27 }; 28 clearTimeout(resizeTimeout); 29 resizeTimeout = setTimeout(later, wait); 30 }; 31 } 32 33 /** 34 * Calculate the actual header height 35 */ 36 function calculateHeaderHeight() { 37 const header = document.querySelector(HEADER_SELECTOR); 38 if (!header) { 39 console.warn('Header element not found'); 40 return 0; 41 } 42 43 // Get computed style to include all spacing 44 const headerRect = header.getBoundingClientRect(); 45 const computedStyle = window.getComputedStyle(header); 46 47 // Include margins in height calculation 48 const marginTop = parseFloat(computedStyle.marginTop) || 0; 49 const marginBottom = parseFloat(computedStyle.marginBottom) || 0; 50 51 return headerRect.height + marginTop + marginBottom; 52 } 53 54 /** 55 * Apply calculated spacing to main content 56 */ 57 function adjustMainContentSpacing() { 58 const main = document.querySelector(MAIN_SELECTOR); 59 if (!main) { 60 console.warn('Main content element not found'); 61 return; 62 } 63 64 // Calculate appropriate padding 65 const basePadding = 20; // Base padding in pixels 66 const calculatedPadding = Math.max(headerHeight + basePadding, 100); 67 68 // Apply the padding 69 main.style.paddingTop = `${calculatedPadding}px`; 70 71 // Add a CSS custom property for other elements to use 72 document.documentElement.style.setProperty('--header-height', `${headerHeight}px`); 73 document.documentElement.style.setProperty('--header-offset', `${calculatedPadding}px`); 74 75 // Debug logging 76 if (window.console && typeof window.console.log === 'function') { 77 console.log(`Header height: ${headerHeight}px, Applied padding: ${calculatedPadding}px`); 78 } 79 } 80 81 /** 82 * Adjust story hero sections specifically 83 */ 84 function adjustStoryHeroSpacing() { 85 const storyHeros = document.querySelectorAll(STORY_HERO_SELECTOR); 86 87 storyHeros.forEach(hero => { 88 if (hero.classList.contains('story-hero-no-image')) { 89 // For heroes without images, ensure proper top spacing 90 const currentMarginTop = parseInt(window.getComputedStyle(hero).marginTop) || 0; 91 if (currentMarginTop > headerHeight * 2) { 92 // If margin is excessive, reduce it 93 hero.style.marginTop = '0'; 94 hero.style.paddingTop = `${Math.max(headerHeight + 40, 120)}px`; 95 } 96 } 97 }); 98 } 99 100 /** 101 * Handle viewport changes and recalculate spacing 102 */ 103 function handleResize() { 104 const newHeaderHeight = calculateHeaderHeight(); 105 106 // Only update if height changed significantly (avoid micro-adjustments) 107 if (Math.abs(newHeaderHeight - headerHeight) > 5) { 108 headerHeight = newHeaderHeight; 109 adjustMainContentSpacing(); 110 adjustStoryHeroSpacing(); 111 } 112 } 113 114 /** 115 * Initialize spacing adjustments 116 */ 117 function init() { 118 // Wait for DOM to be ready 119 if (document.readyState === 'loading') { 120 document.addEventListener('DOMContentLoaded', init); 121 return; 122 } 123 124 // Initial calculation 125 headerHeight = calculateHeaderHeight(); 126 127 if (headerHeight > 0) { 128 adjustMainContentSpacing(); 129 adjustStoryHeroSpacing(); 130 } 131 132 // Set up resize listener with debouncing 133 const debouncedResize = debounce(handleResize, DEBOUNCE_DELAY); 134 window.addEventListener('resize', debouncedResize); 135 136 // Also listen for orientation changes on mobile 137 if ('onorientationchange' in window) { 138 window.addEventListener('orientationchange', () => { 139 // Delay to allow for orientation change to complete 140 setTimeout(debouncedResize, 200); 141 }); 142 } 143 144 // Listen for font loading which might affect header height 145 if (document.fonts && document.fonts.ready) { 146 document.fonts.ready.then(() => { 147 setTimeout(handleResize, 100); 148 }); 149 } 150 151 // For Alpine.js compatibility - recalculate after Alpine initializes 152 if (typeof Alpine !== 'undefined') { 153 document.addEventListener('alpine:init', () => { 154 setTimeout(handleResize, 100); 155 }); 156 } 157 158 // Mark as initialized 159 document.documentElement.classList.add('header-spacing-initialized'); 160 161 console.log('Header spacing system initialized'); 162 } 163 164 /** 165 * Provide public API for manual recalculation 166 */ 167 window.recalculateHeaderSpacing = function() { 168 handleResize(); 169 }; 170 171 /** 172 * CSS-in-JS fallback styles 173 */ 174 function addFallbackStyles() { 175 const style = document.createElement('style'); 176 style.textContent = ` 177 /* Fallback styles for header spacing */ 178 .header-spacing-fallback main#primary { 179 padding-top: 120px; 180 } 181 182 @media (min-width: 1024px) { 183 .header-spacing-fallback main#primary { 184 padding-top: 140px; 185 } 186 } 187 188 @media (max-width: 767px) { 189 .header-spacing-fallback main#primary { 190 padding-top: 100px; 191 } 192 } 193 194 /* Smooth transitions for spacing changes */ 195 main#primary { 196 transition: padding-top 0.3s ease; 197 } 198 199 .story-hero-no-image, 200 .story-hero-with-image { 201 transition: margin-top 0.3s ease, padding-top 0.3s ease; 202 } 203 `; 204 document.head.appendChild(style); 205 } 206 207 // Add fallback styles immediately 208 addFallbackStyles(); 209 210 // Add fallback class in case JS fails 211 document.documentElement.classList.add('header-spacing-fallback'); 212 213 // Initialize when ready 214 init(); 215 216 // Remove fallback class once initialized 217 document.addEventListener('DOMContentLoaded', () => { 218 setTimeout(() => { 219 if (document.documentElement.classList.contains('header-spacing-initialized')) { 220 document.documentElement.classList.remove('header-spacing-fallback'); 221 } 222 }, 500); 223 }); 224 225})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.