Files
freno-dev/src/routes/nessa/index.tsx

363 lines
14 KiB
TypeScript

/**
* Nessa landing page.
*
* Serves `nessa.freno.me/` (and falls back from `src/routes/index.tsx`'s
* `useSite()` branch in dev). Reflects Nessa's actual product: a
* privacy-first fitness app with segment leaderboards,
* clubs, challenges, Apple Watch support, and Free / Plus / Pro pricing tiers.
*
* Content is sourced from `~/code/Nessa/plans/2026-03-16-marketing-strategy-launch-positioning.md`
* and kept in a pure `./content.ts` module so the acceptance matrix is
* testable without spinning up the router.
*/
import { For, Show } from "solid-js";
import { PageHead } from "~/components/PageHead";
import SubdomainHeader from "~/components/SubdomainHeader";
import { useDarkMode } from "~/context/darkMode";
import { useSite } from "~/context/SiteContext";
import { A } from "@solidjs/router";
import { buildMainSiteUrl } from "~/lib/site-context";
import { NESSA_LANDING_META } from "./meta";
import {
TAGLINE,
SUBTITLE,
ICON_DEFAULT,
ICON_DARK,
SCREENSHOTS,
FEATURES,
PRICING,
COMPARISON,
WHY_NESSA
} from "./content";
/** Small SVG checkmark used in pricing cards. */
function CheckIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="mt-0.5 h-5 w-5 shrink-0"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M20 6 9 17l-5-5" />
</svg>
);
}
export default function NessaLanding() {
const site = useSite();
const { isDark } = useDarkMode();
const iconSrc = () => (isDark() ? ICON_DARK : ICON_DEFAULT);
const brandColor = () =>
isDark() ? (site().brandColorDark ?? site().brandColor) : site().brandColor;
return (
<>
<PageHead {...NESSA_LANDING_META} />
<SubdomainHeader />
<main
class="relative min-h-screen w-full overflow-x-hidden"
style={{ "--brand-color": brandColor() }}
>
{/* Soft brand-tinted backdrop */}
<div
class="pointer-events-none fixed inset-0 z-0"
aria-hidden="true"
style={{
background: isDark()
? `radial-gradient(60% 50% at 50% 0%, ${brandColor()}22 0%, transparent 70%), radial-gradient(50% 40% at 80% 100%, ${brandColor()}1a 0%, transparent 70%)`
: `radial-gradient(60% 50% at 50% 0%, ${brandColor()}18 0%, transparent 70%), radial-gradient(50% 40% at 80% 100%, ${brandColor()}12 0%, transparent 70%)`
}}
/>
{/* ─── Hero ───────────────────────────────────────────────── */}
<section class="relative z-10 flex flex-col items-center px-4 pt-24 pb-16 text-center md:pt-32">
<div class="mb-8 flex h-28 w-28 items-center justify-center rounded-[1.75rem] shadow-2xl md:h-32 md:w-32">
<img
src={iconSrc()}
alt="Nessa app icon"
width={128}
height={128}
class="h-full w-full rounded-[1.75rem] object-cover"
/>
</div>
<h1 class="max-w-3xl text-4xl font-bold tracking-tight md:text-6xl">
{TAGLINE}
</h1>
<p class="text-text/85 mt-4 max-w-2xl text-lg md:text-2xl">
{SUBTITLE}
</p>
<div class="mt-10 flex flex-col items-center gap-4 sm:flex-row">
<A
href="/contact"
class="rounded-full px-8 py-3 text-base font-semibold text-white shadow-md transition-transform hover:scale-[1.02] active:scale-95"
style={{ background: brandColor() }}
>
Join the waitlist
</A>
<A
href="/contact"
class="border-surface2 hover:bg-surface0/40 text-accent rounded-full border-2 px-8 py-3 font-semibold transition-colors"
>
Request beta access
</A>
</div>
<p class="text-text/60 mt-4 text-sm">
Launching soon on the App Store.
</p>
</section>
{/* ─── Free-tier feature highlights ───────────────────────── */}
<section class="relative z-10 px-4 py-16" id="features">
<div class="mx-auto max-w-6xl">
<div class="mb-12 text-center">
<h2 class="text-3xl font-bold md:text-4xl">
Everything you need, free forever
</h2>
<p class="text-text/70 mx-auto mt-3 max-w-2xl text-base md:text-lg">
Nessa gives away the features other apps lock behind
subscriptions because your workouts shouldn't cost extra.
</p>
</div>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<For each={FEATURES}>
{(feature) => (
<div class="border-surface0 bg-surface0/30 hover:bg-surface0/50 flex flex-col gap-3 rounded-2xl border-2 p-6 transition-colors">
<div class="flex items-center justify-between">
<h3 class="text-xl font-semibold">{feature.title}</h3>
<Show when={feature.free}>
<span
class="rounded-full px-2 py-0.5 text-xs font-semibold text-white"
style={{ background: brandColor() }}
>
free
</span>
</Show>
</div>
<p class="text-text/80 text-base leading-relaxed">
{feature.description}
</p>
</div>
)}
</For>
</div>
</div>
</section>
{/* ─── Pricing tiers ───────────────────────────────────────── */}
<section class="relative z-10 px-4 py-16">
<div class="mx-auto max-w-6xl">
<div class="mb-12 text-center">
<h2 class="text-3xl font-bold md:text-4xl">
Premium features, affordable pricing
</h2>
<p class="text-text/70 mx-auto mt-3 max-w-2xl text-base md:text-lg">
Choose the plan that fits your training.
</p>
</div>
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
<For each={PRICING}>
{(tier) => {
const highlighted = tier.key === "plus";
return (
<div
class="border-surface0 bg-base/80 flex flex-col rounded-2xl border-2 p-6 backdrop-blur-sm"
classList={{
"scale-[1.02]": highlighted
}}
style={{
"border-color": highlighted ? brandColor() : undefined
}}
>
<div class="mb-4">
<h3 class="text-xl font-semibold">{tier.header}</h3>
<div class="mt-1 text-2xl font-bold">{tier.price}</div>
<Show when={tier.badge}>
<div
class="mt-1 inline-block rounded-full px-2 py-0.5 text-xs font-semibold text-white"
style={{ background: brandColor() }}
>
{tier.badge}
</div>
</Show>
</div>
<p class="text-text/80 mb-6 leading-relaxed">
{tier.headline}
</p>
<ul class="mb-8 flex flex-col gap-3">
<For each={tier.features}>
{(item) => (
<li class="flex items-start gap-3 text-sm">
<CheckIcon />
<span>{item}</span>
</li>
)}
</For>
</ul>
<A
href="/contact"
class="mt-auto w-full rounded-full py-3 text-center text-base font-semibold transition-transform active:scale-95"
classList={{
"text-white": highlighted,
"border-surface2 border-2 hover:bg-surface0/40":
!highlighted
}}
style={{
background: highlighted ? brandColor() : undefined
}}
>
{tier.cta}
</A>
</div>
);
}}
</For>
</div>
</div>
</section>
{/* ─── Feature highlights ─────────────────────────────────── */}
<section class="relative z-10 px-4 py-16">
<div class="mx-auto max-w-4xl">
<h2 class="text-center text-3xl font-bold md:text-4xl">
What you get with Nessa
</h2>
<p class="text-text/70 mt-3 text-center text-base md:text-lg">
Free features other apps charge for, plus affordable premium
tiers.
</p>
<div class="border-surface0 mt-8 overflow-hidden rounded-2xl border-2">
<div class="bg-surface0/60 grid grid-cols-2 px-6 py-4 text-sm font-semibold">
<span>Feature</span>
<span class="text-center" style={{ color: brandColor() }}>
Nessa
</span>
</div>
<For each={COMPARISON}>
{(row, idx) => (
<div
class="grid grid-cols-2 px-6 py-4 text-sm"
classList={{
"bg-surface0/20": idx() % 2 === 1
}}
>
<span>{row.feature}</span>
<span
class="text-center font-medium"
style={{ color: brandColor() }}
>
{row.nessa}
</span>
</div>
)}
</For>
</div>
</div>
</section>
{/* ─── Why Nessa ───────────────────────────────────────────── */}
<section class="relative z-10 px-4 py-16">
<div class="mx-auto max-w-6xl">
<h2 class="mb-10 text-center text-3xl font-bold md:text-4xl">
Why athletes are switching
</h2>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<For each={WHY_NESSA}>
{(item) => (
<div class="border-surface0 bg-surface0/30 rounded-2xl border-2 p-6">
<h3 class="mb-2 text-lg font-semibold">{item.title}</h3>
<p class="text-text/80">{item.body}</p>
</div>
)}
</For>
</div>
</div>
</section>
{/* ─── Screenshots ─────────────────────────────────────────── */}
<section class="relative z-10 px-4 py-16">
<div class="mx-auto max-w-6xl">
<h2 class="mb-10 text-center text-3xl font-bold md:text-4xl">
Built for your whole training life
</h2>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<For each={Object.values(SCREENSHOTS)}>
{(shot) => (
<div class="border-surface0 overflow-hidden rounded-2xl border-2">
<img
src={shot.src}
alt={shot.alt}
class="h-auto w-full object-cover"
loading="lazy"
/>
</div>
)}
</For>
</div>
</div>
</section>
{/* ─── Final CTA ───────────────────────────────────────────── */}
<section class="relative z-10 px-4 py-20">
<div
class="mx-auto max-w-4xl rounded-3xl px-8 py-16 text-center text-white"
style={{ background: brandColor() }}
>
<h2 class="text-3xl font-bold md:text-4xl">
Ready to put your fitness first?
</h2>
<p class="mx-auto mt-3 max-w-xl text-base text-white/90 md:text-lg">
Join the waitlist and be the first to know when Nessa lands on the
App Store.
</p>
<div class="mt-8 flex flex-col items-center justify-center gap-4 sm:flex-row">
<A
href="/contact"
class="rounded-full bg-white px-8 py-3 text-base font-semibold transition-transform hover:scale-[1.02] active:scale-95"
style={{ color: brandColor() }}
>
Join the waitlist
</A>
<A
href="/privacy"
class="text-white/90 underline-offset-4 hover:underline"
>
Privacy policy
</A>
</div>
</div>
</section>
{/* ─── Footer ───────────────────────────────────────────────── */}
<footer class="border-surface0 relative z-10 border-t px-4 py-10">
<div class="text-text/60 mx-auto flex max-w-6xl flex-col items-center justify-between gap-4 text-sm sm:flex-row">
<span>{site().displayName}</span>
<A
href={buildMainSiteUrl()}
class="hover:text-text underline-offset-4 hover:underline"
>
freno.me
</A>
</div>
</footer>
</main>
</>
);
}