Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add particle background effect for landing page #9

Merged
merged 5 commits into from
Mar 11, 2024
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
7 changes: 0 additions & 7 deletions app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import Link from "next/link";

export default function AuthLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<div>
<nav className="absolute left-0 top-0 w-full p-6 text-center text-white">
<Link href="/">
<span className="text-2xl font-bold">Home</span>
</Link>
</nav>
<main className="flex min-h-screen items-center justify-center bg-slate-900">
{children}
</main>
Expand Down
7 changes: 6 additions & 1 deletion app/(auth)/sign-in/[[...sign-in]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { SignIn } from "@clerk/nextjs";
import { Metadata } from "next";

export const metadata: Metadata = {
title: "Second Brain | Sign in",
};

export default function SignInPage() {
return <SignIn redirectUrl={"/dashboard"} />;
return <SignIn redirectUrl={"/notes"} />;
}
5 changes: 5 additions & 0 deletions app/(auth)/sign-up/[[...sign-up]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { SignUp } from "@clerk/nextjs";
import { Metadata } from "next";

export const metadata: Metadata = {
title: "Second Brain | Sign up",
};

export default function SignUpPage() {
return <SignUp afterSignUpUrl={"/api/sign-up"} />;
Expand Down
181 changes: 181 additions & 0 deletions app/(marketing)/landing-bg-animation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/* eslint-disable react/no-unknown-property */

"use client";

import { Canvas, useFrame } from "@react-three/fiber";
import Link from "next/link";
import { useMemo, useRef, useState } from "react";
import { Mesh } from "three";

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

const PARTICLE_COUNT = 1000;

const SPEED_FAST = 20;
const SPEED_NORMAL = 1;

function lerp(start: number, end: number, t: number) {
return start * (1 - t) + end * t;
}

function colorPicker(randomizer: number) {
switch (true) {
case randomizer < 0.1:
return "lightblue";
case randomizer < 0.2:
return "lightgreen";
case randomizer < 0.3:
return "lightpink";
case randomizer < 0.4:
return "lightyellow";
case randomizer < 0.5:
return "lightcoral";
case randomizer < 0.6:
return "lightseagreen";
case randomizer < 0.7:
return "lightcyan";
case randomizer < 0.8:
return "lightgoldenrodyellow";
case randomizer < 0.9:
return "lightgray";
default:
return "lightgrey";
}
}

function generateParticles() {
const particles = new Array(PARTICLE_COUNT).fill(null).map(() => {
const x = (Math.random() - 0.5) * 10;
const y = (Math.random() - 0.5) * 10;
const z = (Math.random() - 0.5) * 20;
const color = colorPicker(Math.random());

return { x, y, z, color };
});

return particles;
}

function Particle({
speed,
particleData,
}: {
speed: number;
particleData: ReturnType<typeof generateParticles>[0];
}) {
const particleRef = useRef<Mesh>(null);

const { x, y, z, color } = particleData;

useFrame(() => {
if (!particleRef.current) return;

particleRef.current.position.z += 0.01 * speed;

if (particleRef.current.position.z > 10) {
particleRef.current.position.x = (Math.random() - 0.5) * 10;
particleRef.current.position.y = (Math.random() - 0.5) * 10;
particleRef.current.position.z = Math.min((Math.random() - 0.5) * 10, -5);
}
});

return (
<mesh ref={particleRef} position={[x, y, z]}>
<sphereGeometry args={[0.02, 10, 10]} />
<meshStandardMaterial color={color} />
</mesh>
);
}

function Scene({ buttonHovered }: { buttonHovered: boolean }) {
const speedRef = useRef(SPEED_NORMAL);

const particles = useMemo(() => generateParticles(), []);

useFrame(() => {
if (buttonHovered) {
speedRef.current = lerp(speedRef.current, SPEED_NORMAL, 0.1);
} else {
speedRef.current = lerp(speedRef.current, SPEED_FAST, 0.1);
}
});

return (
<>
{particles.map((particleData, index) => (
<Particle
key={index}
speed={speedRef.current}
particleData={particleData}
/>
))}
</>
);
}

export default function LandingBgAnimation({
isSignedIn,
}: {
isSignedIn: boolean;
}) {
const [buttonHovered, setButtonHovered] = useState(false);

function handleMouseEnter() {
setButtonHovered(true);
}

function handleMouseLeave() {
setButtonHovered(false);
}

const mouseEvents = {
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
};

return (
<div className="flex h-full w-full items-center justify-center">
<div className="absolute inset-0 -z-10">
<Canvas>
<color attach="background" args={["#020617"]} />
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Scene buttonHovered={buttonHovered} />
</Canvas>
</div>
<div className="flex flex-col gap-4 p-6 md:items-center md:text-center">
<h2 className="text-white">
Welcome to your
<br /> Second Brain
</h2>
{!isSignedIn && (
<div className="max-w-lg text-white">
<p>
Second Brain is your companion for organizing your thoughts and
ideas. Collect and visualize your knowledge in one place.
</p>
<p>Sign in or sign up in order to explore the app.</p>
</div>
)}
<div className="flex gap-2">
{isSignedIn ? (
<Link href="/notes">
<Button {...mouseEvents}>To notes</Button>
</Link>
) : (
<>
<Link href="/sign-in">
<Button {...mouseEvents}>Sign in</Button>
</Link>
<Link href="/sign-up">
<Button variant="outline" {...mouseEvents}>
Sign up
</Button>
</Link>
</>
)}
</div>
</div>
</div>
);
}
52 changes: 25 additions & 27 deletions app/(marketing)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,40 @@
import { auth } from "@clerk/nextjs";
import { Metadata } from "next";
import Link from "next/link";

import { Button } from "@/components/ui/button";
import LandingBgAnimation from "./landing-bg-animation";

export const metadata: Metadata = {
title: "Second Brain Showcase Project",
title: "Second Brain | Home",
description: "This is a showcase portfolio project. Made by @berci.dev",
};

export default function Home() {
const { userId } = auth();

return (
<main className="flex min-h-screen items-center justify-center">
<div className="flex flex-col gap-4 p-6 md:items-center md:text-center">
<h2>
Welcome to the
<br /> CF Showcase
</h2>
<p>Sign in or sign up in order to explore the app.</p>
<div className="flex gap-2">
{userId ? (
<Link href="/dashboard">
<Button>To dashboard</Button>
</Link>
) : (
<>
<Link href="/sign-in">
<Button>Sign in</Button>
</Link>
<Link href="/sign-up">
<Button variant="outline">Sign up</Button>
</Link>
</>
)}
</div>
</div>
<main className="relative flex min-h-screen items-center justify-center">
<LandingBgAnimation isSignedIn={!!userId} />
<p className="absolute bottom-4 left-1/2 -translate-x-1/2 text-center text-sm text-slate-400">
This is a showcase portfolio project, inspired by{" "}
<a
href="https://reflect.app"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
Reflect
</a>
<br />
Made by{" "}
<a
href="https://berci.dev"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
@berci.dev
</a>
</p>
</main>
);
}
18 changes: 0 additions & 18 deletions app/(platform)/dashboard/page.tsx

This file was deleted.

18 changes: 4 additions & 14 deletions app/(platform)/navItems.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
import {
CardStackIcon,
GridIcon,
HomeIcon,
Pencil2Icon,
} from "@radix-ui/react-icons";
import { CardStackIcon, GridIcon, Pencil2Icon } from "@radix-ui/react-icons";

const navItems = [
{
label: "Dashboard",
href: "/dashboard",
icon: HomeIcon,
label: "All notes",
href: "/notes",
icon: CardStackIcon,
},
{
label: "New note",
href: "/notes/new",
icon: Pencil2Icon,
},
{
label: "All notes",
href: "/notes",
icon: CardStackIcon,
},
{
label: "Map",
href: "/map",
Expand Down
15 changes: 12 additions & 3 deletions app/(platform)/search-bar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
import { MagnifyingGlassIcon, TrashIcon } from "@radix-ui/react-icons";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
Expand All @@ -21,6 +21,15 @@ import {

import navItems from "./navItems";

const commandItems: typeof navItems = [
...navItems,
{
href: "/notes/trash",
icon: TrashIcon,
label: "View trash bin",
},
];

export default function SearchBar() {
const [showSearchCommand, setShowSearchCommand] = useState(false);

Expand All @@ -46,7 +55,7 @@ export default function SearchBar() {
>
<div className="flex items-center gap-3">
<MagnifyingGlassIcon />
<span>Search</span>
<span>Commands</span>
</div>
<span>⌘K</span>
</Button>
Expand All @@ -58,7 +67,7 @@ export default function SearchBar() {
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
{navItems.map((item) => (
{commandItems.map((item) => (
<Link href={item.href} key={item.href}>
<CommandItem
onSelect={() => {
Expand Down
2 changes: 1 addition & 1 deletion app/api/sign-up/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,5 @@ export async function GET() {
return new NextResponse("Error creating user", { status: 500 });
}

redirect("/dashboard");
redirect("/notes");
}
Loading
Loading