1/** 2 * Site navigation: full-screen overlay toggle + submenu flyouts. 3 * 4 * Markup: header.php. Styles: assets/css/header.css. 5 * Vanilla JS, no dependencies. Progressive enhancement: without JS the overlay 6 * stays hidden via the [hidden] attribute and the bar still renders. 7 */ 8( function () { 9 'use strict'; 10 11 var toggle = document.querySelector( '.tmf-nav-toggle' ); 12 var overlay = document.getElementById( 'tmf-nav-overlay' ); 13 14 if ( ! toggle || ! overlay ) { 15 return; 16 } 17 18 var closeBtn = overlay.querySelector( '.tmf-nav-close' ); 19 var lastFocused = null; 20 21 function clearSubmenuLayout( subMenu ) { 22 subMenu.style.paddingTop = ''; 23 subMenu.style.height = ''; 24 } 25 26 // Column B's position, height, and padding are now fully CSS-driven (fixed to 27 // the top of the screen, 100vh tall â see header.css). This just strips any 28 // stale inline styles a previous version may have left so the CSS wins. 29 function layoutSubmenu( li ) { 30 var subMenu = li.querySelector( ':scope > .sub-menu' ); 31 if ( subMenu ) { 32 clearSubmenuLayout( subMenu ); 33 } 34 } 35 36 function focusable() { 37 return Array.prototype.slice.call( 38 overlay.querySelectorAll( 39 'a[href], button:not([disabled]), input, [tabindex]:not([tabindex="-1"])' 40 ) 41 ).filter( function ( el ) { 42 return el.offsetParent !== null; 43 } ); 44 } 45 46 function openNav() { 47 lastFocused = document.activeElement; 48 overlay.hidden = false; 49 // Force reflow so the opacity transition runs after display change. 50 void overlay.offsetWidth; 51 overlay.classList.add( 'is-open' ); 52 toggle.setAttribute( 'aria-expanded', 'true' ); 53 document.body.style.overflow = 'hidden'; 54 ( closeBtn || overlay ).focus(); 55 } 56 57 function closeNav() { 58 overlay.classList.remove( 'is-open' ); 59 toggle.setAttribute( 'aria-expanded', 'false' ); 60 document.body.style.overflow = ''; 61 closeOpenSubmenus(); 62 63 var onEnd = function () { 64 overlay.hidden = true; 65 overlay.removeEventListener( 'transitionend', onEnd ); 66 }; 67 overlay.addEventListener( 'transitionend', onEnd ); 68 69 if ( lastFocused ) { 70 lastFocused.focus(); 71 } 72 } 73 74 function closeOpenSubmenus() { 75 var open = overlay.querySelectorAll( '.menu-item-has-children.is-open' ); 76 Array.prototype.forEach.call( open, function ( li ) { 77 li.classList.remove( 'is-open' ); 78 var a = li.querySelector( ':scope > a' ); 79 if ( a ) { 80 a.setAttribute( 'aria-expanded', 'false' ); 81 } 82 var subMenu = li.querySelector( ':scope > .sub-menu' ); 83 if ( subMenu ) { 84 clearSubmenuLayout( subMenu ); 85 } 86 } ); 87 } 88 89 // Open / close. 90 toggle.addEventListener( 'click', openNav ); 91 if ( closeBtn ) { 92 closeBtn.addEventListener( 'click', closeNav ); 93 } 94 95 // Click the scrim (the dimmed area outside the 480px panel) to close. The 96 // panel and its contents are descendants, so e.target === overlay is true 97 // ONLY for the backdrop itself â clicks inside the menu don't bubble a match. 98 overlay.addEventListener( 'click', function ( e ) { 99 if ( e.target === overlay ) { 100 closeNav(); 101 } 102 } ); 103 104 // ESC closes; Tab is trapped inside the overlay while open. 105 document.addEventListener( 'keydown', function ( e ) { 106 if ( overlay.hidden ) { 107 return; 108 } 109 if ( e.key === 'Escape' ) { 110 closeNav(); 111 return; 112 } 113 if ( e.key === 'Tab' ) { 114 var items = focusable(); 115 if ( ! items.length ) { 116 return; 117 } 118 var first = items[ 0 ]; 119 var last = items[ items.length - 1 ]; 120 if ( e.shiftKey && document.activeElement === first ) { 121 e.preventDefault(); 122 last.focus(); 123 } else if ( ! e.shiftKey && document.activeElement === last ) { 124 e.preventDefault(); 125 first.focus(); 126 } 127 } 128 } ); 129 130 // Submenu (column B): open on HOVER on desktop, on TAP on touch. One at a 131 // time. The parent link still navigates on click at desktop. 132 function openSubmenu( li ) { 133 if ( li.classList.contains( 'is-open' ) ) { 134 return; 135 } 136 closeOpenSubmenus(); 137 li.classList.add( 'is-open' ); 138 var a = li.querySelector( ':scope > a' ); 139 if ( a ) { 140 a.setAttribute( 'aria-expanded', 'true' ); 141 } 142 layoutSubmenu( li ); 143 } 144 145 function closeSubmenu( li ) { 146 li.classList.remove( 'is-open' ); 147 var a = li.querySelector( ':scope > a' ); 148 if ( a ) { 149 a.setAttribute( 'aria-expanded', 'false' ); 150 } 151 var subMenu = li.querySelector( ':scope > .sub-menu' ); 152 if ( subMenu ) { 153 clearSubmenuLayout( subMenu ); 154 } 155 } 156
157 var canHover = false; 158 try { 159 canHover = window.matchMedia( '(hover: hover) and (pointer: fine)' ).matches; 160 } catch ( e ) {} 161 162 var parentItems = overlay.querySelectorAll( '.menu-item-has-children' ); 163 Array.prototype.forEach.call( parentItems, function ( li ) { 164 var link = li.querySelector( ':scope > a' ); 165 166 if ( link ) { 167 link.setAttribute( 'aria-expanded', 'false' ); 168 169 // A parent with children is a PURE TRIGGER â it never navigates on 170 // either device (the real overview link, e.g. "All Forts", lives as 171 // the first submenu item). Desktop click opens; touch tap toggles. 172 link.addEventListener( 'click', function ( e ) { 173 e.preventDefault(); 174 if ( canHover ) { 175 openSubmenu( li ); 176 } else if ( li.classList.contains( 'is-open' ) ) { 177 closeSubmenu( li ); 178 } else { 179 openSubmenu( li ); 180 } 181 } ); 182 } 183 184 if ( canHover ) { 185 // Desktop: also open/close on hover; moving into column B (a DOM 186 // descendant) keeps it open. Keyboard: open on focus in, close when 187 // focus leaves the whole item. 188 li.addEventListener( 'mouseenter', function () { openSubmenu( li ); } ); 189 li.addEventListener( 'mouseleave', function () { closeSubmenu( li ); } ); 190 li.addEventListener( 'focusin', function () { openSubmenu( li ); } ); 191 li.addEventListener( 'focusout', function ( e ) { 192 if ( ! li.contains( e.relatedTarget ) ) { 193 closeSubmenu( li ); 194 } 195 } ); 196 } 197 } ); 198 199 // Re-fit the open flyout when the viewport changes (resize or crossing the 200 // desktop breakpoint), since its height and alignment are pixel-based. 201 window.addEventListener( 'resize', function () { 202 var openLi = overlay.querySelector( 203 '.menu-item-has-children.is-open' 204 ); 205 if ( openLi ) { 206 layoutSubmenu( openLi ); 207 } 208 } ); 209}() ); 210 211/** 212 * Land Acknowledgement modal (global). 213 * 214 * Any link inside a .land-acknowledgement element opens the modal emitted by 215 * footer.php. Handling is DELEGATED off document, so the trigger works wherever 216 * the client drops it (Elementor widget, menu item, footer) â including markup 217 * added after load. Close via the Ã, the backdrop, or Escape. 218 * 219 * Deliberately its own IIFE: the navigation block above returns early when the 220 * nav toggle is absent, which would otherwise skip this. 221 */ 222( function () { 223 'use strict'; 224 225 var modal = document.getElementById( 'tmf-land-modal' ); 226 if ( ! modal ) { 227 return; 228 } 229 230 var lastFocused = null; 231 232 function openModal() { 233 lastFocused = document.activeElement; 234 modal.hidden = false; 235 document.body.classList.add( 'tmf-modal-open' ); 236 237 var close = modal.querySelector( '.tmf-land-modal__close' ); 238 if ( close ) { 239 close.focus(); 240 } 241 } 242 243 function closeModal() { 244 modal.hidden = true; 245 document.body.classList.remove( 'tmf-modal-open' ); 246 247 if ( lastFocused ) { 248 lastFocused.focus(); 249 } 250 } 251 252 document.addEventListener( 'click', function ( e ) { 253 // Trigger: any link inside .land-acknowledgement. 254 if ( e.target.closest( '.land-acknowledgement a' ) ) { 255 e.preventDefault(); 256 openModal(); 257 return; 258 } 259 260 if ( e.target.closest( '.tmf-land-modal__close' ) ) { 261 closeModal(); 262 return; 263 } 264 265 // Backdrop click (the overlay itself, not the dialog). 266 if ( e.target === modal ) { 267 closeModal(); 268 } 269 } ); 270 271 document.addEventListener( 'keydown', function ( e ) { 272 if ( ( e.key === 'Escape' || e.key === 'Esc' ) && ! modal.hidden ) { 273 closeModal(); 274 } 275 } ); 276}() );
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.