From fb27a82dcde782c34b0bbacfb348b5038869f1c1 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Tue, 19 May 2026 10:49:58 -0700 Subject: [PATCH 1/3] Surface FlowFuse Certified program with hero card, URL-driven filter, and tile badge --- src/css/style.catalog.css | 176 +++++++++++++++++++++++++++++++++++-- src/integrations/index.njk | 103 +++++++++++++++++++--- 2 files changed, 260 insertions(+), 19 deletions(-) diff --git a/src/css/style.catalog.css b/src/css/style.catalog.css index 61fe2ae508..5f1d835e65 100644 --- a/src/css/style.catalog.css +++ b/src/css/style.catalog.css @@ -51,15 +51,6 @@ color: theme(colors.blue.500); } -/* .ff-certified-tag { - background-color: theme(colors.blue.50); - border: 1px solid theme(colors.blue.600); - padding: 3px 6px; - border-radius: 6px; - align-items: center; - gap: 3px; -} */ - .certified-icon { width: 24px; height: 24px; @@ -67,6 +58,173 @@ stroke: theme(colors.white); } +/* Compensates for catalog.njk's empty `{% block actions %}` div, which always + reserves ~32px of vertical space whether or not a page defines the block. */ +.certified-hero { + margin-top: -32px; +} + +.certified-eyebrow { + display: inline-flex; + align-items: center; + gap: 8px; + color: theme(colors.indigo.700); + font-size: 12px; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + margin-bottom: 8px; + line-height: 1; +} + +.certified-eyebrow .certified-icon { + width: 18px; + height: 18px; +} + +.certified-hero--title { + font-size: 24px; + line-height: 1.25; + font-weight: 700; + color: theme(colors.gray.900); + margin: 0 0 8px 0; +} + +@media (min-width: 768px) { + .certified-hero--title { + font-size: 28px; + } +} + +.certified-hero--lede { + font-size: 16px; + line-height: 1.5; + color: theme(colors.gray.700); + margin: 0 0 20px 0; +} + +.certified-hero--actions { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 20px; +} + +.certified-hero--link { + color: theme(colors.indigo.700); + font-weight: 600; + font-size: 14px; +} + +.certified-hero--link:hover { + color: theme(colors.indigo.800); +} + +.certified-pillars { + list-style: none; + padding: 0; + margin: 0; + display: grid; + gap: 20px; +} + +.certified-pillar { + display: grid; + grid-template-columns: 36px 1fr; + gap: 12px; + align-items: start; +} + +.certified-pillar--icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 9999px; + background-color: theme(colors.white); + border: 1px solid theme(colors.indigo.200); + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); + color: theme(colors.indigo.600); +} + +.certified-pillar--icon svg { + width: 20px; + height: 20px; +} + +.certified-pillar--title { + font-size: 16px; + font-weight: 700; + color: theme(colors.gray.900); + margin: 0 0 4px 0; +} + +.certified-pillar--body { + font-size: 14px; + line-height: 1.5; + color: theme(colors.gray.700); + margin: 0; +} + +.certified-pill { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px 2px 4px; + background-color: theme(colors.indigo.50); + color: theme(colors.indigo.700); + border: 1px solid theme(colors.indigo.200); + border-radius: 9999px; + font-size: 12px; + font-weight: 600; + line-height: 1.4; + white-space: nowrap; +} + +.certified-pill .certified-icon { + width: 16px; + height: 16px; +} + +.certified-toggle { + display: inline-flex; + align-items: center; + gap: 8px; + cursor: pointer; + transition: background-color 150ms ease, color 150ms ease; +} + +.certified-toggle:focus-visible { + outline: 2px solid theme(colors.indigo.600); + outline-offset: 2px; +} + +.certified-toggle .certified-icon { + width: 18px; + height: 18px; + fill: theme(colors.white); + stroke: theme(colors.indigo.600); +} + +.certified-toggle[aria-pressed="true"], +.certified-toggle.certified-pill--active { + background-color: theme(colors.indigo.50); + color: theme(colors.indigo.700); + box-shadow: inset 0 0 0 1px theme(colors.indigo.300); +} + +.certified-toggle[aria-pressed="true"] .certified-icon, +.certified-toggle.certified-pill--active .certified-icon { + fill: theme(colors.indigo.600); + stroke: theme(colors.white); +} + +.certified-toggle--count { + opacity: 0.85; + font-weight: 400; +} + .integration-card a { color: theme(colors.gray.600); } diff --git a/src/integrations/index.njk b/src/integrations/index.njk index d36f1c2930..b597bae11a 100644 --- a/src/integrations/index.njk +++ b/src/integrations/index.njk @@ -33,7 +33,8 @@ Explore the list of integrations and modules available for your Node-RED project currentPage: 0, maxPages: 0 } - let filterCertified = false; + const params = new URLSearchParams(window.location.search); + let filterCertified = params.get('certified') === '1'; const filters = { ai: { checked: false, @@ -84,7 +85,7 @@ Explore the list of integrations and modules available for your Node-RED project label: 'Utility' } } - var catalogue = [] + let catalogue = [] function showElementById (id) { document.getElementById(id).style.display = 'block'; @@ -116,6 +117,10 @@ Explore the list of integrations and modules available for your Node-RED project catalogue = data.catalogue pagination.maxPage = Math.ceil(catalogue.length / pagination.perPage); renderFilters(); + // reflect any URL-driven certified state in the sidebar checkbox + const sidebar = document.getElementById('catalogue-filter-certified'); + if (sidebar) sidebar.checked = filterCertified; + syncCertifiedUI(); filterCatalogue(catalogue); }); } @@ -171,9 +176,42 @@ Explore the list of integrations and modules available for your Node-RED project function toggleCertified () { filterCertified = document.getElementById('catalogue-filter-certified').checked; + syncCertifiedUI(); + syncCertifiedUrl(); filterCatalogue(); } + function setCertified (value) { + filterCertified = !!value; + const sidebar = document.getElementById('catalogue-filter-certified'); + if (sidebar) sidebar.checked = filterCertified; + syncCertifiedUI(); + syncCertifiedUrl(); + filterCatalogue(); + } + + function syncCertifiedUrl () { + const url = new URL(window.location.href); + if (filterCertified) { + url.searchParams.set('certified', '1'); + } else { + url.searchParams.delete('certified'); + } + history.replaceState(null, '', url.toString()); + } + + function syncCertifiedUI () { + const pill = document.getElementById('certified-pill-toggle'); + if (pill) { + pill.setAttribute('aria-pressed', filterCertified ? 'true' : 'false'); + pill.classList.toggle('certified-pill--active', filterCertified); + } + const count = document.getElementById('certified-count'); + if (count && Array.isArray(catalogue)) { + count.textContent = catalogue.filter(n => n.ffCertified).length; + } + } + function filterCatalogue () { const search = document.getElementById('search-catalogue').value; @@ -319,12 +357,10 @@ Explore the list of integrations and modules available for your Node-RED project
  • -
    - @${integration.npmScope || integration.npmOwners[0]}${externalIcon} - - - -
    +
    + @${integration.npmScope || integration.npmOwners[0]}${externalIcon} + Certified +

    ${description}

    @@ -343,13 +379,60 @@ Explore the list of integrations and modules available for your Node-RED project } customElements.define('integration-tile', IntegrationTile); +
    +
    +
    + +
      +
    • + +
      +

      Vetted authors

      +

      Every Certified Node comes from a developer with a track record in their domain — not an anonymous npm publisher.

      +
      +
    • +
    • + +
      +

      Tested for production

      +

      We check each node for reliability, security posture, and current documentation before it ships — and patch CVEs on our own timeline.

      +
      +
    • +
    • + +
      +

      Open source and proprietary, both welcome

      +

      Some Certified Nodes are free and open; others target specific enterprise needs. The certification standard is the same.

      +
      +
    • +
    +
    +
    +
    -
    +
    Loading...
    From 6d220341c873397d2ed65289602260b41b858766 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Thu, 21 May 2026 11:58:23 -0700 Subject: [PATCH 2/3] Address PR feedback around text copy, node count, general cleanup --- src/integrations/index.njk | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/integrations/index.njk b/src/integrations/index.njk index b597bae11a..ffc80f408b 100644 --- a/src/integrations/index.njk +++ b/src/integrations/index.njk @@ -207,8 +207,10 @@ Explore the list of integrations and modules available for your Node-RED project pill.classList.toggle('certified-pill--active', filterCertified); } const count = document.getElementById('certified-count'); - if (count && Array.isArray(catalogue)) { + const wrapper = document.getElementById('certified-count-wrapper'); + if (count && wrapper && Array.isArray(catalogue) && catalogue.length > 0) { count.textContent = catalogue.filter(n => n.ffCertified).length; + wrapper.hidden = false; } } @@ -384,7 +386,7 @@ Explore the list of integrations and modules available for your Node-RED project
    FlowFuse Certified -

    Certified nodes, backed by their authors and supported long-term.

    +

    Certified Nodes, backed by their authors and supported long-term.

    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.

    @@ -394,7 +396,8 @@ Explore the list of integrations and modules available for your Node-RED project type="button" class="ff-btn ff-btn--primary uppercase certified-toggle" aria-pressed="false" - onclick="setCertified(!filterCertified)">Show only Certified() + onclick="setCertified(!filterCertified)">Show only Certified + {# TODO: repoint to a proper FlowFuse-owned Certified Nodes explainer page when one exists. A year-old blog post is not the long-term destination. #} Learn more {% include "components/icons/arrow-long-right.svg" %} @@ -411,14 +414,14 @@ Explore the list of integrations and modules available for your Node-RED project
  • -

    Tested for production

    -

    We check each node for reliability, security posture, and current documentation before it ships — and patch CVEs on our own timeline.

    +

    Supported through production

    +

    FlowFuse stands behind every Certified Node after launch — patching CVEs on our own timeline. Each node is vetted for reliability, security posture, and current documentation before shipping.

  • -

    Open source and proprietary, both welcome

    +

    Free or commercial, same bar

    Some Certified Nodes are free and open; others target specific enterprise needs. The certification standard is the same.

  • @@ -445,7 +448,7 @@ Explore the list of integrations and modules available for your Node-RED project
    -
    Loading...
    +
    Loading...