---
path: /landing
surface: customer
domain: onboarding-public
auth: anon
source: apps/oshun/web/src/app/landing/page.tsx
status: walked
last_walked:
  '2026-06-30 Playwright real-dev-infra deepening — default render, exact
  rooms/letters/tiers, primary and tier CTA destinations, public-auth-funnel
  handoff telemetry, social metadata/PNG previews, sitemap entry, mobile
  standalone no-overflow/44 px CTA targets, reduced-motion fixture default, and
  warmed real-service-worker offline document replay verified in
  apps/oshun/web/e2e/public-marketing.spec.ts'
---

# Landing — OSHUN public marketing front

## Purpose

The public editorial face of OSHUN: one anonymous route that explains the six
rooms, shows product screenshots, and routes readers into the `/welcome` sign-up
panel with `entry=marketing-landing`. Distinct from `/welcome`, which is the
auth-coupled entry surface.

## Entry points

- Marketing campaigns (paid social, search ads) pointing to `/landing`
- Outbound newsletter "view in browser" link
- Direct URL / bookmark — yes (anon)
- Search/social crawlers through canonical, OpenGraph, Twitter card, and JSON-LD
  metadata
- `/legal/lilith` colophon link from the lede

## Layout regions

`page.tsx` is a thin server component:
`const data = await getMarketingLanding(); return <MarketingLanding data={data} />`.
`MarketingLanding` renders inside the shared Lilith `LWebShell`.

- **Masthead**: OSHUN broadsheet masthead with dated issue marker
- **Product preview strip**: two screenshot images under
  `[data-marketing-visual-strip]`
- **Three-column lede**: manifesto copy, primary sign-up CTA, colophon link,
  field-guide pull quote, by-the-numbers cells, and six room inventory
- **Launch rooms in use**: three static letter/scenario pull quotes
- **Membership**: three pricing tiers (`solo`, `hearth`, `institutional`) with
  tier-specific sign-up links
- **Footer**: text-only Lilith-register footer. The "Privacy" and "Colophon"
  entries are rendered as spans, not legal navigation anchors.

## States

- [ ] **Loading** — `getMarketingLanding()` is awaited server-side; no Suspense
      boundary in `page.tsx` itself, so server blocks until data ready
- [x] **Default (populated)** — `public-marketing` verifies the fixture-backed
      room, letter, tier, screenshot, structured-data, metadata, and sitemap
      render
- [ ] **Error (unrecoverable)** — there is no segment `error.tsx`; failures fall
      through the app-level error boundary
- [x] **Offline** — `public-marketing` warms `/landing`, waits for the real
      `public/sw.js` controller, asserts `/landing` is cached in an `oshun-*`
      cache, then reloads offline and verifies a service-worker response without
      the generic offline fallback markers
- [x] **Standalone PWA** — 390 px standalone launch is mocked through the shared
      PWA helper and verified for no horizontal overflow
- [x] **Reduced motion** — the shared E2E fixture emulates
      `prefers-reduced-motion: reduce` while the same default/mobile assertions
      run

## Interactions

- [x] **Primary CTA** — `Begin · 14 days free` links to
      `/welcome?mode=signup&entry=marketing-landing`; clicking it records
      `public_auth_funnel_cta_clicked` with `surface=marketing_landing`,
      `step=marketing_signup`, and `entrySource=marketing-landing`, then
      `/welcome` records the viewed event with the same entry source
- [x] **Pricing tier CTAs** — exact destinations are:
      `/welcome?mode=signup&entry=marketing-landing&tier=Solo`,
      `/welcome?mode=signup&entry=marketing-landing&tier=Hearth`, and
      `/welcome?mode=signup&entry=marketing-landing&tier=Institutional`; the
      Hearth click is browser-driven and records `step=marketing_tier_signup`
- [x] **Colophon link** — `Read the colophon` targets `/legal/lilith`
- [x] **Letters/scenarios** — the three pull quotes are static: "Morning
      practitioner", "Careful reader", and "Skywatching family". There are no
      carousel controls.
- [x] **Footer markers** — footer is present, contains "Privacy" and "Colophon"
      copy, and intentionally has zero descendant anchors

## Data & contracts

- **Reads**: `getMarketingLanding()` from `@/lib/lilith-data/marketing-landing`
  returns `getMarketingLandingFixture()` today. The fixture contains:
  - rooms: Tara, Arete, Veritas, Nyx, Nisaba, Metis
  - letters: Morning practitioner, Careful reader, Skywatching family
  - tiers: Solo `$9`, Hearth `$14`, Institutional `From $4`
- **Writes**: none from this page
- **Caching**: fixture-backed server render; `public/sw.js` treats `/landing` as
  an exact shell navigation route and caches the rendered HTML after a warm
  online navigation
- **Auth/role check**: anon (in middleware `PUBLIC_PREFIXES`)
- **Metadata**: `title: 'OSHUN — One calm shell for six practices'`,
  `description: 'OSHUN connects Tara meditation, Arete habits, Veritas evidence, Nyx skywatching, Nisaba reading, and Metis learning in one calm companion.'`,
  `alternates.canonical: '/landing'`
- **Structured data**: JSON-LD graph includes `Organization`, `WebApplication`,
  `OfferCatalog`, and `BreadcrumbList`
- **Crawler assets**: `/landing/opengraph-image` and `/landing/twitter-image`
  return PNG previews

## Cross-references

- Sibling public entry: [`welcome.md`](./welcome.md) — the auth-coupled variant
- Lilith editorial legal pages: [`legal-lilith.md`](./legal-lilith.md),
  [`legal-lilith-privacy.md`](./legal-lilith-privacy.md)
- Standard legal pages: [`legal-privacy.md`](./legal-privacy.md),
  [`legal-terms.md`](./legal-terms.md)
- Component sources:
  - `apps/oshun/web/src/components/lilith/MarketingLanding.tsx`
  - `apps/oshun/web/src/lib/lilith-data/marketing-landing.ts`
- E2E:
  - `apps/oshun/web/e2e/public-marketing.spec.ts`

## Open questions / known gaps

- [ ] `getMarketingLanding()` is still fixture-backed; BFF/CMS ownership,
      freshness policy, and release process are future wiring work.
- [ ] There is no route-local `error.tsx` for `/landing`.
- [ ] The footer's "Privacy" and "Colophon" markers are text-only by design
      today. If product wants footer navigation, the component needs real
      anchors and the walkthrough should move those checks from negative to
      positive assertions.
