/*! Top10AI v3 nav: hydrates server-rendered markup (hooks: docs/v3-markup-contract.md). No dependencies. * Never navigates programmatically and never touches an affiliate anchor. */ (function () { 'use strict'; var doc = document; var root = doc.documentElement; root.classList.add('js'); var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (reduce) root.classList.add('t3-reduce'); var script = doc.currentScript; var routerSrc = (script && script.src ? script.src.replace(/nav\.v3\.js/, 'router.v3.js') : '/wp-content/uploads/top10ai-v2/v3/router.v3.js'); function $all(sel, ctx) { return Array.prototype.slice.call((ctx || doc).querySelectorAll(sel)); } function on(el, ev, fn, opt) { if (el) el.addEventListener(ev, fn, opt || false); } // header: menu (closes on a link, outside press, Esc or the router; Tab stays inside), language menu, Ctrl K hint var closeMenu = function () {}; function initHeader() { var header = doc.getElementById('site-header'); if (!header) return; var btn = header.querySelector('.t3-menu-btn'); var nav = header.querySelector('.t3-nav'); function setMenu(open, focusBtn) { header.classList.toggle('t3-nav-open', open); if (btn) btn.setAttribute('aria-expanded', open ? 'true' : 'false'); if (open && nav) { var first = nav.querySelector('a[href]'); if (first) first.focus(); } if (!open && focusBtn && btn) btn.focus(); } closeMenu = function (focusBtn) { if (header.classList.contains('t3-nav-open')) setMenu(false, focusBtn); }; on(btn, 'click', function () { setMenu(!header.classList.contains('t3-nav-open'), false); }); on(nav, 'click', function (e) { if (e.target.closest && e.target.closest('a[href]')) closeMenu(false); }); on(doc, 'pointerdown', function (e) { if (header.classList.contains('t3-nav-open') && (e.target === header || !header.contains(e.target))) closeMenu(false); }); var langs = $all('details.t3-lang', header); on(doc, 'click', function (e) { langs.forEach(function (d) { if (d.open && !d.contains(e.target)) d.open = false; }); }); on(doc, 'keydown', function (e) { if (e.key === 'Tab' && header.classList.contains('t3-nav-open')) { var f = $all('a[href],button,summary', header).filter(function (el) { return el.offsetWidth || el.offsetHeight; }); if (f.length) { var i = f.indexOf(doc.activeElement); if (e.shiftKey && i <= 0) { e.preventDefault(); f[f.length - 1].focus(); } else if (!e.shiftKey && (i === f.length - 1 || i < 0)) { e.preventDefault(); f[0].focus(); } } return; } if (e.key !== 'Escape') return; langs.forEach(function (d) { if (d.open) { d.open = false; var s = d.querySelector('summary'); if (s) s.focus(); } }); closeMenu(true); }); var apple = /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent || ''); if (!apple) $all('.t3-kbd').forEach(function (k) { if (k.textContent === '\u2318K') k.textContent = 'Ctrl K'; }); } // router (lazy). The host serves .js as text/plain and browsers refuse such a module, so it is fetched as text and // imported from a same-document blob: URL. Warmed on idle, focus and pointer-over; until it has loaded, router links // and forms are left to the browser (href / action = the Tool Finder; the input has no name), so no CTA is dead. var routerMod = null; var loading = null; function loadRouter() { if (routerMod) return Promise.resolve(routerMod); if (!loading) { loading = fetch(routerSrc, { credentials: 'same-origin' }).then(function (r) { if (!r.ok) throw new Error('router.v3.js ' + r.status); return r.text(); }).then(function (src) { return import(URL.createObjectURL(new Blob([src], { type: 'text/javascript' }))); }).then(function (m) { routerMod = m; return m; }, function (err) { loading = null; if (window.console) console.warn('t3 router failed to load', err); throw err; }); } return loading; } function data() { return window.__T10V3 || { tools: [], tasks: [] }; } function warm() { loadRouter().catch(function () {}); } function initRouter() { if ('requestIdleCallback' in window) window.requestIdleCallback(warm, { timeout: 4000 }); else setTimeout(warm, 2500); $all('.t3-router').forEach(function (r) { var form = r.querySelector('.t3-router-form'); on(r, 'focusin', function () { loadRouter().then(function (m) { m.mount(r, data()); }).catch(function () {}); }); on(form, 'submit', function (e) { if (r.__t3Router || !routerMod) return; e.preventDefault(); form.__t3Pending = true; routerMod.mount(r, data()); }); }); $all('[data-t3-router-open]').forEach(function (a) { on(a, 'pointerover', warm, { once: true }); on(a, 'click', function (e) { if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0 || !routerMod) return; e.preventDefault(); closeMenu(false); routerMod.open(data()); }); }); on(doc, 'keydown', function (e) { if ((e.metaKey || e.ctrlKey) && !e.altKey && (e.key === 'k' || e.key === 'K')) { e.preventDefault(); closeMenu(false); loadRouter().then(function (m) { m.open(data()); }).catch(function () {}); } }); } // motion: .t3-tok gets .in in view; .t3-loop pauses off-screen (animation-play-state via .t3-paused) function initMotion() { var items = $all('.t3-tok'); if (reduce || !('IntersectionObserver' in window)) { items.forEach(function (el) { el.classList.add('in'); }); } else { var io = new IntersectionObserver(function (entries) { entries.forEach(function (en) { if (en.isIntersecting) { en.target.classList.add('in'); io.unobserve(en.target); } }); }, { rootMargin: '0px 0px -8% 0px', threshold: 0.01 }); items.forEach(function (el) { io.observe(el); }); } var loops = $all('.t3-loop'); if (!loops.length) return; if (reduce) { loops.forEach(function (el) { el.classList.add('t3-paused'); }); return; } if (!('IntersectionObserver' in window)) return; var lo = new IntersectionObserver(function (entries) { entries.forEach(function (en) { en.target.classList.toggle('t3-paused', !en.isIntersecting); }); }); loops.forEach(function (el) { lo.observe(el); }); on(doc, 'visibilitychange', function () { loops.forEach(function (el) { el.style.animationPlayState = doc.hidden ? 'paused' : ''; }); }); } // tabs: panels visible without JS; hidden only once .t3-tabs-js is set function initTabs() { $all('[data-t3-tabs]').forEach(function (box) { var tabs = $all('[data-t3-tab]', box); var panels = $all('[data-t3-panel]', box); if (!tabs.length || !panels.length) return; function select(id, focus) { tabs.forEach(function (t) { var sel = t.getAttribute('data-t3-tab') === id; t.setAttribute('aria-selected', sel ? 'true' : 'false'); t.setAttribute('tabindex', sel ? '0' : '-1'); t.classList.toggle('is-active', sel); if (sel && focus) t.focus(); }); panels.forEach(function (p) { p.classList.toggle('is-active', p.getAttribute('data-t3-panel') === id); }); } var start = tabs.filter(function (t) { return t.getAttribute('aria-selected') === 'true'; })[0] || tabs[0]; box.classList.add('t3-tabs-js'); tabs.forEach(function (t, i) { t.setAttribute('role', 'tab'); on(t, 'click', function () { select(t.getAttribute('data-t3-tab'), false); }); on(t, 'keydown', function (e) { var dir = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0; if (!dir) return; if (root.getAttribute('dir') === 'rtl' || doc.body.getAttribute('dir') === 'rtl') dir = -dir; e.preventDefault(); var n = tabs[(i + dir + tabs.length) % tabs.length]; select(n.getAttribute('data-t3-tab'), true); }); }); select(start.getAttribute('data-t3-tab'), false); }); } // row filter: [data-t3-filter=] > button[data-cat] ("" = all); tr[data-cat] hidden, never removed function initFilters() { $all('[data-t3-filter]').forEach(function (box) { var target = doc.getElementById(box.getAttribute('data-t3-filter')); if (!target) return; var btns = $all('button[data-cat]', box); btns.forEach(function (b) { on(b, 'click', function () { var c = b.getAttribute('data-cat'); btns.forEach(function (x) { x.setAttribute('aria-pressed', x === b ? 'true' : 'false'); }); $all('tr[data-cat]', target).forEach(function (tr) { tr.hidden = !!c && tr.getAttribute('data-cat') !== c; }); }); }); }); } // table of contents: aria-current on the link of the section in view function initToc() { var toc = doc.querySelector('.t3-toc'); if (!toc) return; var links = $all('a[href^="#"]', toc); var byId = {}; function mark(a) { links.forEach(function (l) { if (l === a) l.setAttribute('aria-current', 'true'); else l.removeAttribute('aria-current'); }); } links.forEach(function (a) { var el = doc.getElementById(a.getAttribute('href').slice(1)); if (el) byId[el.id] = { a: a, el: el }; on(a, 'click', function () { mark(a); }); }); if (!('IntersectionObserver' in window)) return; var io = new IntersectionObserver(function () { var cur = null; Object.keys(byId).forEach(function (id) { var el = byId[id].el; if (!el.closest('aside') && el.getBoundingClientRect().top <= window.innerHeight * 0.3) cur = byId[id].a; }); mark(cur); }, { rootMargin: '0px 0px -70% 0px' }); Object.keys(byId).forEach(function (id) { io.observe(byId[id].el); }); } // reading progress (transform only) function initProgress() { var bar = doc.querySelector('.t3-progress-bar'); if (!bar) return; var ticking = false; function update() { ticking = false; var max = Math.max(1, root.scrollHeight - window.innerHeight); var p = Math.min(1, Math.max(0, (window.pageYOffset || root.scrollTop) / max)); bar.style.transform = 'scaleX(' + p.toFixed(4) + ')'; } on(window, 'scroll', function () { if (!ticking) { ticking = true; window.requestAnimationFrame(update); } }, { passive: true }); on(window, 'resize', update, { passive: true }); update(); } // logo plates: a failed (else a broken-image icon) is removed so the initial underneath shows function initLogos() { on(doc, 'error', function (e) { var t = e.target; if (t && t.tagName === 'IMG' && t.parentNode && t.parentNode.classList && t.parentNode.classList.contains('t3-logo')) t.parentNode.removeChild(t); }, true); $all('.t3-logo > img').forEach(function (img) { if (img.complete && img.naturalWidth === 0 && img.getAttribute('src')) img.parentNode.removeChild(img); }); } // /blog/ card excerpts: no stylesheet text, date line, repeated title or mid-word end function initExcerpts() { $all('#bl-grid .bl-card-body > p').forEach(function (p) { var was = p.textContent.replace(/\s+/g, ' ').trim(), t = /^[.{<#@]|\{[^}]*:[^}]*;/.test(was) ? '' : was; var h = p.parentNode.querySelector('h3'), ti = h ? h.textContent.replace(/\s+/g, ' ').trim().toLowerCase() : ''; t = t.replace(/^(?:Last updated:?|Source-checked) [A-Z][a-z]+ \d{1,2}, \d{4}\s*(?:· (?:[A-Z][a-z]+ )?)?/, ''); if (ti && !t.toLowerCase().indexOf(ti)) t = t.slice(ti.length).trim(); if (t && t !== was && !/[.!?…]["”’)]?$/.test(t)) t = t.replace(/\s+\S*$/, '').replace(/[\s,;:–—-]+$/, '') + '…'; if (t.length < 2) p.remove(); else if (t !== was) p.textContent = t; }); } function init() { initLogos(); initExcerpts(); initHeader(); initRouter(); initMotion(); initTabs(); initFilters(); initToc(); initProgress(); } if (doc.readyState === 'loading') on(doc, 'DOMContentLoaded', init); else init(); })();