@@ -45,7 +45,7 @@
function setTitleForView ( i ) {
var next = VIEW _TITLES [ i ] ;
if ( ! staticTitle || staticTitle . textContent === next ) return ;
// Fade out → swap text → fade in. The opacity transition is 240ms;
// Fade out -> swap text -> fade in. The opacity transition is 240ms;
// we swap at ~half that so the eye doesn't catch the text change.
staticHead . classList . add ( 'swap-out' ) ;
setTimeout ( function ( ) {
@@ -155,7 +155,7 @@
n <= 12 ? 0.40 :
n <= 24 ? 0.34 :
0.28 ,
// Count → area exponent. ~0.65 keeps the visual hierarchy
// Count -> area exponent. ~0.65 keeps the visual hierarchy
// legible (n=400 reads ~5× bigger than n=30) without the
// loudest bird drowning everything else.
countExp : 0.65 ,
@@ -610,7 +610,7 @@
byHour : new Array ( 24 ) . fill ( 0 ) , // [hour-of-day] detections
} ;
// Map sci → all-time detection count, populated from lifelist for atlas.
// Map sci -> all-time detection count, populated from lifelist for atlas.
var speciesTotals = { } ;
function fetchJson ( url ) {
@@ -654,20 +654,20 @@
// toward the "recent" end of the gradient render in deeper ink; older
// bars fade to a warm light grey. Same hue family throughout.
function barColor ( t ) {
// t = 0 (outer / newest) → 1 (inner / oldest).
// t = 0 (outer / newest) -> 1 (inner / oldest).
// Monochromatic ink palette: same warm hue as the title text
// (--ink: #1a1612 ≈ HSL 25, 14%, 9%). Newest hours render in deep
// ink so the outer perimeter reads bold; older hours fade to a
// warm light grey, the chart looks like a hand-pulled engraving.
var hue = 25 ; // warm-grey hue, matches --ink family
var sat = 12 - t * 8 ; // 12% → 4%
var light = 14 + t * 50 ; // 14% (near-black) → 64% (light grey)
var sat = 12 - t * 8 ; // 12% -> 4%
var light = 14 + t * 50 ; // 14% (near-black) -> 64% (light grey)
return 'hsl(' + hue + ', ' + sat . toFixed ( 0 ) + '%, ' + light . toFixed ( 0 ) + '%)' ;
}
// Editorial detection timeline. One column per species; the black
// square's height up the column encodes detection count (y axis),
// columns run left→ right oldest→ newest detection (x axis). A
// columns run left-> right oldest-> newest detection (x axis). A
// rotated species label sits just above each square. Y-axis count
// ticks on the left, X-axis time labels on the bottom. Always fits
// the viewport - column widths flex, square size steps down as the
@@ -918,7 +918,7 @@
var lifelist = ( DATA . lifelist && DATA . lifelist . species ) || [ ] ;
var recent = ( DATA . recent && DATA . recent . species ) || [ ] ;
// Window count lookup: sci → count in current window.
// Window count lookup: sci -> count in current window.
var winBySci = { } ;
recent . forEach ( function ( s ) { winBySci [ s . sci ] = + s . n ; } ) ;
@@ -1271,7 +1271,7 @@
// native:true they navigate in-page; otherwise they keep the old
// open-in-new-tab behavior for the legacy BirdNET-Pi screens.
var linksHtml = menu . map ( function ( it ) {
var label = ( it . label || '' ) . replace ( 'today' s detections ', ' today ' ) ;
var label = ( it . label || '' ) ;
var attrs = it . native ? '' : ' target="_blank" rel="noopener"' ;
var cls = it . native ? '' : ' class="ext"' ;
return '<a' + cls + ' href="' + it . href + '"' + attrs + '><span>' + label + '</span></a>' ;
@@ -1394,7 +1394,7 @@
var idx = Math . round ( lo + ( hi - lo ) * Math . pow ( t , 1.6 ) ) ;
var v = ( bins [ idx ] || 0 ) / 255 ;
var e = v * v * ( 3 - 2 * v ) ;
// Paper (245,240,230) → ink (26,22,18) ramp.
// Paper (245,240,230) -> ink (26,22,18) ramp.
var r = 245 + Math . round ( ( 26 - 245 ) * e ) ;
var g = 240 + Math . round ( ( 22 - 240 ) * e ) ;
var b = 230 + Math . round ( ( 18 - 230 ) * e ) ;
@@ -1439,7 +1439,7 @@
} ) ;
}
// Pending changes (key → value), saved on click of the Save button.
// Pending changes (key -> value), saved on click of the Save button.
var pending = { } ;
function setSaveState ( msg , cls ) {
@@ -1456,7 +1456,7 @@
var v = cfg . values || { } ;
var preserve = cfg . preserve ;
var html = ''
+ settingsToggle ( 'preserve' , 'Preserve all recordings' , ' don't auto - delete ' , preserve )
+ 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 )
+ settingsSlider ( 'OVERLAP' , 'Chunk overlap' , 'seconds analyzed per pass' , v . OVERLAP , 0 , 2.5 , 0.1 , 1 )
@@ -2053,7 +2053,7 @@
var preserve = cfg . preserve ;
adminBody . innerHTML =
'<div class="admin-settings">'
+ settingsToggle ( 'preserve' , 'Preserve all recordings' , ' don't auto - delete ' , preserve )
+ 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 )
+ settingsSlider ( 'OVERLAP' , 'Chunk overlap' , 'seconds analyzed per pass' , v . OVERLAP , 0 , 2.5 , 0.1 , 1 )
@@ -2130,7 +2130,13 @@
sys . disk _birds && sys . disk _birds . used _pct > 92 ? 'warn' : '' ) ;
var audio = sys . audio || { } , cards = audio . arecord _l || [ ] ;
var mic = cards . find ? cards . find ( function ( c ) { return /usb-audio|microphone| mic /i . test ( c ) ; } ) : null ;
html += adminCard ( 'audio device' , mic || cards [ 0 ] || 'no cards' , '' ) ;
// Without a USB mic, /proc/asound/cards only lists the Pi's HDMI
// audio outputs - which aren't an input source. Flag that clearly
// rather than showing "audio device: vc4hdmi0" as if it were a mic.
html += adminCard ( 'audio device' ,
mic || ( cards . length ? 'no microphone attached' : 'no audio devices' ) ,
mic ? '' : ( cards [ 0 ] || '' ) ,
mic ? '' : 'warn' ) ;
html += '</div>' ;
html += '<h2 class="admin-section-head">services</h2>' ;
@@ -2312,7 +2318,7 @@
var m = location . hash . match ( /^#admin=([a-z]+)/ ) ;
return m ? m [ 1 ] : null ;
}
// #about - brief explainer popup; reached via /about (302 → /#about)
// #about - brief explainer popup; reached via /about (302 -> /#about)
// or the masthead eyebrow. aria-hidden drives the CSS fade/slide.
function openAbout ( ) { document . getElementById ( 'about-modal' ) . setAttribute ( 'aria-hidden' , 'false' ) ; }
function closeAbout ( ) { document . getElementById ( 'about-modal' ) . setAttribute ( 'aria-hidden' , 'true' ) ; }
@@ -2330,7 +2336,7 @@
if ( location . hash === '#about' ) openAbout ( ) ;
window . addEventListener ( 'hashchange' , syncRouter ) ;
// Modal interactions: backdrop / close button → clear the hash.
// Modal interactions: backdrop / close button -> clear the hash.
document . getElementById ( 'detail-modal' ) . addEventListener ( 'click' , function ( ev ) {
if ( ev . target . dataset && ev . target . dataset . close === '1' ) {
if ( location . hash ) { location . hash = '' ; } else { closeDetailModal ( ) ; }
@@ -2344,7 +2350,7 @@
} ) ;
// About popup: backdrop / close / explore button all carry data-close,
// which clears the hash and routes through syncRouter → closeAbout.
// which clears the hash and routes through syncRouter -> closeAbout.
// The masthead eyebrow opens it; Escape dismisses it.
document . getElementById ( 'about-modal' ) . addEventListener ( 'click' , function ( ev ) {
if ( ev . target . dataset && ev . target . dataset . close === '1' ) {
@@ -2452,7 +2458,7 @@
var bins = FFT _SIZE >> 1 ;
var nyquist = sr / 2 ;
// Frequency-band mapping (Hz → bin) for the bird-relevant band.
// Frequency-band mapping (Hz -> bin) for the bird-relevant band.
// Most North American songbirds + corvids range 250 Hz - 8 kHz, but
// hummingbirds, kinglets, and warblers reach 12 kHz. Push the cap
// up so we don't miss the high-frequency tail.
@@ -2483,7 +2489,7 @@
data [ p ] = BG _R ; data [ p + 1 ] = BG _G ; data [ p + 2 ] = BG _B ; data [ p + 3 ] = 255 ;
}
// Precompute row → bin map (log-ish so low freqs get more space).
// Precompute row -> bin map (log-ish so low freqs get more space).
var rowToBin = new Int32Array ( H ) ;
for ( var row = 0 ; row < H ; row ++ ) {
var t = 1 - row / ( H - 1 ) ; // 1 at top, 0 at bottom
@@ -2503,11 +2509,11 @@
var bin2 = rowToBin [ row2 ] ;
var re = real [ bin2 ] , im = imag [ bin2 ] ;
var mag = Math . sqrt ( re * re + im * im ) ;
// log compress; -75 .. -10 dB → 0 .. 1
// log compress; -75 .. -10 dB -> 0 .. 1
var db = 20 * Math . log10 ( mag + 1e-9 ) ;
var v = ( db + 75 ) / 65 ;
if ( v < 0 ) v = 0 ; else if ( v > 1 ) v = 1 ;
// Ink-on-paper palette: low energy → paper, high energy → ink.
// Ink-on-paper palette: low energy -> paper, high energy -> ink.
// Smoothstep for a softer falloff between the two extremes.
var e = v * v * ( 3 - 2 * v ) ;
var r = BG _R + Math . round ( ( FG _R - BG _R ) * e ) ;
@@ -2593,11 +2599,11 @@
var playBtn = ev . target . closest ( '.play' ) ;
if ( playBtn ) {
// Play / pause toggle. Three cases:
// (a) clicking the playing row's button → pause (KEEP audio
// (a) clicking the playing row's button -> pause (KEEP audio
// alive so the user can scrub then resume).
// (b) clicking a paused row's button (it's still modalRecBtn,
// audio still alive, just paused) → resume from cursor.
// (c) clicking a different row's button → stop the old, start
// audio still alive, just paused) -> resume from cursor.
// (c) clicking a different row's button -> stop the old, start
// the new.
var prow = playBtn . closest ( '.rec-row' ) ;
var pfile = prow && prow . dataset . file ;
@@ -2653,7 +2659,7 @@
return ;
}
// Row click anywhere else → toggle strip open/closed.
// Row click anywhere else -> toggle strip open/closed.
var row = ev . target . closest ( '.rec-row' ) ;
if ( ! row ) return ;
var willExpand = ! row . classList . contains ( 'expanded' ) ;
@@ -2727,7 +2733,7 @@
if ( location . hash !== '#sci=' + encodeURIComponent ( sci ) ) {
location . hash = '#sci=' + encodeURIComponent ( sci ) ;
} else {
// Same hash → still re-highlight (the user clicked it again).
// Same hash -> still re-highlight (the user clicked it again).
go ( 2 ) ; highlightAtlas ( sci ) ;
}
}