From 305f149f452d1ef363ec999b90b9d9909d244bc7 Mon Sep 17 00:00:00 2001 From: Yndira-E Date: Tue, 18 Aug 2026 08:36:07 +0200 Subject: [PATCH 1/2] Add unified CTA partials with PostHog tracking matching Nuxt --- .claude/CLAUDE.md | 12 +++- .eleventy.js | 9 +++ nuxt/components/CtaBookDemo.vue | 9 ++- nuxt/components/CtaContactUs.vue | 9 ++- nuxt/components/CtaSignIn.vue | 9 ++- nuxt/components/CtaSignUp.vue | 14 +++-- nuxt/content/handbook/marketing/website.md | 16 +++++- src/_data/ctaDestinations.json | 24 ++++++++ src/_includes/common-cta.njk | 7 ++- .../components/cta/cta-book-demo.njk | 15 +++++ src/_includes/components/cta/cta-button.njk | 57 +++++++++++++++++++ .../components/cta/cta-contact-us.njk | 18 ++++++ src/_includes/components/cta/cta-sign-in.njk | 16 ++++++ src/_includes/components/cta/cta-sign-up.njk | 25 ++++++++ src/_includes/components/top-utility-bar.njk | 5 +- src/_includes/cta-get-started.njk | 7 ++- src/_includes/get-started.njk | 10 ++-- src/_includes/layouts/abm-landing.njk | 31 +++------- src/_includes/layouts/base.njk | 19 +++---- src/_includes/layouts/industry.njk | 42 +++----------- src/_includes/layouts/story.njk | 5 +- src/_includes/layouts/use-case.njk | 8 ++- src/css/style.css | 28 +++++++++ src/events/proveit-2026.njk | 5 +- src/index.njk | 33 +++++------ src/industries.njk | 8 ++- src/industries/aerospace-components.md | 4 -- src/industries/automotive.njk | 11 ++-- src/industries/aviation-aerospace.md | 4 -- src/industries/electronics-appliances.md | 4 -- src/industries/food-beverage.md | 4 -- src/industries/life-sciences.md | 4 -- src/industries/renewables.md | 4 -- src/industries/semiconductors.md | 4 -- src/landing/edge-connectivity.njk | 6 -- src/landing/enterprise-integration.njk | 6 -- src/landing/line-control.njk | 6 -- src/landing/plant-orchestration.njk | 6 -- src/landing/plc.njk | 16 +++--- src/node-red/index.njk | 12 +++- src/partners/certify-hardware.njk | 7 +-- src/partners/ctrlx.njk | 7 +-- src/platform/why-flowfuse.njk | 6 +- src/use-cases.njk | 8 ++- src/use-cases/data-integration.njk | 6 +- src/use-cases/edge-connectivity.njk | 6 +- src/use-cases/it-ot-middleware.njk | 7 ++- src/use-cases/mes.njk | 11 ++-- src/use-cases/scada.njk | 11 ++-- src/use-cases/uns.njk | 11 ++-- src/vs/ignition.njk | 15 +---- src/vs/kepware.njk | 15 +---- src/vs/litmus.njk | 15 +---- 53 files changed, 392 insertions(+), 265 deletions(-) create mode 100644 src/_data/ctaDestinations.json create mode 100644 src/_includes/components/cta/cta-book-demo.njk create mode 100644 src/_includes/components/cta/cta-button.njk create mode 100644 src/_includes/components/cta/cta-contact-us.njk create mode 100644 src/_includes/components/cta/cta-sign-in.njk create mode 100644 src/_includes/components/cta/cta-sign-up.njk diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index cdac3713c1..891223821e 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -268,7 +268,7 @@ This also covers old `/docs/**` paths left behind by a rename in `flowfuse/flowf ## Call-to-Action components -**Nuxt only** — 11ty pages still use hand-written `` links; these components have no 11ty equivalent yet. +Both frameworks have their own implementation of the same system — Vue components on Nuxt, Nunjucks macros on 11ty (see **11ty equivalent** below). The rendering/styling code itself is kept in sync by hand (Vue and Nunjucks share nothing at that level), but the *data* — event name, href, and fixed label per destination — lives in one file, `src/_data/ctaDestinations.json`, imported by both the Vue components and the Nunjucks macros (via a Nunjucks global, see below) so the two can't drift apart on copy or event names without both sides being touched. `site.json`'s `appURL` is combined with `ctaDestinations`' `hrefSuffix` for `signUp`/`signIn`, since the base URL differs per environment and doesn't belong duplicated a third time in `ctaDestinations.json`. There are exactly four CTA destinations, each with its own component with **fixed copy and href** (a PostHog audit found dozens of different button texts pointing at the same four URLs, which made it impossible to tell which copy converted best — see `/handbook/marketing/website#call-to-action-buttons` for the non-engineer-facing explanation and a live gallery of every variant): @@ -299,6 +299,16 @@ Click tracking: `capture(event, { position, variant, plan? })` via `nuxt/composa - **The `ui` prop override doesn't reach compoundVariants-driven classes.** The gotcha above (full-string replacement) only applies to the app.config-level base extension; UButton's own `variant`/`color`-driven classes (e.g. `ghost`'s `hover:bg-{color}/10`) are computed separately and still get merged in via `tv()` regardless of what `ui.base` says. `CtaButton.vue`'s ghost color classes explicitly add `hover:bg-transparent` to cancel that default hover background, since a ghost CTA should have none at all. - **UButton's `to` prop treats any same-origin-looking path as a Nuxt route, even if Nuxt doesn't serve it.** `CtaContactUs`/`CtaBookDemo` point at `/contact-us/` and `/book-demo/`, which are still 11ty — without `external`, clicking does a client-side Vue Router navigation instead of a real page load, and 404s instead of reaching the server-side 11ty proxy. `CtaButton.vue` takes a fixed (non-caller-configurable) `external` prop per destination; `CtaContactUs`/`CtaBookDemo` set it `true`, `CtaSignUp`/`CtaSignIn` set it `false` (moot — those hrefs are already cross-origin). **When `/contact-us/` or `/book-demo/` actually migrates to Nuxt, flip that destination's `external` to `false`** — leaving it `true` would keep forcing a full page reload where a client-side nav would work fine. +### 11ty equivalent + +`src/_includes/components/cta/` holds four Nunjucks macros (`ctaSignUp`, `ctaSignIn`, `ctaContactUs`, `ctaBookDemo`) mirroring the Vue components above — same fixed copy/href/event per destination, same `{ position, variant }` capture payload (no `preview` or `external` prop: 11ty has no handbook gallery to guard against, and every `` is a real page load already, no Vue Router to fight). All four delegate to a shared `ctaButton` macro in the same folder (`cta-button.njk`) for the actual class-building/tracking, same relationship as `CtaButton.vue` to its wrappers. + +No `plan` param on the 11ty side — that's only used on the pricing table, which is Nuxt-only (there's no 11ty pricing page to call it from). If 11ty ever grows a caller that needs it, add it back rather than passing it unused today. + +`ctaSignUp`/`ctaSignIn` take `site` as their first argument (`site.appURL` for the href) instead of reading it from the calling template's context — Nunjucks macros don't inherit the caller's context unless explicitly imported `with context`, and passing `site` explicitly avoids relying on that import mode everywhere the macro is used. + +Every hand-written `` pointing at one of the four destinations has been migrated to these macros — there should be no new ones. A link to a URL outside the four fixed destinations (e.g. `/pricing/`, `/ai/`) is not part of this system and stays hand-written. + ## Naming conventions - All slugs: **kebab-case** diff --git a/.eleventy.js b/.eleventy.js index 01105ef345..6ab6104eda 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -150,6 +150,15 @@ module.exports = function(eleventyConfig) { watch: ["_site/**/*.css", "_site/**/*.js"], }) + // CTA event/href/label data (single source of truth shared with the Nuxt + // Cta* components, which import the same src/_data/ctaDestinations.json) + // registered as a Nunjucks *global* rather than page data: the four + // ctaSignUp/ctaSignIn/ctaContactUs/ctaBookDemo macros need to read it, and + // Nunjucks macros don't see page-context data (addGlobalData) unless it's + // explicitly passed in as an argument - addNunjucksGlobal makes it visible + // everywhere, macros included, with no per-call-site plumbing. + eleventyConfig.addNunjucksGlobal('ctaDestinations', require('./src/_data/ctaDestinations.json')) + // make global accessible in src/_includes/layouts/base.njk for loading of PH scripts eleventyConfig.addGlobalData('POSTHOG_APIKEY', () => process.env.POSTHOG_APIKEY || '' ) eleventyConfig.addGlobalData('DEV_MODE', () => DEV_MODE || DEV_MODE_POSTS) diff --git a/nuxt/components/CtaBookDemo.vue b/nuxt/components/CtaBookDemo.vue index c22e704d5a..0dfb276d78 100644 --- a/nuxt/components/CtaBookDemo.vue +++ b/nuxt/components/CtaBookDemo.vue @@ -2,6 +2,8 @@ // Fixed destination, copy, and tracked event - only the look (variant) and // where it lives on the page (position) vary per insertion. import CtaButton from './cta/CtaButton.vue' +// Shared with the equivalent 11ty macro (cta-book-demo.njk) - see CtaSignUp.vue. +import ctaDestinations from '../../src/_data/ctaDestinations.json' withDefaults(defineProps<{ variant: 'primary' | 'primary-outlined' | 'highlight' | 'highlight-outlined' | 'nav-text' | 'ghost' @@ -14,9 +16,10 @@ withDefaults(defineProps<{ icon?: string }>(), { uppercase: undefined }) -const EVENT = 'cta-book-demo' -const HREF = '/book-demo/' -const LABEL = 'Book a Demo' +const dest = ctaDestinations.bookDemo +const EVENT = dest.event +const HREF = dest.href +const LABEL = dest.label // /book-demo/ is still served by 11ty, not a Nuxt route - see CtaButton.vue's // `external` prop. diff --git a/nuxt/components/CtaContactUs.vue b/nuxt/components/CtaContactUs.vue index e6d5f03185..fbfed47f84 100644 --- a/nuxt/components/CtaContactUs.vue +++ b/nuxt/components/CtaContactUs.vue @@ -2,6 +2,8 @@ // Fixed destination, copy, and tracked event - only the look (variant) and // where it lives on the page (position) vary per insertion. import CtaButton from './cta/CtaButton.vue' +// Shared with the equivalent 11ty macro (cta-contact-us.njk) - see CtaSignUp.vue. +import ctaDestinations from '../../src/_data/ctaDestinations.json' withDefaults(defineProps<{ variant: 'primary' | 'primary-outlined' | 'highlight' | 'highlight-outlined' | 'nav-text' | 'ghost' @@ -14,9 +16,10 @@ withDefaults(defineProps<{ icon?: string }>(), { uppercase: undefined }) -const EVENT = 'cta-contact-us' -const HREF = '/contact-us/' -const LABEL = 'Contact Us' +const dest = ctaDestinations.contactUs +const EVENT = dest.event +const HREF = dest.href +const LABEL = dest.label // /contact-us/ is still served by 11ty, not a Nuxt route - see CtaButton.vue's // `external` prop. diff --git a/nuxt/components/CtaSignIn.vue b/nuxt/components/CtaSignIn.vue index b12149277d..2fd317cc80 100644 --- a/nuxt/components/CtaSignIn.vue +++ b/nuxt/components/CtaSignIn.vue @@ -4,6 +4,8 @@ // First component to give the Sign In link any tracking at all. import CtaButton from './cta/CtaButton.vue' import site from '../../src/_data/site.json' +// Shared with the equivalent 11ty macro (cta-sign-in.njk) - see CtaSignUp.vue. +import ctaDestinations from '../../src/_data/ctaDestinations.json' withDefaults(defineProps<{ variant: 'primary' | 'primary-outlined' | 'highlight' | 'highlight-outlined' | 'nav-text' | 'ghost' @@ -16,9 +18,10 @@ withDefaults(defineProps<{ icon?: string }>(), { uppercase: undefined }) -const EVENT = 'cta-sign-in' -const HREF = site.appURL -const LABEL = 'Sign In' +const dest = ctaDestinations.signIn +const EVENT = dest.event +const HREF = `${site.appURL}${dest.hrefSuffix}` +const LABEL = dest.label