1// Reusable Header Component for DealMate.ai 2// This file creates a consistent header across all pages 3 4class DealMateHeader { 5 constructor() { 6 this.headerHTML = ` 7 <style> 8 /* Residents / DLM Property Management brand lockup (shown only on the 9 residents portal â see setupBrandObserver in header.js). */ 10 .dlm-brand { 11 display: flex; 12 align-items: center; 13 gap: 10px; 14 } 15 .dlm-brand__badge { 16 width: 38px; 17 height: 38px; 18 border-radius: 10px; 19 background: linear-gradient(135deg, #4A90E2 0%, #336699 100%); 20 display: flex; 21 align-items: center; 22 justify-content: center; 23 box-shadow: 0 2px 6px rgba(51, 102, 153, 0.3); 24 flex-shrink: 0; 25 } 26 .dlm-brand__badge svg { 27 width: 22px; 28 height: 22px; 29 } 30 .dlm-brand__text { 31 display: flex; 32 flex-direction: column; 33 line-height: 1.05; 34 } 35 .dlm-brand__name { 36 font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 37 font-weight: 800; 38 font-size: 1.15rem; 39 letter-spacing: -0.02em; 40 color: #1f2d3d; 41 } 42 .dlm-brand__dlm { 43 color: #336699; 44 } 45 .dlm-brand__sub { 46 font-size: 0.6875rem; 47 font-weight: 500; 48 color: #7a8794; 49 margin-top: 2px; 50 letter-spacing: 0.01em; 51 } 52 .dlm-brand__dm { 53 font-weight: 700; 54 color: #5a6b7a; 55 } 56 .dlm-brand__ai { 57 color: #4A90E2; 58 } 59 @media (max-width: 480px) { 60 .dlm-brand__badge { width: 34px; height: 34px; border-radius: 9px; } 61 .dlm-brand__badge svg { width: 20px; height: 20px; } 62 .dlm-brand__name { font-size: 1.02rem; } 63 .dlm-brand__sub { font-size: 0.625rem; } 64 } 65 </style> 66 <header class="main-header"> 67 <div class="header-content"> 68 <div class="logo" style="cursor: pointer;"> 69 <h1> 70 <!-- <span class="robot-emoji">ð¤</span> --> 71 <span class="logo-text">DealMate<span class="logo-accent">.ai</span></span> 72 </h1> 73 <!-- <span class="tagline"> AI-Powered Business Platform</span> --> 74 </div> 75 <div class="header-right"> 76 <div id="locationStatus" class="location-status-header"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px; margin-right:4px;"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg><span id="locationStatusText">Getting your location...</span></div> 77 <button class="hamburger-menu" id="hamburgerMenu"> 78 <span> </span> 79 <span> </span> 80 <span> </span> 81 </button> 82 <nav class="quick-links" style="display:none"></nav> 83 </div> 84 </div> 85 </header> 86 `; 87 } 88 89 // Initialize the header 90 init() { 91 this.render(); 92 this.setupEventListeners(); 93 this.setupBrandObserver(); 94 this.setLocationStatus(); 95 this.openMenuOnIndexIfRequested(); 96 this.applyContentOffset(); 97 } 98 99 // Default DealMate.ai wordmark (shown on every module except residents) 100 defaultLogoHTML() { 101 return ` 102 <h1> 103 <!-- <span class="robot-emoji">ð¤</span> -->
104 <span class="logo-text">DealMate<span class="logo-accent">.ai</span></span> 105 </h1> 106 `; 107 } 108 109 // DLM Property Management lockup, "powered by DealMate.ai" (residents + business portals) 110 dlmLogoHTML() { 111 return ` 112 <span class="dlm-brand"> 113 <span class="dlm-brand__badge" aria-hidden="true"> 114 <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 115 <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" fill="white"/> 116 </svg> 117 </span> 118 <span class="dlm-brand__text"> 119 <span class="dlm-brand__name"><span class="dlm-brand__dlm">DLM</span> Property Management</span> 120 <span class="dlm-brand__sub">powered by <span class="dlm-brand__dm">DealMate<span class="dlm-brand__ai">.ai</span></span></span> 121 </span> 122 </span> 123 `; 124 } 125 126 // Swap the header brand without recreating the .logo element (so the click 127 // handler attached in setupEventListeners survives). mode: 'dlm' | 'default'. 128 applyBrand(mode) { 129 if (this._brandMode === mode) return; 130 const logo = document.querySelector('.logo'); 131 if (!logo) return; 132 logo.innerHTML = mode === 'dlm' ? this.dlmLogoHTML() : this.defaultLogoHTML(); 133 this._brandMode = mode; 134 } 135 136 // The residents and business portals live in the #homeowner / #business 137 // module sections, which the SPA toggles via inline display. SPA navigation 138 // uses pushState (no hashchange), so we watch those elements' visibility to 139 // decide which brand to show. On pages without those sections the brand 140 // stays the default wordmark. 141 setupBrandObserver() { 142 // Modules that carry the DLM Property Management brand. 143 const els = ['homeowner', 'business'] 144 .map(id => document.getElementById(id)) 145 .filter(Boolean); 146 if (!els.length) { 147 this.applyBrand('default'); 148 return; 149 } 150 const sync = () => { 151 const anyVisible = els.some(el => window.getComputedStyle(el).display !== 'none'); 152 this.applyBrand(anyVisible ? 'dlm' : 'default'); 153 }; 154 sync(); 155 try { 156 const observer = new MutationObserver(sync); 157 els.forEach(el => observer.observe(el, { attributes: true, attributeFilter: ['style', 'class'] })); 158 this._brandObserver = observer; 159 } catch (e) { 160 console.warn('Brand observer setup failed', e); 161 } 162 } 163 164 // Render the header HTML 165 render() { 166 // Find existing header or create placeholder 167 let headerElement = document.querySelector('header.main-header'); 168 if (!headerElement) { 169 // If no header exists, create one at the beginning of body 170 document.body.insertAdjacentHTML('afterbegin', this.headerHTML); 171 } else { 172 // Replace existing header 173 headerElement.outerHTML = this.headerHTML; 174 } 175 } 176 177 // Setup event listeners for header functionality 178 setupEventListeners() { 179 // Logo click handler - different behavior for index.html vs other pages 180 const logo = document.querySelector('.logo'); 181 if (logo) { 182 logo.addEventListener('click', () => { 183 console.log('Logo clicked, checking for app...', !!window.app); 184 185 // GUARD: If user is in an active inline form or business portal, 186 // confirm before navigating away to prevent accidental data loss 187 if (window._activeInlineForm) { 188 const confirmed = confirm('You have an open form. Are you sure you want to go back to the homepage?'); 189 if (!confirmed) { 190 console.log('ð¡ï¸ Logo navigation cancelled - user staying in active form'); 191 return; 192 } 193 // User confirmed - clear the form tracking 194 window._activeInlineForm = null; 195 } 196 197 // Check if we're on index.html and have the app object 198 if (window.app && typeof window.app.showModule === 'function') { 199 // On index.html - use app.showModule('welcome') like the original 200 console.log('Using app.showModule'); 201 window.app.showModule('welcome'); 202 if (typeof window.app.closeNavMenu === 'function') { 203 window.app.closeNavMenu(); 204 } 205 } else { 206 // On other pages - redirect to index.html 207 console.log('Redirecting to index.html'); 208 window.location.href = 'index.html'; 209 } 210 }); 211 } 212 213 // Hamburger menu functionality - use the existing app functionality when available 214 const hamburgerMenu = document.getElementById('hamburgerMenu'); 215 if (hamburgerMenu) { 216 217 hamburgerMenu.addEventListener('click', (e) => { 218 console.log('Hamburger clicked, checking for app...', !!window.app); 219 e.preventDefault(); 220 e.stopPropagation(); 221 222 // Business portal view (Solomon 7/11): the header hamburger opens 223 // the business Quick access drawer instead of the site menu. 224 const bp = document.getElementById('businessPortal'); 225 if (bp && window.getComputedStyle(bp).display !== 'none' && typeof window.bizMenuToggle === 'function') { 226 window.bizMenuToggle(true); 227 return; 228 } 229 230 // Check if we're on index.html and have the app object with the hamburger menu functionality 231 if (window.app && typeof window.app.toggleNavMenu === 'function') { 232 // On index.html - use the existing app.toggleNavMenu() functionality 233 console.log('Using app.toggleNavMenu'); 234 window.app.toggleNavMenu(); 235 } else { 236 // On other pages - redirect to index and request the menu be opened 237 console.log('App not available. Redirecting to index.html and requesting menu open'); 238 // Hide the hamburger menu button for now on other pages 239 hamburgerMenu.style.display = 'none'; 240 try { sessionStorage.setItem('dm_open_menu_on_load', '1'); } catch (_) {} 241 window.location.href = 'index.html'; 242 243 244 } 245 }); 246 } 247 } 248 249 // If redirected from another page, wait for app and open the nav menu 250 openMenuOnIndexIfRequested() { 251 const hasOpenMenuQuery = typeof window !== 'undefined' && window.location && (window.location.search.indexOf('openMenu=1') !== -1); 252 const hasMenuQuery = typeof window !== 'undefined' && window.location && (window.location.search.indexOf('menu=1') !== -1); 253 const hasHash = typeof window !== 'undefined' && window.location && (window.location.hash === '#openMenu'); 254 let requested = false;
255 try { requested = requested || sessionStorage.getItem('dm_open_menu_on_load') === '1'; } catch (_) {} 256 requested = requested || hasOpenMenuQuery || hasMenuQuery || hasHash; 257 258 if (!requested) { return; } 259 260 let attempts = 0; 261 const maxAttempts = 60; // ~6 seconds 262 const tryOpen = () => { 263 attempts++; 264 if (window.app && typeof window.app.openNavMenu === 'function' && typeof window.app.showModule === 'function') { 265 console.log('Opening nav menu on load per request'); 266 // Ensure we're showing the welcome section 267 window.app.showModule('welcome'); 268 // Use openNavMenu to force open (don't toggle) 269 window.app.openNavMenu(); 270 try { sessionStorage.removeItem('dm_open_menu_on_load'); } catch (_) {} 271 return; 272 } else if (window.app && typeof window.app.toggleNavMenu === 'function') { 273 // Fallback for older code without openNavMenu 274 console.log('Opening nav menu on load per request (fallback)'); 275 window.app.toggleNavMenu(); 276 try { sessionStorage.removeItem('dm_open_menu_on_load'); } catch (_) {} 277 return; 278 } 279 if (attempts < maxAttempts) { 280 setTimeout(tryOpen, 100); 281 } 282 }; 283 tryOpen(); 284 } 285 286 // Prevent header overlap by adding a top padding to page content when needed 287 applyContentOffset() { 288 try { 289 const headerEl = document.querySelector('header.main-header'); 290 if (!headerEl) return; 291 292 const isIndex = /(^|\/)index\.html(\?|#|$)/i.test(window.location.pathname) || window.location.pathname === '/' || window.location.pathname === ''; 293 if (isIndex && window.app) { 294 // Index handles its own layout 295 return; 296 } 297 298 const headerHeight = headerEl.getBoundingClientRect().height || 60; 299 const existing = document.querySelector('[data-header-spacer]'); 300 if (!existing) { 301 const spacer = document.createElement('div'); 302 spacer.setAttribute('data-header-spacer', 'true'); 303 spacer.style.height = `${Math.ceil(headerHeight + 10)}px`; 304 document.body.insertBefore(spacer, document.body.firstElementChild?.nextSibling || null); 305 } 306 } catch (e) { 307 console.warn('applyContentOffset failed', e); 308 } 309 } 310 311 // Set location status (text-only span; the pin is an inline SVG, no emoji) 312 setLocationStatus() { 313 const locationStatusText = document.getElementById('locationStatusText'); 314 if (locationStatusText) { 315 locationStatusText.textContent = 'Orlando, FL'; 316 } 317 } 318 319 // Method to update location status from external sources 320 updateLocation(city, state) { 321 const locationStatusText = document.getElementById('locationStatusText'); 322 if (locationStatusText) { 323 locationStatusText.textContent = `${city}, ${state}`; 324 } 325 } 326} 327 328// Auto-initialize header when DOM is loaded 329document.addEventListener('DOMContentLoaded', function() { 330 const header = new DealMateHeader(); 331 header.init(); 332 // Optionally initialize footer here so pages don't need inline scripts 333 try { 334 if (window.DealMateFooter) { 335 const footer = new DealMateFooter(); 336 footer.init(); 337 } 338 } catch (e) { 339 console.warn('Footer init failed', e); 340 } 341}); 342 343// Export for manual initialization if needed 344window.DealMateHeader = DealMateHeader;
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.