Skip to content

Unified Cherry City Landing Page

Replace the dual-LP setup (/cherry-city/tour for monthly, /cherry-city/hourly for hourly) with a single unified landing page at /lp/cherry-city that serves both products from one cold-traffic conversion path.

TL;DR

  • New LP at /lp/cherry-city becomes the cold-traffic conversion target
  • Pain-led shared hero → lightweight fork ("monthly or hourly?") → full-width product sections below → shared proof / testimonials / FAQ
  • Existing /cherry-city/tour and /cherry-city/hourly redirect to /lp/cherry-city
  • New custom Pixel + CAPI event (LandingFunnelChoice) fires when a visitor clicks either fork CTA — used as the unified Meta optimization target
  • Most existing LP content is reused; the rebuild is a recomposition, not a from-scratch redesign

Why

Current state: two separate LPs and two separate Meta campaigns produce ~5 paid conversions/week combined at $100/day spend. Meta's Learning Phase requires ~50 events/week to stabilize. Salem is a single small market (~75k pop); the 5% lookalike is already broad. The volume gap is event-cost driven, not audience-size driven.

Solution: unify the two LPs so paid Meta can optimize against a single mid-funnel event (LandingFunnelChoice, fired on fork CTA click) that's intent-loaded but cheap enough to clear 50/week at $100/day budget.

The custom event is the only event that fires on both monthly-curious and hourly-curious paths — Lead only fires on monthly tour-request submit, InitiateCheckout only fires on hourly money-checkout. Without this event, no single optimization target exists for a unified campaign.

Locked decisions

URL and routing

  • New LP at /lp/cherry-city
  • Code path: apps/web/src/app/(landing)/lp/cherry-city/
  • /cherry-city/tour → 301 redirect to /lp/cherry-city
  • /cherry-city/hourly → 301 redirect to /lp/cherry-city
  • /cherry-city (location overview, dynamic [locationSlug] route) — untouched
  • The /lp/<location> namespace is intentional — sets up future expansion to other locations and markets without polluting the location content tree

Page structure

[ HERO — pain-led + subline + scroll cue ]
[ FORK — "How do you want to play?" — monthly card | hourly card ]
[ SHARED PROOF BAR — 500+ bands / 49 studios / 30+ Salem / 24/7 ]
[ MONTHLY FULL SECTION — anchor target #1 ]
[ HOURLY FULL SECTION — anchor target #2 ]
[ SHARED TESTIMONIALS ]
[ SHARED FAQ ]
[ CLOSING CTA — back to fork ]

Mobile: stacks naturally top-to-bottom. Desktop: same vertical flow; only the fork is two-column on desktop, stacked on mobile.

Hero

  • Headline: "Band practice at home sucks. We fixed that."
  • Subline: "Salem's rehearsal studio. Get a monthly lockout or book by the hour."
  • Background: full-bleed photo (suggest reusing /images/marketing/frontman-jump.jpeg from current /tour with same dark overlay treatment)
  • No primary CTA button. Optional small text link "See your options ↓" anchored to the fork section
  • Fold tease: design so the top of the fork peeks into the viewport at hero's end — pulls scroll without a button
  • Why this copy: apartment-displacement is the broadest universal pain; the subline introduces the category (rehearsal studio), the city, and the dual-product structure in one beat. Cold Salem audience is problem-aware (knows the pain) but not solution-aware (doesn't know rehearsal studios as a category) — pain-led is the right awareness-stage hook

Fork section

  • Section heading: "How do you want to play?"
  • Two cards side-by-side on desktop, stacked on mobile

Monthly card:

  • Headline: "Your own room."
  • Visual: BeforeAfterSlider (existing component) — empty studio → stocked studio. This is the only visual asset that genuinely encodes the monthly-vs-hourly distinction (monthly = "you fill this room over time"; hourly = "always stocked"). Photos alone can't carry it because both rooms look the same once a band has set up gear.
  • Bullets: From $285/mo · Your gear, your key · 30+ Salem bands
  • CTA button: "Free tour →"
  • CTA action: smooth-scroll/anchor to monthly full section

Hourly card:

  • Headline: "Walk in. Plug in. Play." (existing approved /hourly headline — preserves any social proof if this copy ever lifts into ad creative via Use Existing Post)
  • Visual: static photo of a stocked hourly studio (suggest reusing /images/marketing/hourly-marketing-studio-2.jpeg)
  • Bullets: From $15/hr · Drums, amps, PA included · Book a studio in 60 seconds
  • CTA button: "See times →"
  • CTA action: smooth-scroll/anchor to hourly full section

Both CTAs fire LandingFunnelChoice on click. See Tracking spec below.

Shared proof bar

Reuse the existing 4-stat layout pattern (current /tour and /hourly both render it):

  • 500+ Bands across PDX & Salem
  • 49 Studios at Cherry City
  • 30+ Salem bands
  • 24/7 Access, always

Use "Salem bands" wording (not "Cherry City bands") — Salem reads more universal for cold-traffic context.

Monthly full section

Reuse from current TourLandingContent.tsx — keep:

  • BeforeAfterSlider (full-size; same asset as fork card)
  • Email-only LeadCaptureForm (existing component, variant="email-only", embedded)
  • Community Manager intro card — keep (social trust signal); render adjacent to or above the form
  • "What you get with a lockout" CardCarouselSection (existing — keep all FeatureCards)
  • "How it works" 3-step StepsSection (existing — keep)
  • PhotoGrid of location images (existing — keep)

Drop from current /tour:

  • The /tour hero (replaced by unified hero)
  • Stats bar (moved to shared proof bar)
  • Testimonials section (moved to shared section)
  • FAQ section (moved to shared section)
  • "Not ready for a lockout?" cross-link section to /hourly (no longer needed — hourly is on the same page)

Hourly full section

Reuse from current HourlyLandingContent.tsx — keep:

  • Embedded HourlyBookingContent for featured Studio B (existing pattern: simplified prop, ModalWrapper auth gating)
  • "All-Inclusive" CardCarouselSection (existing — keep all FeatureCards)
  • "Easy to Get Started" 3-step StepsSection (existing — keep)
  • ResourceCarouselDisplay for "Browse Other Hourly Studios" (existing — keep)
  • HourlyBookingModal for non-featured studios (existing — keep)

Drop from current /hourly:

  • The /hourly hero (replaced by unified hero)
  • Stats bar (moved to shared proof bar)
  • Testimonials section (moved to shared section)
  • FAQ section (moved to shared section)
  • "Playing here all the time?" cross-link section to /tour (no longer needed)
  • The mid-page "Book a Studio" button section that scrolls back to scheduler (redundant once page is unified)

Shared testimonials

Merge the two existing testimonial sets. Several quotes appear on both LPs (Michael, Timothy "Hardhead", Joseph) — render once, not twice. Use existing TestimonialsSection and TestimonialCard components. Carousel layout.

Shared FAQ

Merge the two existing FAQ sets into one FAQSection accordion. Order: monthly-relevant questions first, hourly-relevant questions second, shared questions deduplicated. Source of truth for FAQ items lives in the page server components currently — pull and merge.

Closing CTA

Single block at the bottom. Headline along the lines of "Ready?" or "You ready?" (matches existing approved CTA copy from brand-voice.md). Single button or two-button "Free tour" / "See times" — both anchor back to the fork. Keep tight.

Tracking spec

A new custom event is the centerpiece of this change — it's the unified optimization target that makes the unified campaign possible.

Event name: LandingFunnelChoice

Fires when a visitor clicks either fork CTA on /lp/cherry-city.

Pixel side (browser):

  • New tracking function in apps/web/src/lib/tracking.ts, e.g. trackLandingFunnelChoice({ choice: 'monthly' | 'hourly', locationId })
  • Calls trackMetaEvent('LandingFunnelChoice', { content_name: choice, content_category: 'lp-fork' }, { eventID: <generated> })
  • Follow the existing pattern from trackTourRequested (lines 91–104)
  • Wire to both fork CTA onClick handlers in the new LP

CAPI side (server):

  • New module: apps/api/src/lib/meta/track-funnel-choice.ts, following the pattern of track-lead.ts
  • New API endpoint (e.g., POST /api/meta/funnel-choice) that the LP calls fire-and-forget on CTA click, passing the matching event_id for dedup
  • Use the existing capi-client.ts, hash-identity.ts, and identity-payload machinery (email, fbp, fbc, IP, UA, external_id)
  • Send via Next.js after() with fetch timeout, like other CAPI calls

Custom Conversion in Meta Ads Manager:

  • Define a custom conversion based on the LandingFunnelChoice Pixel event
  • This becomes the optimization event for the new unified campaign

Why both Pixel and CAPI: matches existing dedup pattern documented in docs/marketing/paid-meta.md (PR #652). Maintains EMQ ≥ 8.0 target.

Event name: ScrollDepth75

Fires once per session per URL when the visitor scrolls past 75% of the LP's document height. Seeds the ScrollDepth75 — 90d retargeting audience defined in cherry-city-ppc-prospecting/spec.md.

Pixel side (browser):

  • New tracking function in apps/web/src/lib/tracking.ts, e.g. trackScrollDepth75({ url })
  • Calls trackMetaEvent('ScrollDepth75', { content_name: url, content_category: 'lp-scroll-depth' }, { eventID: <generated> })
  • New useScrollDepth75() hook fires the function once when scrollY + innerHeight >= 0.75 * documentHeight. Guard with sessionStorage keyed on URL so it fires at most once per session per URL.

CAPI side (server):

  • New module: apps/api/src/lib/meta/track-scroll-depth.ts, mirroring track-funnel-choice.ts. event_name = 'ScrollDepth75', event_source_url = url, custom_data.content_name = url.
  • New API endpoint POST /api/meta/scroll-depth that the LP calls fire-and-forget when the threshold trips, passing the matching event_id for dedup.
  • Use the existing capi-client.ts, hash-identity.ts, and identity-payload machinery (fbp, fbc, IP, UA — anonymous visitors, no email/external_id).
  • Send via Next.js after() with fetch timeout, like other CAPI calls.

Why both Pixel and CAPI: same dedup pattern as LandingFunnelChoice. Browser-only events get throttled by ATT/iOS Safari; CAPI mirror keeps coverage high.

Existing events continue to fire downstream

Event Where it fires
ViewContent LP load (existing trackLocationLandingViewed — or wire equivalent for the new LP)
Lead Monthly form submit (existing trackTourRequested)
InitiateCheckout Hourly path post-route-change at /checkout/reservation (existing — useCheckoutFlow line 578/654)
Purchase Hourly checkout success (existing)

These are measurement signals. They are not the optimization target — LandingFunnelChoice is.

Additional (separate specs)

The following are upstream of this LP work and need their own specs / decisions:

  • Ad creative + ad copy — visual format (single image vs carousel vs video), primary text, headline, CTA button, ad ↔ LP message coherence. Open question: does the ad reveal the dual-product nature, or stay singular?
  • Meta campaign migration mechanics — research-backed best practice is to duplicate META_CherryCity_Monthly, modify the duplicate (URL → /lp/cherry-city, optimization event → LandingFunnelChoice, creative → new), preserve original as paused control. Use "Use Existing Post" to carry social proof from winning ads ("Community – Just Got Louder", "No Commitment").

References

Code:

  • apps/web/src/app/(landing)/cherry-city/tour/TourLandingContent.tsx — current monthly LP, primary content source
  • apps/web/src/app/(landing)/cherry-city/hourly/HourlyLandingContent.tsx — current hourly LP, primary content source
  • apps/web/src/app/(marketing)/[locationSlug]/LeadCaptureForm.tsx — email-only form to embed in monthly section
  • apps/web/src/components/organisms/booking/HourlyBookingContent.tsx — hourly scheduler embed
  • apps/web/src/components/molecules/media/BeforeAfterSlider.tsx — empty/full slider component
  • apps/web/src/components/organisms/marketing/sections/ — reusable LP section primitives (CardCarouselSection, FAQSection, StepsSection, TestimonialsSection)
  • apps/web/src/lib/tracking.ts — pattern for new trackLandingFunnelChoice
  • apps/api/src/lib/meta/track-lead.ts — pattern for new track-funnel-choice.ts

Strategy:

  • docs/marketing/icp.md — ICP archetype + hourly ICP differentiation
  • docs/marketing/positioning.md — displacement frame (apartment / garage / storage / basement)
  • docs/marketing/brand-voice.md — voice principles, vocabulary, on/off-voice patterns
  • docs/marketing/paid-meta.md — campaign mechanics, attribution, learning phase rules

External research backing key decisions: