diff --git a/client/src/components/onboarding-brand-header.tsx b/client/src/components/onboarding-brand-header.tsx
new file mode 100644
index 0000000..ba290bd
--- /dev/null
+++ b/client/src/components/onboarding-brand-header.tsx
@@ -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 = (
+
+
+
+ virens
+
+
+ let the good greens flourish
+
+
+ );
+
+ if (variant === "plain") {
+ return {content}
;
+ }
+
+ return (
+
+ );
+}
diff --git a/client/src/components/onboarding-layout.tsx b/client/src/components/onboarding-layout.tsx
new file mode 100644
index 0000000..131770e
--- /dev/null
+++ b/client/src/components/onboarding-layout.tsx
@@ -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 (
+
+ );
+}
diff --git a/client/src/components/public-header.tsx b/client/src/components/public-header.tsx
new file mode 100644
index 0000000..94b27fb
--- /dev/null
+++ b/client/src/components/public-header.tsx
@@ -0,0 +1,48 @@
+import Link from "next/link";
+
+import { VirensLogo } from "@/components/virens-logo";
+
+export function PublicHeader() {
+ return (
+
+ );
+}
diff --git a/client/src/pages/about.tsx b/client/src/pages/about.tsx
new file mode 100644
index 0000000..4f5f4c8
--- /dev/null
+++ b/client/src/pages/about.tsx
@@ -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 (
+ <>
+
+ About Virens
+
+
+
+
+
+
+
+ About Virens
+
+
+ Better bushland, together
+
+
+ Virens helps local communities recognise invasive weeds and care
+ for the places that matter.
+
+
+
+
+ {FEATURES.map(({ icon: Icon, title, description }) => (
+ -
+
+
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+ ))}
+
+
+
+
+
+
+
+ Built for volunteers, rangers and everyone who cares for
+ Country.
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/client/src/pages/index.tsx b/client/src/pages/index.tsx
index c051281..5c2deb8 100644
--- a/client/src/pages/index.tsx
+++ b/client/src/pages/index.tsx
@@ -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 (
-
- Test title
-
-
- Response from server: {data as string}
-
-
- );
-}
+export { default } from "./about";
diff --git a/client/src/pages/signin.tsx b/client/src/pages/signin.tsx
new file mode 100644
index 0000000..994c32d
--- /dev/null
+++ b/client/src/pages/signin.tsx
@@ -0,0 +1,160 @@
+import { Eye, EyeOff, Lock, Mail } from "lucide-react";
+import Head from "next/head";
+import { useRouter } from "next/router";
+import { type FormEvent, useState } from "react";
+
+import { OnboardingBrandHeader } from "@/components/onboarding-brand-header";
+import { OnboardingLayout } from "@/components/onboarding-layout";
+import { PublicHeader } from "@/components/public-header";
+import { Button } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+
+const INPUT_CLASSES =
+ "h-12 w-full rounded-xl border border-input bg-background py-2 pl-10 pr-3 text-sm text-foreground shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
+
+/**
+ * Frontend-only, clickable sign-in prototype. Performs basic required-field
+ * validation only — no credentials, tokens, cookies or sessions are created
+ * or stored anywhere.
+ */
+export default function SignInPage() {
+ const router = useRouter();
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [showPassword, setShowPassword] = useState(false);
+ const [error, setError] = useState(null);
+
+ function handleSubmit(event: FormEvent) {
+ event.preventDefault();
+
+ if (!email.trim() || !password.trim()) {
+ setError("Email and password are required.");
+ return;
+ }
+
+ setError(null);
+ router.push("/identify");
+ }
+
+ return (
+ <>
+
+ Sign in to Virens
+
+
+
+
+
+
+
+
+
+
+ Member access
+
+
+ Welcome back
+
+
+ Sign in to continue identifying weeds and joining local field
+ events.
+
+
+
+
+
+
+ New to Virens?{" "}
+
+ Create an account
+
+
+
+
+
+ >
+ );
+}
diff --git a/client/src/pages/welcome.tsx b/client/src/pages/welcome.tsx
new file mode 100644
index 0000000..f9bf09f
--- /dev/null
+++ b/client/src/pages/welcome.tsx
@@ -0,0 +1,54 @@
+import Head from "next/head";
+import { useRouter } from "next/router";
+
+import { OnboardingBrandHeader } from "@/components/onboarding-brand-header";
+import { OnboardingLayout } from "@/components/onboarding-layout";
+import { PublicHeader } from "@/components/public-header";
+import { Button } from "@/components/ui/button";
+
+export default function WelcomePage() {
+ const router = useRouter();
+
+ return (
+ <>
+
+ Welcome to Virens
+
+
+
+
+
+
+
+ Western Australia
+
+
+ Protect bushland.
+
+ Grow community.
+
+
+ Virens helps you identify invasive weeds, explore local bushland
+ and join people taking action across Western Australia.
+
+
+
+
+
+ By continuing, you agree to our Terms and Privacy Policy.
+
+
+
+
+
+
+
+ >
+ );
+}