4141.instructions .legend { margin-top : 8px ; font-size : .82rem ; color : var (--text-muted ); border-top : 1px solid var (--border ); padding-top : 8px ; }
4242
4343/* Toolbar */
44- .toolbar { max-width : 1400px ; margin : 0 auto; padding : 14px 32px ; display : flex; align-items : center; gap : 12px ; flex-wrap : wrap; }
44+ .toolbar { max-width : 1400px ; margin : 0 auto; padding : 14px 32px 0 ; display : flex; align-items : center; gap : 12px ; flex-wrap : wrap; }
4545.search-box { flex : 1 ; min-width : 260px ; position : relative; }
4646.search-box input { width : 100% ; padding : 9px 14px 9px 36px ; border : 1px solid var (--border ); border-radius : var (--radius ); font-size : .9rem ; outline : none; transition : border .2s ; }
4747.search-box input : focus { border-color : var (--primary ); box-shadow : 0 0 0 3px rgba (0 , 118 , 168 , .12 ); }
4848.search-box svg { position : absolute; left : 10px ; top : 50% ; transform : translateY (-50% ); color : var (--text-muted ); }
4949.result-count { font-size : .88rem ; color : var (--text-muted ); white-space : nowrap; }
50- .btn-clear { padding : 8px 18px ; background : var (--accent ); color : # fff ; border : none; border-radius : var (--radius ); font-size : .85rem ; font-weight : 600 ; cursor : pointer; white-space : nowrap; transition : background .2s ; }
50+
51+ /* Active filters bar */
52+ .active-filters { max-width : 1400px ; margin : 0 auto; padding : 6px 32px 10px ; display : none; align-items : center; gap : 8px ; flex-wrap : wrap; }
53+ .active-filters .visible { display : flex; }
54+ .active-filters-label { font-size : .8rem ; font-weight : 600 ; color : var (--text-muted ); white-space : nowrap; }
55+ .af-chip { display : inline-flex; align-items : center; gap : 4px ; padding : 2px 10px ; border-radius : 12px ; font-size : .76rem ; background : var (--primary ); color : # fff ; cursor : pointer; white-space : nowrap; transition : background .15s ; }
56+ .af-chip : hover { background : var (--primary-dark ); }
57+ .af-chip .af-x { font-weight : 700 ; font-size : .82rem ; line-height : 1 ; }
58+ .btn-clear { padding : 6px 14px ; background : var (--accent ); color : # fff ; border : none; border-radius : var (--radius ); font-size : .8rem ; font-weight : 600 ; cursor : pointer; white-space : nowrap; transition : background .2s ; }
5159.btn-clear : hover { background : # c55a24 ; }
5260
5361/* Layout */
@@ -150,7 +158,11 @@ <h1>Browse Challenge Projects</h1>
150158 < input type ="text " id ="searchInput " placeholder ="Search by title, technology, toolbox, application, partner... ">
151159 </ div >
152160 < span class ="result-count " id ="resultCount "> </ span >
153- < button class ="btn-clear " id ="clearBtn "> Clear Filters</ button >
161+ </ div >
162+ < div class ="active-filters " id ="activeFilters ">
163+ < span class ="active-filters-label "> Filtered by:</ span >
164+ < span id ="activeChips "> </ span >
165+ < button class ="btn-clear " id ="clearBtn "> Clear All</ button >
154166</ div >
155167
156168< div class ="main ">
@@ -166,14 +178,14 @@ <h1>Browse Challenge Projects</h1>
166178
167179 const FILTER_DEFS = [
168180 { key : "technology_trends" , label : "Technology Trends" , tagClass : "tag-trend" } ,
169- { key : "mathworks_platforms" , label : "MathWorks Platforms" , tagClass : "tag-platform" } ,
170- { key : "mathworks_products" , label : "MathWorks Products" , tagClass : "tag-product" } ,
171181 { key : "application_areas" , label : "Application Areas" , tagClass : "tag-area" } ,
172182 { key : "difficulty" , label : "Difficulty" , tagClass : "" , isScalar : true } ,
173183 { key : "project_type" , label : "Project Type" , tagClass : "tag-type" } ,
174184 { key : "hardware_required" , label : "Hardware Required" , tagClass : "" , isScalar : true } ,
175185 { key : "hardware_tags" , label : "Hardware Tags" , tagClass : "tag-hw" } ,
176186 { key : "partners" , label : "Partners" , tagClass : "tag-partner" } ,
187+ { key : "mathworks_platforms" , label : "MathWorks Platforms" , tagClass : "tag-platform" } ,
188+ { key : "mathworks_products" , label : "MathWorks Products" , tagClass : "tag-product" } ,
177189 ] ;
178190
179191 let allProjects = [ ] ;
@@ -186,6 +198,8 @@ <h1>Browse Challenge Projects</h1>
186198 const $count = document . getElementById ( "resultCount" ) ;
187199 const $clear = document . getElementById ( "clearBtn" ) ;
188200 const $status = document . getElementById ( "statusMsg" ) ;
201+ const $activeFilters = document . getElementById ( "activeFilters" ) ;
202+ const $activeChips = document . getElementById ( "activeChips" ) ;
189203
190204 // ── Data loading ──────────────────────────────────────────────
191205 fetch ( "./projects/projects.json" )
@@ -205,15 +219,17 @@ <h1>Browse Challenge Projects</h1>
205219 function buildSidebar ( ) {
206220 let html = "" ;
207221
208- // Partner-only toggle
209- html += `<div class="filter-section">
210- <div class="filter-header" style="cursor:default">Partner Filter</div>
211- <div class="filter-options">
212- <label><input type="checkbox" id="partnerToggle"> Only partnered projects</label>
213- </div>
214- </div>` ;
215-
216222 FILTER_DEFS . forEach ( def => {
223+ // Insert partner toggle right before Partners section
224+ if ( def . key === "partners" ) {
225+ html += `<div class="filter-section">
226+ <div class="filter-header" style="cursor:default">Partner Filter</div>
227+ <div class="filter-options">
228+ <label><input type="checkbox" id="partnerToggle"> Only partnered projects</label>
229+ </div>
230+ </div>` ;
231+ }
232+
217233 const vals = collectValues ( def . key , def . isScalar ) ;
218234 if ( ! vals . length ) return ;
219235 filterState [ def . key ] = new Set ( ) ;
@@ -283,6 +299,7 @@ <h1>Browse Challenge Projects</h1>
283299
284300 renderCards ( filtered ) ;
285301 updateCounts ( filtered ) ;
302+ renderActiveFilters ( ) ;
286303 $count . textContent = `Showing ${ filtered . length } of ${ allProjects . length } projects` ;
287304 }
288305
@@ -314,6 +331,50 @@ <h1>Browse Challenge Projects</h1>
314331 } ) ;
315332 }
316333
334+ // ── Active filter chips ────────────────────────────────────────
335+ function renderActiveFilters ( ) {
336+ let chips = [ ] ;
337+ const q = $search . value . trim ( ) ;
338+ if ( q ) {
339+ chips . push ( `<span class="af-chip" data-action="search">Search: "${ esc ( q ) } " <span class="af-x">✕</span></span>` ) ;
340+ }
341+ if ( partnerOnly ) {
342+ chips . push ( `<span class="af-chip" data-action="partner">Partnered only <span class="af-x">✕</span></span>` ) ;
343+ }
344+ FILTER_DEFS . forEach ( def => {
345+ const sel = filterState [ def . key ] ;
346+ if ( ! sel ) return ;
347+ sel . forEach ( v => {
348+ chips . push ( `<span class="af-chip" data-action="filter" data-key="${ def . key } " data-val="${ escAttr ( v ) } ">${ esc ( v ) } <span class="af-x">✕</span></span>` ) ;
349+ } ) ;
350+ } ) ;
351+
352+ $activeChips . innerHTML = chips . join ( "" ) ;
353+ $activeFilters . classList . toggle ( "visible" , chips . length > 0 ) ;
354+
355+ // click to remove individual chip
356+ $activeChips . querySelectorAll ( ".af-chip" ) . forEach ( chip => {
357+ chip . addEventListener ( "click" , ( ) => {
358+ const action = chip . dataset . action ;
359+ if ( action === "search" ) {
360+ $search . value = "" ;
361+ } else if ( action === "partner" ) {
362+ partnerOnly = false ;
363+ const pt = document . getElementById ( "partnerToggle" ) ;
364+ if ( pt ) pt . checked = false ;
365+ } else if ( action === "filter" ) {
366+ const key = chip . dataset . key ;
367+ const val = chip . dataset . val ;
368+ filterState [ key ] . delete ( val ) ;
369+ // uncheck matching sidebar checkbox
370+ const cb = $sidebar . querySelector ( `input[data-key="${ key } "][value="${ CSS . escape ( val ) } "]` ) ;
371+ if ( cb ) cb . checked = false ;
372+ }
373+ applyFilters ( ) ;
374+ } ) ;
375+ } ) ;
376+ }
377+
317378 // ── Card rendering ────────────────────────────────────────────
318379 function renderCards ( projects ) {
319380 if ( ! projects . length ) {
0 commit comments