feat: redesign app UI — top nav, clean dashboard, warm branding
- Replace sidebar with top navigation bar (like Airbnb/Nextdoor) - Redesign dashboard: home cards, coming up tasks, quick action pills - Remove widget-heavy layout (charts, stats, activity feed) - Add landing page with hero, features, how-it-works, CTA sections - Update auth pages with split layout - Clean white theme with neutral grays, brand orange/teal accents - Friendly copy across all empty states and page headers - Add Bricolage Grotesque + Outfit fonts - Default to light mode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
||||
<div className="w-full max-w-md">
|
||||
{children}
|
||||
<div className="min-h-screen flex bg-[#FAFAF7]">
|
||||
{/* Left brand panel — hidden on mobile */}
|
||||
<div className="hidden lg:flex lg:w-[480px] xl:w-[540px] relative flex-col justify-between bg-[#1C1917] p-10 overflow-hidden">
|
||||
{/* Decorative blurs */}
|
||||
<div className="absolute top-0 right-0 w-80 h-80 rounded-full bg-[#E07A3A]/15 blur-[100px] pointer-events-none" />
|
||||
<div className="absolute bottom-0 left-0 w-64 h-64 rounded-full bg-[#0D7C66]/10 blur-[80px] pointer-events-none" />
|
||||
|
||||
{/* Subtle grid */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.03]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(rgba(255,255,255,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.5) 1px, transparent 1px)",
|
||||
backgroundSize: "48px 48px",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative">
|
||||
<Link href="/" className="flex items-center gap-2.5">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="Casera"
|
||||
width={36}
|
||||
height={36}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
<span className="font-heading text-xl font-bold text-white">
|
||||
Casera
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<h2 className="font-heading text-3xl font-bold text-white leading-snug mb-4">
|
||||
Home maintenance,<br />
|
||||
<span className="text-[#E07A3A]">simplified.</span>
|
||||
</h2>
|
||||
<p className="text-[#A8A29E] leading-relaxed max-w-sm">
|
||||
Track tasks, organize contractors, and store important
|
||||
documents — all in one place built for homeowners.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="relative text-xs text-[#78716C]">
|
||||
© {new Date().getFullYear()} Casera
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right form area */}
|
||||
<div className="flex-1 flex items-center justify-center p-6 sm:p-8">
|
||||
<div className="w-full max-w-md">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -104,9 +104,9 @@ export default function ContractorsPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Contractors"
|
||||
description="Manage your trusted contractors and service providers"
|
||||
actionLabel="Add Contractor"
|
||||
title="Your Pros"
|
||||
description="The people you trust to get the job done"
|
||||
actionLabel="Add a Pro"
|
||||
onAction={() => router.push(`${basePath}/contractors/new`)}
|
||||
>
|
||||
<Button
|
||||
@@ -145,10 +145,10 @@ export default function ContractorsPage() {
|
||||
{filtered.length === 0 ? (
|
||||
<EmptyState
|
||||
icon={Wrench}
|
||||
title="No contractors found"
|
||||
title="No pros saved yet"
|
||||
description={
|
||||
(contractors?.length ?? 0) === 0
|
||||
? "Add your first contractor to keep track of service providers."
|
||||
? "Save your go-to plumber, electrician, or handyman so you always know who to call."
|
||||
: "Try adjusting your search or filters."
|
||||
}
|
||||
actionLabel={contractors.length === 0 ? "Add Contractor" : undefined}
|
||||
|
||||
@@ -32,8 +32,8 @@ export default function DocumentsPage() {
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Documents"
|
||||
description="Manage your property documents and warranties"
|
||||
actionLabel="Add Document"
|
||||
description="Warranties, manuals, receipts — all in one place"
|
||||
actionLabel="Save a Document"
|
||||
onAction={() => router.push(`${basePath}/documents/new`)}
|
||||
/>
|
||||
|
||||
@@ -59,9 +59,9 @@ export default function DocumentsPage() {
|
||||
documents.length === 0 && (
|
||||
<EmptyState
|
||||
icon={FileText}
|
||||
title="No documents yet"
|
||||
description="Add your first document to start organizing your property records."
|
||||
actionLabel="Add Document"
|
||||
title="No documents saved yet"
|
||||
description="Store warranties, manuals, receipts, and more — so they're easy to find when you need them."
|
||||
actionLabel="Save a Document"
|
||||
onAction={() => router.push(`${basePath}/documents/new`)}
|
||||
/>
|
||||
)}
|
||||
@@ -94,8 +94,8 @@ export default function DocumentsPage() {
|
||||
warranties.length === 0 && (
|
||||
<EmptyState
|
||||
icon={FileText}
|
||||
title="No warranties yet"
|
||||
description="Documents with type 'warranty' will appear here."
|
||||
title="No warranties saved yet"
|
||||
description="When you save a document as a warranty, it'll show up here for easy access."
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
+4
-11
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Sidebar } from '@/components/layout/sidebar';
|
||||
import { TopBar } from '@/components/layout/top-bar';
|
||||
import { MobileNav } from '@/components/layout/mobile-nav';
|
||||
import { DataProviderProvider } from '@/lib/demo/data-provider-context';
|
||||
@@ -10,18 +9,12 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<DataProviderProvider value={realProvider}>
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Sidebar - hidden on mobile */}
|
||||
<Sidebar />
|
||||
<TopBar />
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="md:ml-16 lg:ml-64 flex flex-col min-h-screen">
|
||||
<TopBar />
|
||||
<main className="flex-1 p-4 lg:p-6 pb-20 md:pb-6">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
<main className="max-w-7xl mx-auto px-6 py-8 lg:py-12 pb-28 md:pb-12">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
{/* Mobile bottom nav */}
|
||||
<MobileNav />
|
||||
</div>
|
||||
</DataProviderProvider>
|
||||
|
||||
+371
-47
@@ -1,61 +1,385 @@
|
||||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Home,
|
||||
CheckSquare,
|
||||
HardHat,
|
||||
FileText,
|
||||
MapPin,
|
||||
ArrowRight,
|
||||
Plus,
|
||||
CalendarClock,
|
||||
Sparkles,
|
||||
CircleAlert,
|
||||
} from "lucide-react";
|
||||
import { useResidences } from "@/lib/hooks/use-residences";
|
||||
import { useTasks } from "@/lib/hooks/use-tasks";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { LoadingSkeleton } from "@/components/shared/loading-skeleton";
|
||||
import { StatsCards } from "@/components/dashboard/stats-cards";
|
||||
import { RecentActivity } from "@/components/dashboard/recent-activity";
|
||||
import { useDataProvider } from "@/lib/demo/data-provider-context";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import type { MyResidenceResponse } from "@/lib/api/residences";
|
||||
import type { TaskResponse } from "@/lib/api/tasks";
|
||||
|
||||
const TaskCompletionChart = dynamic(
|
||||
() => import("@/components/dashboard/task-completion-chart").then((mod) => ({ default: mod.TaskCompletionChart })),
|
||||
{
|
||||
loading: () => (
|
||||
<div className="rounded-lg border p-6 space-y-4">
|
||||
<Skeleton className="h-5 w-40" />
|
||||
<Skeleton className="h-[300px] w-full" />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
);
|
||||
/* ─── Helpers ─── */
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { data: residences, isLoading } = useResidences();
|
||||
const user = useAuthStore((s) => s.user);
|
||||
function getTimeGreeting() {
|
||||
const h = new Date().getHours();
|
||||
if (h < 12) return "Good morning";
|
||||
if (h < 17) return "Good afternoon";
|
||||
return "Good evening";
|
||||
}
|
||||
|
||||
const list = Array.isArray(residences) ? residences : [];
|
||||
const totalOverdue =
|
||||
list.reduce((sum, r) => sum + (r.task_summary?.overdue ?? 0), 0);
|
||||
const totalDueSoon =
|
||||
list.reduce((sum, r) => sum + (r.task_summary?.due_soon ?? 0), 0);
|
||||
const totalActive =
|
||||
list.reduce((sum, r) => sum + (r.task_summary?.in_progress ?? 0), 0);
|
||||
const totalCompleted =
|
||||
list.reduce((sum, r) => sum + (r.task_summary?.completed ?? 0), 0);
|
||||
function getRelativeDate(dateStr: string) {
|
||||
const date = new Date(dateStr);
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const target = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||||
const diff = Math.round((target.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diff < 0) return "Overdue";
|
||||
if (diff === 0) return "Today";
|
||||
if (diff === 1) return "Tomorrow";
|
||||
if (diff < 7) return date.toLocaleDateString("en-US", { weekday: "long" });
|
||||
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
||||
}
|
||||
|
||||
/* ─── Home Card ─── */
|
||||
|
||||
function HomeCard({
|
||||
data,
|
||||
basePath,
|
||||
}: {
|
||||
data: MyResidenceResponse;
|
||||
basePath: string;
|
||||
}) {
|
||||
const r = data.residence;
|
||||
const overdue = data.task_summary?.overdue ?? 0;
|
||||
const dueSoon = data.task_summary?.due_soon ?? 0;
|
||||
const total = data.task_summary?.total ?? 0;
|
||||
const address = [r.street_address, r.city, r.state_province]
|
||||
.filter(Boolean)
|
||||
.join(", ");
|
||||
const isGood = overdue === 0 && dueSoon === 0;
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<h1 className="text-2xl font-bold tracking-tight">
|
||||
{user?.first_name
|
||||
? `Welcome back, ${user.first_name}`
|
||||
: "Dashboard"}
|
||||
</h1>
|
||||
<Link
|
||||
href={`${basePath}/residences/${r.id}`}
|
||||
className="group block"
|
||||
>
|
||||
<div className="rounded-2xl border border-border bg-card p-5 sm:p-6 transition-all duration-200 hover:shadow-lg hover:shadow-black/[0.04] hover:-translate-y-0.5 hover:border-border/80">
|
||||
{/* Status badge */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="size-11 rounded-xl bg-[#FFF3EB] flex items-center justify-center">
|
||||
<Home className="size-5 text-[#E07A3A]" />
|
||||
</div>
|
||||
{overdue > 0 ? (
|
||||
<span className="inline-flex items-center gap-1 text-xs font-medium text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-500/10 px-2.5 py-1 rounded-full">
|
||||
<CircleAlert className="size-3" />
|
||||
{overdue} overdue
|
||||
</span>
|
||||
) : !isGood && dueSoon > 0 ? (
|
||||
<span className="inline-flex items-center gap-1 text-xs font-medium text-amber-600 bg-amber-50 dark:text-amber-400 dark:bg-amber-500/10 px-2.5 py-1 rounded-full">
|
||||
<CalendarClock className="size-3" />
|
||||
{dueSoon} coming up
|
||||
</span>
|
||||
) : isGood && total > 0 ? (
|
||||
<span className="inline-flex items-center gap-1 text-xs font-medium text-[#0D7C66] bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-500/10 px-2.5 py-1 rounded-full">
|
||||
<Sparkles className="size-3" />
|
||||
All good
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<LoadingSkeleton variant="card-grid" count={4} />
|
||||
) : (
|
||||
<>
|
||||
<StatsCards
|
||||
overdue={totalOverdue}
|
||||
dueSoon={totalDueSoon}
|
||||
active={totalActive}
|
||||
completed={totalCompleted}
|
||||
/>
|
||||
<TaskCompletionChart data={[]} />
|
||||
<RecentActivity />
|
||||
</>
|
||||
)}
|
||||
{/* Name and address */}
|
||||
<h3 className="font-heading text-lg font-semibold leading-tight group-hover:text-primary transition-colors">
|
||||
{r.name}
|
||||
</h3>
|
||||
{address && (
|
||||
<p className="text-sm text-muted-foreground mt-1 flex items-center gap-1.5">
|
||||
<MapPin className="size-3.5 shrink-0" />
|
||||
<span className="truncate">{address}</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Quick stats */}
|
||||
<div className="flex items-center gap-4 mt-4 pt-4 border-t border-border/60">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{total} {total === 1 ? "task" : "tasks"}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground flex items-center gap-1">
|
||||
View home
|
||||
<ArrowRight className="size-3 transition-transform group-hover:translate-x-0.5" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Coming Up (task list) ─── */
|
||||
|
||||
function ComingUp({
|
||||
tasks,
|
||||
basePath,
|
||||
}: {
|
||||
tasks: TaskResponse[];
|
||||
basePath: string;
|
||||
}) {
|
||||
if (tasks.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="font-heading text-lg font-semibold">Coming Up</h2>
|
||||
<Link
|
||||
href={`${basePath}/tasks`}
|
||||
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"
|
||||
>
|
||||
All tasks
|
||||
<ArrowRight className="size-3.5" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
{tasks.map((task) => {
|
||||
const dateLabel = task.next_due_date || task.due_date;
|
||||
const isOverdue =
|
||||
dateLabel && new Date(dateLabel) < new Date() && getRelativeDate(dateLabel) === "Overdue";
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={task.id}
|
||||
href={`${basePath}/tasks/${task.id}`}
|
||||
className="flex items-center gap-3 rounded-xl px-3 py-3 -mx-3 hover:bg-accent/50 transition-colors group"
|
||||
>
|
||||
<div
|
||||
className={`size-2 rounded-full shrink-0 ${
|
||||
isOverdue
|
||||
? "bg-red-500"
|
||||
: task.in_progress
|
||||
? "bg-[#0D7C66]"
|
||||
: "bg-border"
|
||||
}`}
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium leading-snug truncate group-hover:text-primary transition-colors">
|
||||
{task.title}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
{task.residence_name}
|
||||
</p>
|
||||
</div>
|
||||
{dateLabel && (
|
||||
<span
|
||||
className={`text-xs font-medium shrink-0 ${
|
||||
isOverdue ? "text-red-500" : "text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
{getRelativeDate(dateLabel)}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Quick Actions (subtle, not prominent) ─── */
|
||||
|
||||
function QuickActions({ basePath }: { basePath: string }) {
|
||||
const actions = [
|
||||
{ label: "Add task", href: `${basePath}/tasks/new`, icon: CheckSquare },
|
||||
{ label: "Add pro", href: `${basePath}/contractors/new`, icon: HardHat },
|
||||
{ label: "Save doc", href: `${basePath}/documents/new`, icon: FileText },
|
||||
{ label: "Add home", href: `${basePath}/residences/new`, icon: Home },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{actions.map((a) => (
|
||||
<Link
|
||||
key={a.href}
|
||||
href={a.href}
|
||||
className="inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3.5 py-2 text-xs font-medium text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors"
|
||||
>
|
||||
<a.icon className="size-3.5" />
|
||||
{a.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Loading State ─── */
|
||||
|
||||
function DashboardSkeleton() {
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-64" />
|
||||
<Skeleton className="h-5 w-48" />
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="rounded-2xl border bg-card p-6 space-y-4">
|
||||
<Skeleton className="size-11 rounded-xl" />
|
||||
<Skeleton className="h-5 w-3/4" />
|
||||
<Skeleton className="h-4 w-1/2" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Dashboard ─── */
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { data: residences, isLoading: homesLoading } = useResidences();
|
||||
const { data: kanban, isLoading: tasksLoading } = useTasks();
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const { basePath } = useDataProvider();
|
||||
|
||||
const homes = Array.isArray(residences) ? residences : [];
|
||||
const name = user?.first_name || "";
|
||||
const greeting = `${getTimeGreeting()}${name ? `, ${name}` : ""}`;
|
||||
|
||||
// Flatten all tasks from kanban columns, sort by due date, take upcoming ones
|
||||
const allTasks: TaskResponse[] = kanban?.columns
|
||||
?.flatMap((col) => col.tasks)
|
||||
?.filter((t) => !t.is_cancelled && !t.is_archived) ?? [];
|
||||
|
||||
const upcomingTasks = allTasks
|
||||
.filter((t) => t.next_due_date || t.due_date || t.in_progress)
|
||||
.sort((a, b) => {
|
||||
const dateA = a.next_due_date || a.due_date || "";
|
||||
const dateB = b.next_due_date || b.due_date || "";
|
||||
if (!dateA) return 1;
|
||||
if (!dateB) return -1;
|
||||
return new Date(dateA).getTime() - new Date(dateB).getTime();
|
||||
})
|
||||
.slice(0, 6);
|
||||
|
||||
const totalOverdue = homes.reduce(
|
||||
(sum, r) => sum + (r.task_summary?.overdue ?? 0), 0
|
||||
);
|
||||
|
||||
// Status line under greeting
|
||||
const statusMsg = homes.length === 0
|
||||
? ""
|
||||
: totalOverdue > 0
|
||||
? totalOverdue === 1
|
||||
? "One thing needs your attention."
|
||||
: `${totalOverdue} things need your attention.`
|
||||
: allTasks.length > 0
|
||||
? "Everything\u2019s looking good."
|
||||
: "";
|
||||
|
||||
if (homesLoading || tasksLoading) {
|
||||
return <DashboardSkeleton />;
|
||||
}
|
||||
|
||||
/* ─── Empty state: no homes yet ─── */
|
||||
if (homes.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center">
|
||||
<div className="size-20 rounded-3xl bg-gradient-to-br from-[#FFF3EB] to-[#ECFDF5] flex items-center justify-center mb-6">
|
||||
<Home className="size-9 text-[#E07A3A]" />
|
||||
</div>
|
||||
<h1 className="font-heading text-3xl font-bold tracking-tight">
|
||||
Welcome to Casera{name ? `, ${name}` : ""}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-3 max-w-md text-base leading-relaxed">
|
||||
The easiest way to keep your home running smoothly.
|
||||
Add your first home to get started.
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
className="mt-8 rounded-full px-8 h-12 text-base"
|
||||
>
|
||||
<Link href={`${basePath}/residences/new`}>
|
||||
<Plus className="size-5 mr-2" />
|
||||
Add Your Home
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<div className="grid sm:grid-cols-3 gap-5 mt-16 max-w-xl w-full">
|
||||
{[
|
||||
{
|
||||
icon: CheckSquare,
|
||||
title: "Track tasks",
|
||||
body: "Repairs, maintenance, projects — all in one place.",
|
||||
},
|
||||
{
|
||||
icon: HardHat,
|
||||
title: "Save your pros",
|
||||
body: "Never lose a good contractor\u2019s number again.",
|
||||
},
|
||||
{
|
||||
icon: FileText,
|
||||
title: "Store documents",
|
||||
body: "Warranties, manuals, receipts — easy to find.",
|
||||
},
|
||||
].map((tip) => (
|
||||
<div key={tip.title} className="text-center">
|
||||
<tip.icon className="size-5 text-muted-foreground mx-auto mb-2" />
|
||||
<p className="text-sm font-medium">{tip.title}</p>
|
||||
<p className="text-xs text-muted-foreground mt-1 leading-relaxed">
|
||||
{tip.body}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Main dashboard ─── */
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
{/* Greeting */}
|
||||
<div>
|
||||
<h1 className="font-heading text-2xl sm:text-3xl font-bold tracking-tight">
|
||||
{greeting}
|
||||
</h1>
|
||||
{statusMsg && (
|
||||
<p className="text-muted-foreground mt-1.5 text-[15px]">{statusMsg}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Your Homes — the main content */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="font-heading text-lg font-semibold">Your Homes</h2>
|
||||
<Link
|
||||
href={`${basePath}/residences/new`}
|
||||
className="inline-flex items-center gap-1 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
Add
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className={`grid gap-4 ${
|
||||
homes.length === 1
|
||||
? "grid-cols-1 max-w-lg"
|
||||
: "sm:grid-cols-2 lg:grid-cols-3"
|
||||
}`}>
|
||||
{homes.map((home) => (
|
||||
<HomeCard key={home.residence.id} data={home} basePath={basePath} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Coming Up — clean task list */}
|
||||
<ComingUp tasks={upcomingTasks} basePath={basePath} />
|
||||
|
||||
{/* Quick actions — subtle pills at the bottom */}
|
||||
<QuickActions basePath={basePath} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ export default function ResidencesPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Residences"
|
||||
description="Manage your properties"
|
||||
actionLabel="Add Residence"
|
||||
title="Your Homes"
|
||||
description="All the places you look after"
|
||||
actionLabel="Add Home"
|
||||
onAction={() => router.push(`${basePath}/residences/new`)}
|
||||
/>
|
||||
|
||||
@@ -37,9 +37,9 @@ export default function ResidencesPage() {
|
||||
{!isLoading && !error && Array.isArray(residences) && residences.length === 0 && (
|
||||
<EmptyState
|
||||
icon={Home}
|
||||
title="No residences yet"
|
||||
description="Add your first property to start tracking tasks and maintenance."
|
||||
actionLabel="Add Residence"
|
||||
title="No homes added yet"
|
||||
description="Add your home to start keeping track of everything — tasks, documents, contractors, and more."
|
||||
actionLabel="Add Your Home"
|
||||
onAction={() => router.push(`${basePath}/residences/new`)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -21,17 +21,21 @@ export default function SettingsLayout({ children }: { children: React.ReactNode
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-2xl font-bold tracking-tight">Settings</h1>
|
||||
<div>
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight">Settings</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">Manage your account preferences.</p>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-6">
|
||||
<nav className="flex sm:flex-col gap-1 sm:w-48 shrink-0">
|
||||
<nav className="flex sm:flex-col gap-1 sm:w-52 shrink-0">
|
||||
{settingsNav.map((item) => (
|
||||
<Link key={item.href} href={item.href}
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium transition-colors",
|
||||
"hover:bg-accent hover:text-accent-foreground",
|
||||
pathname === item.href ? "bg-accent text-accent-foreground" : "text-muted-foreground"
|
||||
"flex items-center gap-2.5 rounded-xl px-3.5 py-2.5 text-sm font-medium transition-all duration-200",
|
||||
pathname === item.href
|
||||
? "bg-primary/10 text-primary shadow-sm"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
)}>
|
||||
<item.icon className="size-4" />
|
||||
<item.icon className={cn("size-4", pathname === item.href ? "text-primary" : "text-muted-foreground")} />
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function TasksPage() {
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Tasks"
|
||||
description="Manage your home maintenance tasks"
|
||||
description="Everything on your to-do list"
|
||||
actionLabel="New Task"
|
||||
onAction={() => router.push(`${basePath}/tasks/new`)}
|
||||
>
|
||||
@@ -72,9 +72,9 @@ export default function TasksPage() {
|
||||
{!isLoading && !isError && isEmpty && (
|
||||
<EmptyState
|
||||
icon={ClipboardList}
|
||||
title="No tasks yet"
|
||||
description="Create your first task to start tracking home maintenance."
|
||||
actionLabel="New Task"
|
||||
title="Nothing on the list yet"
|
||||
description="When something around the house needs attention, add it here and we'll help you stay on top of it."
|
||||
actionLabel="Add Your First Task"
|
||||
onAction={() => router.push(`${basePath}/tasks/new`)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Sidebar } from '@/components/layout/sidebar';
|
||||
import { TopBar } from '@/components/layout/top-bar';
|
||||
import { MobileNav } from '@/components/layout/mobile-nav';
|
||||
import { DemoBanner } from '@/components/demo/demo-banner';
|
||||
@@ -12,19 +11,12 @@ export default function DemoAppLayout({ children }: { children: React.ReactNode
|
||||
<DataProviderProvider value={demoProvider}>
|
||||
<div className="min-h-screen bg-background">
|
||||
<DemoBanner />
|
||||
<TopBar />
|
||||
|
||||
{/* Sidebar - hidden on mobile */}
|
||||
<Sidebar />
|
||||
<main className="max-w-7xl mx-auto px-6 py-8 lg:py-12 pb-28 md:pb-12">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="md:ml-16 lg:ml-64 flex flex-col min-h-screen">
|
||||
<TopBar />
|
||||
<main className="flex-1 p-4 lg:p-6 pb-20 md:pb-6">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Mobile bottom nav */}
|
||||
<MobileNav />
|
||||
</div>
|
||||
</DataProviderProvider>
|
||||
|
||||
+38
-19
@@ -1,38 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Image from "next/image";
|
||||
import { ArrowRight, Play } from "lucide-react";
|
||||
|
||||
export default function DemoLandingPage() {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center bg-background px-4">
|
||||
<div className="mx-auto max-w-md text-center">
|
||||
<div className="flex min-h-screen flex-col items-center justify-center bg-[#FAFAF7] px-6 relative overflow-hidden">
|
||||
{/* Decorative background */}
|
||||
<div className="absolute top-20 right-[-10%] w-[500px] h-[500px] rounded-full bg-[#E07A3A]/[0.04] blur-3xl pointer-events-none" />
|
||||
<div className="absolute bottom-0 left-[-5%] w-[300px] h-[300px] rounded-full bg-[#0D7C66]/[0.03] blur-3xl pointer-events-none" />
|
||||
|
||||
<div className="relative mx-auto max-w-lg text-center">
|
||||
{/* Logo */}
|
||||
<h1 className="mb-8 text-2xl font-bold tracking-tight text-primary">
|
||||
Casera
|
||||
</h1>
|
||||
<Link href="/" className="inline-flex items-center gap-2.5 mb-10">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="Casera"
|
||||
width={36}
|
||||
height={36}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
<span className="font-heading text-2xl font-bold tracking-tight text-[#1C1917]">
|
||||
Casera
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Hero */}
|
||||
<h2 className="text-3xl font-bold tracking-tight">
|
||||
Try Casera — No Account Needed
|
||||
</h2>
|
||||
<p className="mt-3 text-muted-foreground">
|
||||
Manage your home maintenance, track tasks, organize contractors, and
|
||||
store documents.
|
||||
<h1 className="font-heading text-4xl font-bold tracking-tight text-[#1C1917]">
|
||||
See Casera in action
|
||||
</h1>
|
||||
<p className="mt-4 text-lg text-[#78716C] leading-relaxed">
|
||||
Explore the full app with sample data. No account needed —
|
||||
just click and start exploring.
|
||||
</p>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="mt-8 flex flex-col gap-3">
|
||||
<Button size="lg" asChild>
|
||||
<Link href="/demo/app">Start Demo</Link>
|
||||
</Button>
|
||||
<div className="mt-10 flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<Link
|
||||
href="/demo/app"
|
||||
className="group inline-flex items-center gap-2.5 rounded-full bg-[#E07A3A] px-8 py-4 text-base font-semibold text-white shadow-lg shadow-[#E07A3A]/20 hover:bg-[#C4632A] transition-all"
|
||||
>
|
||||
<Play className="size-4" />
|
||||
Launch Demo
|
||||
<ArrowRight className="size-4 transition-transform group-hover:translate-x-0.5" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Login link */}
|
||||
<p className="mt-6 text-sm text-muted-foreground">
|
||||
<p className="mt-8 text-sm text-[#A8A29E]">
|
||||
Already have an account?{" "}
|
||||
<Link href="/login" className="text-primary hover:underline">
|
||||
Log In
|
||||
<Link href="/login" className="text-[#E07A3A] font-medium hover:underline">
|
||||
Sign In
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
+89
-6
@@ -8,8 +8,9 @@
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-sans: var(--font-outfit);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--font-heading: var(--font-bricolage);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
@@ -48,15 +49,30 @@
|
||||
--radius-4xl: calc(var(--radius) + 16px);
|
||||
|
||||
/* App-specific theme-aware Tailwind utilities */
|
||||
--color-bg-primary: var(--color-bg-primary);
|
||||
--color-bg-secondary: var(--color-bg-secondary);
|
||||
--color-text-primary: var(--color-text-primary);
|
||||
--color-text-secondary: var(--color-text-secondary);
|
||||
--color-text-on-primary: var(--color-text-on-primary);
|
||||
--color-bg-primary: #FFFFFF;
|
||||
--color-bg-secondary: #F7F7F7;
|
||||
--color-text-primary: #1C1917;
|
||||
--color-text-secondary: #78716C;
|
||||
--color-text-on-primary: #FFFFFF;
|
||||
--color-app-primary: var(--color-primary);
|
||||
--color-app-secondary: var(--color-secondary);
|
||||
--color-app-accent: var(--color-accent);
|
||||
--color-app-error: var(--color-error);
|
||||
|
||||
/* Landing page brand colors (theme-independent) */
|
||||
--color-brand-orange: #E07A3A;
|
||||
--color-brand-orange-dark: #C4632A;
|
||||
--color-brand-orange-light: #FFF3EB;
|
||||
--color-brand-teal: #0D7C66;
|
||||
--color-brand-teal-light: #ECFDF5;
|
||||
--color-brand-slate: #1C1917;
|
||||
--color-brand-warm: #FFFFFF;
|
||||
|
||||
/* Animation tokens */
|
||||
--animate-fade-up: fade-up 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
--animate-fade-in: fade-in 0.6s ease both;
|
||||
--animate-slide-in-right: slide-in-right 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
--animate-scale-in: scale-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
:root {
|
||||
@@ -71,3 +87,70 @@
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* Keyframes */
|
||||
@keyframes fade-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(24px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slide-in-right {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(40px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scale-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.92);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-12px); }
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
|
||||
/* Utility classes for staggered animations */
|
||||
.stagger-1 { animation-delay: 0ms; }
|
||||
.stagger-2 { animation-delay: 100ms; }
|
||||
.stagger-3 { animation-delay: 200ms; }
|
||||
.stagger-4 { animation-delay: 300ms; }
|
||||
.stagger-5 { animation-delay: 400ms; }
|
||||
.stagger-6 { animation-delay: 500ms; }
|
||||
|
||||
/* Noise texture overlay for backgrounds */
|
||||
.noise-overlay::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0.03;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
+11
-4
@@ -1,15 +1,22 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Suspense } from "react";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Bricolage_Grotesque, Outfit, Geist_Mono } from "next/font/google";
|
||||
import { ThemeProvider } from "@/lib/themes/theme-provider";
|
||||
import { QueryProvider } from "@/lib/query/query-provider";
|
||||
import { PostHogProvider } from "@/lib/analytics/posthog-provider";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
const bricolage = Bricolage_Grotesque({
|
||||
variable: "--font-bricolage",
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const outfit = Outfit({
|
||||
variable: "--font-outfit",
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
@@ -46,7 +53,7 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
className={`${bricolage.variable} ${outfit.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<PostHogProvider>
|
||||
|
||||
+496
-3
@@ -1,5 +1,498 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import {
|
||||
CheckSquare,
|
||||
HardHat,
|
||||
FileText,
|
||||
ArrowRight,
|
||||
Shield,
|
||||
Users,
|
||||
Bell,
|
||||
ChevronRight,
|
||||
Home,
|
||||
} from "lucide-react";
|
||||
|
||||
export default function Home() {
|
||||
redirect('/app');
|
||||
const features = [
|
||||
{
|
||||
icon: CheckSquare,
|
||||
title: "Smart Task Tracking",
|
||||
description:
|
||||
"Kanban boards, recurring schedules, and due date reminders keep every repair and project on track.",
|
||||
color: "bg-[#FFF3EB] text-[#E07A3A]",
|
||||
},
|
||||
{
|
||||
icon: HardHat,
|
||||
title: "Contractor Rolodex",
|
||||
description:
|
||||
"Store contact details, specialties, and notes for every service provider. Never lose a plumber's number again.",
|
||||
color: "bg-[#ECFDF5] text-[#0D7C66]",
|
||||
},
|
||||
{
|
||||
icon: FileText,
|
||||
title: "Document Vault",
|
||||
description:
|
||||
"Warranties, manuals, leases — organized by property and always at your fingertips when you need them.",
|
||||
color: "bg-[#FEF3C7] text-[#92400E]",
|
||||
},
|
||||
];
|
||||
|
||||
const steps = [
|
||||
{
|
||||
number: "01",
|
||||
title: "Add your home",
|
||||
description:
|
||||
"Create a residence and invite household members to collaborate on maintenance together.",
|
||||
},
|
||||
{
|
||||
number: "02",
|
||||
title: "Track everything",
|
||||
description:
|
||||
"Add tasks, log contractors, upload documents. Build your home's complete history over time.",
|
||||
},
|
||||
{
|
||||
number: "03",
|
||||
title: "Stay ahead",
|
||||
description:
|
||||
"Get reminders for upcoming maintenance. Never miss a filter change, inspection, or warranty deadline.",
|
||||
},
|
||||
];
|
||||
|
||||
const highlights = [
|
||||
{
|
||||
icon: Shield,
|
||||
title: "Private & Secure",
|
||||
description: "Your data stays yours. We never share or sell your information.",
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: "Household Sharing",
|
||||
description: "Invite family members with simple invite codes. Everyone stays in sync.",
|
||||
},
|
||||
{
|
||||
icon: Bell,
|
||||
title: "Smart Reminders",
|
||||
description: "Recurring tasks and due dates keep you ahead of every maintenance need.",
|
||||
},
|
||||
{
|
||||
icon: Home,
|
||||
title: "Multi-Property",
|
||||
description: "Manage one home or many. Each property gets its own organized space.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-white text-[#1C1917] font-sans selection:bg-[#E07A3A]/20">
|
||||
{/* ─── Navigation ─── */}
|
||||
<nav className="fixed top-0 w-full z-50 bg-white/80 backdrop-blur-xl border-b border-[#E7E5E4]/60">
|
||||
<div className="max-w-7xl mx-auto px-6 flex items-center justify-between h-16">
|
||||
<Link href="/" className="flex items-center gap-2.5">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="Casera"
|
||||
width={32}
|
||||
height={32}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
<span className="font-heading text-xl font-bold tracking-tight text-[#1C1917]">
|
||||
Casera
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<div className="hidden md:flex items-center gap-8">
|
||||
<a
|
||||
href="#features"
|
||||
className="text-sm font-medium text-[#78716C] hover:text-[#1C1917] transition-colors"
|
||||
>
|
||||
Features
|
||||
</a>
|
||||
<a
|
||||
href="#how-it-works"
|
||||
className="text-sm font-medium text-[#78716C] hover:text-[#1C1917] transition-colors"
|
||||
>
|
||||
How It Works
|
||||
</a>
|
||||
<Link
|
||||
href="/demo"
|
||||
className="text-sm font-medium text-[#78716C] hover:text-[#1C1917] transition-colors"
|
||||
>
|
||||
Demo
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Link
|
||||
href="/login"
|
||||
className="text-sm font-medium text-[#78716C] hover:text-[#1C1917] transition-colors hidden sm:inline-flex"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
<Link
|
||||
href="/register"
|
||||
className="inline-flex items-center gap-1.5 rounded-full bg-[#1C1917] px-5 py-2 text-sm font-semibold text-white hover:bg-[#292524] transition-colors"
|
||||
>
|
||||
Get Started
|
||||
<ArrowRight className="size-3.5" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* ─── Hero ─── */}
|
||||
<section className="relative pt-32 pb-24 md:pt-40 md:pb-32 overflow-hidden">
|
||||
{/* Decorative background */}
|
||||
<div className="absolute inset-0 pointer-events-none">
|
||||
<div className="absolute top-20 right-[-10%] w-[600px] h-[600px] rounded-full bg-[#E07A3A]/[0.04] blur-3xl" />
|
||||
<div className="absolute bottom-0 left-[-5%] w-[400px] h-[400px] rounded-full bg-[#0D7C66]/[0.03] blur-3xl" />
|
||||
{/* Subtle grid pattern */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.03]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(#1C1917 1px, transparent 1px), linear-gradient(90deg, #1C1917 1px, transparent 1px)",
|
||||
backgroundSize: "64px 64px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto px-6 relative">
|
||||
<div className="max-w-3xl">
|
||||
{/* Badge */}
|
||||
<div className="animate-fade-up stagger-1 inline-flex items-center gap-2 rounded-full border border-[#E7E5E4] bg-white px-4 py-1.5 text-sm text-[#78716C] mb-8 shadow-sm">
|
||||
<span className="inline-block size-2 rounded-full bg-[#0D7C66] animate-pulse" />
|
||||
Now available for homeowners
|
||||
</div>
|
||||
|
||||
<h1 className="animate-fade-up stagger-2 font-heading text-5xl sm:text-6xl md:text-7xl font-bold tracking-tight leading-[1.08]">
|
||||
Your home,{" "}
|
||||
<span className="text-[#E07A3A]">perfectly maintained.</span>
|
||||
</h1>
|
||||
|
||||
<p className="animate-fade-up stagger-3 mt-6 text-lg md:text-xl text-[#78716C] max-w-xl leading-relaxed">
|
||||
Track tasks, organize contractors, and store important
|
||||
documents. Everything you need to keep your home running
|
||||
smoothly — in one place.
|
||||
</p>
|
||||
|
||||
<div className="animate-fade-up stagger-4 mt-10 flex flex-wrap gap-4">
|
||||
<Link
|
||||
href="/register"
|
||||
className="group inline-flex items-center gap-2 rounded-full bg-[#E07A3A] px-7 py-3.5 text-base font-semibold text-white shadow-lg shadow-[#E07A3A]/20 hover:bg-[#C4632A] transition-all hover:shadow-xl hover:shadow-[#E07A3A]/25"
|
||||
>
|
||||
Get Started — Free
|
||||
<ArrowRight className="size-4 transition-transform group-hover:translate-x-0.5" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/demo"
|
||||
className="group inline-flex items-center gap-2 rounded-full border-2 border-[#E7E5E4] bg-white px-7 py-3.5 text-base font-semibold text-[#1C1917] hover:border-[#D6D3D1] hover:bg-[#F5F5F4] transition-all"
|
||||
>
|
||||
Try the Demo
|
||||
<ChevronRight className="size-4 text-[#A8A29E] transition-transform group-hover:translate-x-0.5" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hero visual — abstract app preview */}
|
||||
<div className="animate-fade-up stagger-5 hidden lg:block absolute top-12 right-6 w-[420px]">
|
||||
<div className="relative">
|
||||
{/* Main card */}
|
||||
<div className="rounded-2xl bg-white border border-[#E7E5E4] shadow-xl shadow-black/[0.04] p-5">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="size-8 rounded-lg bg-[#FFF3EB] flex items-center justify-center">
|
||||
<CheckSquare className="size-4 text-[#E07A3A]" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-semibold">Upcoming Tasks</div>
|
||||
<div className="text-xs text-[#A8A29E]">3 due this week</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2.5">
|
||||
{[
|
||||
{ task: "Replace HVAC filter", due: "Tomorrow", status: "bg-[#FEF3C7] text-[#92400E]" },
|
||||
{ task: "Schedule gutter cleaning", due: "Wed", status: "bg-[#ECFDF5] text-[#0D7C66]" },
|
||||
{ task: "Check smoke detectors", due: "Fri", status: "bg-[#FFF3EB] text-[#E07A3A]" },
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.task}
|
||||
className="flex items-center justify-between rounded-lg bg-white px-3.5 py-2.5"
|
||||
>
|
||||
<span className="text-sm font-medium">{item.task}</span>
|
||||
<span
|
||||
className={`text-xs font-medium px-2 py-0.5 rounded-full ${item.status}`}
|
||||
>
|
||||
{item.due}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Floating stat card */}
|
||||
<div
|
||||
className="absolute -bottom-8 -left-8 rounded-xl bg-white border border-[#E7E5E4] shadow-lg shadow-black/[0.04] p-4 w-48"
|
||||
style={{ animation: "float 6s ease-in-out infinite" }}
|
||||
>
|
||||
<div className="text-xs text-[#A8A29E] mb-1">This Month</div>
|
||||
<div className="text-2xl font-bold font-heading text-[#0D7C66]">12 Done</div>
|
||||
<div className="mt-2 h-1.5 rounded-full bg-[#F7F7F7] overflow-hidden">
|
||||
<div className="h-full w-3/4 rounded-full bg-[#0D7C66]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── Social proof strip ─── */}
|
||||
<section className="border-y border-[#E7E5E4] bg-white/50">
|
||||
<div className="max-w-7xl mx-auto px-6 py-8 flex flex-wrap items-center justify-center gap-x-12 gap-y-4 text-center">
|
||||
{[
|
||||
{ value: "Free", label: "to get started" },
|
||||
{ value: "100%", label: "private & secure" },
|
||||
{ value: "All-in-one", label: "home management" },
|
||||
].map((stat) => (
|
||||
<div key={stat.label} className="flex items-baseline gap-2">
|
||||
<span className="text-lg font-bold font-heading text-[#1C1917]">
|
||||
{stat.value}
|
||||
</span>
|
||||
<span className="text-sm text-[#A8A29E]">{stat.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── Features ─── */}
|
||||
<section id="features" className="py-24 md:py-32">
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
<div className="max-w-2xl mb-16">
|
||||
<p className="text-sm font-semibold text-[#E07A3A] uppercase tracking-wider mb-3">
|
||||
Features
|
||||
</p>
|
||||
<h2 className="font-heading text-3xl md:text-4xl font-bold tracking-tight">
|
||||
Everything your home needs, nothing it doesn't.
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-[#78716C] leading-relaxed">
|
||||
Casera brings all your home maintenance into one clear,
|
||||
organized space. No bloat, no learning curve.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{features.map((feature, i) => (
|
||||
<div
|
||||
key={feature.title}
|
||||
className={`group relative rounded-2xl border border-[#E7E5E4] bg-white p-8 transition-all hover:shadow-lg hover:shadow-black/[0.04] hover:-translate-y-0.5 stagger-${i + 1}`}
|
||||
>
|
||||
<div
|
||||
className={`inline-flex items-center justify-center size-12 rounded-xl ${feature.color} mb-5`}
|
||||
>
|
||||
<feature.icon className="size-5" />
|
||||
</div>
|
||||
<h3 className="font-heading text-xl font-bold mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-[#78716C] leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── How It Works ─── */}
|
||||
<section
|
||||
id="how-it-works"
|
||||
className="py-24 md:py-32 bg-[#F7F7F7] relative noise-overlay"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-6 relative">
|
||||
<div className="text-center max-w-2xl mx-auto mb-16">
|
||||
<p className="text-sm font-semibold text-[#0D7C66] uppercase tracking-wider mb-3">
|
||||
How It Works
|
||||
</p>
|
||||
<h2 className="font-heading text-3xl md:text-4xl font-bold tracking-tight">
|
||||
Up and running in minutes
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8 md:gap-12">
|
||||
{steps.map((step) => (
|
||||
<div key={step.number} className="text-center md:text-left">
|
||||
<div className="inline-flex items-center justify-center size-14 rounded-2xl bg-[#1C1917] text-white font-heading font-bold text-lg mb-5">
|
||||
{step.number}
|
||||
</div>
|
||||
<h3 className="font-heading text-xl font-bold mb-2">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="text-[#78716C] leading-relaxed">
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── Highlights Grid ─── */}
|
||||
<section className="py-24 md:py-32">
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
<div className="text-center max-w-2xl mx-auto mb-16">
|
||||
<h2 className="font-heading text-3xl md:text-4xl font-bold tracking-tight">
|
||||
Built for real homeowners
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-[#78716C]">
|
||||
Thoughtful details that make home maintenance feel manageable.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{highlights.map((item) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="rounded-2xl border border-[#E7E5E4] bg-white p-6 hover:shadow-md hover:shadow-black/[0.03] transition-all"
|
||||
>
|
||||
<item.icon className="size-6 text-[#E07A3A] mb-4" />
|
||||
<h3 className="font-heading font-bold text-base mb-1.5">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-sm text-[#78716C] leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── CTA Section ─── */}
|
||||
<section className="py-24 md:py-32 relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-[#1C1917]" />
|
||||
{/* Orange glow */}
|
||||
<div className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full bg-[#E07A3A]/10 blur-[120px] pointer-events-none" />
|
||||
<div className="absolute bottom-0 left-0 w-[400px] h-[400px] rounded-full bg-[#0D7C66]/10 blur-[100px] pointer-events-none" />
|
||||
|
||||
<div className="max-w-3xl mx-auto px-6 text-center relative">
|
||||
<h2 className="font-heading text-3xl md:text-5xl font-bold tracking-tight text-white">
|
||||
Ready to take control of your home?
|
||||
</h2>
|
||||
<p className="mt-5 text-lg text-[#A8A29E] max-w-xl mx-auto leading-relaxed">
|
||||
Join homeowners who've simplified their maintenance routine.
|
||||
Free to start, no credit card required.
|
||||
</p>
|
||||
<div className="mt-10 flex flex-wrap justify-center gap-4">
|
||||
<Link
|
||||
href="/register"
|
||||
className="group inline-flex items-center gap-2 rounded-full bg-[#E07A3A] px-8 py-4 text-base font-semibold text-white shadow-lg shadow-[#E07A3A]/20 hover:bg-[#C4632A] transition-all"
|
||||
>
|
||||
Get Started — Free
|
||||
<ArrowRight className="size-4 transition-transform group-hover:translate-x-0.5" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/demo"
|
||||
className="inline-flex items-center gap-2 rounded-full border-2 border-white/15 px-8 py-4 text-base font-semibold text-white hover:border-white/30 hover:bg-white/5 transition-all"
|
||||
>
|
||||
Try the Demo
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ─── Footer ─── */}
|
||||
<footer className="bg-[#1C1917] border-t border-white/5 text-[#A8A29E]">
|
||||
<div className="max-w-7xl mx-auto px-6 py-16">
|
||||
<div className="grid md:grid-cols-4 gap-10">
|
||||
{/* Brand */}
|
||||
<div className="md:col-span-1">
|
||||
<Link href="/" className="flex items-center gap-2.5 mb-4">
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="Casera"
|
||||
width={28}
|
||||
height={28}
|
||||
className="rounded-md"
|
||||
/>
|
||||
<span className="font-heading text-lg font-bold text-white">
|
||||
Casera
|
||||
</span>
|
||||
</Link>
|
||||
<p className="text-sm leading-relaxed">
|
||||
Home maintenance made simple. Track tasks, organize
|
||||
contractors, store documents.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Product */}
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-white mb-4">
|
||||
Product
|
||||
</h4>
|
||||
<ul className="space-y-2.5 text-sm">
|
||||
<li>
|
||||
<a href="#features" className="hover:text-white transition-colors">
|
||||
Features
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/demo" className="hover:text-white transition-colors">
|
||||
Demo
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#how-it-works" className="hover:text-white transition-colors">
|
||||
How It Works
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Account */}
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-white mb-4">
|
||||
Account
|
||||
</h4>
|
||||
<ul className="space-y-2.5 text-sm">
|
||||
<li>
|
||||
<Link href="/login" className="hover:text-white transition-colors">
|
||||
Sign In
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/register" className="hover:text-white transition-colors">
|
||||
Create Account
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/forgot-password" className="hover:text-white transition-colors">
|
||||
Reset Password
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Legal */}
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-white mb-4">Legal</h4>
|
||||
<ul className="space-y-2.5 text-sm">
|
||||
<li>
|
||||
<span className="cursor-default">Privacy Policy</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="cursor-default">Terms of Service</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 pt-8 border-t border-white/5 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<p className="text-xs">
|
||||
© {new Date().getFullYear()} Casera. All rights reserved.
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
Made for homeowners, by homeowners.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user