@@ -253,28 +253,53 @@ async function renderBtr() {
253253 sec . hidden = false ;
254254
255255 // Playable tiles carry data-player (open in a modal); the rest link out.
256- const tile = ( player , url , img , ph , label , sub ) =>
257- `<a class="btr-item" ${ player ? `href="#" data-player="${ escAttr ( player ) } "` : `href="${ escAttr ( url ) } " target="_blank"` } >` +
256+ // data-label drives the live filter below. ✓ marks already-played items.
257+ const tile = ( player , url , img , ph , label , sub , played ) =>
258+ `<a class="btr-item" data-label="${ escAttr ( String ( label || '' ) . toLowerCase ( ) ) } " ${ player ? `href="#" data-player="${ escAttr ( player ) } "` : `href="${ escAttr ( url ) } " target="_blank"` } >` +
258259 ( img ? `<img src="${ escAttr ( img ) } " alt="" loading="lazy" referrerpolicy="no-referrer" />` : `<span class="btr-ph">${ ph } </span>` ) +
260+ ( played ? '<span class="btr-played" title="Played">✓</span>' : '' ) +
259261 `<span class="btr-label">${ escapeHtml ( label ) } </span>` +
260262 ( sub ? `<span class="btr-sub">${ escapeHtml ( sub ) } </span>` : '' ) + `</a>` ;
261263 const group = ( title , items ) => items . length
262- ? `<div class="btr-group"><h3>${ title } </h3><div class="btr-items">${ items . join ( '' ) } </div></div>` : '' ;
264+ ? `<div class="btr-group" data-group ><h3>${ title } <span class="btr-count"> ${ items . length } </span> </h3><div class="btr-items">${ items . join ( '' ) } </div></div>` : '' ;
263265
264266 if ( ! tv . length && ! radio . length && ! pods . length && ! movies . length ) {
265267 el ( 'btr' ) . innerHTML = '<p class="muted">Connected — add Live TV, radio, podcast or movie favorites on bittorrented.com and they’ll show up here.</p>' ;
266268 return ;
267269 }
270+ const music = movies . filter ( ( m ) => m . contentType === 'music' ) ;
271+ const books = movies . filter ( ( m ) => m . contentType === 'book' ) ;
272+ const shows = movies . filter ( ( m ) => ! [ 'music' , 'book' ] . includes ( m . contentType ) ) ;
273+ // Full list (no caps) + a live client-side filter across every section.
268274 el ( 'btr' ) . innerHTML =
269- group ( 'Live TV' , tv . slice ( 0 , 12 ) . map ( ( c ) => tile ( c . player , c . url , c . logo , '📺' , c . name ) ) ) +
270- group ( 'Podcasts' , pods . slice ( 0 , 12 ) . map ( ( p ) => {
275+ `<input id="btr-filter" class="btr-filter" type="search" placeholder="Filter favorites…" autocomplete="off" />` +
276+ `<div id="btr-groups">` +
277+ group ( 'Live TV' , tv . map ( ( c ) => tile ( c . player , c . url , c . logo , '📺' , c . name ) ) ) +
278+ group ( 'Podcasts' , pods . map ( ( p ) => {
271279 const ep = ( p . episodes || [ ] ) [ 0 ] ;
272- return tile ( ep ?. player , p . url , p . image , '🎙' , p . title , ep ? `▶ ${ ep . title } ` : 'no recent episodes' ) ;
280+ const played = ! ! ( ep && ep . progress && ep . progress . completed ) ;
281+ const sub = ep ? ( played ? `✓ ${ ep . title } ` : `▶ ${ ep . title } ` ) : 'no recent episodes' ;
282+ return tile ( ep ?. player , p . url , p . image , '🎙' , p . title , sub , played ) ;
273283 } ) ) +
274- group ( 'Radio' , radio . slice ( 0 , 12 ) . map ( ( s ) => tile ( s . player , s . url , s . logo , '📻' , s . name ) ) ) +
275- group ( 'Music' , movies . filter ( ( m ) => m . contentType === 'music' ) . slice ( 0 , 12 ) . map ( ( m ) => tile ( m . player , m . url , m . poster , '🎵' , m . title ) ) ) +
276- group ( 'Books' , movies . filter ( ( m ) => m . contentType === 'book' ) . slice ( 0 , 12 ) . map ( ( m ) => tile ( m . player , m . url , m . poster , '📖' , m . title ) ) ) +
277- group ( 'Movies & Shows' , movies . filter ( ( m ) => ! [ 'music' , 'book' ] . includes ( m . contentType ) ) . slice ( 0 , 12 ) . map ( ( m ) => tile ( m . player , m . url , m . poster , '🎬' , m . title ) ) ) ;
284+ group ( 'Radio' , radio . map ( ( s ) => tile ( s . player , s . url , s . logo , '📻' , s . name ) ) ) +
285+ group ( 'Music' , music . map ( ( m ) => tile ( m . player , m . url , m . poster , '🎵' , m . title ) ) ) +
286+ group ( 'Books' , books . map ( ( m ) => tile ( m . player , m . url , m . poster , '📖' , m . title ) ) ) +
287+ group ( 'Movies & Shows' , shows . map ( ( m ) => tile ( m . player , m . url , m . poster , '🎬' , m . title ) ) ) +
288+ `</div>` ;
289+
290+ const fi = el ( 'btr-filter' ) ;
291+ if ( fi ) fi . addEventListener ( 'input' , ( ) => {
292+ const q = fi . value . trim ( ) . toLowerCase ( ) ;
293+ document . querySelectorAll ( '#btr-groups [data-group]' ) . forEach ( ( g ) => {
294+ let any = false ;
295+ g . querySelectorAll ( '.btr-item' ) . forEach ( ( it ) => {
296+ const show = ! q || ( it . getAttribute ( 'data-label' ) || '' ) . includes ( q ) ;
297+ it . style . display = show ? '' : 'none' ;
298+ if ( show ) any = true ;
299+ } ) ;
300+ g . style . display = any ? '' : 'none' ;
301+ } ) ;
302+ } ) ;
278303}
279304
280305// Open a bittorrented /api/player URL in a themed modal iframe. Append the
0 commit comments