feat(ads): ChatGPT ad images and direct offer (money-back guarantee, no trial) for paid-ads visitors

Merged
RI
1d ago
+640-97

Why

Paid-ads visitors get a pay-first offer: no free trial, backed by a 14-day money-back guarantee. Every ad URL will carry offer=direct.

Ships together with the backend. The backend part (no trial for signups whose metadata has offer: 'direct') lands separately in the Cap-go/capgo repo. Both PRs must ship together. If this goes out alone, ad visitors will see "money-back guarantee" copy but still get the trial. If the backend goes out alone, nothing changes because no signup sends offer.

What

Detection and persistence (apps/web/src/lib/attribution.ts, Attribution.astro)

  • offer query param is captured only when its value is exactly direct. Any other value is ignored, both from the URL and from stored data.
  • It is stored with the same first-touch cookie + localStorage mechanism as utm_*, so it survives going from the landing page to /register/. Once set, it stays set (first touch, 180 days).
  • getSignupUserMetadataForRegistration() adds offer: 'direct'. Since #1235, register.ts spreads it into the POST /auth/register body. RegisterUserInput declares offer?: 'direct', and the backend schema in Cap-go/capgo.app#3782 accepts an optional offer and only acts on direct.
  • getSignupAttributionForEvents() and PostHog attribution props add signup_offer: 'direct'.
  • Link decoration also forwards offer=direct on register and console links.
  • The synchronous inline head script sets <html data-offer="direct"> before first paint, either from the URL or from stored attribution.

Copy swap without a flash

  • In global.css, [data-offer='direct'] [data-trial-copy] is hidden, and html:not([data-offer='direct']) [data-direct-copy] is hidden. Both variants are static HTML, and CSS shows exactly one of them.
  • The new OfferText.astro component renders the trial text plus its direct variant.
  • Normal visitors get the same visible copy as before. JSON-LD and FAQ data are untouched.
WhereNormal visitoroffer=direct
Header CTA (desktop + mobile)Start for freeGet started
SignupCta button (hero + final CTA on all pages using it)Get started for freeGet started
SignupCta note14-day unlimited free trial. No credit card required14-day money-back guarantee. Cancel anytime.
/solutions/[builder]-to-mobile/ CTA subtitleStart the 14-day free trial, export your {builder} project, …Pick a plan with a 14-day money-back guarantee, export your {builder} project, …
/solutions/webapp-to-mobile/ CTA subtitleStart the 14-day free trial, wrap your app with Capacitor, …Pick a plan with a 14-day money-back guarantee, wrap your app with Capacitor, …
/solutions/build-without-mac/ CTA subtitleRun your first signed iOS build during the 14-day free trial, …Run your first signed iOS build with a 14-day money-back guarantee, …
/live-update/ comparison "Try before you pay"14-day unlimited free trial. No credit card required.14-day money-back guarantee. Cancel anytime.
/live-update/ final CTA meta14-day unlimited free trial14-day money-back guarantee
/native-build/ comparison hookYes. 14-day free trial, no credit card.Yes. 14-day money-back guarantee.
/native-build/ pricing lead… Start with a 14-day free trial, then pick monthly or annual billing.… Pick monthly or annual billing, with a 14-day money-back guarantee.
/native-build/ plan buttons14-day free trialGet started
/native-build/ final CTA metaNo credit card to start · 14-day free trial · Cancel anytime14-day money-back guarantee · Cancel anytime (credit-card line hidden)
/register/ pills14-day unlimited free trial · No credit card required14-day money-back guarantee · Cancel anytime
/pricing/ H114 days free, no credit card. Scale to millions.14-day money-back guarantee. Scale to millions.
/pricing/ plan buttons (Plans.astro)14-day unlimited free trial (a11y name "Start {plan} free trial")Get started (a11y name "Get started with the {plan} plan")
/pricing/ calculator CTAs (PricingCalculator.astro, desktop + mobile bar)14-day unlimited free trialGet started
/pricing/ "How pricing works" highlight (PricingExplainer.astro)14-day trial, no credit card14-day money-back guarantee
/pricing/ FAQ section title / subtitleTrial & Support / Free trials, discounts, and getting helpGuarantee & Support / Money-back guarantee, discounts, and getting help
/pricing/ FAQ answer "unsure about active users" (visible only, JSON-LD unchanged)…take advantage of Capgo's free trial……pick the plan that matches your best estimate. Every plan comes with a 14-day money-back guarantee…

New message keys are in apps/shared/copy/messages.ts: direct_offer_cta_get_started, direct_offer_guarantee, direct_offer_guarantee_note, direct_offer_cancel_anytime, *_cta_subtitle_direct (×3), native_build_v2_comp_try_capgo_hook_direct, native_build_builder_pricing_text_direct, plans_that_scale_with_your_business_direct, pricing_explainer_trial_direct, free_trial_explanation_direct, pricing_faq_trial_support_title_direct, pricing_faq_trial_support_subtitle_direct, direct_offer_plan_cta_aria. Contexts were regenerated with bun run generate:message-contexts.

Verification

After rebasing onto main (#1235, #1239 API signup + Turnstile, #1240 TS 7 + oxlint):

  • bun run lint (oxlint): exit 0. No new warnings in changed files; the one in check-contrast.mjs (no-useless-escape) was already there.
  • Tests: the CI set (abtest, unifiedRss, attribution, scripts/register.test.ts, services/registration.test.ts) plus pricing-faq: 42 pass. The register tests assert offer: 'direct' in the API request body and signup_offer on user_signed_up.
  • astro check 0 errors, build:web OK, seo:check 0 errors / 0 warnings, contrast:check passed.
  • Contrast check: plan CTAs now carry a stable data-plan-cta hook. The old selector matched the trial-specific aria-label, which can't be correct for both variants. The check measures the visible label variant on /pricing/ and again on /pricing/?offer=direct.
  • Turnstile: built with Cloudflare's always-pass test site key. The widget renders on /register/ for both normal and direct-offer visitors, below the swapped badges (see the refreshed screenshot).

Before the rebase:

  • bun test apps/web/test/attribution.test.js: 25 pass. New tests cover offer parsing and validation, persistence and merge, deserialization, PostHog props, link forwarding, and registration metadata/events with stored and URL offer.
  • cd apps/web && bun run check (astro check): 0 errors, 0 warnings.
  • bun run build:web: OK.
  • bun run seo:check: 0 errors, 0 warnings.
  • Headless Chrome against the built dist:
    • /register/ with a fresh profile has no data-offer.
    • After visiting /live-update/?offer=direct, /register/ (no param) gets data-offer="direct" and shows the guarantee pills and the "Get started" header.
    • ?offer=foo gets no flag.
    • The lovable-to-mobile hero shows "Get started for free / 14-day unlimited free trial…" normally and "Get started / 14-day money-back guarantee. Cancel anytime." with ?offer=direct.
  • /pricing/ screenshots with and without the direct offer (persisted from a lovable-to-mobile ad landing) look correct. The HTML scan of /pricing/ finds only the payment-methods FAQ ("We accept all major credit cards"), which is not trial copy.
  • An HTML scan of the six target pages finds no trial wording outside data-trial-copy elements (scripts/JSON-LD excluded).

Screenshots

Built from this branch and served locally. Captured with headless Chrome at 1440px, with a fresh profile per capture. Left: normal visitor. Right: ?offer=direct.

/solutions/lovable-to-mobile/: hero
lovable hero

/solutions/lovable-to-mobile/: final CTA
lovable final CTA

/live-update/: final CTA
live-update final CTA

/native-build/: pricing and plan buttons
native-build pricing

/pricing/: headline and plan buttons
pricing hero and plans

/register/: fresh visit vs. reached after visiting /solutions/lovable-to-mobile/?offer=direct (no query param on /register/)
register

Ad images (previously #1233)

27 square ad images (1080×1080 PNG) for the ChatGPT ad campaigns, one per ad. No image is used twice:

  • 5 builder pages from #1226 (Lovable, Base44, Bolt, Replit, v0) × 3 angles: store listing, no Mac, live updates.
  • 4 use-case pages × 3 angles:
    • /live-update/: ship fixes without review, upload command, rollback.
    • /native-build/: one command, cloud iOS builds, QR sharing.
    • /solutions/build-without-mac/: no Mac, ship from Windows, credentials deleted after the build.
    • /solutions/webapp-to-mobile/: store listing, one codebase, cloud builds.

The CTA on each image says "Get started" with "14-day money-back guarantee" under it. That matches the offer=direct ad offer: no free trial, money-back guarantee instead. Text on the images only repeats claims the landing pages already make.

Images are served at https://capgo.app/ads/chatgpt/{campaign}-{variant}.png. docs/ads/chatgpt-campaigns.md lists the 9 campaigns with each ad's title, copy, image and URL.

Ad image screenshots

Each row is one campaign; the columns are ads A, B and C.

CampaignABC
LovableLovable ad a_storeLovable ad b_nomacLovable ad c_updates
Base44Base44 ad a_storeBase44 ad b_nomacBase44 ad c_updates
BoltBolt ad a_storeBolt ad b_nomacBolt ad c_updates
ReplitReplit ad a_storeReplit ad b_nomacReplit ad c_updates
v0v0 ad a_storev0 ad b_nomacv0 ad c_updates
Live UpdateLive Update ad a_otaLive Update ad b_minutesLive Update ad c_rollback
Native BuildNative Build ad a_commandNative Build ad b_cloudNative Build ad c_qr
Build without MacBuild without Mac ad a_nomacBuild without Mac ad b_windowsBuild without Mac ad c_keys
Web app to mobileWeb app to mobile ad a_storeWeb app to mobile ad b_codebaseWeb app to mobile ad c_build

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added direct-offer messaging across pricing, signup, navigation, FAQs, and solution pages.
    • Visitors arriving with offer=direct now see a 14-day money-back guarantee and cancellation terms instead of free-trial messaging.
    • Direct-offer attribution is retained and included in registration and analytics data.
    • Added localized “Get started” labels and accessible plan-specific CTA text.
  • Documentation

    • Added ChatGPT advertising campaign guidance, landing pages, tracking links, and creative specifications.
  • Tests

    • Expanded coverage for direct-offer attribution and pricing CTA validation.