Improve mobile responsiveness
All checks were successful
Deploy Site / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m33s
All checks were successful
Deploy Site / docker (ubuntu-latest, 2.44.0) (push) Successful in 1m33s
This commit is contained in:
parent
d6ad12a6e2
commit
1b4e893bfa
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4
|
||||||
|
}
|
@ -2,6 +2,6 @@
|
|||||||
My personal portfolio website hosted [here](https://rainnny.club)
|
My personal portfolio website hosted [here](https://rainnny.club)
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
- [ ] Mobile Responsiveness
|
- [x] Mobile Responsiveness
|
||||||
- [ ] Discord Integration (Status, Activity, etc)
|
- [ ] Discord Integration (Status, Activity, etc)
|
||||||
- [ ] Add Configuration
|
- [ ] Add Configuration
|
@ -22,6 +22,7 @@
|
|||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"sharp": "^0.33.5",
|
||||||
"tailwind-merge": "^2.5.2",
|
"tailwind-merge": "^2.5.2",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
|
@ -11,33 +11,33 @@ const inter = Inter({ subsets: ["latin"] });
|
|||||||
* The metadata for this app.
|
* The metadata for this app.
|
||||||
*/
|
*/
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "RainnnyCLUB",
|
title: "RainnnyCLUB",
|
||||||
description:
|
description:
|
||||||
"My name is Braydon and I am a self-taught software engineer living in Canada.",
|
"My name is Braydon and I am a self-taught software engineer living in Canada.",
|
||||||
};
|
};
|
||||||
export const viewport: Viewport = {
|
export const viewport: Viewport = {
|
||||||
themeColor: "#5555FF",
|
themeColor: "#5555FF",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The primary layout for this app.
|
* The primary layout for this app.
|
||||||
*/
|
*/
|
||||||
const RootLayout = ({
|
const RootLayout = ({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>): ReactElement => (
|
}>): ReactElement => (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute="class"
|
attribute="class"
|
||||||
defaultTheme="dark"
|
defaultTheme="dark"
|
||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<TooltipProvider>{children}</TooltipProvider>
|
<TooltipProvider>{children}</TooltipProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
export default RootLayout;
|
export default RootLayout;
|
||||||
|
@ -3,17 +3,17 @@ import Navbar from "@/components/landing/navbar";
|
|||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
const LandingPage = (): ReactElement => (
|
const LandingPage = (): ReactElement => (
|
||||||
<main
|
<main
|
||||||
className="h-screen flex flex-col"
|
className="h-screen flex flex-col"
|
||||||
style={{
|
style={{
|
||||||
background:
|
background:
|
||||||
"linear-gradient(to top, hsla(240, 8%, 8%, 0.5), hsl(var(--background)))",
|
"linear-gradient(to top, hsla(240, 8%, 8%, 0.5), hsl(var(--background)))",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<div className="h-full flex flex-col justify-center">
|
<div className="px-5 xs:px-7 h-full flex flex-col justify-center">
|
||||||
<Greeting />
|
<Greeting />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
export default LandingPage;
|
export default LandingPage;
|
||||||
|
@ -3,69 +3,70 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 100%;
|
||||||
--foreground: 240 10% 3.9%;
|
--foreground: 240 10% 3.9%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 240 10% 3.9%;
|
--card-foreground: 240 10% 3.9%;
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 240 10% 3.9%;
|
--popover-foreground: 240 10% 3.9%;
|
||||||
--primary: 240 5.9% 10%;
|
--primary: 240 5.9% 10%;
|
||||||
--primary-foreground: 0 0% 98%;
|
--primary-foreground: 0 0% 98%;
|
||||||
--secondary: 240 4.8% 95.9%;
|
--secondary: 240 4.8% 95.9%;
|
||||||
--secondary-foreground: 240 5.9% 10%;
|
--secondary-foreground: 240 5.9% 10%;
|
||||||
--muted: 240 4.8% 95.9%;
|
--muted: 240 4.8% 95.9%;
|
||||||
--muted-foreground: 240 3.8% 46.1%;
|
--muted-foreground: 240 3.8% 46.1%;
|
||||||
--accent: 240 4.8% 95.9%;
|
--accent: 240 4.8% 95.9%;
|
||||||
--accent-foreground: 240 5.9% 10%;
|
--accent-foreground: 240 5.9% 10%;
|
||||||
--destructive: 0 72.22% 50.59%;
|
--destructive: 0 72.22% 50.59%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 240 5.9% 90%;
|
--border: 240 5.9% 90%;
|
||||||
--input: 240 5.9% 90%;
|
--input: 240 5.9% 90%;
|
||||||
--ring: 240 5% 64.9%;
|
--ring: 240 5% 64.9%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
|
||||||
--chart-1: 12 76% 61%;
|
--chart-1: 12 76% 61%;
|
||||||
--chart-2: 173 58% 39%;
|
--chart-2: 173 58% 39%;
|
||||||
--chart-3: 197 37% 24%;
|
--chart-3: 197 37% 24%;
|
||||||
--chart-4: 43 74% 66%;
|
--chart-4: 43 74% 66%;
|
||||||
--chart-5: 27 87% 67%;
|
--chart-5: 27 87% 67%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 0 0% 0%;
|
--background: 0 0% 0%;
|
||||||
--foreground: 0 0% 98%;
|
--foreground: 0 0% 98%;
|
||||||
--card: 240 10% 3.9%;
|
--card: 240 10% 3.9%;
|
||||||
--card-foreground: 0 0% 98%;
|
--card-foreground: 0 0% 98%;
|
||||||
--popover: 240 10% 3.9%;
|
--popover: 240 10% 3.9%;
|
||||||
--popover-foreground: 0 0% 98%;
|
--popover-foreground: 0 0% 98%;
|
||||||
--primary: 0 0% 98%;
|
--primary: 0 0% 98%;
|
||||||
--primary-foreground: 240 5.9% 10%;
|
--primary-foreground: 240 5.9% 10%;
|
||||||
--secondary: 240 3.7% 15.9%;
|
--secondary: 240 3.7% 15.9%;
|
||||||
--secondary-foreground: 0 0% 98%;
|
--secondary-foreground: 0 0% 98%;
|
||||||
--muted: 240 3.7% 15.9%;
|
--muted: 240 3.7% 15.9%;
|
||||||
--muted-foreground: 240 5% 64.9%;
|
--muted-foreground: 240 5% 64.9%;
|
||||||
--accent: 240 3.7% 15.9%;
|
--accent: 240 3.7% 15.9%;
|
||||||
--accent-foreground: 0 0% 98%;
|
--accent-foreground: 0 0% 98%;
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 85.7% 97.3%;
|
--destructive-foreground: 0 85.7% 97.3%;
|
||||||
--border: 240 3.7% 15.9%;
|
--border: 240 3.7% 15.9%;
|
||||||
--input: 240 3.7% 15.9%;
|
--input: 240 3.7% 15.9%;
|
||||||
--ring: 240 4.9% 83.9%;
|
--ring: 240 4.9% 83.9%;
|
||||||
|
|
||||||
--chart-1: 220 70% 50%;
|
--chart-1: 220 70% 50%;
|
||||||
--chart-2: 160 60% 45%;
|
--chart-2: 160 60% 45%;
|
||||||
--chart-3: 30 80% 55%;
|
--chart-3: 30 80% 55%;
|
||||||
--chart-4: 280 65% 60%;
|
--chart-4: 280 65% 60%;
|
||||||
--chart-5: 340 75% 55%;
|
--chart-5: 340 75% 55%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
body {
|
||||||
}
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,59 +9,59 @@ import { FlipWords } from "@/components/ui/flip-words";
|
|||||||
import Navigation from "./navigation";
|
import Navigation from "./navigation";
|
||||||
|
|
||||||
const Greeting = (): ReactElement => {
|
const Greeting = (): ReactElement => {
|
||||||
const now: Moment = moment(Date.now());
|
const now: Moment = moment(Date.now());
|
||||||
return (
|
return (
|
||||||
<section className="flex flex-col gap-5 items-center">
|
<section className="flex flex-col gap-5 items-center">
|
||||||
<BlurFade delay={0.3} inView>
|
<BlurFade delay={0.3} inView>
|
||||||
<Image
|
<Image
|
||||||
className="shadow-2xl shadow-blue-500 rounded-full select-none pointer-events-none"
|
className="shadow-2xl shadow-blue-500 rounded-full scale-90 sm:scale-100 select-none pointer-events-none transition-all transform-gpu"
|
||||||
src="/me.png"
|
src="/me.png"
|
||||||
alt="My Selfie (:"
|
alt="My Selfie (:"
|
||||||
width={174}
|
width={174}
|
||||||
height={174}
|
height={174}
|
||||||
/>
|
/>
|
||||||
</BlurFade>
|
</BlurFade>
|
||||||
|
|
||||||
<BlurFade delay={0.6} inView>
|
<BlurFade delay={0.6} inView>
|
||||||
<h1
|
<h1
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex gap-2 justify-center items-center text-4xl font-bold select-none pointer-events-none",
|
"flex gap-2 justify-center items-center text-3xl sm:text-4xl font-bold select-none pointer-events-none transition-all transform-gpu",
|
||||||
"text-black dark:text-transparent bg-clip-text bg-gradient-to-br from-zinc-300/60 to-white",
|
"text-black dark:text-transparent bg-clip-text bg-gradient-to-br from-zinc-300/60 to-white"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Hello, I'm
|
Hello, I'm
|
||||||
<span className="text-blue-600 dark:text-transparent bg-clip-text bg-gradient-to-br from-blue-600 to-blue-300">
|
<span className="text-blue-600 dark:text-transparent bg-clip-text bg-gradient-to-br from-blue-600 to-blue-300">
|
||||||
Braydon
|
Braydon
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<Image
|
<Image
|
||||||
src="/waving-hand.gif"
|
src="/waving-hand.gif"
|
||||||
alt="Waving Hand"
|
alt="Waving Hand"
|
||||||
width={32}
|
width={32}
|
||||||
height={32}
|
height={32}
|
||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
</BlurFade>
|
</BlurFade>
|
||||||
|
|
||||||
<BlurFade delay={0.9} inView>
|
<BlurFade delay={0.9} inView>
|
||||||
<FlipWords
|
<FlipWords
|
||||||
className={cn(
|
className={cn(
|
||||||
"-mt-3 p-0 max-w-[23rem] text-center select-none pointer-events-none",
|
"-mt-3 p-0 max-w-[23rem] text-sm sm:text-base text-center select-none pointer-events-none transition-all transform-gpu",
|
||||||
"text-black dark:!text-transparent bg-clip-text bg-gradient-to-br from-zinc-300/85 to-white",
|
"text-black dark:!text-transparent bg-clip-text bg-gradient-to-br from-zinc-300/85 to-white"
|
||||||
)}
|
)}
|
||||||
words={[
|
words={[
|
||||||
`A ${now.diff(moment([2002, 10, 13]), "years")} year old${" "}
|
`A ${now.diff(moment([2002, 10, 13]), "years")} year old${" "}
|
||||||
passionate software engineer living in Canada with ${moment([2016, 8, 1]).fromNow(true)} of experience!`,
|
passionate software engineer living in Canada with ${moment([2016, 8, 1]).fromNow(true)} of experience!`,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</BlurFade>
|
</BlurFade>
|
||||||
|
|
||||||
<BlurFade className="mt-3.5" delay={1.25} inView>
|
<BlurFade className="mt-3.5" delay={1.25} inView>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
</BlurFade>
|
</BlurFade>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Greeting;
|
export default Greeting;
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
const HomelabContent = (): ReactElement => (
|
const HomelabContent = (): ReactElement => (
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<b>Server Rack:</b> 22U, 32" Depth
|
<b>Server Rack:</b> 22U, 32" Depth
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<b>Router:</b> UDM Pro
|
<b>Router:</b> UDM Pro
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<b>UPS:</b> 1350VA
|
<b>UPS:</b> 1350VA
|
||||||
</li>
|
</li>
|
||||||
<li className="my-2.5" />
|
<li className="my-2.5" />
|
||||||
<li>
|
<li>
|
||||||
<b>Proxmox Node-01:</b>
|
<b>Proxmox Node-01:</b>
|
||||||
<li>
|
<li>
|
||||||
- <b>Motherboard:</b> Prime B550-PLUS
|
- <b>Motherboard:</b> Prime B550-PLUS
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
- <b>CPU:</b> Ryzen 5 5600G
|
- <b>CPU:</b> Ryzen 5 5600G
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
- <b>RAM:</b> 38GB of DDR4 @ 3200Mhz
|
- <b>RAM:</b> 38GB of DDR4 @ 3200Mhz
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
- <b>Storage:</b> 8TB (x2 4TB, x1 4TB Parity) Unraid Array
|
- <b>Storage:</b> 8TB (x2 4TB, x1 4TB Parity) Unraid Array
|
||||||
</li>
|
</li>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
export default HomelabContent;
|
export default HomelabContent;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { MagicCard } from "@/components/ui/magic-card";
|
import { MagicCard } from "@/components/ui/magic-card";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import moment, { Moment } from "moment";
|
import moment, { Moment } from "moment";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { UseThemeProps } from "next-themes/dist/types";
|
import { UseThemeProps } from "next-themes/dist/types";
|
||||||
@ -9,102 +8,122 @@ import Link from "next/link";
|
|||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
|
|
||||||
type Project = {
|
type Project = {
|
||||||
name: string;
|
name: string;
|
||||||
link?: string | undefined;
|
link?: string | undefined;
|
||||||
previewContent: ReactElement;
|
previewContent: ReactElement;
|
||||||
startDate: Moment;
|
startDate: Moment;
|
||||||
endDate?: Moment | undefined;
|
endDate?: Moment | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const projects: Project[] = [
|
const projects: Project[] = [
|
||||||
{
|
{
|
||||||
name: "This Website!",
|
name: "This Website!",
|
||||||
link: "https://github.com/Rainnny7/rainnny.club",
|
link: "https://github.com/Rainnny7/rainnny.club",
|
||||||
previewContent: <div>This website!</div>,
|
previewContent: <div>This website!</div>,
|
||||||
startDate: moment([2024, 7, 29]),
|
startDate: moment([2024, 7, 29]),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "WildNetwork",
|
name: "WildNetwork",
|
||||||
link: "https://discord.gg/WildPrison",
|
link: "https://discord.gg/WildPrison",
|
||||||
previewContent: (
|
previewContent: (
|
||||||
<p>
|
<p>
|
||||||
WildNetwork is a Minecraft server that contains multiple gamemodes, one
|
WildNetwork is a Minecraft server that contains multiple
|
||||||
of which is Prison, which is the most popular. I first joined the server
|
gamemodes, one of which is Prison, which is the most popular. I
|
||||||
as a Developer where I would work behind the scenes to create new
|
first joined the server as a Developer where I would work behind
|
||||||
features, now I'm currently working as a System Administrator.
|
the scenes to create new features, now I'm currently
|
||||||
</p>
|
working as a System Administrator.
|
||||||
),
|
</p>
|
||||||
startDate: moment([2020, 7, 1]),
|
),
|
||||||
},
|
startDate: moment([2020, 7, 1]),
|
||||||
{
|
},
|
||||||
name: "Lucity",
|
{
|
||||||
link: "https://youtube.com/@iamLucid",
|
name: "Bonfire",
|
||||||
previewContent: (
|
link: "https://bonfire.wtf",
|
||||||
<p>
|
previewContent: (
|
||||||
Lucity was a minigame network for the game Minecraft, and was owned by
|
<p>
|
||||||
the YouTuber iamLucid. When I worked at Lucity, I was the development
|
Bonfire is a platform similar to Discord that a friend and I are
|
||||||
lead, I focused mainly on infrastructure, databases, and monitoring
|
working on together in our free time. Bonfire is perfect for
|
||||||
systems. A few things that I have made - a dynamically managed server
|
connecting with friends or building a global community.
|
||||||
system, proxy rotation via the TCPShield API, and an API that can
|
Personalize your space to chat, and hang out.
|
||||||
interact with the entire network from a normal Java app.
|
</p>
|
||||||
</p>
|
),
|
||||||
),
|
startDate: moment([2024, 3, 30]),
|
||||||
startDate: moment([2020, 7, 1]),
|
},
|
||||||
endDate: moment([2022, 10, 30]),
|
{
|
||||||
},
|
name: "Lucity",
|
||||||
{
|
link: "https://youtube.com/@iamLucid",
|
||||||
name: "Rainplex",
|
previewContent: (
|
||||||
previewContent: (
|
<p>
|
||||||
<p>
|
Lucity was a minigame network for the game Minecraft, and was
|
||||||
Rainplex was a remake of the once popular Minecraft server, Mineplex.
|
owned by the YouTuber iamLucid. When I worked at Lucity, I was
|
||||||
Rainplex initially came to light using the plugin, Skript where it just
|
the development lead, I focused mainly on infrastructure,
|
||||||
contained a Hub. After some time, the entirety of the network was
|
databases, and monitoring systems. A few things that I have made
|
||||||
re-coded in the Java programming from the ground up. Rainplex went
|
- a dynamically managed server system, proxy rotation via the
|
||||||
through numerous re-codes over the time it was active, however I have
|
TCPShield API, and an API that can interact with the entire
|
||||||
since abandoned development due to lack of free time.
|
network from a normal Java app.
|
||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
startDate: moment([2018, 8, 1]),
|
startDate: moment([2020, 7, 1]),
|
||||||
endDate: moment([2021, 6, 11]),
|
endDate: moment([2022, 10, 30]),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Arcane Client",
|
name: "Rainplex",
|
||||||
link: "https://github.com/ArcaneClientNET",
|
previewContent: (
|
||||||
previewContent: (
|
<p>
|
||||||
<p>
|
Rainplex was a remake of the once popular Minecraft server,
|
||||||
Arcane is the all-in-one Minecraft mod pack. This client was built to be
|
Mineplex. Rainplex initially came to light using the plugin,
|
||||||
similar to LunarClient for portfolio and experience sake. I have since
|
Skript where it just contained a Hub. After some time, the
|
||||||
abandoned development due to lack of free time.
|
entirety of the network was re-coded in the Java programming
|
||||||
</p>
|
from the ground up. Rainplex went through numerous re-codes over
|
||||||
),
|
the time it was active, however I have since abandoned
|
||||||
startDate: moment([2021, 6, 1]),
|
development due to lack of free time.
|
||||||
endDate: moment([2021, 10, 1]),
|
</p>
|
||||||
},
|
),
|
||||||
|
startDate: moment([2018, 8, 1]),
|
||||||
|
endDate: moment([2021, 6, 11]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Arcane Client",
|
||||||
|
link: "https://github.com/ArcaneClientNET",
|
||||||
|
previewContent: (
|
||||||
|
<p>
|
||||||
|
Arcane is the all-in-one Minecraft mod pack. This client was
|
||||||
|
built to be similar to LunarClient for portfolio and experience
|
||||||
|
sake. I have since abandoned development due to lack of free
|
||||||
|
time.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
startDate: moment([2021, 6, 1]),
|
||||||
|
endDate: moment([2021, 10, 1]),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const MyWork = (): ReactElement => {
|
const MyWork = (): ReactElement => {
|
||||||
const { theme }: UseThemeProps = useTheme();
|
const { theme }: UseThemeProps = useTheme();
|
||||||
return (
|
return (
|
||||||
<div className="max-w-[50rem] flex flex-wrap gap-3 justify-center">
|
<div className="max-w-[50rem] flex flex-wrap gap-3 justify-center">
|
||||||
{projects.map((project, index) => (
|
{projects.map((project, index) => (
|
||||||
<Link key={index} href={project.link || "#"} target="_blank">
|
<Link key={index} href={project.link || "#"} target="_blank">
|
||||||
<MagicCard
|
<MagicCard
|
||||||
className="w-[15rem] p-3.5"
|
className="w-[15rem] p-3.5"
|
||||||
gradientColor={theme === "dark" ? "#262626" : "#D9D9D955"}
|
gradientColor={
|
||||||
>
|
theme === "dark" ? "#262626" : "#D9D9D955"
|
||||||
<h1 className="font-bold select-none pointer-events-none">
|
}
|
||||||
{project.name}
|
>
|
||||||
</h1>
|
<h1 className="font-bold select-none pointer-events-none">
|
||||||
|
{project.name}
|
||||||
|
</h1>
|
||||||
|
|
||||||
{/* Years Active */}
|
{/* Years Active */}
|
||||||
<h2>
|
<h2>
|
||||||
{project.startDate.format("MMM YYYY")}
|
{project.startDate.format("MMM YYYY")}
|
||||||
{project.endDate && ` - ${project.endDate.format("MMM YYYY")}`}
|
{project.endDate &&
|
||||||
</h2>
|
` - ${project.endDate.format("MMM YYYY")}`}
|
||||||
</MagicCard>
|
</h2>
|
||||||
</Link>
|
</MagicCard>
|
||||||
))}
|
</Link>
|
||||||
</div>
|
))}
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export default MyWork;
|
export default MyWork;
|
||||||
|
@ -1,145 +1,145 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "@/components/ui/tooltip";
|
|
||||||
import SimpleTooltip from "@/components/ui/simple-tooltip";
|
import SimpleTooltip from "@/components/ui/simple-tooltip";
|
||||||
|
|
||||||
type Skill = {
|
type Skill = {
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
link: string;
|
link: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const skillset: Skill[] = [
|
const skillset: Skill[] = [
|
||||||
// Languages
|
// Languages
|
||||||
{
|
{
|
||||||
name: "Java",
|
name: "Java",
|
||||||
icon: "https://img.icons8.com/color/2x/java-coffee-cup-logo.png",
|
icon: "https://img.icons8.com/color/2x/java-coffee-cup-logo.png",
|
||||||
link: "https://www.java.com",
|
link: "https://www.java.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "JavaScript",
|
name: "JavaScript",
|
||||||
icon: "https://img.icons8.com/fluent/2x/javascript.png",
|
icon: "https://img.icons8.com/fluent/2x/javascript.png",
|
||||||
link: "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
|
link: "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "CSS",
|
name: "CSS",
|
||||||
icon: "https://img.icons8.com/fluent/2x/css3.png",
|
icon: "https://img.icons8.com/fluent/2x/css3.png",
|
||||||
link: "https://www.w3schools.com/css",
|
link: "https://www.w3schools.com/css",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Operating Systems
|
// Operating Systems
|
||||||
{
|
{
|
||||||
name: "Linux",
|
name: "Linux",
|
||||||
icon: "https://img.icons8.com/color/2x/linux.png",
|
icon: "https://img.icons8.com/color/2x/linux.png",
|
||||||
link: "https://www.linux.org",
|
link: "https://www.linux.org",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Bash",
|
name: "Bash",
|
||||||
icon: "https://img.icons8.com/color/2x/bash.png",
|
icon: "https://img.icons8.com/color/2x/bash.png",
|
||||||
link: "https://www.gnu.org/software/bash",
|
link: "https://www.gnu.org/software/bash",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Databases
|
// Databases
|
||||||
{
|
{
|
||||||
name: "MariaDB",
|
name: "MariaDB",
|
||||||
icon: "https://img.icons8.com/fluent/2x/maria-db.png",
|
icon: "https://img.icons8.com/fluent/2x/maria-db.png",
|
||||||
link: "https://mariadb.org",
|
link: "https://mariadb.org",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MongoDB",
|
name: "MongoDB",
|
||||||
icon: "https://img.icons8.com/color/2x/mongodb.png",
|
icon: "https://img.icons8.com/color/2x/mongodb.png",
|
||||||
link: "https://www.mongodb.com",
|
link: "https://www.mongodb.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Redis",
|
name: "Redis",
|
||||||
icon: "https://img.icons8.com/color/2x/redis.png",
|
icon: "https://img.icons8.com/color/2x/redis.png",
|
||||||
link: "https://redis.io",
|
link: "https://redis.io",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Software
|
// Software
|
||||||
{
|
{
|
||||||
name: "Git",
|
name: "Git",
|
||||||
icon: "https://img.icons8.com/color/2x/git.png",
|
icon: "https://img.icons8.com/color/2x/git.png",
|
||||||
link: "https://git-scm.com",
|
link: "https://git-scm.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Docker",
|
name: "Docker",
|
||||||
icon: "https://img.icons8.com/fluent/2x/docker.png",
|
icon: "https://img.icons8.com/fluent/2x/docker.png",
|
||||||
link: "https://www.docker.com",
|
link: "https://www.docker.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Jenkins",
|
name: "Jenkins",
|
||||||
icon: "https://img.icons8.com/color/2x/jenkins.png",
|
icon: "https://img.icons8.com/color/2x/jenkins.png",
|
||||||
link: "https://www.jenkins.io",
|
link: "https://www.jenkins.io",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Figma",
|
name: "Figma",
|
||||||
icon: "https://img.icons8.com/fluent/2x/figma.png",
|
icon: "https://img.icons8.com/fluent/2x/figma.png",
|
||||||
link: "https://www.figma.com",
|
link: "https://www.figma.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Postman",
|
name: "Postman",
|
||||||
icon: "https://img.icons8.com/dusk/2x/postman-api.png",
|
icon: "https://img.icons8.com/dusk/2x/postman-api.png",
|
||||||
link: "https://www.postman.com",
|
link: "https://www.postman.com",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Frameworks & Libraries
|
// Frameworks & Libraries
|
||||||
{
|
{
|
||||||
name: "Maven",
|
name: "Maven",
|
||||||
icon: "/maven.png",
|
icon: "/maven.png",
|
||||||
link: "https://maven.apache.org",
|
link: "https://maven.apache.org",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "NPM",
|
name: "NPM",
|
||||||
icon: "https://img.icons8.com/color/2x/npm.png",
|
icon: "https://img.icons8.com/color/2x/npm.png",
|
||||||
link: "https://www.npmjs.com",
|
link: "https://www.npmjs.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "React",
|
name: "React",
|
||||||
icon: "https://img.icons8.com/dusk/2x/react.png",
|
icon: "https://img.icons8.com/dusk/2x/react.png",
|
||||||
link: "https://reactjs.org/",
|
link: "https://reactjs.org/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "NextJS",
|
name: "NextJS",
|
||||||
icon: "https://img.icons8.com/color/2x/nextjs.png",
|
icon: "https://img.icons8.com/color/2x/nextjs.png",
|
||||||
link: "https://nextjs.org/",
|
link: "https://nextjs.org/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "TailwindCSS",
|
name: "TailwindCSS",
|
||||||
icon: "https://img.icons8.com/color/2x/tailwindcss.png",
|
icon: "https://img.icons8.com/color/2x/tailwindcss.png",
|
||||||
link: "https://tailwindcss.com",
|
link: "https://tailwindcss.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Redux",
|
name: "Redux",
|
||||||
icon: "https://img.icons8.com/color/2x/redux.png",
|
icon: "https://img.icons8.com/color/2x/redux.png",
|
||||||
link: "https://redux.js.org",
|
link: "https://redux.js.org",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Nginx",
|
name: "Nginx",
|
||||||
icon: "https://img.icons8.com/color/2x/nginx.png",
|
icon: "https://img.icons8.com/color/2x/nginx.png",
|
||||||
link: "https://www.nginx.com",
|
link: "https://www.nginx.com",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Skills = (): ReactElement => (
|
const Skills = (): ReactElement => (
|
||||||
<div className="max-w-[30rem] flex flex-wrap gap-3 justify-center">
|
<div className="max-w-[30rem] flex flex-wrap gap-3 justify-center">
|
||||||
{skillset.map((skill, index) => (
|
{skillset.map((skill, index) => (
|
||||||
<Link key={index} className="cursor-default" href={skill.link}>
|
<Link
|
||||||
<SimpleTooltip content={skill.name}>
|
key={index}
|
||||||
<Image
|
className="cursor-default hover:opacity-75 transition-all transform-gpu"
|
||||||
src={skill.icon}
|
href={skill.link}
|
||||||
alt={`${skill.name} Skill Logo`}
|
target="_blank"
|
||||||
width={36}
|
>
|
||||||
height={36}
|
<SimpleTooltip content={skill.name}>
|
||||||
/>
|
<Image
|
||||||
</SimpleTooltip>
|
src={skill.icon}
|
||||||
</Link>
|
alt={`${skill.name} Skill Logo`}
|
||||||
))}
|
width={36}
|
||||||
</div>
|
height={36}
|
||||||
|
/>
|
||||||
|
</SimpleTooltip>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
export default Skills;
|
export default Skills;
|
||||||
|
@ -2,114 +2,115 @@ import Image from "next/image";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import {
|
import {
|
||||||
NavigationMenu,
|
NavigationMenu,
|
||||||
NavigationMenuContent,
|
NavigationMenuContent,
|
||||||
NavigationMenuIndicator,
|
NavigationMenuItem,
|
||||||
NavigationMenuItem,
|
NavigationMenuLink,
|
||||||
NavigationMenuLink,
|
NavigationMenuList,
|
||||||
NavigationMenuList,
|
NavigationMenuTrigger,
|
||||||
NavigationMenuTrigger,
|
navigationMenuTriggerStyle,
|
||||||
NavigationMenuViewport,
|
|
||||||
} from "@/components/ui/navigation-menu";
|
} from "@/components/ui/navigation-menu";
|
||||||
import { navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
|
|
||||||
import { HeartIcon } from "@heroicons/react/24/solid";
|
import { HeartIcon } from "@heroicons/react/24/solid";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import ThemeSwitcher from "./theme-switcher";
|
import ThemeSwitcher from "./theme-switcher";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { BookOpenIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
import { SignalIcon } from "@heroicons/react/24/outline";
|
BookOpenIcon,
|
||||||
|
CodeBracketIcon,
|
||||||
|
SignalIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
import BlurFade from "@/components/ui/blur-fade";
|
import BlurFade from "@/components/ui/blur-fade";
|
||||||
import { CodeBracketIcon } from "@heroicons/react/24/outline";
|
|
||||||
|
|
||||||
const Navbar = (): ReactElement => (
|
const Navbar = (): ReactElement => (
|
||||||
<BlurFade className="pt-1" delay={1.35} inView>
|
<BlurFade className="pt-1" delay={1.35} inView>
|
||||||
<nav className="py-4 flex gap-14 justify-center items-center border-b">
|
<nav className="px-3 xs:px-7 py-4 flex gap-3 xs:gap-10 sm:gap-14 justify-center items-center border-b transition-all transform-gpu">
|
||||||
<Branding />
|
<Branding />
|
||||||
<Links />
|
<Links />
|
||||||
</nav>
|
<ThemeSwitcher />
|
||||||
</BlurFade>
|
</nav>
|
||||||
|
</BlurFade>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Branding = (): ReactElement => (
|
const Branding = (): ReactElement => (
|
||||||
<Link
|
<Link
|
||||||
className="flex gap-4 items-center hover:opacity-75 cursor-default transition-all transform-gpu"
|
className="flex gap-3 items-center hover:opacity-75 cursor-default transition-all transform-gpu"
|
||||||
href="/"
|
href="/"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
className="rounded-full"
|
className="rounded-full"
|
||||||
src="/me.png"
|
src="/me.png"
|
||||||
alt="My Selfie (:"
|
alt="My Selfie (:"
|
||||||
width={40}
|
width={40}
|
||||||
height={40}
|
height={40}
|
||||||
/>
|
/>
|
||||||
<h1 className="text-xl font-bold">RainnnyCLUB</h1>
|
<h1 className="hidden sm:flex text-xl font-bold">RainnnyCLUB</h1>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Links = (): ReactElement => (
|
const Links = (): ReactElement => (
|
||||||
<NavigationMenu>
|
<NavigationMenu>
|
||||||
<NavigationMenuList>
|
<NavigationMenuList>
|
||||||
{/* Useful Links */}
|
{/* Useful Links */}
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
<NavigationMenuTrigger>Useful Links</NavigationMenuTrigger>
|
<NavigationMenuTrigger>Useful Links</NavigationMenuTrigger>
|
||||||
<NavigationMenuContent>
|
<NavigationMenuContent>
|
||||||
<NavigationMenuLink>
|
<NavigationMenuLink>
|
||||||
<UsefulLinksContent />
|
<UsefulLinksContent />
|
||||||
</NavigationMenuLink>
|
</NavigationMenuLink>
|
||||||
</NavigationMenuContent>
|
</NavigationMenuContent>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
|
|
||||||
{/* Donate */}
|
{/* Donate */}
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
<Link href="https://buymeacoffee.com/Rainnny7" legacyBehavior passHref>
|
<Link
|
||||||
<NavigationMenuLink
|
href="https://buymeacoffee.com/Rainnny7"
|
||||||
className={cn(navigationMenuTriggerStyle(), "gap-2")}
|
legacyBehavior
|
||||||
target="_blank"
|
passHref
|
||||||
>
|
>
|
||||||
<span>Buy me a Coffee</span>
|
<NavigationMenuLink
|
||||||
<HeartIcon
|
className={cn(navigationMenuTriggerStyle(), "gap-2")}
|
||||||
className="text-red-500 animate-pulse"
|
target="_blank"
|
||||||
width={20}
|
>
|
||||||
height={20}
|
<span className="hidden sm:flex">Buy me a Coffee</span>
|
||||||
/>
|
<span className="sm:hidden">Donate</span>
|
||||||
</NavigationMenuLink>
|
<HeartIcon
|
||||||
</Link>
|
className="text-red-500 animate-pulse"
|
||||||
</NavigationMenuItem>
|
width={20}
|
||||||
|
height={20}
|
||||||
{/* Theme Switcher */}
|
/>
|
||||||
<NavigationMenuItem>
|
</NavigationMenuLink>
|
||||||
<ThemeSwitcher />
|
</Link>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
</NavigationMenuList>
|
</NavigationMenuList>
|
||||||
</NavigationMenu>
|
</NavigationMenu>
|
||||||
);
|
);
|
||||||
|
|
||||||
const UsefulLinksContent = (): ReactElement => (
|
const UsefulLinksContent = (): ReactElement => (
|
||||||
<div className="p-3 flex gap-5">
|
<div className="p-3 flex gap-5">
|
||||||
{/* Git */}
|
{/* Git */}
|
||||||
<Link href="https://git.rainnny.club" target="_blank">
|
<Link href="https://git.rainnny.club" target="_blank">
|
||||||
<Button className="gap-3 cursor-default" variant="ghost">
|
<Button className="gap-3 cursor-default" variant="ghost">
|
||||||
<CodeBracketIcon width={24} height={24} />
|
<CodeBracketIcon width={24} height={24} />
|
||||||
<span>Gitea</span>
|
<span>Gitea</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Wiki */}
|
{/* Wiki */}
|
||||||
<Link href="https://docs.rainnny.club" target="_blank">
|
<Link href="https://docs.rainnny.club" target="_blank">
|
||||||
<Button className="gap-3 cursor-default" variant="ghost">
|
<Button className="gap-3 cursor-default" variant="ghost">
|
||||||
<BookOpenIcon width={24} height={24} />
|
<BookOpenIcon width={24} height={24} />
|
||||||
<span>Wiki</span>
|
<span>Wiki</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Status Page */}
|
{/* Status Page */}
|
||||||
<Link href="https://status.rainnny.club" target="_blank">
|
<Link href="https://status.rainnny.club" target="_blank">
|
||||||
<Button className="gap-3 cursor-default" variant="ghost">
|
<Button className="gap-3 cursor-default" variant="ghost">
|
||||||
<SignalIcon width={24} height={24} />
|
<SignalIcon width={24} height={24} />
|
||||||
<span>Service Status</span>
|
<span>Service Status</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Navbar;
|
export default Navbar;
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
import { ReactElement, useState } from "react";
|
import { ReactElement, useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { BriefcaseIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
import { WrenchIcon } from "@heroicons/react/24/outline";
|
BriefcaseIcon,
|
||||||
import { ServerStackIcon } from "@heroicons/react/24/outline";
|
ServerStackIcon,
|
||||||
|
WrenchIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import BlurFade from "@/components/ui/blur-fade";
|
import BlurFade from "@/components/ui/blur-fade";
|
||||||
import HomelabContent from "./nav-content/homelab";
|
import HomelabContent from "./nav-content/homelab";
|
||||||
@ -12,64 +14,66 @@ import Skills from "./nav-content/skills";
|
|||||||
import MyWork from "./nav-content/my-work";
|
import MyWork from "./nav-content/my-work";
|
||||||
|
|
||||||
type Item = {
|
type Item = {
|
||||||
name: string;
|
name: string;
|
||||||
icon: ReactElement;
|
icon: ReactElement;
|
||||||
content: ReactElement;
|
content: ReactElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
const items: Item[] = [
|
const items: Item[] = [
|
||||||
{
|
{
|
||||||
name: "My Work",
|
name: "My Work",
|
||||||
icon: <BriefcaseIcon width={22} height={22} />,
|
icon: <BriefcaseIcon width={22} height={22} />,
|
||||||
content: <MyWork />,
|
content: <MyWork />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Skills",
|
name: "Skills",
|
||||||
icon: <WrenchIcon width={22} height={22} />,
|
icon: <WrenchIcon width={22} height={22} />,
|
||||||
content: <Skills />,
|
content: <Skills />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Homelab",
|
name: "Homelab",
|
||||||
icon: <ServerStackIcon width={22} height={22} />,
|
icon: <ServerStackIcon width={22} height={22} />,
|
||||||
content: <HomelabContent />,
|
content: <HomelabContent />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Navigation = (): ReactElement => {
|
const Navigation = (): ReactElement => {
|
||||||
const [selected, setSelected] = useState<Item | undefined>(undefined);
|
const [selected, setSelected] = useState<Item | undefined>(undefined);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{/* Selection Buttons */}
|
{/* Selection Buttons */}
|
||||||
<div className="flex gap-5 justify-center">
|
<div className="flex gap-3 sm:gap-6 justify-center transition-all transform-gpu">
|
||||||
{items.map((item, index) => {
|
{items.map((item, index) => {
|
||||||
const active: boolean = selected === item;
|
const active: boolean = selected === item;
|
||||||
return (
|
return (
|
||||||
<BlurFade key={index} delay={0.9 + 0.3 * index} inView>
|
<BlurFade key={index} delay={0.9 + 0.3 * index} inView>
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-6 gap-2 bg-white/75 dark:bg-zinc-800/75 cursor-default hover:opacity-75 transition-all transform-gpu",
|
"px-3 sm:px-4 py-6 gap-2 shadow-sm bg-white/75 dark:bg-zinc-800/75 rounded-xl cursor-default hover:opacity-75 transition-all transform-gpu",
|
||||||
active && "opacity-70",
|
active && "opacity-70"
|
||||||
)}
|
)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
active ? setSelected(undefined) : setSelected(item)
|
active
|
||||||
}
|
? setSelected(undefined)
|
||||||
>
|
: setSelected(item)
|
||||||
{item.icon}
|
}
|
||||||
{item.name}
|
>
|
||||||
</Button>
|
{item.icon}
|
||||||
</BlurFade>
|
{item.name}
|
||||||
);
|
</Button>
|
||||||
})}
|
</BlurFade>
|
||||||
</div>
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Selected Content */}
|
{/* Selected Content */}
|
||||||
{selected && (
|
{selected && (
|
||||||
<BlurFade key={selected.name} delay={0.05} inView>
|
<BlurFade key={selected.name} delay={0.05} inView>
|
||||||
<div className="mt-4 p-4 border rounded-xl">{selected.content}</div>
|
<div className="mt-5">{selected.content}</div>
|
||||||
</BlurFade>
|
</BlurFade>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Navigation;
|
export default Navigation;
|
||||||
|
@ -8,31 +8,31 @@ import { motion } from "framer-motion";
|
|||||||
import { UseThemeProps } from "next-themes/dist/types";
|
import { UseThemeProps } from "next-themes/dist/types";
|
||||||
|
|
||||||
const ThemeSwitcher = (): ReactElement => {
|
const ThemeSwitcher = (): ReactElement => {
|
||||||
const { theme, setTheme }: UseThemeProps = useTheme();
|
const { theme, setTheme }: UseThemeProps = useTheme();
|
||||||
const isLight = theme === "light";
|
const isLight = theme === "light";
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className="relative mx-7 px-5 py-1.5 flex items-center cursor-default hover:opacity-85"
|
className="relative px-5 py-1.5 flex items-center cursor-default hover:opacity-85"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setTheme(isLight ? "dark" : "light")}
|
onClick={() => setTheme(isLight ? "dark" : "light")}
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ rotate: 0, scale: 1 }}
|
initial={{ rotate: 0, scale: 1 }}
|
||||||
animate={{ rotate: isLight ? 0 : -90, scale: isLight ? 1 : 0 }}
|
animate={{ rotate: isLight ? 0 : -90, scale: isLight ? 1 : 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
className="absolute"
|
className="absolute"
|
||||||
>
|
>
|
||||||
<Sun className="w-[1.2rem] h-[1.2rem]" />
|
<Sun className="w-[1.2rem] h-[1.2rem]" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ rotate: 90, scale: 0 }}
|
initial={{ rotate: 90, scale: 0 }}
|
||||||
animate={{ rotate: isLight ? 90 : 0, scale: isLight ? 0 : 1 }}
|
animate={{ rotate: isLight ? 90 : 0, scale: isLight ? 0 : 1 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
className="absolute"
|
className="absolute"
|
||||||
>
|
>
|
||||||
<MoonStar className="w-[1.2rem] h-[1.2rem]" />
|
<MoonStar className="w-[1.2rem] h-[1.2rem]" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ThemeSwitcher;
|
export default ThemeSwitcher;
|
||||||
|
@ -5,5 +5,5 @@ import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|||||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
import { type ThemeProviderProps } from "next-themes/dist/types";
|
||||||
|
|
||||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||||
}
|
}
|
||||||
|
@ -1,56 +1,57 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default:
|
||||||
destructive:
|
"bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
destructive:
|
||||||
outline:
|
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
outline:
|
||||||
secondary:
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
secondary:
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
},
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
size: {
|
},
|
||||||
default: "h-10 px-4 py-2",
|
size: {
|
||||||
sm: "h-9 rounded-md px-3",
|
default: "h-10 px-4 py-2",
|
||||||
lg: "h-11 rounded-md px-8",
|
sm: "h-9 rounded-md px-3",
|
||||||
icon: "h-10 w-10",
|
lg: "h-11 rounded-md px-8",
|
||||||
},
|
icon: "h-10 w-10",
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
},
|
||||||
variant: "default",
|
defaultVariants: {
|
||||||
size: "default",
|
variant: "default",
|
||||||
},
|
size: "default",
|
||||||
}
|
},
|
||||||
)
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
VariantProps<typeof buttonVariants> {
|
VariantProps<typeof buttonVariants> {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : "button";
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
Button.displayName = "Button"
|
Button.displayName = "Button";
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
export { Button, buttonVariants };
|
||||||
|
@ -1,99 +1,107 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { AnimatePresence, motion, LayoutGroup } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export const FlipWords = ({
|
export const FlipWords = ({
|
||||||
words,
|
words,
|
||||||
duration = 3000,
|
duration = 3000,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
words: string[];
|
words: string[];
|
||||||
duration?: number;
|
duration?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [currentWord, setCurrentWord] = useState(words[0]);
|
const [currentWord, setCurrentWord] = useState(words[0]);
|
||||||
const [isAnimating, setIsAnimating] = useState<boolean>(false);
|
const [isAnimating, setIsAnimating] = useState<boolean>(false);
|
||||||
|
|
||||||
// thanks for the fix Julian - https://github.com/Julian-AT
|
// thanks for the fix Julian - https://github.com/Julian-AT
|
||||||
const startAnimation = useCallback(() => {
|
const startAnimation = useCallback(() => {
|
||||||
const word = words[words.indexOf(currentWord) + 1] || words[0];
|
const word = words[words.indexOf(currentWord) + 1] || words[0];
|
||||||
setCurrentWord(word);
|
setCurrentWord(word);
|
||||||
setIsAnimating(true);
|
setIsAnimating(true);
|
||||||
}, [currentWord, words]);
|
}, [currentWord, words]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAnimating)
|
if (!isAnimating)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
startAnimation();
|
startAnimation();
|
||||||
}, duration);
|
}, duration);
|
||||||
}, [isAnimating, duration, startAnimation]);
|
}, [isAnimating, duration, startAnimation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence
|
<AnimatePresence
|
||||||
onExitComplete={() => {
|
onExitComplete={() => {
|
||||||
setIsAnimating(false);
|
setIsAnimating(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{
|
initial={{
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
y: 10,
|
y: 10,
|
||||||
}}
|
}}
|
||||||
animate={{
|
animate={{
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
y: 0,
|
y: 0,
|
||||||
}}
|
}}
|
||||||
transition={{
|
transition={{
|
||||||
type: "spring",
|
type: "spring",
|
||||||
stiffness: 100,
|
stiffness: 100,
|
||||||
damping: 10,
|
damping: 10,
|
||||||
}}
|
}}
|
||||||
exit={{
|
exit={{
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
y: -40,
|
y: -40,
|
||||||
x: 40,
|
x: 40,
|
||||||
filter: "blur(8px)",
|
filter: "blur(8px)",
|
||||||
scale: 2,
|
scale: 2,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-10 inline-block relative text-left text-neutral-900 dark:text-neutral-100 px-2",
|
"z-10 inline-block relative text-left text-neutral-900 dark:text-neutral-100 px-2",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
key={currentWord}
|
key={currentWord}
|
||||||
>
|
>
|
||||||
{/* edit suggested by Sajal: https://x.com/DewanganSajal */}
|
{/* edit suggested by Sajal: https://x.com/DewanganSajal */}
|
||||||
{currentWord.split(" ").map((word, wordIndex) => (
|
{currentWord.split(" ").map((word, wordIndex) => (
|
||||||
<motion.span
|
<motion.span
|
||||||
key={word + wordIndex}
|
key={word + wordIndex}
|
||||||
initial={{ opacity: 0, y: 10, filter: "blur(8px)" }}
|
initial={{ opacity: 0, y: 10, filter: "blur(8px)" }}
|
||||||
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
||||||
transition={{
|
transition={{
|
||||||
delay: wordIndex * 0.3,
|
delay: wordIndex * 0.3,
|
||||||
duration: 0.3,
|
duration: 0.3,
|
||||||
}}
|
}}
|
||||||
className="inline-block whitespace-nowrap"
|
className="inline-block whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{word.split("").map((letter, letterIndex) => (
|
{word.split("").map((letter, letterIndex) => (
|
||||||
<motion.span
|
<motion.span
|
||||||
key={word + letterIndex}
|
key={word + letterIndex}
|
||||||
initial={{ opacity: 0, y: 10, filter: "blur(8px)" }}
|
initial={{
|
||||||
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
opacity: 0,
|
||||||
transition={{
|
y: 10,
|
||||||
delay: wordIndex * 0.3 + letterIndex * 0.05,
|
filter: "blur(8px)",
|
||||||
duration: 0.2,
|
}}
|
||||||
}}
|
animate={{
|
||||||
className="inline-block"
|
opacity: 1,
|
||||||
>
|
y: 0,
|
||||||
{letter}
|
filter: "blur(0px)",
|
||||||
</motion.span>
|
}}
|
||||||
))}
|
transition={{
|
||||||
<span className="inline-block"> </span>
|
delay: wordIndex * 0.3 + letterIndex * 0.05,
|
||||||
</motion.span>
|
duration: 0.2,
|
||||||
))}
|
}}
|
||||||
</motion.div>
|
className="inline-block"
|
||||||
</AnimatePresence>
|
>
|
||||||
);
|
{letter}
|
||||||
|
</motion.span>
|
||||||
|
))}
|
||||||
|
<span className="inline-block"> </span>
|
||||||
|
</motion.span>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,59 +6,59 @@ import { motion, useMotionTemplate, useMotionValue } from "framer-motion";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export interface MagicCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
export interface MagicCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
gradientSize?: number;
|
gradientSize?: number;
|
||||||
gradientColor?: string;
|
gradientColor?: string;
|
||||||
gradientOpacity?: number;
|
gradientOpacity?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MagicCard({
|
export function MagicCard({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
gradientSize = 200,
|
gradientSize = 200,
|
||||||
gradientColor = "#262626",
|
gradientColor = "#262626",
|
||||||
gradientOpacity = 0.8,
|
gradientOpacity = 0.8,
|
||||||
}: MagicCardProps) {
|
}: MagicCardProps) {
|
||||||
const mouseX = useMotionValue(-gradientSize);
|
const mouseX = useMotionValue(-gradientSize);
|
||||||
const mouseY = useMotionValue(-gradientSize);
|
const mouseY = useMotionValue(-gradientSize);
|
||||||
|
|
||||||
const handleMouseMove = useCallback(
|
const handleMouseMove = useCallback(
|
||||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
const { left, top } = e.currentTarget.getBoundingClientRect();
|
const { left, top } = e.currentTarget.getBoundingClientRect();
|
||||||
mouseX.set(e.clientX - left);
|
mouseX.set(e.clientX - left);
|
||||||
mouseY.set(e.clientY - top);
|
mouseY.set(e.clientY - top);
|
||||||
},
|
},
|
||||||
[mouseX, mouseY],
|
[mouseX, mouseY]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMouseLeave = useCallback(() => {
|
const handleMouseLeave = useCallback(() => {
|
||||||
mouseX.set(-gradientSize);
|
mouseX.set(-gradientSize);
|
||||||
mouseY.set(-gradientSize);
|
mouseY.set(-gradientSize);
|
||||||
}, [mouseX, mouseY, gradientSize]);
|
}, [mouseX, mouseY, gradientSize]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mouseX.set(-gradientSize);
|
mouseX.set(-gradientSize);
|
||||||
mouseY.set(-gradientSize);
|
mouseY.set(-gradientSize);
|
||||||
}, [mouseX, mouseY, gradientSize]);
|
}, [mouseX, mouseY, gradientSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative flex size-full overflow-hidden rounded-xl bg-neutral-100 dark:bg-neutral-900 border text-black dark:text-white",
|
"group relative flex size-full overflow-hidden rounded-xl bg-neutral-100 dark:bg-neutral-900 border text-black dark:text-white",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="relative z-10">{children}</div>
|
<div className="relative z-10">{children}</div>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="pointer-events-none absolute -inset-px rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
className="pointer-events-none absolute -inset-px rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||||
style={{
|
style={{
|
||||||
background: useMotionTemplate`
|
background: useMotionTemplate`
|
||||||
radial-gradient(${gradientSize}px circle at ${mouseX}px ${mouseY}px, ${gradientColor}, transparent 100%)
|
radial-gradient(${gradientSize}px circle at ${mouseX}px ${mouseY}px, ${gradientColor}, transparent 100%)
|
||||||
`,
|
`,
|
||||||
opacity: gradientOpacity,
|
opacity: gradientOpacity,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,123 +6,123 @@ import { ChevronDown } from "lucide-react";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const NavigationMenu = React.forwardRef<
|
const NavigationMenu = React.forwardRef<
|
||||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, ...props }, ref) => (
|
||||||
<NavigationMenuPrimitive.Root
|
<NavigationMenuPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<NavigationMenuViewport />
|
<NavigationMenuViewport />
|
||||||
</NavigationMenuPrimitive.Root>
|
</NavigationMenuPrimitive.Root>
|
||||||
));
|
));
|
||||||
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
|
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
|
||||||
|
|
||||||
const NavigationMenuList = React.forwardRef<
|
const NavigationMenuList = React.forwardRef<
|
||||||
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<NavigationMenuPrimitive.List
|
<NavigationMenuPrimitive.List
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex flex-1 list-none items-center justify-center space-x-1",
|
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
|
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
|
||||||
|
|
||||||
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
||||||
|
|
||||||
const navigationMenuTriggerStyle = cva(
|
const navigationMenuTriggerStyle = cva(
|
||||||
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium cursor-default transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50",
|
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium cursor-default transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
||||||
);
|
);
|
||||||
|
|
||||||
const NavigationMenuTrigger = React.forwardRef<
|
const NavigationMenuTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, ...props }, ref) => (
|
||||||
<NavigationMenuPrimitive.Trigger
|
<NavigationMenuPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}{" "}
|
{children}{" "}
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</NavigationMenuPrimitive.Trigger>
|
</NavigationMenuPrimitive.Trigger>
|
||||||
));
|
));
|
||||||
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
|
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
const NavigationMenuContent = React.forwardRef<
|
const NavigationMenuContent = React.forwardRef<
|
||||||
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<NavigationMenuPrimitive.Content
|
<NavigationMenuPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
|
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
|
||||||
|
|
||||||
const NavigationMenuLink = NavigationMenuPrimitive.Link;
|
const NavigationMenuLink = NavigationMenuPrimitive.Link;
|
||||||
|
|
||||||
const NavigationMenuViewport = React.forwardRef<
|
const NavigationMenuViewport = React.forwardRef<
|
||||||
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||||
<NavigationMenuPrimitive.Viewport
|
<NavigationMenuPrimitive.Viewport
|
||||||
className={cn(
|
className={cn(
|
||||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
NavigationMenuViewport.displayName =
|
NavigationMenuViewport.displayName =
|
||||||
NavigationMenuPrimitive.Viewport.displayName;
|
NavigationMenuPrimitive.Viewport.displayName;
|
||||||
|
|
||||||
const NavigationMenuIndicator = React.forwardRef<
|
const NavigationMenuIndicator = React.forwardRef<
|
||||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<NavigationMenuPrimitive.Indicator
|
<NavigationMenuPrimitive.Indicator
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||||
</NavigationMenuPrimitive.Indicator>
|
</NavigationMenuPrimitive.Indicator>
|
||||||
));
|
));
|
||||||
NavigationMenuIndicator.displayName =
|
NavigationMenuIndicator.displayName =
|
||||||
NavigationMenuPrimitive.Indicator.displayName;
|
NavigationMenuPrimitive.Indicator.displayName;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
navigationMenuTriggerStyle,
|
navigationMenuTriggerStyle,
|
||||||
NavigationMenu,
|
NavigationMenu,
|
||||||
NavigationMenuList,
|
NavigationMenuList,
|
||||||
NavigationMenuItem,
|
NavigationMenuItem,
|
||||||
NavigationMenuContent,
|
NavigationMenuContent,
|
||||||
NavigationMenuTrigger,
|
NavigationMenuTrigger,
|
||||||
NavigationMenuLink,
|
NavigationMenuLink,
|
||||||
NavigationMenuIndicator,
|
NavigationMenuIndicator,
|
||||||
NavigationMenuViewport,
|
NavigationMenuViewport,
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ReactElement, ReactNode } from "react";
|
import { ReactElement, ReactNode } from "react";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { SIDE_OPTIONS } from "@radix-ui/react-popper";
|
import { SIDE_OPTIONS } from "@radix-ui/react-popper";
|
||||||
|
|
||||||
@ -10,20 +10,20 @@ import { SIDE_OPTIONS } from "@radix-ui/react-popper";
|
|||||||
* The props for a simple tooltip.
|
* The props for a simple tooltip.
|
||||||
*/
|
*/
|
||||||
type SimpleTooltipProps = {
|
type SimpleTooltipProps = {
|
||||||
/**
|
/**
|
||||||
* The content to display in the tooltip.
|
* The content to display in the tooltip.
|
||||||
*/
|
*/
|
||||||
content: string | ReactElement;
|
content: string | ReactElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The side to display the tooltip on.
|
* The side to display the tooltip on.
|
||||||
*/
|
*/
|
||||||
side?: (typeof SIDE_OPTIONS)[number];
|
side?: (typeof SIDE_OPTIONS)[number];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The children to render in this tooltip.
|
* The children to render in this tooltip.
|
||||||
*/
|
*/
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,13 +33,13 @@ type SimpleTooltipProps = {
|
|||||||
* @return the tooltip jsx
|
* @return the tooltip jsx
|
||||||
*/
|
*/
|
||||||
const SimpleTooltip = ({
|
const SimpleTooltip = ({
|
||||||
content,
|
content,
|
||||||
side,
|
side,
|
||||||
children,
|
children,
|
||||||
}: SimpleTooltipProps): ReactElement => (
|
}: SimpleTooltipProps): ReactElement => (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||||
<TooltipContent side={side}>{content}</TooltipContent>
|
<TooltipContent side={side}>{content}</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
export default SimpleTooltip;
|
export default SimpleTooltip;
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const TooltipProvider = TooltipPrimitive.Provider
|
const TooltipProvider = TooltipPrimitive.Provider;
|
||||||
|
|
||||||
const Tooltip = TooltipPrimitive.Root
|
const Tooltip = TooltipPrimitive.Root;
|
||||||
|
|
||||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||||
|
|
||||||
const TooltipContent = React.forwardRef<
|
const TooltipContent = React.forwardRef<
|
||||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
<TooltipPrimitive.Content
|
<TooltipPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { type ClassValue, clsx } from "clsx"
|
import { type ClassValue, clsx } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
@ -1,80 +1,83 @@
|
|||||||
import type { Config } from "tailwindcss"
|
import type {Config} from "tailwindcss"
|
||||||
|
|
||||||
|
const defaultTheme = require('tailwindcss/defaultTheme')
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
darkMode: ["class"],
|
darkMode: ["class"],
|
||||||
content: [
|
content: [
|
||||||
'./pages/**/*.{ts,tsx}',
|
"./src/**/*.{ts,tsx}",
|
||||||
'./components/**/*.{ts,tsx}',
|
],
|
||||||
'./app/**/*.{ts,tsx}',
|
prefix: "",
|
||||||
'./src/**/*.{ts,tsx}',
|
theme: {
|
||||||
],
|
screens: {
|
||||||
prefix: "",
|
'xs': '475px',
|
||||||
theme: {
|
...defaultTheme.screens,
|
||||||
container: {
|
},
|
||||||
center: true,
|
container: {
|
||||||
padding: "2rem",
|
center: true,
|
||||||
screens: {
|
padding: "2rem",
|
||||||
"2xl": "1400px",
|
screens: {
|
||||||
},
|
"2xl": "1400px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
border: "hsl(var(--border))",
|
||||||
|
input: "hsl(var(--input))",
|
||||||
|
ring: "hsl(var(--ring))",
|
||||||
|
background: "hsl(var(--background))",
|
||||||
|
foreground: "hsl(var(--foreground))",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(var(--primary))",
|
||||||
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(var(--muted))",
|
||||||
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(var(--accent))",
|
||||||
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: "hsl(var(--popover))",
|
||||||
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(var(--card))",
|
||||||
|
foreground: "hsl(var(--card-foreground))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: "var(--radius)",
|
||||||
|
md: "calc(var(--radius) - 2px)",
|
||||||
|
sm: "calc(var(--radius) - 4px)",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
"accordion-down": {
|
||||||
|
from: {height: "0"},
|
||||||
|
to: {height: "var(--radix-accordion-content-height)"},
|
||||||
|
},
|
||||||
|
"accordion-up": {
|
||||||
|
from: {height: "var(--radix-accordion-content-height)"},
|
||||||
|
to: {height: "0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extend: {
|
plugins: [require("tailwindcss-animate")],
|
||||||
colors: {
|
|
||||||
border: "hsl(var(--border))",
|
|
||||||
input: "hsl(var(--input))",
|
|
||||||
ring: "hsl(var(--ring))",
|
|
||||||
background: "hsl(var(--background))",
|
|
||||||
foreground: "hsl(var(--foreground))",
|
|
||||||
primary: {
|
|
||||||
DEFAULT: "hsl(var(--primary))",
|
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
DEFAULT: "hsl(var(--secondary))",
|
|
||||||
foreground: "hsl(var(--secondary-foreground))",
|
|
||||||
},
|
|
||||||
destructive: {
|
|
||||||
DEFAULT: "hsl(var(--destructive))",
|
|
||||||
foreground: "hsl(var(--destructive-foreground))",
|
|
||||||
},
|
|
||||||
muted: {
|
|
||||||
DEFAULT: "hsl(var(--muted))",
|
|
||||||
foreground: "hsl(var(--muted-foreground))",
|
|
||||||
},
|
|
||||||
accent: {
|
|
||||||
DEFAULT: "hsl(var(--accent))",
|
|
||||||
foreground: "hsl(var(--accent-foreground))",
|
|
||||||
},
|
|
||||||
popover: {
|
|
||||||
DEFAULT: "hsl(var(--popover))",
|
|
||||||
foreground: "hsl(var(--popover-foreground))",
|
|
||||||
},
|
|
||||||
card: {
|
|
||||||
DEFAULT: "hsl(var(--card))",
|
|
||||||
foreground: "hsl(var(--card-foreground))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
lg: "var(--radius)",
|
|
||||||
md: "calc(var(--radius) - 2px)",
|
|
||||||
sm: "calc(var(--radius) - 4px)",
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
"accordion-down": {
|
|
||||||
from: { height: "0" },
|
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
|
||||||
},
|
|
||||||
"accordion-up": {
|
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
|
||||||
to: { height: "0" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require("tailwindcss-animate")],
|
|
||||||
} satisfies Config
|
} satisfies Config
|
||||||
|
|
||||||
export default config
|
export default config
|
Loading…
x
Reference in New Issue
Block a user