diff --git a/.eleventy.js b/.eleventy.js index 9b45e65cc5..eab1146380 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -226,6 +226,13 @@ module.exports = function(eleventyConfig) { eleventyConfig.addFilter("limit", (arr, limit) => arr.slice(0, limit )); + // Marketing image for a nav highlight card: the linked page's image front matter + eleventyConfig.addFilter("pageImageForUrl", (collection, url) => { + const normalized = url && !url.endsWith("/") ? `${url}/` : url; + const match = (collection || []).find((p) => p.url === normalized); + return (match && match.data && match.data.image) || null; + }); + eleventyConfig.addFilter('console', function (value) { const str = util.inspect(value, { showHidden: false, depth: null }); return `
${unescape(str)}
;` @@ -973,11 +980,11 @@ module.exports = function(eleventyConfig) { if (content) { const chevronDown = loadSVG('chevron-down') - return `
  • ${iconSvg}${label}${chevronDown}${content}
  • ` + return `
  • ${iconSvg}${label}${chevronDown}${content}
  • ` } else if (link) { - return `
  • ${iconSvg}${label}
  • ` + return `
  • ${iconSvg}${label}
  • ` } else { - return `
  • ${iconSvg}${label}
  • ` + return `
  • ${iconSvg}${label}
  • ` } }); diff --git a/.gitignore b/.gitignore index d0ff276216..82a74429dc 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,16 @@ src/blueprints/* .netlify -deno.lock \ No newline at end of file +deno.lock + +# In-repo npm cache and tooling config written by the dev container +.npm/ +.config/ + +# Claude Code local (personal) settings; shared .claude/ config stays tracked +.claude/settings.local.json + +# Local-only dev container + tooling artifacts (not shared) +.playwright-mcp/ +CLAUDE.local.md +compose.yaml \ No newline at end of file diff --git a/lib/image-handler.js b/lib/image-handler.js index ed5bb39690..da3ab85c7d 100644 --- a/lib/image-handler.js +++ b/lib/image-handler.js @@ -217,10 +217,22 @@ module.exports = function imageHandler( // Instead it generates HTML picture tags are equivalent of how the pipeline would work // But containing only one, non-converted, resized or compressed, image if (DEV_MODE) { - // Naive copy of the image to the output folder + // Naive copy of the image to the output folder. + // Disambiguate the output filename with a short hash of the resolved + // source path so two different source images that share a basename + // (e.g. a hero foreground "industries/food-beverage.jpg" and a hero + // video poster "industries/hero-video/food-beverage.jpg") don't + // overwrite each other at /img/. Production is unaffected: + // its pipeline already hashes by content via filenameFormat above. + const parsedName = path.parse(imgPath) + const srcHash = require("crypto") + .createHash("sha1") + .update(imgPath) + .digest("hex") + .slice(0, 8) const imgOutputPath = path.join( imgOpts.outputDir, - path.basename(imgPath) + `${parsedName.name}-${srcHash}${parsedName.ext}` ) const imgOutputURL = path.join( "/", diff --git a/nuxt/components/AppFooter.vue b/nuxt/components/AppFooter.vue index 5ea34b2370..b9685d8f87 100644 --- a/nuxt/components/AppFooter.vue +++ b/nuxt/components/AppFooter.vue @@ -1,11 +1,114 @@ diff --git a/nuxt/components/AppHeader.vue b/nuxt/components/AppHeader.vue index 890543e9df..e642e87b33 100644 --- a/nuxt/components/AppHeader.vue +++ b/nuxt/components/AppHeader.vue @@ -1,5 +1,11 @@ + + diff --git a/nuxt/layouts/default.vue b/nuxt/layouts/default.vue index 328839dea7..43602ed01d 100644 --- a/nuxt/layouts/default.vue +++ b/nuxt/layouts/default.vue @@ -3,6 +3,7 @@ Skip to main content
    +
    diff --git a/src/_data/featureCatalog.yaml b/src/_data/featureCatalog.yaml index 91475c41b1..9a22957ab1 100644 --- a/src/_data/featureCatalog.yaml +++ b/src/_data/featureCatalog.yaml @@ -626,7 +626,7 @@ sections: - id: immersive-editor-drawer label: Customisable Immersive Editor Drawer - description: "Pin, move, resize, or full-screen the immersive editor drawer — your preferences are remembered between sessions." + description: "Pin, move, resize, or full-screen the immersive editor drawer. Your preferences are remembered between sessions." docsLink: null changelog: - url: /changelog/2026/04/immersive-editor-drawer/ @@ -674,7 +674,7 @@ sections: - id: snapshot-compare label: Snapshot Comparison - description: "Compare two snapshots side-by-side with a navigable diff view — step through every changed, added, or deleted node and see property and code diffs." + description: "Compare two snapshots side-by-side with a navigable diff view. Step through every changed, added, or deleted node and see property and code diffs." docsLink: /docs/user/snapshots/ changelog: - url: '/changelog/2026/04/snapshot-diff-viewer/' diff --git a/src/_data/navHighlights.json b/src/_data/navHighlights.json new file mode 100644 index 0000000000..7e64a99026 --- /dev/null +++ b/src/_data/navHighlights.json @@ -0,0 +1,23 @@ +{ + "_comment": "Nav highlight cards, one per top-nav dropdown. Marketing owns this file: each entry is the promo card shown at the left of the corresponding mega dropdown on xl+ screens. Swap an entry to feature a campaign, webinar, customer story or launch. Each card renders as the image plus the title (which also serves as the link's accessible name); keep titles short (2 lines max). Set image to a site-absolute path to override, otherwise the card uses the linked page's front-matter image, falling back to /images/og-blog.jpg. Consumed by Eleventy (base.njk) and by Nuxt (nuxt/components/AppHeader.vue).", + "platform": { + "title": "Industrial AI, governed and built in", + "link": "/ai/", + "image": "/images/ai/ai-hero.png" + }, + "solutions": { + "title": "Scale automotive operations", + "link": "/industries/automotive/", + "image": "/images/industries/automotive.jpg" + }, + "resources": { + "title": "Free Node-RED training", + "link": "https://node-red-academy.learnworlds.com/", + "image": "/images/education/education_hero.jpg" + }, + "company": { + "title": "See how teams ship with FlowFuse", + "link": "/customer-stories/", + "image": "/images/stories/arch_systems.jpeg" + } +} diff --git a/src/_data/site.json b/src/_data/site.json index 5c66fb80c0..c0d134afd3 100644 --- a/src/_data/site.json +++ b/src/_data/site.json @@ -6,8 +6,8 @@ "em": "https://job-boards.greenhouse.io/flowfuse/jobs/5566913004" }, "messaging": { - "tagLine": "Build Industrial Applications That Scale Across Your Enterprise", - "subtitle": "Standardize, govern, and deploy operational applications across plants, production lines and teams from a single platform.", + "tagLine": "The edge-native platform for industrial applications", + "subtitle": "Build, deploy, and govern operational applications across every plant and production line, distributed to the edge and accelerated by AI.", "title": "Build workflows and integrations that optimize your industrial operations", "keywords": "Node-RED, Application Development, IoT, IIoT, Low-Code, open source, Integration, Workflow, Automation, Data Processing, Data Integration, Data Transformation, Data Visualization, Industrial Automation, Industrial IoT, Industry 4.0" }, diff --git a/src/_data/team/michael-davis.json b/src/_data/team/michael-davis.json index e51058b40f..50c8f67086 100644 --- a/src/_data/team/michael-davis.json +++ b/src/_data/team/michael-davis.json @@ -10,7 +10,7 @@ "reports_to": "ZJ van de Weg", "facts": [ "20+ years in go-to-market leadership, scaling SaaS ventures and building high-performing teams", - "Passionate about wine-fueled adventures—trekking vineyard hillsides and unearthing hidden cellars across Europe", + "Passionate about wine-fueled adventures, trekking vineyard hillsides and unearthing hidden cellars across Europe", "The father of three daughters who enjoys running a high-stakes negotiation firm at home" ] } diff --git a/src/_includes/analytics/body.html b/src/_includes/analytics/body.html index e90e273409..fcada64ba4 100644 --- a/src/_includes/analytics/body.html +++ b/src/_includes/analytics/body.html @@ -1,5 +1,5 @@ - diff --git a/src/_includes/components/flowfuse-square.svg b/src/_includes/components/flowfuse-square.svg index f117e04e8b..7ce1777f2d 100644 --- a/src/_includes/components/flowfuse-square.svg +++ b/src/_includes/components/flowfuse-square.svg @@ -1,2 +1,2 @@ - + \ No newline at end of file diff --git a/src/_includes/components/flowfuse-wordmark-footer.njk b/src/_includes/components/flowfuse-wordmark-footer.njk new file mode 100644 index 0000000000..38ebde749b --- /dev/null +++ b/src/_includes/components/flowfuse-wordmark-footer.njk @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/_includes/components/flowfuse-wordmark.njk b/src/_includes/components/flowfuse-wordmark.njk index cc886a85cb..6dba8f8275 100644 --- a/src/_includes/components/flowfuse-wordmark.njk +++ b/src/_includes/components/flowfuse-wordmark.njk @@ -1,4 +1,13 @@ + + + + + + + + + @@ -10,37 +19,37 @@ + fill="url(#ffWaveWordmark)" /> + fill="url(#ffWaveWordmark)" /> + fill="url(#ffWaveWordmark)" /> + fill="url(#ffWaveWordmark)" /> + fill="url(#ffWaveWordmark)" /> + fill="url(#ffWaveWordmark)" /> + fill="url(#ffWaveWordmark)" /> + fill="url(#ffWaveWordmark)" /> + fill="url(#ffWaveWordmark)" /> + fill="url(#ffWaveWordmark)" /> + fill="url(#ffWaveWordmark)" /> diff --git a/src/_includes/components/icons/wind-turbine.svg b/src/_includes/components/icons/wind-turbine.svg index 4a4e3104f9..8a83fd49ad 100644 --- a/src/_includes/components/icons/wind-turbine.svg +++ b/src/_includes/components/icons/wind-turbine.svg @@ -1,4 +1,4 @@ - + diff --git a/src/_includes/components/industry-use-cases.njk b/src/_includes/components/industry-use-cases.njk new file mode 100644 index 0000000000..ede161b460 --- /dev/null +++ b/src/_includes/components/industry-use-cases.njk @@ -0,0 +1,27 @@ +{# ============================================================ + USE CASES FOR THIS INDUSTRY - derived grid. + Lists every use-case page whose `industries[]` front-matter + contains the current page's fileSlug. Mapping source of truth: + the Use Cases Asana project (Industry field). + ============================================================ #} +{% set industrySlug = page.fileSlug %} +
    +
    +

    Use cases in {{ industryDisplayName or title }}

    +

    Operational workflow patterns teams in this industry build and own with FlowFuse.

    +
    + {% for uc in collections["use-case"] | sort(false, true, "data.title") %} + {% if uc.data.industries and industrySlug in uc.data.industries %} + +

    {{ uc.data.title }}

    +

    {{ uc.data.problem }}

    + + View use case + {% include "components/icons/arrow-right.svg" %} + +
    + {% endif %} + {% endfor %} +
    +
    +
    diff --git a/src/_includes/components/nav-highlight.njk b/src/_includes/components/nav-highlight.njk new file mode 100644 index 0000000000..c66dca9bd0 --- /dev/null +++ b/src/_includes/components/nav-highlight.njk @@ -0,0 +1,13 @@ +{# Mega-dropdown highlight card: the linked page's marketing image with a + short title underneath. Expects `hl` set by the caller: + {% set hl = navHighlights.platform %}{% include "components/nav-highlight.njk" %} + Content is configured in src/_data/navHighlights.js (marketing-owned). + Card image: hl.image override, else the linked page's `image:` front + matter (its marketing image), else the generic og-blog fallback. #} +{% set hlImage = hl.image or (collections.all | pageImageForUrl(hl.link)) or "/images/og-blog.jpg" %} +
  • + + {{ hl.title }} + + +
  • diff --git a/src/_includes/components/related-resources.njk b/src/_includes/components/related-resources.njk index 9dea7522fd..4b0759e225 100644 --- a/src/_includes/components/related-resources.njk +++ b/src/_includes/components/related-resources.njk @@ -39,7 +39,7 @@ diff --git a/src/_includes/components/top-utility-bar.njk b/src/_includes/components/top-utility-bar.njk new file mode 100644 index 0000000000..572a0921b6 --- /dev/null +++ b/src/_includes/components/top-utility-bar.njk @@ -0,0 +1,78 @@ +{# Slim top utility bar: rotating announcements (left) + quick links (right). + Pattern adapted from tailscale.com's slim top bar. The announcement slot + rotates through any live events (same behaviour as the old events-banner) + plus the standing promos below. Implements the intent of website PR #5067. #} + + + diff --git a/src/_includes/components/use-case-chips.njk b/src/_includes/components/use-case-chips.njk new file mode 100644 index 0000000000..a8ca45da52 --- /dev/null +++ b/src/_includes/components/use-case-chips.njk @@ -0,0 +1,30 @@ +{# ============================================================ + USE CASE CONTEXT BAND - labeled links to the outcome and + industry lens pages. Reads `industries[]` and `values[]` + (slugs) from page front-matter. The slug->label maps mirror + the Use Cases Asana project fields (source of truth). + Rendered near the end of the use-case page, not under the hero. + ============================================================ #} +{% set industryLabels = { + "automotive": "Automotive", + "food-beverage": "Food & Beverage", + "life-sciences": "Life Sciences", + "aviation-aerospace": "Aviation & Aerospace", + "aerospace-components": "Aerospace Components", + "renewables": "Renewables", + "semiconductors": "Semiconductors", + "electronics-appliances": "Electronics & Appliances" +} %} +{% if industries %} +
    +
    + {% if industries %} + Common in these industries +

    + {% for slug in industries %}{{ industryLabels[slug] or slug }}{% if not loop.last %}, {% endif %}{% endfor %} + · all industries +

    + {% endif %} +
    +
    +{% endif %} diff --git a/src/_includes/components/use-case-links.njk b/src/_includes/components/use-case-links.njk new file mode 100644 index 0000000000..6ccc0867c2 --- /dev/null +++ b/src/_includes/components/use-case-links.njk @@ -0,0 +1,23 @@ +{# ============================================================ + USE CASE LINKS - systematic cross-link cards. + The host page declares which use cases to feature: + {% set featuredUseCases = ["slug-a", "slug-b"] %} + {% include "components/use-case-links.njk" %} + Card content (title, problem line, url) derives from each + use-case page's own front matter, so copy lives in one place. + ============================================================ #} +{% if featuredUseCases %} +
    + {% for slug in featuredUseCases %} + {% for uc in collections["use-case"] %} + {% if uc.fileSlug == slug %} + +

    {{ uc.data.title }}

    +

    {{ uc.data.problem }}

    + View use case {% include "components/icons/arrow-right.svg" %} +
    + {% endif %} + {% endfor %} + {% endfor %} +
    +{% endif %} diff --git a/src/_includes/explore-more-content.njk b/src/_includes/explore-more-content.njk index 6ae0c0a6af..281a5f258c 100644 --- a/src/_includes/explore-more-content.njk +++ b/src/_includes/explore-more-content.njk @@ -22,9 +22,9 @@ {%- endfor -%}
    - - See all - {% include "components/icons/arrow-long-right.svg" %} + + See all + {% include "components/icons/arrow-long-right.svg" %}
    diff --git a/src/_includes/faq.njk b/src/_includes/faq.njk index 26458fc806..ed8950a1ea 100644 --- a/src/_includes/faq.njk +++ b/src/_includes/faq.njk @@ -16,8 +16,8 @@ type="button" aria-expanded="false" aria-controls="answer-{{ loop.index }}"> - {{ faq.question }} -
    {{ faq.question }} +
    {% include "components/icons/chevron-down.svg" %}
    diff --git a/src/_includes/feature_lists/features-table-base.njk b/src/_includes/feature_lists/features-table-base.njk index 889238ad70..976879994f 100644 --- a/src/_includes/feature_lists/features-table-base.njk +++ b/src/_includes/feature_lists/features-table-base.njk @@ -1,4 +1,4 @@ -{# Column header — outside overflow container so sticky works on mobile #} +{# Column header, outside overflow container so sticky works on mobile #}
    @@ -16,7 +16,7 @@
    -{# Table body — horizontally scrollable on mobile #} +{# Table body, horizontally scrollable on mobile #}
    {% set hostingKey = 'selfHosted' if hosting == 'self-hosted' else 'cloud' %} diff --git a/src/_includes/layouts/abm-landing.njk b/src/_includes/layouts/abm-landing.njk index dc87bd01e4..d73c82d069 100644 --- a/src/_includes/layouts/abm-landing.njk +++ b/src/_includes/layouts/abm-landing.njk @@ -34,7 +34,7 @@ nohero: true href="{% include "sign-up-url.njk" %}" onclick="capture('cta-try-now', {'reference': '{{ hero.buttonReference }}'})"> - Start Free Trial + Start Free Trial {% include "components/icons/arrow-right.svg" %} diff --git a/src/_includes/layouts/base.njk b/src/_includes/layouts/base.njk index a6da57aa43..db2c3e3c5d 100644 --- a/src/_includes/layouts/base.njk +++ b/src/_includes/layouts/base.njk @@ -163,20 +163,23 @@ eleventyComputed: Skip to main content -
    -
    - - {% include "../components/events-banner.njk" %} - -
    + + {% include "../components/top-utility-bar.njk" %} + +
    + +
    +
    {{ content | safe }} @@ -286,66 +365,181 @@ eleventyComputed: {% include "components/hm-promo-banner.njk" %}