1/* Shared header, footer, and settings-driven content. Edit site-config.js, not this file. */ 2(function () { 3 var C = window.DASA || {}; 4 var page = document.body.getAttribute('data-page'); 5 6 var SHIELD = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#F2B134" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l7 3v5c0 5-3.5 8.5-7 10-3.5-1.5-7-5-7-10V6l7-3z"/><path d="M9 12l2 2 4-4"/></svg>'; 7 var links = [ 8 ['home', '/', 'Home'], 9 ['schedule', '/schedule', 'Schedule & Register'], 10 ['self-paced', '/self-paced', 'Self-Paced'], 11 ['faqs', '/faqs', 'FAQs'], 12 ['resources', '/resources', 'Resources'], 13 ['contact', '/contact', 'Contact'], 14 ]; 15 16 function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); } 17 18 // Header 19 var header = document.getElementById('site-header'); 20 if (header) { 21 header.className = 'site-header'; 22 header.innerHTML = 23 '<div class="wrap">' + 24 '<a class="brand" href="/"><span class="mark">' + SHIELD + '</span><span><b>NYS DASA Training</b><small>at Daemen University</small></span></a>' + 25 '<button class="menu-btn" type="button" aria-expanded="false" aria-controls="site-nav">Menu</button>' + 26 '<nav class="nav" id="site-nav" aria-label="Main">' + 27 links.map(function (l) { return '<a href="' + l[1] + '"' + (l[0] === page ? ' aria-current="page"' : '') + '>' + l[2] + '</a>'; }).join('') + 28 '<a class="btn primary" href="/schedule#register">Register</a>' + 29 '</nav>' + 30 '</div>'; 31 var btn = header.querySelector('.menu-btn'); 32 btn.addEventListener('click', function () { 33 var open = header.classList.toggle('open'); 34 btn.setAttribute('aria-expanded', open ? 'true' : 'false'); 35 }); 36 } 37 38 // Footer 39 var footer = document.getElementById('site-footer'); 40 if (footer) { 41 footer.className = 'site-footer'; 42 footer.innerHTML = 43 '<div class="wrap">' + 44 '<div><b class="name">NYS DASA Training at Daemen University</b>' + esc(C.address) + '</div>' + 45 '<div><span class="lbl">CONTACT</span><a href="mailto:' + esc(C.email) + '">' + esc(C.email) + '</a><br>' + esc(C.phone) + '</div>' + 46 '<div><span class="lbl">FOLLOW</span>' + (C.facebook ? '<a href="' + esc(C.facebook) + '" target="_blank" rel="noopener">Like our page on Facebook</a>' : 'Facebook page coming soon') + '</div>' + 47 '</div>'; 48 } 49 50 // Links from settings 51 document.querySelectorAll('[data-link]').forEach(function (a) { 52 var url = C[a.getAttribute('data-link')]; 53 if (url) { a.href = url; if (/^https?:/.test(url)) { a.target = '_blank'; a.rel = 'noopener'; } } 54 else { a.setAttribute('aria-disabled', 'true'); } 55 }); 56 document.querySelectorAll('[data-text]').forEach(function (el) { el.textContent = C[el.getAttribute('data-text')] || ''; }); 57 58 // Schedule 59 var MONTHS = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC']; 60 var DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; 61 function parse(d) { var p = String(d).split('-'); return new Date(+p[0], +p[1] - 1, +p[2]); } 62 function longDate(d) { return DAYS[d.getDay()] + ', ' + ['January','February','March','April','May','June','July','August','September','October','November','December'][d.getMonth()] + ' ' + d.getDate(); } 63 var today = new Date(); today.setHours(0, 0, 0, 0); 64 var upcoming = (C.classes || []).filter(function (c) { return c && c.date && parse(c.date) >= today; }) 65 .sort(function (a, b) { return parse(a.date) - parse(b.date); }); 66 67 document.querySelectorAll('[data-schedule]').forEach(function (box) { 68 var limit = +box.getAttribute('data-schedule') || 99; 69 var items = upcoming.slice(0, limit); 70 if (!items.length) { 71 box.innerHTML = '<div class="empty">New class dates will be posted soon. Email <a href="mailto:' + esc(C.email) + '">' + esc(C.email) + '</a> to ask about the next class, or start with the self-paced option.</div>'; 72 return; 73 } 74 box.innerHTML = items.map(function (c) { 75 var d = parse(c.date), wk = c.type === 'weeknight'; 76 var title = wk ? 'Weeknight class · ' + longDate(d) + (c.date2 ? ' & ' + longDate(parse(c.date2)) : '') : 'Saturday class · ' + longDate(d); 77 var time = wk ? C.weeknightTime : C.saturdayTime; 78 var st = c.status || 'open'; 79 var pill = st === 'full' ? '<span class="pill full">Full</span>' : st === 'few' ? '<span class="pill few">Few seats</span>' : '<span class="pill open">Open</span>'; 80 var reg = st === 'full' ? '' : '<a class="btn primary" href="' + esc(C.registrationForm) + '" target="_blank" rel="noopener">Register</a>'; 81 return '<div class="class"><div class="date"><span>' + MONTHS[d.getMonth()] + '</span><b>' + d.getDate() + '</b></div>' + 82 '<div class="info"><b>' + title + '</b><span>' + esc(time) + ' · Zoom</span></div>' + pill + reg + '</div>'; 83 }).join(''); 84 }); 85 86 // Hybrid sessions 87 var hybrid = (C.hybridSessions || []).filter(function (c) { return c && c.date && parse(c.date) >= today; }) 88 .sort(function (a, b) { return parse(a.date) - parse(b.date); });
89 document.querySelectorAll('[data-hybrid]').forEach(function (box) { 90 if (!hybrid.length) { 91 box.innerHTML = '<div class="empty">New hybrid session dates will be posted soon. Email <a href="mailto:' + esc(C.email) + '">' + esc(C.email) + '</a> to ask about the next one.</div>'; 92 return; 93 } 94 box.innerHTML = hybrid.map(function (c) { 95 var d = parse(c.date), st = c.status || 'open'; 96 var pill = st === 'full' ? '<span class="pill full">Full</span>' : st === 'few' ? '<span class="pill few">Few seats</span>' : '<span class="pill open">Open</span>'; 97 var reg = st === 'full' ? '' : '<a class="btn teal" href="' + esc(C.registrationForm) + '" target="_blank" rel="noopener">Register</a>'; 98 return '<div class="class"><div class="date teal"><span>' + MONTHS[d.getMonth()] + '</span><b>' + d.getDate() + '</b></div>' + 99 '<div class="info"><b>Live Zoom session · ' + longDate(d) + '</b><span>' + ((c.time || C.hybridTime) ? esc(c.time || C.hybridTime) + ' · ' : '') + 'Finish the self-paced modules before this date</span></div>' + pill + reg + '</div>'; 100 }).join(''); 101 }); 102 document.querySelectorAll('[data-hybrid-dates]').forEach(function (el) { 103 el.textContent = hybrid.length ? hybrid.slice(0, 3).map(function (c) { var d = parse(c.date); return DAYS[d.getDay()].slice(0, 3) + ', ' + ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][d.getMonth()] + ' ' + d.getDate(); }).join(' · ') : 'Dates coming soon'; 104 }); 105 106 // Next class pill (home hero) 107 var all = upcoming.map(function (c) { return { d: parse(c.date), label: c.type === 'weeknight' ? '6-hour weeknight class' : '6-hour Saturday class' }; }) 108 .concat(hybrid.map(function (c) { return { d: parse(c.date), label: 'Self-paced + Zoom session' }; })) 109 .sort(function (a, b) { return a.d - b.d; }); 110 document.querySelectorAll('[data-next-class]').forEach(function (el) { 111 if (!all.length) return; 112 var n = all[0], days = Math.round((n.d - today) / 864e5); 113 var when = days === 0 ? 'today' : days === 1 ? 'tomorrow' : 'in ' + days + ' days'; 114 el.lastElementChild.innerHTML = '<b>Next class ' + when + '</b> · ' + esc(n.label) + ', ' + ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][n.d.getMonth()] + ' ' + n.d.getDate(); 115 }); 116 document.querySelectorAll('[data-count-classes]').forEach(function (el) { el.setAttribute('data-count', all.length); el.textContent = all.length; }); 117 118 // Motion: count-up numbers and reveal on scroll (skipped if the visitor prefers reduced motion) 119 var still = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 120 if (!still && 'IntersectionObserver' in window) { 121 document.documentElement.classList.add('motion'); 122 var io = new IntersectionObserver(function (entries) { 123 entries.forEach(function (e) { 124 if (!e.isIntersecting) return; 125 io.unobserve(e.target); 126 e.target.classList.add('in'); 127 if (e.target.hasAttribute('data-count')) { 128 var el = e.target, to = +el.getAttribute('data-count'), pre = el.getAttribute('data-prefix') || '', suf = el.getAttribute('data-suffix') || '', t0 = null; 129 var step = function (t) { if (!t0) t0 = t; var p = Math.min(1, (t - t0) / 1200); el.textContent = pre + Math.round(to * (1 - Math.pow(1 - p, 3))) + suf; if (p < 1) requestAnimationFrame(step); }; 130 requestAnimationFrame(step); 131 } 132 }); 133 }, { threshold: 0.15 }); 134 document.querySelectorAll('.reveal, [data-count]').forEach(function (el, i) { 135 if (el.classList.contains('reveal')) el.style.transitionDelay = ((i % 4) * 80) + 'ms'; 136 io.observe(el); 137 }); 138 } 139 140 // Resource lists 141 function list(id, items) { 142 var ul = document.getElementById(id); 143 if (!ul) return; 144 ul.innerHTML = (items || []).map(function (it) { 145 return it.url 146 ? '<li><a href="' + esc(it.url) + '" target="_blank" rel="noopener"><span>' + esc(it.name) + '</span><small>Open â</small></a></li>' 147 : '<li><span class="item"><span>' + esc(it.name) + '</span><small>Link coming soon</small></span></li>'; 148 }).join(''); 149 } 150 list('materials', C.materials); 151 list('videos', C.videos); 152})();
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.