add rebrand landing page (#211)

This commit is contained in:
KM Koushik
2025-09-05 22:32:56 +10:00
committed by GitHub
parent 1c8bb550d9
commit 3e6d4d12df
24 changed files with 1508 additions and 513 deletions
@@ -0,0 +1,61 @@
"use client";
import Image from "next/image";
export function FeatureCard({
title,
content,
imageSrc,
}: {
title?: string;
content?: string;
imageSrc?: string;
}) {
return (
<div className="rounded-[18px] bg-primary/20 p-1 ">
<div className="h-full rounded-[14px] bg-primary/20 p-0.5 shadow-sm">
<div className="bg-background rounded-xl h-full flex flex-col">
<div className="relative w-full aspect-[16/9] rounded-t-xl">
{imageSrc ? (
<Image
src={imageSrc}
alt={title || "Feature image"}
fill
className="object-cover rounded-t-xl"
priority={false}
/>
) : (
<>
<Image
src="/hero-light.png"
alt="Feature image"
fill
className="object-cover dark:hidden rounded-t-xl"
priority={false}
/>
<Image
src="/hero-dark.png"
alt="Feature image"
fill
className="object-cover hidden dark:block rounded-t-xl"
priority={false}
/>
</>
)}
</div>
<div className="p-5 flex-1 flex flex-col">
<h3 className="text-base sm:text-lg text-primary font-sans">
{title || ""}
</h3>
{content ? (
<p className="mt-2 text-sm leading-relaxed">{content}</p>
) : (
<div className="mt-2 text-sm text-muted-foreground min-h-[1.5rem]"></div>
)}
</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,28 @@
"use client";
export function FeatureCardPlain({
title,
content,
}: {
title?: string;
content?: string;
}) {
return (
<div className="rounded-[18px] bg-primary/20 p-1">
<div className="h-full rounded-[14px] bg-primary/20 p-0.5 shadow-sm">
<div className="bg-background rounded-xl h-full flex flex-col">
<div className="p-5 flex-1 flex flex-col">
<h3 className="text-base sm:text-lg text-primary font-sans">
{title || ""}
</h3>
{content ? (
<p className="mt-2 text-sm leading-relaxed">{content}</p>
) : (
<div className="mt-2 text-sm text-muted-foreground min-h-[1.5rem]"></div>
)}
</div>
</div>
</div>
</div>
);
}
@@ -1,42 +1,53 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@usesend/ui/src/button";
const REPO = "unsend-dev/unsend";
const REPO_URL = `https://github.com/${REPO}`;
const API_URL = `https://api.github.com/repos/${REPO}`;
const REVALIDATE_SECONDS = 60 * 60 * 24 * 7; // 7 days
export function GitHubStarsButton() {
const [stars, setStars] = useState<number | null>(null);
useEffect(() => {
let cancelled = false;
async function load() {
try {
const res = await fetch(API_URL, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
cache: "no-store",
});
if (!res.ok) return;
const json = await res.json();
if (!cancelled && typeof json.stargazers_count === "number") {
setStars(json.stargazers_count);
}
} catch (err) {
// ignore network errors; keep placeholder
}
function formatCompact(n: number): string {
if (n < 1000) return n.toLocaleString();
const units = [
{ v: 1_000_000_000, s: " B" },
{ v: 1_000_000, s: " M" },
{ v: 1_000, s: " K" },
];
for (const u of units) {
if (n >= u.v) {
const num = n / u.v;
const rounded = Math.round(num * 10) / 10; // 1 decimal
const str = rounded.toFixed(1).replace(/\.0$/, "");
return str + u.s;
}
load();
return () => {
cancelled = true;
};
}, []);
}
return n.toLocaleString();
}
const formatted = stars?.toLocaleString() ?? "—";
export async function GitHubStarsButton() {
const headers: Record<string, string> = {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "usesend-marketing",
};
if (process.env.GITHUB_TOKEN)
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
let stars: number | null = null;
try {
const res = await fetch(API_URL, {
headers,
next: { revalidate: REVALIDATE_SECONDS },
});
if (res.ok) {
const json = (await res.json()) as { stargazers_count?: number };
if (typeof json.stargazers_count === "number")
stars = json.stargazers_count;
}
} catch {
// ignore network errors; show placeholder
}
const formatted = stars == null ? "—" : formatCompact(stars);
return (
<Button variant="outline" size="lg" className="px-4 gap-2">
@@ -48,7 +59,10 @@ export function GitHubStarsButton() {
className="flex items-center gap-2"
>
<GitHubIcon className="h-4 w-4" />
<span>Star on GitHub</span>
<span>GitHub</span>
<span className="rounded-md bg-muted px-1.5 py-0.5 text-xs tabular-nums text-muted-foreground">
{formatted}
</span>
</a>
</Button>
);
+108
View File
@@ -0,0 +1,108 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { Button } from "@usesend/ui/src/button";
const REPO = "unsend-dev/unsend";
const REPO_URL = `https://github.com/${REPO}`;
const APP_URL = "https://app.usesend.com";
export function TopNav() {
const [open, setOpen] = useState(false);
const pathname = usePathname();
const isHome = pathname === "/";
const pricingHref = isHome ? "#pricing" : "/#pricing";
return (
<header className="py-4 border-b border-border sticky top-0 z-20 backdrop-blur supports-[backdrop-filter]:bg-sidebar-background/80">
<div className="mx-auto max-w-6xl px-6 flex items-center justify-between gap-4 text-sm">
<Link href="/" className="flex items-center gap-2 group">
<Image src="/logo-squircle.png" alt="useSend" width={24} height={24} />
<span className="text-primary font-mono text-[16px] group-hover:opacity-90">useSend</span>
</Link>
{/* Desktop nav */}
<nav className="hidden sm:flex items-center gap-4 text-muted-foreground">
<Link href={pricingHref} className="hover:text-foreground">
Pricing
</Link>
<a
href="https://docs.usesend.com"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground"
>
Docs
</a>
<a
href={REPO_URL}
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground"
>
GitHub
</a>
<Button size="sm" className="ml-2">
<a href={APP_URL} target="_blank" rel="noopener noreferrer">
Get started
</a>
</Button>
</nav>
{/* Mobile hamburger */}
<button
aria-label="Open menu"
className="sm:hidden inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:text-foreground hover:bg-accent focus:outline-none focus:ring-2 focus:ring-border"
onClick={() => setOpen((v) => !v)}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-6 w-6">
{open ? (
<path d="M6 18 18 6M6 6l12 12" strokeLinecap="round" strokeLinejoin="round" />
) : (
<path d="M3 6h18M3 12h18M3 18h18" strokeLinecap="round" strokeLinejoin="round" />
)}
</svg>
</button>
</div>
{/* Mobile menu panel */}
{open ? (
<div className="sm:hidden border-t border-border bg-sidebar-background/95 backdrop-blur">
<div className="mx-auto max-w-6xl px-6 py-3 flex flex-col gap-2">
<Link href={pricingHref} className="py-2 text-muted-foreground hover:text-foreground" onClick={() => setOpen(false)}>
Pricing
</Link>
<a
href="https://docs.usesend.com"
target="_blank"
rel="noopener noreferrer"
className="py-2 text-muted-foreground hover:text-foreground"
onClick={() => setOpen(false)}
>
Docs
</a>
<a
href={REPO_URL}
target="_blank"
rel="noopener noreferrer"
className="py-2 text-muted-foreground hover:text-foreground"
onClick={() => setOpen(false)}
>
GitHub
</a>
<div className="pt-2">
<Button className="w-full">
<a href={APP_URL} target="_blank" rel="noopener noreferrer" onClick={() => setOpen(false)}>
Get started
</a>
</Button>
</div>
</div>
</div>
) : null}
</header>
);
}