Skip to content
This repository was archived by the owner on Sep 20, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions client/src/components/onboarding-brand-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { cn } from "@/lib/utils";

import { VirensLogo } from "./virens-logo";

export interface OnboardingBrandHeaderProps {
/** "plain": centered mark for a plain white screen (e.g. About).
* "hero": mark sits over a decorative gradient banner (e.g. Welcome, Sign in). */
variant?: "plain" | "hero";
className?: string;
}

/**
* Shared brand mark for the pre-sign-in onboarding screens (About, Welcome,
* Sign in). Reuses the same Leaf-in-badge treatment as the authenticated
* AppShell header (see app-shell.tsx) instead of a separate logo, since no
* dedicated Virens logo asset exists in the repo.
*/
export function OnboardingBrandHeader({
variant = "plain",
className,
}: OnboardingBrandHeaderProps) {
const content = (
<div className="flex flex-col items-center text-center">
<VirensLogo className="h-[52px] w-11" labelled />
<p className="mt-1 text-[32px] font-bold leading-none tracking-tight text-primary">
virens
</p>
<p className="mt-3 text-[15px] font-semibold text-muted-foreground">
let the good greens flourish
</p>
</div>
);

if (variant === "plain") {
return <div className={cn("pt-7", className)}>{content}</div>;
}

return (
<div
className={cn(
"relative flex min-h-[320px] items-center justify-center overflow-hidden rounded-3xl border border-border bg-[#f1f6ee] px-8 py-12 sm:min-h-[440px]",
className,
)}
>
<span
aria-hidden="true"
className="pointer-events-none absolute -bottom-16 -left-16 h-48 w-48 rounded-full bg-[#dbe8d6] sm:h-64 sm:w-64"
/>
<span
aria-hidden="true"
className="pointer-events-none absolute -right-16 -top-16 h-48 w-48 rounded-full bg-[#fff0bd] sm:h-64 sm:w-64"
/>
<div className="relative">{content}</div>
</div>
);
}
33 changes: 33 additions & 0 deletions client/src/components/onboarding-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { ReactNode } from "react";

import { fontSans } from "@/lib/fonts";
import { cn } from "@/lib/utils";

export interface OnboardingLayoutProps {
children: ReactNode;
className?: string;
}

/**
* Responsive public-site shell. It deliberately omits the signed-in
* Identify/Explore/Map navigation while sharing the same brand tokens.
*/
export function OnboardingLayout({
children,
className,
}: OnboardingLayoutProps) {
return (
<div
className={cn("min-h-screen bg-[#f7f8f6] font-sans", fontSans.variable)}
>
<div
className={cn(
"mx-auto flex min-h-screen w-full max-w-6xl flex-col bg-background lg:my-6 lg:min-h-[calc(100vh-3rem)] lg:overflow-hidden lg:rounded-2xl lg:border lg:border-border lg:shadow-sm",
className,
)}
>
{children}
</div>
</div>
);
}
48 changes: 48 additions & 0 deletions client/src/components/public-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Link from "next/link";

import { VirensLogo } from "@/components/virens-logo";

export function PublicHeader() {
return (
<header className="border-b border-border bg-background">
<div className="flex h-20 items-center justify-between px-5 sm:px-8 lg:px-10">
<Link href="/welcome" className="flex items-center gap-2.5">
<VirensLogo className="h-10 w-8" labelled />
<span className="text-2xl font-bold tracking-tight text-primary">
virens
</span>
</Link>

<nav
aria-label="Public navigation"
className="flex items-center gap-4 text-sm font-medium sm:gap-7"
>
<Link
href="/"
className="hidden text-muted-foreground transition-colors hover:text-primary sm:inline"
>
About
</Link>
<Link
href="/explore"
className="hidden text-muted-foreground transition-colors hover:text-primary md:inline"
>
Explore
</Link>
<Link
href="/map"
className="hidden text-muted-foreground transition-colors hover:text-primary md:inline"
>
Map
</Link>
<Link
href="/signin"
className="rounded-lg bg-primary px-5 py-2.5 text-primary-foreground transition-colors hover:bg-primary/90"
>
Sign in
</Link>
</nav>
</div>
</header>
);
}
105 changes: 105 additions & 0 deletions client/src/pages/about.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { BookOpen, Camera, Heart, User } from "lucide-react";
import Head from "next/head";
import { useRouter } from "next/router";

import { OnboardingLayout } from "@/components/onboarding-layout";
import { PublicHeader } from "@/components/public-header";
import { Button } from "@/components/ui/button";

const FEATURES = [
{
icon: Camera,
title: "Identify invasive weeds",
description:
"Photograph a plant and get clear identification cues in seconds.",
},
{
icon: BookOpen,
title: "Explore local ecology",
description:
"Learn about nearby bushland, native habitats and priority species.",
},
{
icon: User,
title: "Take action in the field",
description:
"Report sightings, discover events and support restoration teams.",
},
];

export default function AboutPage() {
const router = useRouter();

return (
<>
<Head>
<title>About Virens</title>
</Head>
<OnboardingLayout>
<PublicHeader />
<div className="flex flex-1 flex-col px-6 pb-7 sm:px-10 lg:px-16">
<main className="flex flex-1 flex-col justify-center py-8">
<div className="mx-auto max-w-2xl text-center">
<p className="text-sm font-semibold uppercase tracking-[0.18em] text-primary">
About Virens
</p>
<h1 className="mt-3 text-4xl font-bold leading-tight text-foreground sm:text-5xl">
Better bushland, together
</h1>
<p className="mt-4 text-base leading-7 text-muted-foreground">
Virens helps local communities recognise invasive weeds and care
for the places that matter.
</p>
</div>

<ul className="mt-10 grid gap-4 md:grid-cols-3">
{FEATURES.map(({ icon: Icon, title, description }) => (
<li
key={title}
className="flex items-start gap-4 rounded-xl border border-border bg-muted p-5"
>
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-secondary text-primary">
<Icon className="h-4 w-4" aria-hidden="true" />
</span>
<div className="min-w-0">
<p className="text-sm font-semibold text-foreground">
{title}
</p>
<p className="text-sm text-muted-foreground">
{description}
</p>
</div>
</li>
))}
</ul>

<div className="mt-4 flex items-center gap-3 rounded-xl bg-[#fff1b8] p-4">
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-highlight/60 text-highlight-foreground">
<Heart className="h-4 w-4" aria-hidden="true" />
</span>
<p className="text-sm font-medium text-highlight-foreground">
Built for volunteers, rangers and everyone who cares for
Country.
</p>
</div>
</main>

<footer className="flex flex-col items-center justify-between gap-5 border-t border-border pt-6 text-center md:flex-row md:text-left">
<p className="text-xs text-muted-foreground">
Virens · Version 1.0.0
<br />
Privacy policy · Terms of use · Contact
</p>
<Button
size="lg"
className="h-12 w-full rounded-lg px-9 text-base font-semibold md:w-auto"
onClick={() => router.push("/welcome")}
>
Next
</Button>
</footer>
</div>
</OnboardingLayout>
</>
);
}
37 changes: 1 addition & 36 deletions client/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1 @@
import { Inter as FontSans } from "next/font/google";
import { useState } from "react";

import { usePings } from "@/hooks/pings";
import { cn } from "@/lib/utils";

import { Button } from "../components/ui/button";

const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
});

export default function Home() {
const [clicked, setClicked] = useState(false);
const { data, isLoading } = usePings({
enabled: clicked,
});

return (
<main
className={cn(
"flex min-h-screen flex-col items-center gap-4 p-24 font-sans",
fontSans.variable,
)}
>
<h1 className="text-3xl text-primary">Test title</h1>
<Button onClick={() => setClicked(true)}>
{isLoading ? "Loading" : "Ping"}
</Button>
<p>
Response from server: <span>{data as string}</span>
</p>
</main>
);
}
export { default } from "./about";
Loading
Loading