feat(marketing): public homepage replacing / → /login redirect
- Add (marketing) route group: /, /pricing, /privacy, /terms - Add (app) route group: moves all authenticated pages, app home → /home - Root / is now a static marketing page (zero DB imports, zero auth) - NavAuthButton client component: shows "Open Tia →" if logged in, else "Continue with Google" - Plausible analytics hook in marketing layout - Auto-generated OG image via opengraph-image.tsx - Middleware updated to allowlist marketing routes - All /-redirects updated to /home (login, onboarding, invite, circle join) - BottomNav home tab updated: / → /home Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f1d4374609
commit
2a09c027fa
38 changed files with 911 additions and 41 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useFamily } from "../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { getGuideline, getAgeInMonths } from "@/lib/guidelines";
|
import { getGuideline, getAgeInMonths } from "@/lib/guidelines";
|
||||||
import { api } from "@/lib/api";
|
import { api } from "@/lib/api";
|
||||||
import { CalendarView } from "@/components/CalendarView";
|
import { CalendarView } from "@/components/CalendarView";
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useFamily } from "../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { Button, Input, ConfirmDialog } from "@/components/ui";
|
import { Button, Input, ConfirmDialog } from "@/components/ui";
|
||||||
import type { AIChat, ChatSession } from "@/types";
|
import type { AIChat, ChatSession } from "@/types";
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, use } from "react";
|
import { useState, useEffect, useCallback, use } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useFamily } from "../../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import type { CirclePost, CircleComment, Circle } from "@/types";
|
import type { CirclePost, CircleComment, Circle } from "@/types";
|
||||||
|
|
||||||
const REACTIONS = ["❤️", "😂", "👍", "🙏", "😮"];
|
const REACTIONS = ["❤️", "😂", "👍", "🙏", "😮"];
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect, use } from "react";
|
import { useState, useEffect, use } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useFamily } from "../../../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
|
|
||||||
type State = "loading" | "preview" | "joining" | "success" | "error";
|
type State = "loading" | "preview" | "joining" | "success" | "error";
|
||||||
|
|
||||||
|
|
@ -89,7 +89,7 @@ export default function JoinCirclePage({ params }: { params: Promise<{ token: st
|
||||||
>
|
>
|
||||||
{familyId ? `Join ${circleName}` : "Log in to Join"}
|
{familyId ? `Join ${circleName}` : "Log in to Join"}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => router.push("/")} className="mt-3 text-xs text-gray-400">
|
<button onClick={() => router.push("/home")} className="mt-3 text-xs text-gray-400">
|
||||||
Not now
|
Not now
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
|
|
@ -115,7 +115,7 @@ export default function JoinCirclePage({ params }: { params: Promise<{ token: st
|
||||||
<div className="text-5xl mb-4">😕</div>
|
<div className="text-5xl mb-4">😕</div>
|
||||||
<h1 className="text-lg font-bold mb-2 text-gray-800 dark:text-white">Invite issue</h1>
|
<h1 className="text-lg font-bold mb-2 text-gray-800 dark:text-white">Invite issue</h1>
|
||||||
<p className="text-gray-500 text-sm mb-5">{errorMsg}</p>
|
<p className="text-gray-500 text-sm mb-5">{errorMsg}</p>
|
||||||
<button onClick={() => router.push("/")} className="w-full py-3 bg-gray-100 dark:bg-gray-700 rounded-xl text-sm">Go to Home</button>
|
<button onClick={() => router.push("/home")} className="w-full py-3 bg-gray-100 dark:bg-gray-700 rounded-xl text-sm">Go to Home</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useFamily } from "../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import type { Circle } from "@/types";
|
import type { Circle } from "@/types";
|
||||||
|
|
||||||
type PendingInvite = {
|
type PendingInvite = {
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
EmptyState, LoadingShimmer, ConfirmDialog, WashiTape,
|
EmptyState, LoadingShimmer, ConfirmDialog, WashiTape,
|
||||||
Badge, Avatar, Tabs, TabPanel,
|
Badge, Avatar, Tabs, TabPanel,
|
||||||
} from "@/components/ui";
|
} from "@/components/ui";
|
||||||
import { useTheme } from "../../ThemeProvider";
|
import { useTheme } from "@/app/ThemeProvider";
|
||||||
|
|
||||||
export default function DevComponentsPage() {
|
export default function DevComponentsPage() {
|
||||||
const { theme, toggle } = useTheme();
|
const { theme, toggle } = useTheme();
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useFamily } from "../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
|
|
||||||
interface Child {
|
interface Child {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useFamily } from "../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { Button, Card, Input, ConfirmDialog } from "@/components/ui";
|
import { Button, Card, Input, ConfirmDialog } from "@/components/ui";
|
||||||
import { WHO_BOY_WEIGHT, WHO_GIRL_WEIGHT, getAgeInMonthsFromBirth, getPercentile } from "@/lib/growth-standards";
|
import { WHO_BOY_WEIGHT, WHO_GIRL_WEIGHT, getAgeInMonthsFromBirth, getPercentile } from "@/lib/growth-standards";
|
||||||
import { formatAge } from "@/lib/formatting";
|
import { formatAge } from "@/lib/formatting";
|
||||||
|
|
@ -19,7 +19,7 @@ import {
|
||||||
Filler,
|
Filler,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import { Line } from "react-chartjs-2";
|
import { Line } from "react-chartjs-2";
|
||||||
import { useTheme } from "../ThemeProvider";
|
import { useTheme } from "@/app/ThemeProvider";
|
||||||
|
|
||||||
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler);
|
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler);
|
||||||
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useTheme } from "./ThemeProvider";
|
import { useTheme } from "@/app/ThemeProvider";
|
||||||
import { useFamily } from "./FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { useStageCheck, type BabyStage } from "@/hooks/useStageCheck";
|
import { useStageCheck, type BabyStage } from "@/hooks/useStageCheck";
|
||||||
import { LogModal, type LogType } from "@/components/LogModal";
|
import { LogModal, type LogType } from "@/components/LogModal";
|
||||||
import { getOfflineQueue, processOfflineQueue } from "@/lib/offline-queue";
|
import { getOfflineQueue, processOfflineQueue } from "@/lib/offline-queue";
|
||||||
19
src/app/(app)/layout.tsx
Normal file
19
src/app/(app)/layout.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { ThemeProvider } from "@/app/ThemeProvider";
|
||||||
|
import { FamilyProvider } from "@/app/FamilyProvider";
|
||||||
|
import { PageTransition } from "@/components/PageTransition";
|
||||||
|
import { BottomNav } from "@/components/BottomNav";
|
||||||
|
|
||||||
|
export default function AppLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ThemeProvider>
|
||||||
|
<FamilyProvider>
|
||||||
|
<PageTransition>{children}</PageTransition>
|
||||||
|
<BottomNav />
|
||||||
|
</FamilyProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useFamily } from "../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { PageHeader } from "@/components/PageHeader";
|
import { PageHeader } from "@/components/PageHeader";
|
||||||
import { TabBar } from "@/components/TabBar";
|
import { TabBar } from "@/components/TabBar";
|
||||||
import { VaccineTab } from "@/components/medical/VaccineTab";
|
import { VaccineTab } from "@/components/medical/VaccineTab";
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect, useRef, useCallback } from "react";
|
import { useState, useEffect, useRef, useCallback } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useFamily } from "../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { Button, ConfirmDialog, Modal } from "@/components/ui";
|
import { Button, ConfirmDialog, Modal } from "@/components/ui";
|
||||||
|
|
||||||
const PRESET_FOLDERS = [
|
const PRESET_FOLDERS = [
|
||||||
|
|
@ -68,7 +68,7 @@ export default function OnboardingPage() {
|
||||||
if (!data.authenticated) {
|
if (!data.authenticated) {
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
} else if (data.familyId) {
|
} else if (data.familyId) {
|
||||||
router.push("/");
|
router.push("/home");
|
||||||
}
|
}
|
||||||
setCheckingAuth(false);
|
setCheckingAuth(false);
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +127,7 @@ export default function OnboardingPage() {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
router.push("/");
|
router.push("/home");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (checkingAuth) {
|
if (checkingAuth) {
|
||||||
|
|
@ -268,7 +268,7 @@ export default function OnboardingPage() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex gap-2 pt-2">
|
<div className="flex gap-2 pt-2">
|
||||||
<Button variant="ghost" fullWidth onClick={() => router.push("/")}>Skip for now</Button>
|
<Button variant="ghost" fullWidth onClick={() => router.push("/home")}>Skip for now</Button>
|
||||||
<Button fullWidth loading={saving} onClick={handleVaccSave}>Save & Go Home</Button>
|
<Button fullWidth loading={saving} onClick={handleVaccSave}>Save & Go Home</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useTheme } from "../ThemeProvider";
|
import { useTheme } from "@/app/ThemeProvider";
|
||||||
import { useFamily } from "../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { Button, Card, Input, Select, Badge } from "@/components/ui";
|
import { Button, Card, Input, Select, Badge } from "@/components/ui";
|
||||||
|
|
||||||
interface Member {
|
interface Member {
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useFamily } from "../../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { Button } from "@/components/ui";
|
import { Button } from "@/components/ui";
|
||||||
import { GARMENT_CATEGORIES, GARMENT_SIZE_ORDER } from "@/db/schema/wardrobe";
|
import { GARMENT_CATEGORIES, GARMENT_SIZE_ORDER } from "@/db/schema/wardrobe";
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useFamily } from "../../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { Button } from "@/components/ui";
|
import { Button } from "@/components/ui";
|
||||||
|
|
||||||
interface OutfitItem {
|
interface OutfitItem {
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useFamily } from "../../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { Button } from "@/components/ui";
|
import { Button } from "@/components/ui";
|
||||||
|
|
||||||
interface PackingItem {
|
interface PackingItem {
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useFamily } from "../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
import { GARMENT_CATEGORIES, GARMENT_SIZE_ORDER } from "@/db/schema/wardrobe";
|
import { GARMENT_CATEGORIES, GARMENT_SIZE_ORDER } from "@/db/schema/wardrobe";
|
||||||
|
|
||||||
interface Garment {
|
interface Garment {
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useFamily } from "../../FamilyProvider";
|
import { useFamily } from "@/app/FamilyProvider";
|
||||||
|
|
||||||
interface SavedOutfit {
|
interface SavedOutfit {
|
||||||
id: string;
|
id: string;
|
||||||
95
src/app/(marketing)/layout.tsx
Normal file
95
src/app/(marketing)/layout.tsx
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { NavAuthButton } from "@/components/marketing/NavAuthButton";
|
||||||
|
import Script from "next/script";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: {
|
||||||
|
default: "Tia — Your baby's digital heirloom",
|
||||||
|
template: "%s | Tia",
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
"Tia is a digital heirloom for your baby — not just a tracker. Log daily moments, track the IAP vaccination schedule, and build a living archive your child will one day inherit.",
|
||||||
|
openGraph: {
|
||||||
|
type: "website",
|
||||||
|
siteName: "Tia",
|
||||||
|
title: "Tia — Your baby's digital heirloom",
|
||||||
|
description:
|
||||||
|
"Log every feed, milestone, and memory. Built for Indian families. Privacy-first. Free during early access.",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: "Tia — Your baby's digital heirloom",
|
||||||
|
description:
|
||||||
|
"Log every feed, milestone, and memory. Built for Indian families. Privacy-first. Free during early access.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function MarketingLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Privacy-respecting analytics — no cookies, no tracking */}
|
||||||
|
<Script
|
||||||
|
defer
|
||||||
|
data-domain={process.env.NEXT_PUBLIC_APP_URL?.replace("https://", "").replace("http://", "")}
|
||||||
|
src="https://plausible.io/js/script.js"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Top nav */}
|
||||||
|
<header className="sticky top-0 z-50 bg-white/90 backdrop-blur-sm border-b border-rose-100">
|
||||||
|
<div className="max-w-5xl mx-auto px-4 py-3 flex items-center justify-between">
|
||||||
|
<Link href="/" className="flex items-center gap-2">
|
||||||
|
<span className="text-2xl">🌸</span>
|
||||||
|
<span className="text-lg font-bold text-gray-900" style={{ fontFamily: "var(--font-caveat)" }}>
|
||||||
|
Tia
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<nav className="hidden sm:flex items-center gap-6 text-sm text-gray-600">
|
||||||
|
<Link href="/pricing" className="hover:text-rose-600 transition-colors">Pricing</Link>
|
||||||
|
<Link href="/privacy" className="hover:text-rose-600 transition-colors">Privacy</Link>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<NavAuthButton />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>{children}</main>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="bg-gray-50 border-t border-gray-100 mt-20">
|
||||||
|
<div className="max-w-5xl mx-auto px-4 py-12">
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start gap-8">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<span className="text-xl">🌸</span>
|
||||||
|
<span className="text-base font-bold text-gray-900" style={{ fontFamily: "var(--font-caveat)" }}>
|
||||||
|
Tia
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-500 max-w-xs">
|
||||||
|
A digital heirloom for your baby. Every moment, preserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 text-sm">
|
||||||
|
<p className="font-semibold text-gray-700 mb-1">Pages</p>
|
||||||
|
<Link href="/pricing" className="text-gray-500 hover:text-rose-600 transition-colors">Pricing</Link>
|
||||||
|
<Link href="/privacy" className="text-gray-500 hover:text-rose-600 transition-colors">Privacy Policy</Link>
|
||||||
|
<Link href="/terms" className="text-gray-500 hover:text-rose-600 transition-colors">Terms of Service</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-10 pt-6 border-t border-gray-200 text-xs text-gray-400">
|
||||||
|
© {new Date().getFullYear()} Tia. Built with love in India. We don't sell your data — we preserve it.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
66
src/app/(marketing)/opengraph-image.tsx
Normal file
66
src/app/(marketing)/opengraph-image.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { ImageResponse } from "next/og";
|
||||||
|
|
||||||
|
export const alt = "Tia — Your baby's digital heirloom";
|
||||||
|
export const size = { width: 1200, height: 630 };
|
||||||
|
export const contentType = "image/png";
|
||||||
|
|
||||||
|
export default function OgImage() {
|
||||||
|
return new ImageResponse(
|
||||||
|
(
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(135deg, #fdf2f2 0%, #fef3c7 50%, #fdf2f2 100%)",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
fontFamily: "sans-serif",
|
||||||
|
padding: 80,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: 72, marginBottom: 24 }}>🌸</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 60,
|
||||||
|
fontWeight: 800,
|
||||||
|
color: "#111827",
|
||||||
|
textAlign: "center",
|
||||||
|
lineHeight: 1.2,
|
||||||
|
marginBottom: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tia
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 28,
|
||||||
|
color: "#f43f5e",
|
||||||
|
fontWeight: 600,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Your baby's digital heirloom
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 22,
|
||||||
|
color: "#6b7280",
|
||||||
|
textAlign: "center",
|
||||||
|
maxWidth: 800,
|
||||||
|
lineHeight: 1.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Log every feed, milestone, and memory.
|
||||||
|
Built for Indian families. Privacy-first. Free during early access.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{ ...size }
|
||||||
|
);
|
||||||
|
}
|
||||||
410
src/app/(marketing)/page.tsx
Normal file
410
src/app/(marketing)/page.tsx
Normal file
|
|
@ -0,0 +1,410 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Tia — Your baby's digital heirloom",
|
||||||
|
description:
|
||||||
|
"Tia is a digital heirloom for your baby — not just a tracker. Log daily moments, track the IAP vaccination schedule with Telegram alerts, and build a living archive your child will one day inherit.",
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Section: Hero ───────────────────────────────────────────────
|
||||||
|
function Hero() {
|
||||||
|
return (
|
||||||
|
<section className="relative overflow-hidden bg-gradient-to-br from-rose-50 via-amber-50 to-rose-50 pt-16 pb-24 px-4">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
<div className="inline-flex items-center gap-2 bg-rose-100 text-rose-700 text-xs font-semibold px-3 py-1.5 rounded-full mb-6">
|
||||||
|
<span>✨</span> Free during early access
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-4xl sm:text-5xl font-bold text-gray-900 leading-tight mb-5">
|
||||||
|
Your baby's story,{" "}
|
||||||
|
<span className="text-rose-500" style={{ fontFamily: "var(--font-caveat)", fontSize: "1.1em" }}>
|
||||||
|
preserved for a lifetime.
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-lg text-gray-600 leading-relaxed mb-8 max-w-xl mx-auto">
|
||||||
|
Tia is a digital heirloom — not just a tracker. Every feed, every first
|
||||||
|
word, every vaccination, archived in one private place your child will
|
||||||
|
one day look back on.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="inline-flex items-center gap-2 bg-rose-500 hover:bg-rose-600 text-white font-semibold px-7 py-3.5 rounded-full text-base transition-colors shadow-md shadow-rose-200"
|
||||||
|
>
|
||||||
|
<svg className="w-5 h-5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#fff" fillOpacity=".9"/>
|
||||||
|
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#fff" fillOpacity=".9"/>
|
||||||
|
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z" fill="#fff" fillOpacity=".9"/>
|
||||||
|
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#fff" fillOpacity=".9"/>
|
||||||
|
</svg>
|
||||||
|
Continue with Google
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<p className="mt-4 text-xs text-gray-400">No credit card. No setup fee. Your data is yours.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Decorative blobs */}
|
||||||
|
<div className="absolute -top-20 -right-20 w-64 h-64 bg-rose-100 rounded-full opacity-40 blur-3xl pointer-events-none" />
|
||||||
|
<div className="absolute -bottom-16 -left-16 w-48 h-48 bg-amber-100 rounded-full opacity-50 blur-2xl pointer-events-none" />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Section: The Problem ────────────────────────────────────────
|
||||||
|
function TheProblem() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 px-4 bg-white">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<p className="text-xs font-semibold text-rose-500 uppercase tracking-widest mb-4">The 3am reality</p>
|
||||||
|
|
||||||
|
<h2 className="text-3xl font-bold text-gray-900 mb-6 leading-tight">
|
||||||
|
You're awake at 3am.<br />
|
||||||
|
When did she last feed?
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4 text-gray-600 leading-relaxed">
|
||||||
|
<p>
|
||||||
|
You scroll back through WhatsApp messages to your mother-in-law. You check a
|
||||||
|
sticky note on the refrigerator. You open a notes app you downloaded last week
|
||||||
|
and another one you downloaded the week before that.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Every precious detail — the exact weight at the six-week checkup, the tiny smile
|
||||||
|
at eleven weeks, the first solid feed — is scattered across six apps, two
|
||||||
|
notebooks, and a dozen photographs that may or may not be backed up.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
And somewhere underneath the exhaustion, a quieter fear: <em>I'm going to forget this.</em>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
{[
|
||||||
|
{ icon: "📱", text: "Notes scattered across 4+ apps" },
|
||||||
|
{ icon: "😰", text: "Fear of losing precious moments" },
|
||||||
|
{ icon: "🌙", text: "No answers at 3am" },
|
||||||
|
].map(item => (
|
||||||
|
<div key={item.text} className="flex items-start gap-3 bg-rose-50 rounded-xl p-4">
|
||||||
|
<span className="text-2xl flex-shrink-0">{item.icon}</span>
|
||||||
|
<p className="text-sm text-gray-700 font-medium">{item.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Section: Features ──────────────────────────────────────────
|
||||||
|
const FEATURES = [
|
||||||
|
{
|
||||||
|
icon: "🍼",
|
||||||
|
title: "Log in 5 seconds",
|
||||||
|
body: "Feed, sleep, diaper — one tap. Tia timestamps everything automatically. At 3am, you should be sleeping, not typing.",
|
||||||
|
example: "\"Fed 90ml at 2:47am\" → logged and archived forever.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "💉",
|
||||||
|
title: "IAP vaccination schedule",
|
||||||
|
body: "Tia tracks your baby's vaccination schedule against the Indian Academy of Pediatrics (IAP) recommended schedule. Reminders arrive via Telegram — the one app you actually check.",
|
||||||
|
example: "BCG, OPV, Hepatitis B — all tracked. Telegram alert: \"Pentavalent 2 due in 3 days.\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "🔮",
|
||||||
|
title: "Ask Tia",
|
||||||
|
body: "Ask Tia parenting logistics questions — feeding windows, sleep patterns, when to introduce solids. For anything that sounds medical, Tia will always refer you to your pediatrician. That restraint is intentional. It's a trust feature, not a limitation.",
|
||||||
|
example: "\"Is 90ml normal at 6 weeks?\" → Tia gives context, then: \"Your pediatrician can confirm this for your baby specifically.\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "📚",
|
||||||
|
title: "The heirloom archive",
|
||||||
|
body: "Every log, photo, milestone, and memory becomes part of a permanent, private archive. Not a feed. Not a highlights reel. A complete record of your child's earliest years — searchable, exportable, and theirs to keep.",
|
||||||
|
example: "\"Show me everything from her first month\" → every feed, every photo, every note.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "👨👩👧",
|
||||||
|
title: "Family circle",
|
||||||
|
body: "Role-based access so everyone in your baby's life can be as involved as they should be. Grandparents get view-only access to milestones. The nanny gets caregiver access to log feeds. You stay the admin.",
|
||||||
|
example: "Nani in Jaipur sees today's photos in real time. The daai logs the afternoon feed.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "📈",
|
||||||
|
title: "Growth tracking",
|
||||||
|
body: "Weight, length, and head circumference plotted on clear charts. See how your baby is growing over time and carry the full history into every pediatric visit.",
|
||||||
|
example: "\"She's been in the 60th percentile for weight since month two.\"",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function Features() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 px-4 bg-gray-50">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<p className="text-xs font-semibold text-rose-500 uppercase tracking-widest mb-4 text-center">What Tia does</p>
|
||||||
|
<h2 className="text-3xl font-bold text-gray-900 text-center mb-12">
|
||||||
|
Everything in one private place.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{FEATURES.map(f => (
|
||||||
|
<div key={f.title} className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<span className="text-3xl flex-shrink-0">{f.icon}</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-gray-900 text-lg mb-2">{f.title}</h3>
|
||||||
|
<p className="text-gray-600 text-sm leading-relaxed mb-3" dangerouslySetInnerHTML={{ __html: f.body }} />
|
||||||
|
<div className="bg-rose-50 rounded-lg px-3 py-2 text-xs text-rose-700 italic">
|
||||||
|
{f.example}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Section: Founder Story ─────────────────────────────────────
|
||||||
|
function FounderStory() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 px-4 bg-white">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<p className="text-xs font-semibold text-rose-500 uppercase tracking-widest mb-4">Why Tia exists</p>
|
||||||
|
|
||||||
|
<div className="bg-amber-50 border border-amber-200 rounded-2xl p-6 sm:p-8">
|
||||||
|
{/*
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ PLACEHOLDER — Manohar, this section is yours to write. │
|
||||||
|
│ │
|
||||||
|
│ Tell the story of your daughter. The specific 3am moment in │
|
||||||
|
│ Gurugram that made you start building. The fear that she'd │
|
||||||
|
│ grow up and you'd have nothing but blurry photos. The reason │
|
||||||
|
│ you want her to be able to read her own first chapter one day. │
|
||||||
|
│ │
|
||||||
|
│ Replace everything between these comment tags with your own │
|
||||||
|
│ words. This section is the moat — it has to be in your voice. │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
*/}
|
||||||
|
<div className="flex items-center gap-3 mb-5">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-rose-200 flex items-center justify-center text-lg">👨💻</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-gray-900">Manohar Gupta</p>
|
||||||
|
<p className="text-xs text-gray-500">Founder, Tia · Gurugram</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<blockquote className="text-gray-700 leading-relaxed space-y-4 italic text-sm sm:text-base">
|
||||||
|
<p>
|
||||||
|
[PLACEHOLDER] My daughter was born and within two weeks I realised I was already
|
||||||
|
forgetting things. Not forgetting them completely — just losing the texture. The
|
||||||
|
exact weight on day five. The time of the first real smile. The way she sounded
|
||||||
|
when she figured out her own hands.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
[PLACEHOLDER] I wanted to build something that would let her read her own first
|
||||||
|
chapter one day. Not a social media highlight reel. A real archive — private,
|
||||||
|
complete, and hers to keep. That's Tia.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
[PLACEHOLDER] — Replace these paragraphs with your story in your own words.
|
||||||
|
The specific 3am moment. The specific fear. Why Gurugram, why this daughter, why now.
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Section: The Heirloom Vision ───────────────────────────────
|
||||||
|
function HeirloomVision() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 px-4 bg-gradient-to-br from-rose-50 to-amber-50">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
<p className="text-xs font-semibold text-rose-500 uppercase tracking-widest mb-4">The heirloom vision</p>
|
||||||
|
|
||||||
|
<h2 className="text-3xl font-bold text-gray-900 mb-6 leading-tight">
|
||||||
|
One day, your child will be able to{" "}
|
||||||
|
<span className="text-rose-500" style={{ fontFamily: "var(--font-caveat)", fontSize: "1.1em" }}>
|
||||||
|
read their own story.
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-gray-600 leading-relaxed mb-8 max-w-xl mx-auto">
|
||||||
|
Everything you log today is a letter to your future child. The 2:47am feed.
|
||||||
|
The first solid. The doctor visit you worried about for a week. The photo from
|
||||||
|
the moment you realised she could recognise your voice.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 text-left">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
icon: "📖",
|
||||||
|
title: "A complete record",
|
||||||
|
desc: "Not highlights. Everything — because you can't know yet which moment will matter most.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "🔒",
|
||||||
|
title: "Private and permanent",
|
||||||
|
desc: "No public feed. No algorithm. Your family's archive, locked to your family.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "💾",
|
||||||
|
title: "Fully exportable",
|
||||||
|
desc: "Your data is yours. Export everything at any time. The heirloom is portable.",
|
||||||
|
},
|
||||||
|
].map(item => (
|
||||||
|
<div key={item.title} className="bg-white/80 rounded-2xl p-5 border border-rose-100">
|
||||||
|
<span className="text-2xl mb-3 block">{item.icon}</span>
|
||||||
|
<h3 className="font-bold text-gray-900 mb-1">{item.title}</h3>
|
||||||
|
<p className="text-sm text-gray-600 leading-relaxed" dangerouslySetInnerHTML={{ __html: item.desc }} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Section: Privacy & Trust ───────────────────────────────────
|
||||||
|
function Privacy() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 px-4 bg-white">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<p className="text-xs font-semibold text-rose-500 uppercase tracking-widest mb-4">Privacy & trust</p>
|
||||||
|
|
||||||
|
<h2 className="text-3xl font-bold text-gray-900 mb-4">
|
||||||
|
We don't sell your data —<br />
|
||||||
|
<span className="text-rose-500">we preserve it.</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-gray-600 leading-relaxed mb-8">
|
||||||
|
Tia is a baby-tracking app. Your child's records are not the product.
|
||||||
|
They are the point.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
icon: "🏛️",
|
||||||
|
title: "Row-Level Security",
|
||||||
|
desc: "Your family's data is isolated at the database level. No other user — no other family — can reach your records.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "🚫",
|
||||||
|
title: "No ads. No data resale.",
|
||||||
|
desc: "We do not sell, share, or monetise your data with third parties. Ever.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "📤",
|
||||||
|
title: "Full export at any time",
|
||||||
|
desc: "Export everything: logs, photos, milestones, vaccinations. Your data leaves with you whenever you want.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "🔐",
|
||||||
|
title: "Google Sign-In only",
|
||||||
|
desc: "We don't store passwords. Authentication is handled by Google — the same account you already trust for everything else.",
|
||||||
|
},
|
||||||
|
].map(item => (
|
||||||
|
<div key={item.title} className="flex items-start gap-4 p-4 bg-gray-50 rounded-xl">
|
||||||
|
<span className="text-xl flex-shrink-0 mt-0.5">{item.icon}</span>
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-gray-900 text-sm">{item.title}</p>
|
||||||
|
<p className="text-sm text-gray-600 leading-relaxed mt-0.5" dangerouslySetInnerHTML={{ __html: item.desc }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Section: Early Access ──────────────────────────────────────
|
||||||
|
function EarlyAccess() {
|
||||||
|
return (
|
||||||
|
<section className="py-16 px-4 bg-gray-50 border-y border-gray-100">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<p className="text-xs font-semibold text-rose-500 uppercase tracking-widest mb-4 text-center">Private early access</p>
|
||||||
|
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 text-center mb-6">
|
||||||
|
Built by a parent, being tested by parents.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
icon: "🌱",
|
||||||
|
title: "Free during early access",
|
||||||
|
desc: "Core features — activity logging and the heirloom archive — are free while Tia is in early access. Early families keep their terms for life.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "🇮🇳",
|
||||||
|
title: "India-native from day one",
|
||||||
|
desc: "IAP vaccination schedule. Telegram alerts. Indian naming conventions. Built for how Indian families actually live.",
|
||||||
|
},
|
||||||
|
].map(item => (
|
||||||
|
<div key={item.title} className="bg-white rounded-2xl p-5 border border-gray-100 shadow-sm">
|
||||||
|
<span className="text-2xl mb-3 block">{item.icon}</span>
|
||||||
|
<h3 className="font-bold text-gray-900 mb-2">{item.title}</h3>
|
||||||
|
<p className="text-sm text-gray-600 leading-relaxed">{item.desc}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Section: Final CTA ─────────────────────────────────────────
|
||||||
|
function FinalCTA() {
|
||||||
|
return (
|
||||||
|
<section className="py-24 px-4 bg-gradient-to-br from-rose-500 to-rose-600 text-white text-center">
|
||||||
|
<div className="max-w-xl mx-auto">
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold mb-4 leading-tight">
|
||||||
|
Start preserving your<br />child's story today.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-rose-100 mb-8 leading-relaxed">
|
||||||
|
Free during early access. No credit card. Your data is yours — always.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="inline-flex items-center gap-2 bg-white text-rose-600 font-bold px-8 py-4 rounded-full text-base hover:bg-rose-50 transition-colors shadow-lg"
|
||||||
|
>
|
||||||
|
<svg className="w-5 h-5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
|
||||||
|
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
|
||||||
|
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z" fill="#FBBC05"/>
|
||||||
|
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
|
||||||
|
</svg>
|
||||||
|
Continue with Google
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<p className="mt-4 text-xs text-rose-200">
|
||||||
|
Invite-only early access — join from a shared link or reach out directly.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Page ───────────────────────────────────────────────────────
|
||||||
|
export default function MarketingHomePage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Hero />
|
||||||
|
<TheProblem />
|
||||||
|
<Features />
|
||||||
|
<FounderStory />
|
||||||
|
<HeirloomVision />
|
||||||
|
<Privacy />
|
||||||
|
<EarlyAccess />
|
||||||
|
<FinalCTA />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
81
src/app/(marketing)/pricing/page.tsx
Normal file
81
src/app/(marketing)/pricing/page.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Pricing",
|
||||||
|
description: "Founder pricing — early families keep their terms for life.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PricingPage() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-2xl mx-auto px-4 py-20">
|
||||||
|
<p className="text-xs font-semibold text-rose-500 uppercase tracking-widest mb-4 text-center">Pricing</p>
|
||||||
|
|
||||||
|
<h1 className="text-3xl sm:text-4xl font-bold text-gray-900 text-center mb-4">
|
||||||
|
Founder pricing.
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-500 text-center mb-12 max-w-md mx-auto">
|
||||||
|
Early families keep their terms for life.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
|
||||||
|
<div className="flex items-start justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-bold text-gray-900">Free — always</h2>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">The core heirloom experience</p>
|
||||||
|
</div>
|
||||||
|
<span className="bg-green-100 text-green-700 text-xs font-semibold px-3 py-1 rounded-full">Included</span>
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-2 text-sm text-gray-700">
|
||||||
|
{[
|
||||||
|
"Activity logging — feed, sleep, diaper, and more",
|
||||||
|
"The heirloom archive — every log, searchable forever",
|
||||||
|
"Milestone tracking",
|
||||||
|
"IAP vaccination schedule",
|
||||||
|
"Telegram alerts for upcoming vaccinations",
|
||||||
|
"Ask Tia (parenting logistics AI)",
|
||||||
|
"Family circle with role-based access",
|
||||||
|
"Growth tracking",
|
||||||
|
"Full data export — the archive is yours",
|
||||||
|
].map(item => (
|
||||||
|
<li key={item} className="flex items-start gap-2">
|
||||||
|
<span className="text-rose-400 flex-shrink-0 mt-0.5">✓</span>
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm p-6">
|
||||||
|
<div className="flex items-start justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-bold text-gray-900">Media storage</h2>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">Photos and video in the archive</p>
|
||||||
|
</div>
|
||||||
|
<span className="bg-amber-100 text-amber-700 text-xs font-semibold px-3 py-1 rounded-full">Paid / metered</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-600 leading-relaxed">
|
||||||
|
Storing photos and video at scale has a real infrastructure cost. Media storage is
|
||||||
|
the one thing we charge for — metered, so you only pay for what you use.
|
||||||
|
Pricing details will be shared before the early-access period ends.
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-gray-400 mt-3">
|
||||||
|
Founder families joining during early access will be the first to know — and will
|
||||||
|
keep whatever terms we agree on, for life.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-12 text-center">
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="inline-flex items-center gap-2 bg-rose-500 hover:bg-rose-600 text-white font-semibold px-7 py-3.5 rounded-full text-base transition-colors"
|
||||||
|
>
|
||||||
|
Continue with Google — it's free
|
||||||
|
</Link>
|
||||||
|
<p className="text-xs text-gray-400 mt-3">No credit card required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
80
src/app/(marketing)/privacy/page.tsx
Normal file
80
src/app/(marketing)/privacy/page.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Privacy Policy",
|
||||||
|
description: "How Tia handles your family's data. We don't sell it — we preserve it.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PrivacyPage() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-2xl mx-auto px-4 py-20">
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">Privacy Policy</h1>
|
||||||
|
<p className="text-sm text-gray-400 mb-10">Last updated: May 2026</p>
|
||||||
|
|
||||||
|
<div className="prose prose-gray max-w-none space-y-8 text-gray-700 leading-relaxed">
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">The short version</h2>
|
||||||
|
<p>
|
||||||
|
We don't sell your data. We don't show you ads. We don't share your records
|
||||||
|
with third parties. Your family's data exists in Tia for one purpose: to
|
||||||
|
be preserved for you and your child.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">What we collect</h2>
|
||||||
|
<ul className="list-disc pl-5 space-y-2 text-sm">
|
||||||
|
<li>Your name and email address, via Google Sign-In.</li>
|
||||||
|
<li>Activity logs you enter (feeds, sleep, diapers, medical records, milestones).</li>
|
||||||
|
<li>Photos and media you upload to the heirloom archive.</li>
|
||||||
|
<li>Device and session information for security purposes.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">How we protect it</h2>
|
||||||
|
<p>
|
||||||
|
Your family's data is isolated at the database level using Row-Level Security (RLS).
|
||||||
|
No other user — no other family — can access your records. We use HTTPS everywhere.
|
||||||
|
Sessions are managed with secure, httpOnly cookies.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">Who we share it with</h2>
|
||||||
|
<p>
|
||||||
|
We use the following third-party services to operate Tia. We do not sell your data
|
||||||
|
to any third party, ever.
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-5 space-y-2 text-sm mt-3">
|
||||||
|
<li><strong>Google</strong> — authentication only.</li>
|
||||||
|
<li><strong>Cloudflare R2</strong> — media storage (photos, video).</li>
|
||||||
|
<li><strong>Telegram</strong> — vaccination alerts, if you opt in.</li>
|
||||||
|
<li><strong>Resend</strong> — transactional email (invites, verification).</li>
|
||||||
|
<li><strong>Plausible Analytics</strong> — privacy-preserving, cookie-free analytics on our marketing pages only. No tracking inside the app.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">Your rights</h2>
|
||||||
|
<ul className="list-disc pl-5 space-y-2 text-sm">
|
||||||
|
<li><strong>Export:</strong> You can export all your data at any time from Settings. The heirloom is portable and yours.</li>
|
||||||
|
<li><strong>Deletion:</strong> You can delete your account and all associated data. Contact us at <a href="mailto:tia@manohargupta.com" className="text-rose-500 hover:underline">tia@manohargupta.com</a>.</li>
|
||||||
|
<li><strong>Correction:</strong> You can edit or delete any log or record within the app.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">Contact</h2>
|
||||||
|
<p>
|
||||||
|
Questions about this policy? Email us at{" "}
|
||||||
|
<a href="mailto:tia@manohargupta.com" className="text-rose-500 hover:underline">
|
||||||
|
tia@manohargupta.com
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
87
src/app/(marketing)/terms/page.tsx
Normal file
87
src/app/(marketing)/terms/page.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Terms of Service",
|
||||||
|
description: "Terms of service for Tia.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TermsPage() {
|
||||||
|
return (
|
||||||
|
<div className="max-w-2xl mx-auto px-4 py-20">
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">Terms of Service</h1>
|
||||||
|
<p className="text-sm text-gray-400 mb-10">Last updated: May 2026</p>
|
||||||
|
|
||||||
|
<div className="space-y-8 text-gray-700 leading-relaxed">
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">Acceptance</h2>
|
||||||
|
<p>
|
||||||
|
By using Tia, you agree to these terms. If you don't agree, please don't use
|
||||||
|
the service.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">What Tia is</h2>
|
||||||
|
<p>
|
||||||
|
Tia is a baby-tracking and heirloom-archiving application for families. It is
|
||||||
|
not a medical service. Tia does not provide medical advice, diagnosis, or
|
||||||
|
treatment. For any health concerns about your child, consult your pediatrician.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">Your account</h2>
|
||||||
|
<p>
|
||||||
|
You are responsible for maintaining the security of your account. You must
|
||||||
|
be at least 18 years old to create an account. Family members you invite
|
||||||
|
are governed by these same terms.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">Your data</h2>
|
||||||
|
<p>
|
||||||
|
You own your data. We do not claim any ownership over the records, photos,
|
||||||
|
or content you create in Tia. You can export and delete your data at any time.
|
||||||
|
See our <a href="/privacy" className="text-rose-500 hover:underline">Privacy Policy</a> for details.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">Acceptable use</h2>
|
||||||
|
<p>
|
||||||
|
You agree not to use Tia to store illegal content, harm others, or interfere
|
||||||
|
with the service. We may terminate accounts that violate these terms.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">Service availability</h2>
|
||||||
|
<p>
|
||||||
|
Tia is provided as-is during early access. We make no guarantees of uptime
|
||||||
|
or availability, though we work hard to keep the service running reliably.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">Changes to these terms</h2>
|
||||||
|
<p>
|
||||||
|
We may update these terms. We will notify you by email before any material
|
||||||
|
changes take effect.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-3">Contact</h2>
|
||||||
|
<p>
|
||||||
|
Questions? Email{" "}
|
||||||
|
<a href="mailto:tia@manohargupta.com" className="text-rose-500 hover:underline">
|
||||||
|
tia@manohargupta.com
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -24,7 +24,7 @@ export default function InvitePage({ params }: { params: { token: string } }) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
router.push("/");
|
router.push("/home");
|
||||||
} else {
|
} else {
|
||||||
setError(data.error || "Invalid invite");
|
setError(data.error || "Invalid invite");
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +55,7 @@ export default function InvitePage({ params }: { params: { token: string } }) {
|
||||||
<div className="text-center p-8">
|
<div className="text-center p-8">
|
||||||
<div className="text-4xl mb-4">❌</div>
|
<div className="text-4xl mb-4">❌</div>
|
||||||
<p className="text-red-500">{error}</p>
|
<p className="text-red-500">{error}</p>
|
||||||
<button onClick={() => router.push("/")} className="mt-4 px-4 py-2 bg-rose-400 text-white rounded-lg">
|
<button onClick={() => router.push("/home")} className="mt-4 px-4 py-2 bg-rose-400 text-white rounded-lg">
|
||||||
Go Home
|
Go Home
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono, Caveat } from "next/font/google";
|
import { Geist, Geist_Mono, Caveat } from "next/font/google";
|
||||||
import { ThemeProvider } from "./ThemeProvider";
|
|
||||||
import { FamilyProvider } from "./FamilyProvider";
|
|
||||||
import { PageTransition } from "@/components/PageTransition";
|
|
||||||
import { BottomNav } from "@/components/BottomNav";
|
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
|
|
@ -39,12 +35,7 @@ export default function RootLayout({
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body className={`${geistSans.variable} ${geistMono.variable} ${caveat.variable} min-h-full antialiased`}>
|
<body className={`${geistSans.variable} ${geistMono.variable} ${caveat.variable} min-h-full antialiased`}>
|
||||||
<ThemeProvider>
|
{children}
|
||||||
<FamilyProvider>
|
|
||||||
<PageTransition>{children}</PageTransition>
|
|
||||||
<BottomNav />
|
|
||||||
</FamilyProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ export default function LoginPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign-in success.
|
// Sign-in success.
|
||||||
router.push(data.familyId ? "/" : "/onboarding");
|
router.push(data.familyId ? "/home" : "/onboarding");
|
||||||
} catch {
|
} catch {
|
||||||
setError("Something went wrong");
|
setError("Something went wrong");
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ href: "/", icon: "🏡", label: "Home" },
|
{ href: "/home", icon: "🏡", label: "Home" },
|
||||||
{ href: "/activity", icon: "📝", label: "Activity" },
|
{ href: "/activity", icon: "📝", label: "Activity" },
|
||||||
{ href: "/ai", icon: "🔮", label: "Ask AI" },
|
{ href: "/ai", icon: "🔮", label: "Ask AI" },
|
||||||
{ href: "/menu", icon: "☰", label: "Menu" },
|
{ href: "/menu", icon: "☰", label: "Menu" },
|
||||||
|
|
@ -20,7 +20,7 @@ export function BottomNav() {
|
||||||
return (
|
return (
|
||||||
<nav className="fixed bottom-0 inset-x-0 bg-white dark:bg-gray-900 border-t border-gray-100 dark:border-gray-800 flex justify-around items-center py-2 z-40">
|
<nav className="fixed bottom-0 inset-x-0 bg-white dark:bg-gray-900 border-t border-gray-100 dark:border-gray-800 flex justify-around items-center py-2 z-40">
|
||||||
{TABS.map(tab => {
|
{TABS.map(tab => {
|
||||||
const isActive = tab.href === "/" ? pathname === "/" : pathname?.startsWith(tab.href);
|
const isActive = pathname?.startsWith(tab.href);
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={tab.href}
|
key={tab.href}
|
||||||
|
|
|
||||||
34
src/components/marketing/NavAuthButton.tsx
Normal file
34
src/components/marketing/NavAuthButton.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export function NavAuthButton() {
|
||||||
|
const [isAuth, setIsAuth] = useState<boolean | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("/api/auth/profile")
|
||||||
|
.then(r => setIsAuth(r.ok))
|
||||||
|
.catch(() => setIsAuth(false));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (isAuth === true) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href="/home"
|
||||||
|
className="px-4 py-2 bg-rose-500 text-white rounded-full text-sm font-semibold hover:bg-rose-600 transition-colors"
|
||||||
|
>
|
||||||
|
Open Tia →
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="px-4 py-2 bg-rose-500 text-white rounded-full text-sm font-semibold hover:bg-rose-600 transition-colors"
|
||||||
|
>
|
||||||
|
Continue with Google
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -4,11 +4,18 @@ import type { NextRequest } from "next/server";
|
||||||
// Public routes that don't require authentication
|
// Public routes that don't require authentication
|
||||||
const publicRoutes = [
|
const publicRoutes = [
|
||||||
"/",
|
"/",
|
||||||
|
"/pricing",
|
||||||
|
"/privacy",
|
||||||
|
"/terms",
|
||||||
"/login",
|
"/login",
|
||||||
"/admin-login",
|
"/admin-login",
|
||||||
|
"/m",
|
||||||
|
"/invite",
|
||||||
|
"/verify",
|
||||||
"/api/auth/signin",
|
"/api/auth/signin",
|
||||||
"/api/admin/auth",
|
"/api/admin/auth",
|
||||||
"/api/onboarding",
|
"/api/onboarding",
|
||||||
|
"/api/profile",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Protected API routes that need authentication
|
// Protected API routes that need authentication
|
||||||
|
|
@ -68,4 +75,4 @@ export const config = {
|
||||||
"/api/:path*",
|
"/api/:path*",
|
||||||
"/((?!_next/static|_next/image|favicon.ico).*)",
|
"/((?!_next/static|_next/image|favicon.ico).*)",
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue