diff --git a/avian/frontend/apt.js b/avian/frontend/apt.js index 6923c87..05504b0 100644 --- a/avian/frontend/apt.js +++ b/avian/frontend/apt.js @@ -160,15 +160,29 @@ applySiteTitle(siteTitle); var winBtns = [].slice.call(winPick.querySelectorAll('button')); - var currentHours = +readLS('bird:window', '24') || 24; + var START_HOUR = +(readLS('bird:todayStart', '6')) || 6; + var windowMode = readLS('bird:window', '24'); + var currentHours = windowMode === 'today' ? todayHours() : (+windowMode || 24); + function todayHours() { + var now = new Date(); + var start = new Date(now); start.setHours(START_HOUR, 0, 0, 0); + if (now < start) start.setDate(start.getDate() - 1); + return Math.max(1, (now - start) / 3600000); + } + function effectiveHours() { + if (windowMode === 'today') { currentHours = todayHours(); } + return currentHours; + } winBtns.forEach(function (b) { - b.setAttribute('aria-current', (+b.dataset.h === currentHours) ? 'true' : 'false'); + var match = windowMode === 'today' ? b.dataset.h === 'today' : +b.dataset.h === currentHours; + b.setAttribute('aria-current', match ? 'true' : 'false'); }); winBtns.forEach(function (b) { b.addEventListener('click', function () { winBtns.forEach(function (x) { x.setAttribute('aria-current', x === b ? 'true' : 'false'); }); - currentHours = +b.dataset.h; - writeLS('bird:window', String(currentHours)); + windowMode = b.dataset.h; + currentHours = windowMode === 'today' ? todayHours() : +windowMode; + writeLS('bird:window', windowMode); syncPill(winPick); // Actual data refresh is wired below via refreshRecent(). }); @@ -850,9 +864,10 @@ // a bare "window" with the span it actually covers. Thresholds match // the winPick buttons (1H / 12H / 24H / 7D / ALL). function windowLabel(h) { + if (windowMode === 'today') return 'today'; if (h <= 1) return 'this hour'; if (h <= 12) return 'past 12h'; - if (h <= 24) return 'today'; + if (h <= 24) return 'past 24h'; if (h <= 168) return 'this week'; return 'all time'; } @@ -1381,8 +1396,8 @@ // changes the picker again before it resolves - or a slower poll // lands later - we discard the stale response so the collage // never reverts to a different window. - var forHours = currentHours; - return fetchJson('./avian/api/birdnet-api.php?action=recent&hours=' + forHours) + var forHours = effectiveHours(); + return fetchJson('./avian/api/birdnet-api.php?action=recent&hours=' + Math.round(forHours)) .then(function (j) { if (forHours !== currentHours) return; // window changed mid-flight DATA.recent = j; renderWindowDependent(animate); @@ -1390,13 +1405,13 @@ .catch(function (e) { console.warn('recent fetch failed', e); }); } function refreshAll(animate) { - var forHours = currentHours; + var forHours = effectiveHours(); return Promise.all([ fetchJson('./avian/api/birdnet-api.php?action=stats').catch(function () { return null; }), fetchJson('./avian/api/birdnet-api.php?action=lifelist').catch(function () { return null; }), fetchJson('./avian/api/birdnet-api.php?action=timeseries&days=30').catch(function () { return null; }), fetchJson('./avian/api/birdnet-api.php?action=firstseen&limit=10').catch(function () { return null; }), - fetchJson('./avian/api/birdnet-api.php?action=recent&hours=' + forHours).catch(function () { return null; }), + fetchJson('./avian/api/birdnet-api.php?action=recent&hours=' + Math.round(forHours)).catch(function () { return null; }), ]).then(function (parts) { DATA.stats = parts[0]; DATA.lifelist = parts[1]; @@ -1817,8 +1832,39 @@ + ' ' + ''; } + function todayStartRow() { + var h = START_HOUR; + var label = (h === 0 ? '12' : h > 12 ? '' + (h - 12) : '' + h) + ':00 ' + (h < 12 ? 'AM' : 'PM'); + return '' + + '
' + + '
' + + '
' + + ' Today starts at' + + ' when the TODAY window resets' + + '
' + + ' ' + label + '' + + '
' + + '
' + + ' ' + + '
' + + '
'; + } function wireSettingsControls(scope) { scope = scope || document; + var todaySlider = scope.querySelector('[data-today-start]'); + if (todaySlider) { + todaySlider.addEventListener('input', function () { + var h = +todaySlider.value; + var label = (h === 0 ? '12' : h > 12 ? '' + (h - 12) : '' + h) + ':00 ' + (h < 12 ? 'AM' : 'PM'); + var valEl = scope.querySelector('[data-value-for="todayStart"]'); + if (valEl) valEl.textContent = label; + }); + todaySlider.addEventListener('change', function () { + START_HOUR = +todaySlider.value; + writeLS('bird:todayStart', String(START_HOUR)); + if (windowMode === 'today') { currentHours = todayHours(); refreshRecent(true); } + }); + } scope.querySelectorAll('.switch').forEach(function (sw) { sw.addEventListener('click', function () { var on = sw.getAttribute('aria-checked') !== 'true'; @@ -2389,6 +2435,7 @@ '
' + themeRow() + titleRow() + + todayStartRow() + settingsToggle('preserve', 'Preserve all recordings', "don't auto-delete", preserve) + settingsSlider('CONFIDENCE', 'Confidence threshold', 'min score to log a detection', v.CONFIDENCE, 0.1, 0.95, 0.05, 2) + settingsSlider('SENSITIVITY', 'Sensitivity', 'analyzer sensitivity', v.SENSITIVITY, 0.5, 1.5, 0.05, 2) diff --git a/avian/frontend/index.html b/avian/frontend/index.html index e175704..432b134 100644 --- a/avian/frontend/index.html +++ b/avian/frontend/index.html @@ -27,6 +27,7 @@
+