/* tovtorah site ui v2 (02/11): the story player, the reading bar under the header, phone table cards. Markup and texts live in the template parts; this file holds behaviour only (no Hebrew here, labels come from data attributes). No tracking. Playback starts only from a click; a story that was playing on the previous page of the same visit continues from the same second (sessionStorage: position + playing flag, gone when the tab closes). */ (function () { 'use strict'; var d = document; /* ---------- reading bar under the sticky header ---------- */ var hdr = d.querySelector('.tt-header'); if (hdr) { var rb = d.createElement('span'); rb.className = 'tt-readbar'; rb.setAttribute('aria-hidden', 'true'); hdr.appendChild(rb); var ticking = false; var paintRead = function () { ticking = false; var max = d.documentElement.scrollHeight - window.innerHeight; var r = max > 0 ? window.scrollY / max : 0; if (r < 0) { r = 0; } if (r > 1) { r = 1; } rb.style.setProperty('--read', r.toFixed(4)); }; window.addEventListener('scroll', function () { if (!ticking) { ticking = true; requestAnimationFrame(paintRead); } }, { passive: true }); window.addEventListener('resize', paintRead, { passive: true }); paintRead(); } /* ---------- tables of 3+ columns: on a phone each row becomes a card, labels come from the header ---------- */ var tbls = d.querySelectorAll('table.tt-table'); for (var ti = 0; ti < tbls.length; ti++) { var heads = tbls[ti].querySelectorAll('thead th'); if (heads.length < 3) { continue; } var trs = tbls[ti].querySelectorAll('tbody tr'); for (var ri = 0; ri < trs.length; ri++) { var cells = trs[ri].children; for (var ci = 0; ci < cells.length; ci++) { if (!heads[ci]) { continue; } if (cells[ci].hasAttribute('data-label')) { continue; } cells[ci].setAttribute('data-label', heads[ci].textContent.replace(/\s+/g, ' ').trim()); } } tbls[ti].classList.add('tt-table--cards'); } /* ---------- story player ---------- */ var root = d.getElementById('ttAud'); var bb = d.getElementById('ttAudBB'); if (!root) { if (bb) { bb.hidden = true; } return; } var a = d.getElementById('ttAudEl'); var disc = d.getElementById('ttAudDisc'); var panel = d.getElementById('ttAudPanel'); var pp = d.getElementById('ttAudPP'); var x = d.getElementById('ttAudX'); var seek = d.getElementById('ttAudSeek'); var cur = d.getElementById('ttAudCur'); var dur = d.getElementById('ttAudDur'); var back = d.getElementById('ttAudBack'); var fwd = d.getElementById('ttAudFwd'); var tipB = d.getElementById('ttAudTipB'); if (!a) { return; } if (!disc) { return; } if (!panel) { return; } if (!pp) { return; } if (!seek) { return; } var rings = []; [['ttAudRing', 213.63], ['ttAudRing2', 169.65]].forEach(function (r) { var el = d.getElementById(r[0]); if (el) { rings.push({ el: el, c: r[1] }); } }); var triggers = [disc]; if (bb) { triggers.push(bb); } var D = parseFloat(root.getAttribute('data-dur')) || 1571; var KEY = 'ttAudS'; var SKIP = 15; var dragging = false; var raf = 0; var tipTimer = 0; var pend = null; var userPaused = false; var blocked = false; /* the browser refused to continue on this page: the story still counts as playing */ var opener = disc; var lastSave = 0; var store = null; try { store = window.sessionStorage; } catch (e) { store = null; } function fmt(s) { s = Math.max(0, Math.floor(s || 0)); var m = Math.floor(s / 60); var r = s % 60; return m + ':' + (r < 10 ? '0' : '') + r; } function total() { var t = a.duration; if (t) { if (isFinite(t)) { return t; } } return D; } function pos() { return pend !== null ? pend : (a.currentTime || 0); } function setCls(name, on) { root.classList.toggle(name, on); if (bb) { bb.classList.toggle(name, on); } } function focusEl(el) { if (!el) { return; } try { el.focus({ preventScroll: true }); } catch (e) { el.focus(); } } /* --- remember the second (this tab only) --- */ function load() { if (!store) { return null; } try { return JSON.parse(store.getItem(KEY) || 'null'); } catch (e) { return null; } } function save(force) { if (!store) { return; } var now = Date.now(); if (!force) { if (now - lastSave < 2000) { return; } } lastSave = now; var t = pos(); if (t < 3) { return; } try { store.setItem(KEY, JSON.stringify({ t: Math.round(t * 10) / 10, p: !a.paused || blocked, at: now })); } catch (e) {} } function forget() { if (!store) { return; } try { store.removeItem(KEY); } catch (e) {} } function paint() { var T = total(); var t = pos(); var p = T ? t / T : 0; if (p > 1) { p = 1; } rings.forEach(function (r) { r.el.style.strokeDashoffset = (r.c * (1 - p)).toFixed(2); }); seek.max = T.toFixed(1); if (!dragging) { seek.value = t.toFixed(1); } seek.style.setProperty('--p', (p * 100).toFixed(2) + '%'); if (cur) { cur.textContent = fmt(t); } if (dur) { dur.textContent = fmt(T); } seek.setAttribute('aria-valuetext', fmt(t) + ' ' + (seek.getAttribute('data-of') || '/') + ' ' + fmt(T)); } function loop() { paint(); if (!a.paused) { raf = requestAnimationFrame(loop); } } function seekTo(t) { var T = total(); if (t < 0) { t = 0; } if (t > T - 0.5) { t = T - 0.5; } if (a.readyState > 0) { try { a.currentTime = t; } catch (e) {} pend = null; } else { pend = t; } paint(); save(true); } function setPlaying(on) { setCls('is-playing', on); pp.setAttribute('aria-label', pp.getAttribute(on ? 'data-l-pause' : 'data-l-play')); cancelAnimationFrame(raf); if (on) { setCls('is-started', true); setCls('is-ended', false); setCls('is-resume', false); hideTip(); raf = requestAnimationFrame(loop); } else { paint(); } } function play() { userPaused = false; blocked = false; var pr = a.play(); if (pr) { if (pr.catch) { pr.catch(function () { setPlaying(false); }); } } return pr; } function hideTip() { clearTimeout(tipTimer); root.classList.remove('tip-on'); } function touch() { setCls('is-touched', true); hideTip(); } function isOpen() { return root.classList.contains('is-open'); } function setExpanded(v) { triggers.forEach(function (b) { b.setAttribute('aria-expanded', v ? 'true' : 'false'); }); } function openPanel(focusPP) { panel.hidden = false; root.classList.add('is-open'); d.documentElement.classList.add('tt-aud-open'); setExpanded(true); paint(); if (focusPP) { focusEl(pp); } } function closePanel(back) { panel.hidden = true; root.classList.remove('is-open'); d.documentElement.classList.remove('tt-aud-open'); setExpanded(false); if (back) { focusEl(opener); } } function toggleFrom(btn) { touch(); opener = btn; if (isOpen()) { closePanel(false); return; } openPanel(true); if (a.paused) { if (!userPaused) { play(); } } } disc.addEventListener('click', function () { toggleFrom(disc); }); if (bb) { bb.addEventListener('click', function () { toggleFrom(bb); }); } pp.addEventListener('click', function () { touch(); if (a.paused) { play(); } else { userPaused = true; blocked = false; a.pause(); } }); if (back) { back.addEventListener('click', function () { seekTo(pos() - SKIP); }); } if (fwd) { fwd.addEventListener('click', function () { seekTo(pos() + SKIP); }); } if (x) { x.addEventListener('click', function () { closePanel(true); }); } d.addEventListener('keydown', function (e) { if (e.key === 'Escape') { if (isOpen()) { closePanel(true); } } }); d.addEventListener('click', function (e) { if (!isOpen()) { return; } if (root.contains(e.target)) { return; } if (bb) { if (bb.contains(e.target)) { return; } } closePanel(false); }); seek.addEventListener('input', function () { dragging = true; var v = parseFloat(seek.value) || 0; if (a.readyState > 0) { try { a.currentTime = v; } catch (e) {} } else { pend = v; } var T = total(); seek.style.setProperty('--p', (T ? v / T * 100 : 0).toFixed(2) + '%'); if (cur) { cur.textContent = fmt(v); } }); seek.addEventListener('change', function () { dragging = false; paint(); save(true); }); a.addEventListener('loadedmetadata', function () { if (pend !== null) { var t = pend; pend = null; try { a.currentTime = t; } catch (e) {} } paint(); }); a.addEventListener('play', function () { setPlaying(true); save(true); }); a.addEventListener('pause', function () { setPlaying(false); save(true); }); a.addEventListener('timeupdate', function () { save(false); if (a.paused) { paint(); } }); a.addEventListener('waiting', function () { setCls('is-buffering', true); }); a.addEventListener('playing', function () { setCls('is-buffering', false); }); a.addEventListener('error', function () { setCls('is-buffering', false); setPlaying(false); }); a.addEventListener('ended', function () { forget(); userPaused = true; blocked = false; try { a.currentTime = 0; } catch (e) {} setPlaying(false); setCls('is-started', false); setCls('is-ended', true); paint(); if (!isOpen()) { openPanel(false); } }); window.addEventListener('pagehide', function () { save(true); }); if ('mediaSession' in navigator) { try { var ttl = d.getElementById('ttAudTtl'); navigator.mediaSession.metadata = new MediaMetadata({ title: ttl ? ttl.textContent : '', artist: root.getAttribute('data-artist') || '', album: root.getAttribute('data-album') || '', artwork: [{ src: root.getAttribute('data-art') || '', type: 'image/png' }] }); navigator.mediaSession.setActionHandler('play', function () { play(); }); navigator.mediaSession.setActionHandler('pause', function () { userPaused = true; blocked = false; a.pause(); }); navigator.mediaSession.setActionHandler('seekbackward', function () { seekTo(pos() - SKIP); }); navigator.mediaSession.setActionHandler('seekforward', function () { seekTo(pos() + SKIP); }); } catch (e) {} } /* keep clear of the site's fixed bottom layers: phone bar, /donate/ dock, cookie card */ var OBS = ['.tt-bottombar', '.tt-dondock', '#avinu-cc2']; function place() { var vh = window.innerHeight; var off = 0; for (var i = 0; i < OBS.length; i++) { var el = d.querySelector(OBS[i]); if (!el) { continue; } var cs = getComputedStyle(el); if (cs.display === 'none') { continue; } if (cs.visibility === 'hidden') { continue; } if (cs.position !== 'fixed') { continue; } var r = el.getBoundingClientRect(); if (r.height < 1) { continue; } if (r.top >= vh - 1) { continue; } if (vh - r.top > off) { off = vh - r.top; } } root.style.setProperty('--tt-aud-off', Math.round(off) + 'px'); } function placeSoon() { place(); setTimeout(place, 320); } window.addEventListener('resize', placeSoon, { passive: true }); if ('MutationObserver' in window) { var mo = new MutationObserver(placeSoon); mo.observe(d.documentElement, { attributes: true, attributeFilter: ['class'] }); for (var k = 0; k < OBS.length; k++) { var t = d.querySelector(OBS[k]); if (t) { mo.observe(t, { attributes: true, attributeFilter: ['class', 'style', 'hidden'] }); } } } function showTip(ms) { if (isOpen()) { return; } if (!window.matchMedia('(min-width: 861px)').matches) { return; } root.classList.add('tip-on'); clearTimeout(tipTimer); tipTimer = setTimeout(hideTip, ms); } /* a story that was playing on the previous page continues; blocked by the browser = wait for a tap */ var st = load(); var resumed = false; if (st) { if (st.t > 3) { if (st.t < D - 3) { resumed = true; pend = st.t; userPaused = !st.p; setCls('is-started', true); setCls('is-resume', true); if (tipB) { tipB.textContent = (root.getAttribute('data-l-resume') || '') + ' ' + fmt(st.t); } var fresh = Date.now() - (st.at || 0) < 1800000; if (st.p) { if (fresh) { var pr = play(); if (pr) { if (pr.catch) { pr.catch(function () { blocked = true; showTip(9000); }); } } } else { userPaused = false; showTip(9000); } } else { showTip(9000); } } } } paint(); place(); root.classList.add('is-ready'); if (bb) { bb.classList.add('is-ready'); } setTimeout(place, 600); setTimeout(place, 2000); /* the hint bubble (wide screens) and the halo open by themselves only on the page a visitor entered from outside */ var ref = d.referrer || ''; var entry = ref.indexOf(location.protocol + '//' + location.host) !== 0; if (entry) { if (!resumed) { setCls('is-entry', true); tipTimer = setTimeout(function () { showTip(7000); }, 2600); } } })();