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:
Manohar Gupta 2026-05-25 23:26:26 +05:30
parent f1d4374609
commit 2a09c027fa
38 changed files with 911 additions and 41 deletions

View file

@ -2,7 +2,7 @@
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { useFamily } from "../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import { getGuideline, getAgeInMonths } from "@/lib/guidelines";
import { api } from "@/lib/api";
import { CalendarView } from "@/components/CalendarView";

View file

@ -1,7 +1,7 @@
"use client";
import { useState, useEffect } from "react";
import { useFamily } from "../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import { Button, Input, ConfirmDialog } from "@/components/ui";
import type { AIChat, ChatSession } from "@/types";

View file

@ -2,7 +2,7 @@
import { useState, useEffect, useCallback, use } from "react";
import { useRouter } from "next/navigation";
import { useFamily } from "../../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import type { CirclePost, CircleComment, Circle } from "@/types";
const REACTIONS = ["❤️", "😂", "👍", "🙏", "😮"];

View file

@ -2,7 +2,7 @@
import { useState, useEffect, use } from "react";
import { useRouter } from "next/navigation";
import { useFamily } from "../../../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
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"}
</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
</button>
</>
@ -115,7 +115,7 @@ export default function JoinCirclePage({ params }: { params: Promise<{ token: st
<div className="text-5xl mb-4">😕</div>
<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>
<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>

View file

@ -3,7 +3,7 @@
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { useFamily } from "../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import type { Circle } from "@/types";
type PendingInvite = {

View file

@ -6,7 +6,7 @@ import {
EmptyState, LoadingShimmer, ConfirmDialog, WashiTape,
Badge, Avatar, Tabs, TabPanel,
} from "@/components/ui";
import { useTheme } from "../../ThemeProvider";
import { useTheme } from "@/app/ThemeProvider";
export default function DevComponentsPage() {
const { theme, toggle } = useTheme();

View file

@ -2,7 +2,7 @@
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { useFamily } from "../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
interface Child {
id: string;

View file

@ -2,7 +2,7 @@
import { useState, useEffect } from "react";
import Link from "next/link";
import { useFamily } from "../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import { Button, Card, Input, ConfirmDialog } from "@/components/ui";
import { WHO_BOY_WEIGHT, WHO_GIRL_WEIGHT, getAgeInMonthsFromBirth, getPercentile } from "@/lib/growth-standards";
import { formatAge } from "@/lib/formatting";
@ -19,7 +19,7 @@ import {
Filler,
} from "chart.js";
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);

View file

@ -2,8 +2,8 @@
import { useState, useEffect, useRef } from "react";
import Link from "next/link";
import { useTheme } from "./ThemeProvider";
import { useFamily } from "./FamilyProvider";
import { useTheme } from "@/app/ThemeProvider";
import { useFamily } from "@/app/FamilyProvider";
import { useStageCheck, type BabyStage } from "@/hooks/useStageCheck";
import { LogModal, type LogType } from "@/components/LogModal";
import { getOfflineQueue, processOfflineQueue } from "@/lib/offline-queue";

19
src/app/(app)/layout.tsx Normal file
View 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>
);
}

View file

@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { useFamily } from "../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import { PageHeader } from "@/components/PageHeader";
import { TabBar } from "@/components/TabBar";
import { VaccineTab } from "@/components/medical/VaccineTab";

View file

@ -2,7 +2,7 @@
import { useState, useEffect, useRef, useCallback } from "react";
import Link from "next/link";
import { useFamily } from "../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import { Button, ConfirmDialog, Modal } from "@/components/ui";
const PRESET_FOLDERS = [

View file

@ -68,7 +68,7 @@ export default function OnboardingPage() {
if (!data.authenticated) {
router.push("/login");
} else if (data.familyId) {
router.push("/");
router.push("/home");
}
setCheckingAuth(false);
}
@ -127,7 +127,7 @@ export default function OnboardingPage() {
console.error(e);
}
setSaving(false);
router.push("/");
router.push("/home");
};
if (checkingAuth) {
@ -268,7 +268,7 @@ export default function OnboardingPage() {
)}
<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>
</div>
</>

View file

@ -3,8 +3,8 @@
import { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useTheme } from "../ThemeProvider";
import { useFamily } from "../FamilyProvider";
import { useTheme } from "@/app/ThemeProvider";
import { useFamily } from "@/app/FamilyProvider";
import { Button, Card, Input, Select, Badge } from "@/components/ui";
interface Member {

View file

@ -3,7 +3,7 @@
import { useRef, useState } from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";
import { useFamily } from "../../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import { Button } from "@/components/ui";
import { GARMENT_CATEGORIES, GARMENT_SIZE_ORDER } from "@/db/schema/wardrobe";

View file

@ -2,7 +2,7 @@
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { useFamily } from "../../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import { Button } from "@/components/ui";
interface OutfitItem {

View file

@ -2,7 +2,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useFamily } from "../../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import { Button } from "@/components/ui";
interface PackingItem {

View file

@ -3,7 +3,7 @@
import { useState, useEffect, useCallback } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useFamily } from "../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
import { GARMENT_CATEGORIES, GARMENT_SIZE_ORDER } from "@/db/schema/wardrobe";
interface Garment {

View file

@ -3,7 +3,7 @@
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { useFamily } from "../../FamilyProvider";
import { useFamily } from "@/app/FamilyProvider";
interface SavedOutfit {
id: string;

View 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&apos;t sell your data we preserve it.
</div>
</div>
</footer>
</>
);
}

View 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&apos;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 }
);
}

View 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&apos;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&apos;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&apos;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&apos;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&apos;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&apos;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&apos;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&apos;t know yet which moment will matter most.",
},
{
icon: "🔒",
title: "Private and permanent",
desc: "No public feed. No algorithm. Your family&apos;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&apos;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&apos;s records are not the product.
They are the point.
</p>
<div className="space-y-3">
{[
{
icon: "🏛️",
title: "Row-Level Security",
desc: "Your family&apos;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&apos;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&apos;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 />
</>
);
}

View 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&apos;s free
</Link>
<p className="text-xs text-gray-400 mt-3">No credit card required.</p>
</div>
</div>
);
}

View 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&apos;t sell your data. We don&apos;t show you ads. We don&apos;t share your records
with third parties. Your family&apos;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&apos;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>
);
}

View 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&apos;t agree, please don&apos;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>
);
}

View file

@ -24,7 +24,7 @@ export default function InvitePage({ params }: { params: { token: string } }) {
const data = await res.json();
if (data.success) {
router.push("/");
router.push("/home");
} else {
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-4xl mb-4"></div>
<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
</button>
</div>

View file

@ -1,9 +1,5 @@
import type { Metadata } from "next";
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";
const geistSans = Geist({
@ -39,12 +35,7 @@ export default function RootLayout({
return (
<html lang="en" suppressHydrationWarning>
<body className={`${geistSans.variable} ${geistMono.variable} ${caveat.variable} min-h-full antialiased`}>
<ThemeProvider>
<FamilyProvider>
<PageTransition>{children}</PageTransition>
<BottomNav />
</FamilyProvider>
</ThemeProvider>
{children}
</body>
</html>
);

View file

@ -49,7 +49,7 @@ export default function LoginPage() {
}
// Sign-in success.
router.push(data.familyId ? "/" : "/onboarding");
router.push(data.familyId ? "/home" : "/onboarding");
} catch {
setError("Something went wrong");
} finally {

View file

@ -4,7 +4,7 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
const TABS = [
{ href: "/", icon: "🏡", label: "Home" },
{ href: "/home", icon: "🏡", label: "Home" },
{ href: "/activity", icon: "📝", label: "Activity" },
{ href: "/ai", icon: "🔮", label: "Ask AI" },
{ href: "/menu", icon: "☰", label: "Menu" },
@ -20,7 +20,7 @@ export function BottomNav() {
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">
{TABS.map(tab => {
const isActive = tab.href === "/" ? pathname === "/" : pathname?.startsWith(tab.href);
const isActive = pathname?.startsWith(tab.href);
return (
<Link
key={tab.href}

View 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>
);
}

View file

@ -4,11 +4,18 @@ import type { NextRequest } from "next/server";
// Public routes that don't require authentication
const publicRoutes = [
"/",
"/pricing",
"/privacy",
"/terms",
"/login",
"/admin-login",
"/m",
"/invite",
"/verify",
"/api/auth/signin",
"/api/admin/auth",
"/api/onboarding",
"/api/profile",
];
// Protected API routes that need authentication
@ -68,4 +75,4 @@ export const config = {
"/api/:path*",
"/((?!_next/static|_next/image|favicon.ico).*)",
],
};
};