From 50726a958cd5503f74862ebee89a0d2c2d1fe6b7 Mon Sep 17 00:00:00 2001 From: ZJ van de Weg Date: Fri, 7 Aug 2026 14:25:53 -0700 Subject: [PATCH 01/11] Migrate features page to Nuxt at /product, extract security component Follow-ups from PR #5539 review: the features page moves from 11ty (src/platform/features.njk) to Nuxt at /product/, the SOC2/security "Built for Enterprise Manufacturing" block becomes a reusable EnterpriseSecurity component shared with the pricing page, and the page's ad-hoc inline diff --git a/nuxt/components/icons/AirGappedDeploymentIcon.vue b/nuxt/components/icons/AirGappedDeploymentIcon.vue new file mode 100644 index 0000000000..78f7b34816 --- /dev/null +++ b/nuxt/components/icons/AirGappedDeploymentIcon.vue @@ -0,0 +1,13 @@ + diff --git a/nuxt/components/icons/AuditLogsIcon.vue b/nuxt/components/icons/AuditLogsIcon.vue new file mode 100644 index 0000000000..d2176b8c25 --- /dev/null +++ b/nuxt/components/icons/AuditLogsIcon.vue @@ -0,0 +1,10 @@ + diff --git a/nuxt/components/icons/RoleBasedAccessIcon.vue b/nuxt/components/icons/RoleBasedAccessIcon.vue new file mode 100644 index 0000000000..60d9dc315b --- /dev/null +++ b/nuxt/components/icons/RoleBasedAccessIcon.vue @@ -0,0 +1,9 @@ + diff --git a/nuxt/components/icons/SingleSignOnIcon.vue b/nuxt/components/icons/SingleSignOnIcon.vue new file mode 100644 index 0000000000..66efe026e2 --- /dev/null +++ b/nuxt/components/icons/SingleSignOnIcon.vue @@ -0,0 +1,6 @@ + diff --git a/nuxt/components/icons/Soc2BadgeIcon.vue b/nuxt/components/icons/Soc2BadgeIcon.vue new file mode 100644 index 0000000000..4be78ccbbf --- /dev/null +++ b/nuxt/components/icons/Soc2BadgeIcon.vue @@ -0,0 +1,84 @@ + diff --git a/nuxt/pages/pricing/index.vue b/nuxt/pages/pricing/index.vue index 8117d578d4..046c2044ff 100644 --- a/nuxt/pages/pricing/index.vue +++ b/nuxt/pages/pricing/index.vue @@ -118,6 +118,8 @@ useSchemaOrg([ + +

+// Ported from src/platform/features.njk (11ty). Cta* components enforce +// fixed copy/destinations (see CLAUDE.md), so the old custom-copy "Request a +// Demo" buttons become ; "View Pricing" isn't one of the four +// unified destinations, so it keeps a plain ff-btn link, same precedent as +// BlogPostCta.vue's pricing fallback. +useSeoMeta({ + title: 'FlowFuse Features', + description: 'FlowFuse provides the features companies require to reliably deliver industrial applications to devices and cloud in a collaborative, secure manner.', + ogUrl: 'https://flowfuse.com/product/', + twitterSite: '@FlowFuseinc', +}) + +const capture = useCapture() + +const TIERS = [ + { id: 'edge', label: 'FlowFuse Edge' }, + { id: 'hub', label: 'FlowFuse Hub' }, + { id: 'fleet', label: 'FlowFuse Fleet' }, +] +const activeTier = ref('edge') +const stepRefs = ref>({}) +let observer: IntersectionObserver | null = null + +onMounted(() => { + const steps = Object.values(stepRefs.value).filter((el): el is HTMLElement => !!el) + if (!steps.length) return + observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) activeTier.value = (entry.target as HTMLElement).dataset.tier ?? activeTier.value + }) + }, { rootMargin: '-45% 0px -45% 0px', threshold: 0 }) + steps.forEach(el => observer!.observe(el)) +}) + +onUnmounted(() => observer?.disconnect()) + +// Scroll-driven 3D rotation for the hero image (n8n-style): a flat PNG on a +// CSS perspective plane that eases from a tilt to flat as you scroll down. +const heroInner = ref(null) +const START_Y = -8 // starting rotateY in degrees; negative = left edge recedes +const SCROLL_DIST = 500 // px of scroll over which it flattens +let ticking = false + +function updateHeroTilt () { + ticking = false + if (!heroInner.value) return + const y = window.scrollY || window.pageYOffset || 0 + const p = Math.max(0, Math.min(1, y / SCROLL_DIST)) + heroInner.value.style.transform = `rotateY(${(START_Y * (1 - p)).toFixed(2)}deg)` +} + +function onHeroScroll () { + if (!ticking) { ticking = true; requestAnimationFrame(updateHeroTilt) } +} + +onMounted(() => { + if (!heroInner.value) return + if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) { + heroInner.value.style.transform = 'none' + return + } + window.addEventListener('scroll', onHeroScroll, { passive: true }) + window.addEventListener('resize', onHeroScroll, { passive: true }) + updateHeroTilt() +}) + +onUnmounted(() => { + window.removeEventListener('scroll', onHeroScroll) + window.removeEventListener('resize', onHeroScroll) +}) + + + + + diff --git a/nuxt/redirects.ts b/nuxt/redirects.ts index 35b1ba83b1..d1bc2f649b 100644 --- a/nuxt/redirects.ts +++ b/nuxt/redirects.ts @@ -34,9 +34,9 @@ export const redirects: Record = { '/handbook/development/how-we-work/packaging/': { redirect: { to: '/handbook/engineering/packaging/', statusCode: 301 } }, '/handbook/development/how-we-work/contributing/': { redirect: { to: '/handbook/engineering/contributing/', statusCode: 301 } }, '/handbook/development/how-we-work/': { redirect: { to: '/handbook/engineering/', statusCode: 301 } }, - '/product/': { redirect: { to: '/platform/features/', statusCode: 301 } }, - '/product/why-flowfuse/': { redirect: { to: '/platform/features/', statusCode: 301 } }, - '/features/': { redirect: { to: '/platform/features/', statusCode: 301 } }, + '/platform/features/': { redirect: { to: '/product/', statusCode: 301 } }, + '/product/why-flowfuse/': { redirect: { to: '/product/', statusCode: 301 } }, + '/features/': { redirect: { to: '/product/', statusCode: 301 } }, '/blog/2024/03/flowfuse-self-hosted-starter-resource-limits/': { redirect: { to: '/blog/2024/03/flowfuse-self-hosted-open-source-resource-limits/', statusCode: 301 } }, '/unified-namespace/': { redirect: { to: '/use-cases/uns/', statusCode: 301 } }, '/solutions/manufacturing/': { redirect: { to: '/use-cases/it-ot-middleware/', statusCode: 301 } }, @@ -52,7 +52,7 @@ export const redirects: Record = { '/blueprints/other/remote-buffering/': { redirect: { to: '/blueprints/getting-started/store-and-forward/', statusCode: 301 } }, '/vs/tulip/': { redirect: { to: '/landing/tulip/', statusCode: 301 } }, '/product/security/': { redirect: { to: '/platform/security/', statusCode: 301 } }, - '/product/features/': { redirect: { to: '/platform/features/', statusCode: 301 } }, + '/product/features/': { redirect: { to: '/product/', statusCode: 301 } }, '/product/device-agent/': { redirect: { to: '/platform/device-agent/', statusCode: 301 } }, '/product/dashboard/': { redirect: { to: '/platform/dashboard/', statusCode: 301 } }, '/team/': { redirect: { to: '/about/', statusCode: 301 } }, diff --git a/nuxt/server/middleware/legacy.ts b/nuxt/server/middleware/legacy.ts index e6e60f891d..8839d8e6b5 100644 --- a/nuxt/server/middleware/legacy.ts +++ b/nuxt/server/middleware/legacy.ts @@ -10,7 +10,7 @@ const NUXT_ROUTES = new Set(['/terms', '/privacy-policy', '/integrations', '/res const NUXT_ROUTE_PREFIXES = ['/integrations/'] // Route prefixes handled by Nuxt (all paths starting with these are served by Nuxt). -const NUXT_PREFIXES = ['/handbook', '/ebooks', '/whitepaper', '/pricing', '/docs', '/changelog', '/application-guide', '/blog'] +const NUXT_PREFIXES = ['/handbook', '/ebooks', '/whitepaper', '/pricing', '/docs', '/changelog', '/application-guide', '/blog', '/product'] export default defineEventHandler(async (event) => { if (process.env.NODE_ENV !== 'development') return diff --git a/src/_data/chrome.json b/src/_data/chrome.json index d21dd985bd..e5393f1485 100644 --- a/src/_data/chrome.json +++ b/src/_data/chrome.json @@ -12,7 +12,7 @@ "links": [ { "label": "Features", - "href": "/platform/features/", + "href": "/product/", "icon": "star" }, { @@ -421,7 +421,7 @@ "links": [ { "label": "Features", - "href": "/platform/features/" + "href": "/product/" }, { "label": "Security Statement", diff --git a/src/platform/features.njk b/src/platform/features.njk deleted file mode 100644 index bf2241c1a1..0000000000 --- a/src/platform/features.njk +++ /dev/null @@ -1,300 +0,0 @@ ---- -layout: page -nohero: true -meta: - title: FlowFuse Features -description: - FlowFuse provides the features companies require to reliably deliver industrial applications to devices and cloud in a collaborative, secure manner. -sitemapPriority: 0.8 -enterprise: - heading: 'Built for Enterprise Manufacturing' - description: "FlowFuse is SOC 2 Type I and Type II certified, with role-based access control, single sign-on, audit logging, and air-gapped, self-hosted deployment options, built for the security and compliance requirements of large-scale manufacturing organizations." - badge: "components/icons/soc-2-badge.svg" - badgeLabel: "SOC 2 Type II" - items: - - icon: "components/icons/single-sign-on.svg" - label: "Single Sign-On" - - icon: "components/icons/audit-logs.svg" - label: "Audit Logs" - - icon: "components/icons/role-based-access.svg" - label: "Role-Based Access Control" - - icon: "components/icons/air-gapped-deployment.svg" - label: "Air-Gapped / Self-Hosted Deployment" - linkText: "Review our security and compliance details" - linkHref: "/platform/security/" ---- - - -
- -
-
- -
-
- -
-

- Build, deploy, and govern industrial applications - in record time. -

-

Bridge the gap between OT and IT teams using FlowFuse, the only comprehensive application platform with industrial AI and governance baked in.

- -
- -
-
- {% image "./images/features/flowfuse-features-hero.png", "FlowFuse Features", [750] %} -
-
-
- -
- -
-

- The needs of modern industry requires modern solutions -

- -
-
-

Vendor-Free Open Source

-

Every factory is different, so why deploy the same solution as everyone else? Build your way with ready-made blueprints, your own app store, or anything in between — we never dictate your tools.

-
-
-

Flexible and Secure

-

From whole-factory rollouts to last-mile fixes across any industry. Build your solution, then secure it with granular RBAC, auditing, version control, and traceability.

-
-
-

Seamless Collaboration

-

OT teams prototype and deploy fast while IT keeps the governance, security, and auditability they need — no trade-offs.

-
-
- -
-
-

Find the Product for How You Work

-
- - - -
- -
-
-
FlowFuse Edge
-

You run the plant floor

-

Connect PLCs, machines, and controllers. Standardize automation across sites instead of reinventing it plant by plant. Built for OT and digitalization leadership running production at scale.

-

Ideal fit if you're dealing with: multi-plant rollouts, PLC/SCADA integration, machine connectivity, or Node-RED sprawl across sites.

- Learn more about FlowFuse Edge → -
-
- {% image "./images/home/home-edge-connectivity.png", "FlowFuse Edge: industrial edge connectivity", [600] %} -
-
- -
-
-
FlowFuse Hub
-

You move data across the business

-

Integrate and orchestrate data across ERPs, databases, and cloud APIs — governed centrally, not stitched together app by app. Built for central IT and enterprise architecture teams.

-

Ideal fit if you're dealing with: system integration, API orchestration, data pipelines, or governance across business systems.

- Learn more about FlowFuse Hub → -
-
- {% image "./images/home/home-data-integration.png", "FlowFuse Hub: enterprise data integration", [600] %} -
-
- -
-
-
FlowFuse Fleet
-

You manage a Node-RED fleet

-

Push flows, ship updates, and roll back remotely across thousands of distributed devices, without waiting on a firmware release. Built for hardware OEMs, partners, and asset operators.

-

Ideal fit if you're dealing with: distributed devices, remote sites, sensors and telemetry at scale, or shipping Node-RED inside your own product.

- Learn more about FlowFuse Fleet → -
-
- {% image "./images/screen-pseudo-devices.png", "FlowFuse Fleet: device fleet management console", [600] %} -
-
- -
-
-
-
- - - -
-
-
-

The FlowFuse Platform is the Foundation for All of your IT/OT Applications

-

Leveraging enterprise-grade development, deployment, and governance solutions, you can build faster, scale more efficiently, and secure deployments of any size.

- -
- -
-
- -
-
-
-

{{ enterprise.heading | safe }}

-

{{ enterprise.description }}

- - {{ enterprise.linkText }} - {% include "components/icons/arrow-long-right.svg" %} - -
-
-
-
- {% include enterprise.badge %} -
-

{{ enterprise.badgeLabel }}

-
- {% for item in enterprise.items %} -
-
- {% include item.icon %} -
-

{{ item.label }}

-
- {% endfor %} -
-
-
- - -
-
-
-
-
-
-

Get Started with FlowFuse

-

Your first operational application could be running this week. Request a demo to see how, or explore pricing to find the right fit.

- -
-
-
-
From 19beb763ab0826f5e46a89acd7b8be9de2270217 Mon Sep 17 00:00:00 2001 From: Yndira-E Date: Mon, 10 Aug 2026 11:12:42 +0200 Subject: [PATCH 02/11] Fix broken /product links by adding it to prerender routes --- nuxt/nuxt.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/nuxt/nuxt.config.ts b/nuxt/nuxt.config.ts index 453e6a88a7..bdd538f507 100644 --- a/nuxt/nuxt.config.ts +++ b/nuxt/nuxt.config.ts @@ -237,6 +237,7 @@ export default defineNuxtConfig({ '/privacy-policy', '/integrations', '/pricing', + '/product', '/ebooks/beginner-guide-to-a-professional-nodered/', '/ebooks/ultimate-guide-to-building-applications-with-flowfuse-dashboard-for-node-red/', '/whitepaper/uns-decoupling-data-producers-and-consumers/', From bdee9f8824a597963852246ad241c53f68303595 Mon Sep 17 00:00:00 2001 From: Yndira-E Date: Mon, 10 Aug 2026 11:53:32 +0200 Subject: [PATCH 03/11] Fix enterprise section styling --- nuxt/pages/pricing/index.vue | 10 +++++++--- nuxt/pages/product/index.vue | 17 +++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/nuxt/pages/pricing/index.vue b/nuxt/pages/pricing/index.vue index 046c2044ff..e425229eb5 100644 --- a/nuxt/pages/pricing/index.vue +++ b/nuxt/pages/pricing/index.vue @@ -116,11 +116,15 @@ useSchemaOrg([ - + +

+ - + -
+
+
+

{