1+ ( function registerFeatured ( ) {
2+ const namespace = window . features = window . features || { } ;
3+
4+ function renderItem ( item , container ) {
5+ const html = `
6+ <div class="featured-item" role="group" aria-label="${ item . title } ">
7+ <div class="featured-visual" style="background:${ item . color || 'var(--primary)' } ">
8+ <img class="featured-icon" src="${ getIconUrl ( item . name || '' ) } " alt="${ item . title } " onerror="this.onerror=null;this.src='https://raw.githubusercontent.com/Portable-Linux-Apps/Portable-Linux-Apps.github.io/main/icons/blank.png'"/>
9+ </div>
10+ <div class="featured-body">
11+ <div class="featured-title">${ item . title } </div>
12+ <div class="featured-desc">${ item . desc } </div>
13+ </div>
14+ </div>` ;
15+ container . innerHTML = html ;
16+ }
17+
18+ function init ( options = { } ) {
19+ const container = options . container || document . getElementById ( 'featuredBanner' ) ;
20+ const state = options . state || { } ;
21+ const items = options . items || ( window ?. require ? ( window . require ( './config/featured' ) || [ ] ) : [ ] ) ;
22+ if ( ! container ) return null ;
23+ container . innerHTML = `
24+ <div class="featured-inner">
25+ <button class="featured-prev" aria-label="Previous">◀</button>
26+ <div class="featured-slot"></div>
27+ <button class="featured-next" aria-label="Next">▶</button>
28+ <div class="featured-dots" aria-hidden="true"></div>
29+ </div>` ;
30+
31+ const slot = container . querySelector ( '.featured-slot' ) ;
32+ const prev = container . querySelector ( '.featured-prev' ) ;
33+ const next = container . querySelector ( '.featured-next' ) ;
34+ const dots = container . querySelector ( '.featured-dots' ) ;
35+
36+ let idx = 0 ;
37+ let timer = null ;
38+
39+ function updateDots ( ) {
40+ dots . innerHTML = items . map ( ( _ , i ) => `<button class="dot" data-idx="${ i } " aria-label="${ i + 1 } "></button>` ) . join ( '' ) ;
41+ const btns = dots . querySelectorAll ( '.dot' ) ;
42+ btns . forEach ( b => b . addEventListener ( 'click' , ( ) => { goTo ( parseInt ( b . dataset . idx , 10 ) ) ; } ) ) ;
43+ }
44+
45+ function show ( index ) {
46+ if ( ! items || ! items . length ) { slot . innerHTML = '' ; return ; }
47+ idx = ( index + items . length ) % items . length ;
48+ renderItem ( items [ idx ] , slot ) ;
49+ const btns = dots . querySelectorAll ( '.dot' ) ;
50+ btns . forEach ( ( b , i ) => b . classList . toggle ( 'active' , i === idx ) ) ;
51+ // add click handler on slot to open details
52+ const item = items [ idx ] ;
53+ const itemEl = slot . querySelector ( '.featured-item' ) ;
54+ if ( itemEl ) {
55+ itemEl . style . cursor = 'pointer' ;
56+ itemEl . onclick = ( ) => { if ( typeof showDetails === 'function' ) showDetails ( item . name ) ; } ;
57+ }
58+ }
59+
60+ function goTo ( i ) { show ( i ) ; resetTimer ( ) ; }
61+ function nextItem ( ) { show ( idx + 1 ) ; }
62+ function prevItem ( ) { show ( idx - 1 ) ; }
63+ function resetTimer ( ) { if ( timer ) { clearInterval ( timer ) ; } timer = setInterval ( nextItem , 6000 ) ; }
64+
65+ prev . addEventListener ( 'click' , ( ) => { prevItem ( ) ; resetTimer ( ) ; } ) ;
66+ next . addEventListener ( 'click' , ( ) => { nextItem ( ) ; resetTimer ( ) ; } ) ;
67+
68+ updateDots ( ) ;
69+ show ( 0 ) ;
70+ resetTimer ( ) ;
71+
72+ // Accessibility: keyboard
73+ container . addEventListener ( 'keydown' , ( e ) => {
74+ if ( e . key === 'ArrowLeft' ) { prevItem ( ) ; resetTimer ( ) ; }
75+ if ( e . key === 'ArrowRight' ) { nextItem ( ) ; resetTimer ( ) ; }
76+ } ) ;
77+
78+ return Object . freeze ( { show, goTo, destroy ( ) { clearInterval ( timer ) ; } } ) ;
79+ }
80+
81+ namespace . featured = Object . freeze ( { init } ) ;
82+ } ) ( ) ;
0 commit comments