// root_url and root_data_url are injected by html-header.blk
// MAIN
document.addEventListener('DOMContentLoaded', function() {
let savedMessage = get_cookie("showMessage");
if ( savedMessage ){
showMessage( unescape( savedMessage ), "info" );
set_cookie( "showMessage", "", -1, "/" );
}
initMobileNav();
});
// Mobile nav (hamburger). Desktop CSS hides the toggle, so this is effectively a no-op
// on wide screens. Closes on Escape, on outside click, and after picking a nav link.
function initMobileNav(){
var header = document.getElementById('header');
var toggle = document.getElementById('nav-toggle');
var nav = document.getElementById('header-nav');
if ( !header || !toggle || !nav ) return;
function setOpen( open ){
header.classList.toggle( 'is-open', open );
toggle.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
}
toggle.addEventListener( 'click', function( e ){
e.stopPropagation();
setOpen( !header.classList.contains('is-open') );
});
// Close when a link is picked (so reopening feels right when staying on the same SPA-ish page)
nav.addEventListener( 'click', function( e ){
if ( e.target.closest('a') ) setOpen( false );
});
// Click outside the header closes the menu
document.addEventListener( 'click', function( e ){
if ( header.classList.contains('is-open') && !header.contains( e.target ) ) setOpen( false );
});
// Escape closes the menu
document.addEventListener( 'keydown', function( e ){
if ( e.key === 'Escape' && header.classList.contains('is-open') ) setOpen( false );
});
}
function set_cookie( c_name, value, exdays, path ){
let expires = new Date();
expires.setTime( expires.getTime() + ( exdays * 86400000 ) );
let c_value = encodeURIComponent( value );
if ( exdays !== null ){
c_value = c_value + "; expires=" + expires.toUTCString();
}
if ( path !== null ){
c_value = c_value + "; path=" + path;
}
document.cookie = c_name + "=" + c_value;
}
function get_cookie( c_name ){
let parts = document.cookie.split( ";" );
for ( let i = 0; i < parts.length; i++ ){
let eq = parts[i].indexOf( "=" );
if ( eq < 0 ) continue;
let name = parts[i].substr( 0, eq ).replace( /^\s+|\s+$/g, "" );
if ( name === c_name ){
return decodeURIComponent( parts[i].substr( eq + 1 ) );
}
}
return undefined;
}
// ------------------------------------------------------------
// Slide animations (vanilla replacement for jQuery slideDown/slideUp).
// Animate height from/to the element's natural height with overflow hidden,
// then clean up the inline styles so layout returns to normal afterwards.
// ------------------------------------------------------------
function slideDown( el, duration, done ){
if ( !el ) return;
el.style.removeProperty('display');
let display = getComputedStyle( el ).display;
if ( display === 'none' ) display = 'block';
el.style.display = display;
const target = el.scrollHeight;
el.style.overflow = 'hidden';
el.style.height = '0px';
el.style.transition = 'height ' + duration + 'ms ease';
el.offsetHeight; // force reflow so the transition runs
el.style.height = target + 'px';
window.setTimeout(function(){
el.style.removeProperty('height');
el.style.removeProperty('overflow');
el.style.removeProperty('transition');
if ( typeof done === 'function' ) done();
}, duration);
}
function slideUp( el, duration, done ){
if ( !el ) return;
el.style.height = el.scrollHeight + 'px';
el.style.overflow = 'hidden';
el.style.transition = 'height ' + duration + 'ms ease';
el.offsetHeight; // force reflow
el.style.height = '0px';
window.setTimeout(function(){
el.style.display = 'none';
el.style.removeProperty('height');
el.style.removeProperty('overflow');
el.style.removeProperty('transition');
if ( typeof done === 'function' ) done();
}, duration);
}
function showMessage( message, type ){
let messageBar = document.getElementById('message-bar');
if ( !messageBar ) return;
let holdTime = Math.max( 2000, message.length * ( 500 / ( type == "alert" ? 2 : 6 ) ) );
messageBar.className = 'message-bar ' + type;
messageBar.innerHTML = message;
slideDown( messageBar, 500, function(){
setTimeout( function(){
slideUp( messageBar, 500 );
}, holdTime );
});
}
// ------------------------------------------------------------
// In-site confirmation dialog - a styled replacement for the browser's native
// confirm(). Builds a