PageSourceSearch

https://aidarwinawards.org/js/ui-management.js

js aidarwinawards.org collected 2026-09-25 20:30:27 UTC 7,448 bytes, 212 lines download raw bytes

1/**
2 * UI Management for Phase-Aware Components
3 * Handles rendering of navigation, CTAs, and status messages based on current phase
4 */
5
6class UIManager {
7    constructor(phaseManager) {
8        this.phaseManager = phaseManager;
9        this.config = null;
10        this.basePath = this.calculateBasePath();
11    }
12
13    /**
14     * Calculate the base path based on current page depth in directory structure
15     * Returns appropriate relative path prefix (e.g., './', '../', '../../')
16     */
17    calculateBasePath() {
18        const fullPath = window.location.pathname;
19        let relativePath;
20        
21        if (window.location.protocol === 'file:') {
22            // For file://, find 'docs/' and get everything after it
23            const parts = fullPath.split('/docs/');
24            relativePath = parts.length > 1 ? parts[1] : fullPath;
25        } else {
26            // For http(s)://, remove leading /
27            relativePath = fullPath.slice(1);
28        }
29        
30        // Calculate depth from relative path
31        const depth = relativePath.split('/').filter(part => part && !part.endsWith('.html')).length;
32        return depth > 0 ? '../'.repeat(depth) : './';
33    }
34
35    init() {
36        // Wait for DOM to be ready
37        if (document.readyState === 'loading') {
38            document.addEventListener('DOMContentLoaded', () => this.render());
39        } else {
40            this.render();
41        }
42    }
43
44    render() {
45        this.config = this.phaseManager.getPhaseConfig();
46        
47        this.renderStatusBanner();
48        this.renderNavigation();
49        this.updatePrimaryCTA();
50        this.renderSecondaryActions();
51    }
52
53    renderStatusBanner() {
54        const statusMessage = this.phaseManager.getStatusMessage();
55        if (!statusMessage) return;
56
57        // Check if banner was previously dismissed
58        const dismissedKey = `status-banner-dismissed-${this.config.phase}-${this.config.awardsYear}`;
59        if (statusMessage.dismissible && localStorage.getItem(dismissedKey)) {
60            return;
61        }
62
63        const existingBanner = document.querySelector('.status-banner');
64        if (existingBanner) {
65            existingBanner.remove();
66        }
67
68        const banner = document.createElement('div');
69        banner.className = `status-banner ${statusMessage.type}`;
70        banner.innerHTML = `
71            <div class="status-banner-content">
72                <span>${statusMessage.icon}</span>
73                <span>${statusMessage.message}</span>
74            </div>
75            ${statusMessage.dismissible ? '<button class="status-banner-dismiss" aria-label="Dismiss banner">&times;</button>' : ''}
76        `;
77
78        // Insert at the top of the page
79        document.body.insertBefore(banner, document.body.firstChild);
80
81        // Handle dismiss
82        if (statusMessage.dismissible) {
83            const dismissBtn = banner.querySelector('.status-banner-dismiss');
84            dismissBtn.addEventListener('click', () => {
85                banner.remove();
86                localStorage.setItem(dismissedKey, 'true');
87            });
88        }
89    }
90
91    renderNavigation() {
92        if (!this.config.showNavigation) return;
93
94        const existingNav = document.querySelector('.phase-nav');
95        if (existingNav) {
96            existingNav.remove();
97        }
98
99        const nav = document.createElement('nav');
100        nav.className = 'phase-nav';
101        nav.setAttribute('aria-label', 'Phase navigation');
102
103        const breadcrumbList = document.createElement('ul');
104        breadcrumbList.className = 'nav-breadcrumb';
105
106        // Add home link
107        const homeItem = document.createElement('li');
108        if (this.isCurrentPage('index.html')) {
109            homeItem.className = 'current';
110            homeItem.textContent = 'Home';
111            homeItem.setAttribute('aria-current', 'page');
112        } else {
113            const homeLink = document.createElement('a');
114            homeLink.href = this.basePath + 'index.html';
115            homeLink.textContent = 'Home';
116            homeItem.appendChild(homeLink);
117        }
118        breadcrumbList.appendChild(homeItem);
119
120        // Add phase-specific navigation items
121        this.config.navigation.forEach(item => {
122            const listItem = document.createElement('li');
123            
124            if (this.isCurrentPage(item.href)) {
125                listItem.className = 'current';
126                listItem.textContent = item.text;
127                listItem.setAttribute('aria-current', 'page');
128            } else {
129                const link = document.createElement('a');
130                link.href = this.basePath + item.href;
131                link.textContent = item.text;
132                
133                if (item.highlight) {
134                    link.classList.add('highlight');
135                }
136                
137                listItem.appendChild(link);
138            }
139            
140            breadcrumbList.appendChild(listItem);
141        });
142
143        nav.appendChild(breadcrumbList);
144
145        // Insert after header
146        const header = document.querySelector('header');
147        if (header) {
148            header.insertAdjacentElement('afterend', nav);
149        } else {
150            document.body.insertBefore(nav, document.body.firstChild);
151        }
152    }
153
154    updatePrimaryCTA() {
155        const button = document.getElementById('nominees-button');
156        if (!button) return;
157
158        const cta = this.config.primaryCTA;
159        const buttonText = button.querySelector('.button-text');
160        const buttonSubtext = button.querySelector('.button-subtext');
161
162        if (buttonText) buttonText.textContent = cta.text;
163        if (buttonSubtext) buttonSubtext.textContent = cta.subtext;
164
165        // Update click handler with base path
166        button.onclick = () => window.location.href = this.basePath + cta.href;
167    }
168
169    renderSecondaryActions() {
170        const existingActions = document.querySelector('.secondary-actions');
171        if (existingActions) {
172            existingActions.remove();
173        }
174
175        if (this.config.secondaryActions.length === 0) return;
176
177        const actionsContainer = document.createElement('div');
178        actionsContainer.className = 'secondary-actions';
179
180        this.config.secondaryActions.forEach(action => {
181            const link = document.createElement('a');
182            link.href = this.basePath + action.href;
183            link.className = 'secondary-cta';
184            link.textContent = action.text;
185            actionsContainer.appendChild(link);
186        });
187
188        // Insert after primary CTA (home page only)
189        const primaryCTA = document.getElementById('nominees-cta-container');
190        if (primaryCTA) {
191            primaryCTA.insertAdjacentElement('afterend', actionsContainer);
192        }
193    }
194
195    isCurrentPage(href) {
196        const currentPath = window.location.pathname;
197        const currentFile = currentPath.split('/').pop() || 'index.html';
198        const targetFile = href.split('/').pop();
199        
200        return currentFile === targetFile || 
201               (currentFile === '' && targetFile === 'index.html') ||
202               (currentFile === 'index.html' && targetFile === '');
203    }
204}
205
206// Auto-initialize when phase management is available
207document.addEventListener('DOMContentLoaded', function() {
208    if (typeof phaseManager !== 'undefined') {
209        const uiManager = new UIManager(phaseManager);
210        uiManager.init();
211    }
212});

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.