Rebrand product as Manyangles and add Authentik
This commit is contained in:
+157
-52
@@ -1,7 +1,15 @@
|
||||
import Link from "next/link";
|
||||
import { CameraIcon } from "lucide-react";
|
||||
import {
|
||||
ArrowRightIcon,
|
||||
CheckCircle2Icon,
|
||||
DownloadIcon,
|
||||
ImagesIcon,
|
||||
ShieldCheckIcon,
|
||||
UploadIcon,
|
||||
} from "lucide-react";
|
||||
import { createServerCaller } from "@/trpc/server";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { BrandMark } from "@/components/brand-mark";
|
||||
import { formatEventDate } from "@/lib/utils";
|
||||
import {
|
||||
@@ -12,73 +20,170 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
|
||||
const features = [
|
||||
{
|
||||
number: "01",
|
||||
icon: UploadIcon,
|
||||
title: "One link for everyone",
|
||||
body: "Guests upload from any phone. No account, app, or shared camera roll required.",
|
||||
},
|
||||
{
|
||||
number: "02",
|
||||
icon: ShieldCheckIcon,
|
||||
title: "You decide what’s public",
|
||||
body: "Every photo lands in a private review queue before it reaches the shared gallery.",
|
||||
},
|
||||
{
|
||||
number: "03",
|
||||
icon: DownloadIcon,
|
||||
title: "Originals stay original",
|
||||
body: "Keep the full-quality files while Manyangles makes the gallery fast for everyone else.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
function AlbumPreview() {
|
||||
return (
|
||||
<div className="reveal-2 relative mx-auto w-full max-w-xl pb-8 sm:pb-10">
|
||||
<Card className="album-preview overflow-hidden rounded-2xl py-0">
|
||||
<CardHeader className="flex min-h-18 grid-cols-none flex-row items-center justify-between gap-4 border-b px-5 py-4 sm:px-6">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<span className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary-foreground/15 text-primary-foreground ring-1 ring-primary-foreground/25">
|
||||
<ImagesIcon aria-hidden="true" />
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<CardTitle className="truncate text-base font-semibold">Saturday album</CardTitle>
|
||||
<CardDescription>Brooklyn · 84 photos</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="secondary" className="gap-1.5">
|
||||
<span className="size-1.5 rounded-full bg-current" aria-hidden="true" />
|
||||
Live
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
<CardContent className="p-2.5 sm:p-3">
|
||||
<div className="grid h-[20rem] grid-cols-3 grid-rows-3 gap-2 sm:h-[25rem] sm:gap-2.5" aria-hidden="true">
|
||||
<div className="memory-photo memory-photo-one row-span-2" />
|
||||
<div className="memory-photo memory-photo-two col-span-2" />
|
||||
<div className="memory-photo memory-photo-three" />
|
||||
<div className="memory-photo memory-photo-four row-span-2" />
|
||||
<div className="memory-photo memory-photo-five" />
|
||||
<div className="memory-photo memory-photo-six" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="absolute right-3 bottom-0 left-3 flex items-center gap-3 rounded-xl border border-border/80 bg-card/90 p-3 shadow-xl backdrop-blur-xl sm:right-8 sm:left-auto sm:min-w-64 sm:p-4">
|
||||
<span className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
||||
<CheckCircle2Icon aria-hidden="true" />
|
||||
</span>
|
||||
<span className="min-w-0">
|
||||
<span className="block text-sm font-semibold">12 photos added</span>
|
||||
<span className="block truncate text-xs text-muted-foreground">Original quality · ready to review</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function HomePage() {
|
||||
const caller = await createServerCaller();
|
||||
const listed = await caller.event.listed();
|
||||
const viewer = await caller.viewer.me();
|
||||
const [listed, viewer] = await Promise.all([
|
||||
caller.event.listed(),
|
||||
caller.viewer.me(),
|
||||
]);
|
||||
const demoEvent = listed[0];
|
||||
|
||||
return (
|
||||
<main className="page-pad mx-auto flex min-h-[calc(100dvh-3.5rem)] w-full max-w-5xl flex-col gap-12 py-10 sm:min-h-[calc(100dvh-4rem)] sm:py-16">
|
||||
<div className="reveal flex max-w-2xl flex-col gap-5">
|
||||
<p className="text-xs font-medium tracking-[0.22em] text-primary uppercase sm:text-sm">
|
||||
Photos from the day
|
||||
</p>
|
||||
<h1 className="font-display text-4xl leading-[1.1] sm:text-6xl">
|
||||
Keep the originals. Share the day.
|
||||
</h1>
|
||||
<p className="max-w-xl text-base text-muted-foreground sm:text-lg">
|
||||
Guests upload from a link. You choose what the public sees. Full-quality
|
||||
files stay yours.
|
||||
</p>
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3">
|
||||
{viewer.openSignup ? (
|
||||
<Button asChild size="lg" className="tap-target w-full sm:w-auto">
|
||||
<Link href="/sign-up">
|
||||
<CameraIcon data-icon="inline-start" aria-hidden="true" />
|
||||
Host an event
|
||||
</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<Button asChild size="lg" className="tap-target w-full sm:w-auto">
|
||||
<Link href="/sign-up">Have an invite?</Link>
|
||||
</Button>
|
||||
)}
|
||||
<Button asChild variant="outline" size="lg" className="tap-target w-full sm:w-auto">
|
||||
<Link href="/sign-in">Sign in</Link>
|
||||
</Button>
|
||||
<main>
|
||||
<section className="page-pad mx-auto grid min-h-[calc(100svh-4rem)] w-full max-w-6xl items-center gap-12 py-14 sm:py-20 lg:grid-cols-[0.88fr_1.12fr] lg:gap-16 lg:py-24">
|
||||
<div className="reveal flex max-w-xl flex-col gap-6">
|
||||
<div className="flex items-center gap-3 text-xs font-bold tracking-[0.16em] text-primary uppercase">
|
||||
<span className="h-px w-9 bg-primary" aria-hidden="true" />
|
||||
Shared albums for real life
|
||||
</div>
|
||||
<h1 className="font-display text-[clamp(3.4rem,7vw,5.8rem)] leading-[0.92] font-semibold tracking-[-0.055em]">
|
||||
Every angle.<br /><span className="text-primary">One shared album.</span>
|
||||
</h1>
|
||||
<p className="max-w-lg text-lg leading-7 text-muted-foreground sm:text-xl sm:leading-8">
|
||||
Give guests one link to add their photos. Review everything in one place, share the best, and keep every original.
|
||||
</p>
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3">
|
||||
{viewer.openSignup ? (
|
||||
<Button asChild size="lg" className="tap-target w-full sm:w-auto">
|
||||
<Link href="/sign-up">Create an event<ArrowRightIcon data-icon="inline-end" aria-hidden="true" /></Link>
|
||||
</Button>
|
||||
) : (
|
||||
<Button asChild size="lg" className="tap-target w-full sm:w-auto"><Link href="/sign-up">Use an invite</Link></Button>
|
||||
)}
|
||||
{demoEvent ? (
|
||||
<Button asChild variant="outline" size="lg" className="tap-target w-full sm:w-auto"><Link href={`/e/${demoEvent.slug}`}>Open a gallery</Link></Button>
|
||||
) : (
|
||||
<Button asChild variant="outline" size="lg" className="tap-target w-full sm:w-auto"><Link href="/sign-in">Sign in</Link></Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-2 border-t border-border/80 pt-4 text-[0.6875rem] font-bold tracking-[0.08em] text-muted-foreground uppercase">
|
||||
<span>No app required</span><span>Original quality</span><span>Private by default</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AlbumPreview />
|
||||
</section>
|
||||
|
||||
<section className="border-y border-border/80 bg-card/80 backdrop-blur-sm" aria-labelledby="how-it-works">
|
||||
<div className="page-pad mx-auto w-full max-w-6xl py-14 sm:py-20">
|
||||
<p className="text-xs font-bold tracking-[0.14em] text-primary uppercase">The whole handoff, handled</p>
|
||||
<h2 id="how-it-works" className="mt-3 max-w-2xl text-4xl font-semibold tracking-[-0.035em] sm:text-5xl">
|
||||
Built for the photos that never make it out of the group chat.
|
||||
</h2>
|
||||
<div className="mt-8 grid gap-3 md:grid-cols-3">
|
||||
{features.map((feature) => {
|
||||
const Icon = feature.icon;
|
||||
return (
|
||||
<Card key={feature.title} className="feature-panel overflow-hidden bg-background/60 shadow-none">
|
||||
<CardHeader>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<span className="flex size-10 items-center justify-center rounded-lg bg-primary text-primary-foreground"><Icon aria-hidden="true" /></span>
|
||||
<span className="font-mono text-xs font-bold tracking-widest text-muted-foreground">{feature.number}</span>
|
||||
</div>
|
||||
<CardTitle className="text-xl font-semibold">{feature.title}</CardTitle>
|
||||
<CardDescription className="leading-6">{feature.body}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{listed.length > 0 ? (
|
||||
<section className="reveal-2 flex flex-col gap-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight sm:text-3xl">Open galleries</h2>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<section className="page-pad mx-auto flex w-full max-w-6xl flex-col gap-6 py-14 sm:py-20">
|
||||
<div className="flex flex-col gap-1 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-bold tracking-[0.14em] text-primary uppercase">Open now</p>
|
||||
<h2 className="mt-2 text-4xl font-semibold tracking-[-0.035em]">Public galleries</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">Join the day from wherever you are.</p>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{listed.map((event) => (
|
||||
<Link
|
||||
key={event.id}
|
||||
href={`/e/${event.slug}`}
|
||||
className="group block min-w-0 rounded-xl focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none"
|
||||
>
|
||||
<Link key={event.id} href={`/e/${event.slug}`} className="group block min-w-0 rounded-xl focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none">
|
||||
<Card className="h-full transition-transform duration-200 ease-out group-hover:-translate-y-0.5 group-hover:shadow-md motion-reduce:transition-none motion-reduce:group-hover:translate-y-0">
|
||||
<CardHeader>
|
||||
<div className="mb-2 flex size-10 items-center justify-center rounded-full bg-accent text-primary">
|
||||
<BrandMark className="size-5" />
|
||||
</div>
|
||||
<CardTitle className="truncate text-xl font-semibold tracking-tight">
|
||||
{event.title}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{formatEventDate(event.startsAt) ?? "Open gallery"}
|
||||
</CardDescription>
|
||||
<div className="mb-2 flex size-10 items-center justify-center rounded-lg bg-primary text-primary-foreground"><BrandMark className="size-5" /></div>
|
||||
<CardTitle className="truncate text-xl font-semibold tracking-tight">{event.title}</CardTitle>
|
||||
<CardDescription>{formatEventDate(event.startsAt) ?? "Open gallery"}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<span className="text-sm text-primary">View gallery</span>
|
||||
</CardContent>
|
||||
<CardContent className="flex items-center gap-1 text-sm font-medium text-primary">View gallery<ArrowRightIcon aria-hidden="true" /></CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<footer className="border-t border-border/80 bg-foreground text-background">
|
||||
<div className="page-pad mx-auto flex w-full max-w-6xl items-center justify-between gap-4 py-7 text-xs text-background/65">
|
||||
<span className="font-heading text-lg font-semibold text-background">Manyangles</span><span>Keep the moment, not the compression.</span>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user