@@ -33,7 +33,8 @@ Explore the list of integrations and modules available for your Node-RED project
3333 currentPage: 0 ,
3434 maxPages: 0
3535 }
36- let filterCertified = false ;
36+ const params = new URLSearchParams (window .location .search );
37+ let filterCertified = params .get (' certified' ) === ' 1' ;
3738 const filters = {
3839 ai: {
3940 checked: false ,
@@ -84,7 +85,7 @@ Explore the list of integrations and modules available for your Node-RED project
8485 label: ' Utility'
8586 }
8687 }
87- var catalogue = []
88+ let catalogue = []
8889
8990 function showElementById (id ) {
9091 document .getElementById (id).style .display = ' block' ;
@@ -116,6 +117,10 @@ Explore the list of integrations and modules available for your Node-RED project
116117 catalogue = data .catalogue
117118 pagination .maxPage = Math .ceil (catalogue .length / pagination .perPage );
118119 renderFilters ();
120+ // reflect any URL-driven certified state in the sidebar checkbox
121+ const sidebar = document .getElementById (' catalogue-filter-certified' );
122+ if (sidebar) sidebar .checked = filterCertified;
123+ syncCertifiedUI ();
119124 filterCatalogue (catalogue);
120125 });
121126 }
@@ -171,9 +176,42 @@ Explore the list of integrations and modules available for your Node-RED project
171176
172177 function toggleCertified () {
173178 filterCertified = document .getElementById (' catalogue-filter-certified' ).checked ;
179+ syncCertifiedUI ();
180+ syncCertifiedUrl ();
174181 filterCatalogue ();
175182 }
176183
184+ function setCertified (value ) {
185+ filterCertified = !! value;
186+ const sidebar = document .getElementById (' catalogue-filter-certified' );
187+ if (sidebar) sidebar .checked = filterCertified;
188+ syncCertifiedUI ();
189+ syncCertifiedUrl ();
190+ filterCatalogue ();
191+ }
192+
193+ function syncCertifiedUrl () {
194+ const url = new URL (window .location .href );
195+ if (filterCertified) {
196+ url .searchParams .set (' certified' , ' 1' );
197+ } else {
198+ url .searchParams .delete (' certified' );
199+ }
200+ history .replaceState (null , ' ' , url .toString ());
201+ }
202+
203+ function syncCertifiedUI () {
204+ const pill = document .getElementById (' certified-pill-toggle' );
205+ if (pill) {
206+ pill .setAttribute (' aria-pressed' , filterCertified ? ' true' : ' false' );
207+ pill .classList .toggle (' certified-pill--active' , filterCertified);
208+ }
209+ const count = document .getElementById (' certified-count' );
210+ if (count && Array .isArray (catalogue)) {
211+ count .textContent = catalogue .filter (n => n .ffCertified ).length ;
212+ }
213+ }
214+
177215
178216 function filterCatalogue () {
179217 const search = document .getElementById (' search-catalogue' ).value ;
@@ -319,12 +357,10 @@ Explore the list of integrations and modules available for your Node-RED project
319357 <li class =" integration-card group border border-gray-300 rounded-xl bg-white drop-shadow-md" >
320358 <a href =" ${nodeUrl}" ${linkAttrs} class =" h-48 flex flex-col" >
321359 <div class =" integration-card--details p-3 grow min-h-0" >
322- <div class =" flex justify-between text-sm items-center" >
323- <span >@${ integration .npmScope || integration .npmOwners [0 ]}${ externalIcon} </span >
324- <span class =" ff-certified-tag" style =" display : ${certified ? ' flex' : ' none' }" >
325- <certified-icon />
326- </span >
327- </div >
360+ <div class =" flex justify-between text-sm items-center gap-2" >
361+ <span class =" truncate" >@${ integration .npmScope || integration .npmOwners [0 ]}${ externalIcon} </span >
362+ <span class =" certified-pill" style =" display : ${certified ? ' inline-flex' : ' none' }" title =" FlowFuse Certified" ><certified-icon ></certified-icon ><span >Certified</span ></span >
363+ </div >
328364 <label class =" group-hover:text-indigo-600 cursor-pointer" >${ integration .name } </label >
329365 <p class =" text-sm my-2 leading-5" >${ description} </p >
330366 </div >
@@ -343,13 +379,60 @@ Explore the list of integrations and modules available for your Node-RED project
343379 }
344380 customElements .define (' integration-tile' , IntegrationTile);
345381< / script>
382+ < section class = " certified-hero" >
383+ < div class = " container m-auto md:max-w-6xl px-6" >
384+ < div class = " certified-hero--card bg-indigo-50 rounded-lg border-[3px] border-indigo-200 drop-shadow-xl p-6 md:p-10 grid md:grid-cols-12 gap-8 md:gap-10 items-start" >
385+ < div class = " md:col-span-5" >
386+ < span class = " certified-eyebrow" >< certified- icon>< / certified- icon>< span> FlowFuse Certified< / span>< / span>
387+ < h2 class = " certified-hero--title text-balance" > Certified nodes, backed by their authors and supported long- term.< / h2>
388+ < p class = " certified-hero--lede" >
389+ Choosing a Node - RED node for production raises questions you can' t always answer from a README. Is it actively maintained? Is it secure? Will the maintainer still be around in two years? Certified Nodes answer those questions.
390+ </p>
391+ <div class="certified-hero--actions">
392+ <button
393+ id="certified-pill-toggle"
394+ type="button"
395+ class="ff-btn ff-btn--primary uppercase certified-toggle"
396+ aria-pressed="false"
397+ onclick="setCertified(!filterCertified)"><certified-icon></certified-icon><span>Show only Certified</span><span class="certified-toggle--count">(<span id="certified-count">…</span>)</span></button>
398+ <a class="certified-hero--link inline-flex items-center gap-1" href="/blog/2025/07/certified-nodes-v2/">
399+ Learn more {% include "components/icons/arrow-long-right.svg" %}
400+ </a>
401+ </div>
402+ </div>
403+ <ul class="certified-pillars md:col-span-7">
404+ <li class="certified-pillar">
405+ <span class="certified-pillar--icon" aria-hidden="true">{% include "components/icons/users.svg" %}</span>
406+ <div>
407+ <h3 class="certified-pillar--title">Vetted authors</h3>
408+ <p class="certified-pillar--body">Every Certified Node comes from a developer with a track record in their domain — not an anonymous npm publisher.</p>
409+ </div>
410+ </li>
411+ <li class="certified-pillar">
412+ <span class="certified-pillar--icon" aria-hidden="true">{% include "components/icons/shield-check.svg" %}</span>
413+ <div>
414+ <h3 class="certified-pillar--title">Tested for production</h3>
415+ <p class="certified-pillar--body">We check each node for reliability, security posture, and current documentation before it ships — and patch CVEs on our own timeline.</p>
416+ </div>
417+ </li>
418+ <li class="certified-pillar">
419+ <span class="certified-pillar--icon" aria-hidden="true">{% include "components/icons/code-bracket.svg" %}</span>
420+ <div>
421+ <h3 class="certified-pillar--title">Open source and proprietary, both welcome</h3>
422+ <p class="certified-pillar--body">Some Certified Nodes are free and open; others target specific enterprise needs. The certification standard is the same.</p>
423+ </div>
424+ </li>
425+ </ul>
426+ </div>
427+ </div>
428+ </section>
346429<div class="container m-auto text-left md:max-w-6xl pt-8 pb-12 w-full ff-full-bg gap-4 flex">
347430 <div class="catalogue-filters w-52 shrink-0 hidden md:block">
348431 <label>Filters</label>
349432 <ul>
350433 <li>
351434 <input type="checkbox" id="catalogue-filter-certified" onchange="toggleCertified()"/>
352- < label class = " inline-flex gap-1 items-center" for = " catalogue-filter-certified" > FlowFuse Certified < certified- icon / >< / label>
435+ <label class="inline-flex gap-1 items-center" for="catalogue-filter-certified">FlowFuse Certified <certified-icon></certified-icon ></label>
353436 </li>
354437 </ul>
355438 <label>Categories</label>
@@ -360,7 +443,7 @@ Explore the list of integrations and modules available for your Node-RED project
360443 </div>
361444 <div class="grow max-md:max-w-lg mx-auto">
362445 <input id="search-catalogue" class="catalogue-search" type="text" placeholder="Search Integrations" onkeyup="filterCatalogue()" onchange="filterCatalogue()"/>
363- < div class = " catalogue-meta" >
446+ <div class="catalogue-meta" aria-live="polite" aria-atomic="true" >
364447 <div id="count-container" style="display: none;"><span id="integrations-count">X</span> Integrations</div>
365448 <div id="count-placeholder"><span id="integrations-count">Loading...</div>
366449 </div>
0 commit comments