PageSourceSearch

https://dealmate.ai/js/header.js

js dealmate.ai collected 2026-10-05 14:54:16 UTC 15,357 bytes, 344 lines download raw bytes

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> &nbsp;</span>
79                            <span> &nbsp;</span>
80                            <span> &nbsp;</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.