├── .eslintrc.json
├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── app
├── api
│ └── sentry-example-api
│ │ └── route.js
├── global-error.jsx
├── globals.css
├── layout.tsx
├── page.tsx
├── provider.tsx
└── sentry-example-page
│ └── page.jsx
├── components.json
├── components
├── Approach.tsx
├── Clients.tsx
├── Experience.tsx
├── Footer.tsx
├── Grid.tsx
├── Hero.tsx
├── MagicButton.tsx
├── RecentProjects.tsx
└── ui
│ ├── BentoGrid.tsx
│ ├── CanvasRevealEffect.tsx
│ ├── FloatingNavbar.tsx
│ ├── Globe.tsx
│ ├── GradientBg.tsx
│ ├── GridGlobe.tsx
│ ├── HoverBorder.tsx
│ ├── InfiniteCards.tsx
│ ├── LayoutGrid.tsx
│ ├── MovingBorders.tsx
│ ├── Pin.tsx
│ ├── Spotlight.tsx
│ └── TextGenerateEffect.tsx
├── data
├── confetti.json
├── globe.json
└── index.ts
├── lib
└── utils.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── app.svg
├── appName.svg
├── b1.svg
├── b4.svg
├── b5.svg
├── bg.png
├── c.svg
├── cloud.svg
├── cloudName.svg
├── confetti.gif
├── dock.svg
├── dockerName.svg
├── exp1.svg
├── exp2.svg
├── exp3.svg
├── exp4.svg
├── fm.svg
├── footer-grid.svg
├── git.svg
├── grid.svg
├── gsap.svg
├── host.svg
├── hostName.svg
├── insta.svg
├── jsm-logo.png
├── link.svg
├── next.svg
├── p1.svg
├── p2.svg
├── p3.svg
├── p4.svg
├── profile.svg
├── re.svg
├── s.svg
├── stream.svg
├── streamName.svg
├── tail.svg
├── three.svg
├── ts.svg
├── twit.svg
└── wha.svg
├── sentry.client.config.ts
├── sentry.edge.config.ts
├── sentry.server.config.ts
├── tailwind.config.ts
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | # Sentry Config File
39 | .sentryclirc
40 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnPaste": true,
3 | "editor.formatOnSave": true,
4 | "[typescriptreact]": {
5 | "editor.defaultFormatter": "esbenp.prettier-vscode"
6 | },
7 | "[typescript]": {
8 | "editor.defaultFormatter": "esbenp.prettier-vscode"
9 | },
10 | "editor.codeActionsOnSave": {
11 | "source.fixAll.eslint": "explicit"
12 | },
13 | "editor.wordWrap": "on"
14 | }
15 |
--------------------------------------------------------------------------------
/app/api/sentry-example-api/route.js:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 |
3 | export const dynamic = "force-dynamic";
4 |
5 | // A faulty API route to test Sentry's error monitoring
6 | export function GET() {
7 | throw new Error("Sentry Example API Route Error");
8 | return NextResponse.json({ data: "Testing Sentry Error..." });
9 | }
10 |
--------------------------------------------------------------------------------
/app/global-error.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as Sentry from "@sentry/nextjs";
4 | import Error from "next/error";
5 | import { useEffect } from "react";
6 |
7 | export default function GlobalError({ error }) {
8 | useEffect(() => {
9 | Sentry.captureException(error);
10 | }, [error]);
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 240 10% 3.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 240 10% 3.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 240 10% 3.9%;
15 |
16 | --primary: 240 5.9% 10%;
17 | --primary-foreground: 0 0% 98%;
18 |
19 | --secondary: 240 4.8% 95.9%;
20 | --secondary-foreground: 240 5.9% 10%;
21 |
22 | --muted: 240 4.8% 95.9%;
23 | --muted-foreground: 240 3.8% 46.1%;
24 |
25 | --accent: 240 4.8% 95.9%;
26 | --accent-foreground: 240 5.9% 10%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 0 0% 98%;
30 |
31 | --border: 240 5.9% 90%;
32 | --input: 240 5.9% 90%;
33 | --ring: 240 10% 3.9%;
34 |
35 | --radius: 0.5rem;
36 | }
37 |
38 | .dark {
39 | --background: 240 10% 3.9%;
40 | --foreground: 0 0% 98%;
41 |
42 | --card: 240 10% 3.9%;
43 | --card-foreground: 0 0% 98%;
44 |
45 | --popover: 240 10% 3.9%;
46 | --popover-foreground: 0 0% 98%;
47 |
48 | --primary: 0 0% 98%;
49 | --primary-foreground: 240 5.9% 10%;
50 |
51 | --secondary: 240 3.7% 15.9%;
52 | --secondary-foreground: 0 0% 98%;
53 |
54 | --muted: 240 3.7% 15.9%;
55 | --muted-foreground: 240 5% 64.9%;
56 |
57 | --accent: 240 3.7% 15.9%;
58 | --accent-foreground: 0 0% 98%;
59 |
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 0 0% 98%;
62 |
63 | --border: 240 3.7% 15.9%;
64 | --input: 240 3.7% 15.9%;
65 | --ring: 240 4.9% 83.9%;
66 | }
67 | }
68 |
69 | @layer base {
70 | * {
71 | @apply border-border !scroll-smooth;
72 | }
73 | body {
74 | @apply bg-background text-foreground;
75 | }
76 | button {
77 | @apply active:outline-none;
78 | }
79 | }
80 |
81 | @layer utilities {
82 | .heading {
83 | @apply font-bold text-4xl md:text-5xl text-center;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 |
4 | import "./globals.css";
5 | import { ThemeProvider } from "./provider";
6 |
7 | const inter = Inter({ subsets: ["latin"] });
8 |
9 | export const metadata: Metadata = {
10 | title: "Adrian's Portfolio",
11 | description: "Modern & Minimal JS Mastery Portfolio",
12 | };
13 |
14 | export default function RootLayout({
15 | children,
16 | }: Readonly<{
17 | children: React.ReactNode;
18 | }>) {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
31 | {children}
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { navItems } from "@/data";
4 |
5 | import Hero from "@/components/Hero";
6 | import Grid from "@/components/Grid";
7 | import Footer from "@/components/Footer";
8 | import Clients from "@/components/Clients";
9 | import Approach from "@/components/Approach";
10 | import Experience from "@/components/Experience";
11 | import RecentProjects from "@/components/RecentProjects";
12 | import { FloatingNav } from "@/components/ui/FloatingNavbar";
13 |
14 | const Home = () => {
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default Home;
32 |
--------------------------------------------------------------------------------
/app/provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { ThemeProvider as NextThemesProvider } from "next-themes";
5 | import { type ThemeProviderProps } from "next-themes/dist/types";
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children} ;
9 | }
10 |
--------------------------------------------------------------------------------
/app/sentry-example-page/page.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Head from "next/head";
4 | import * as Sentry from "@sentry/nextjs";
5 |
6 | export default function Page() {
7 | return (
8 |
9 |
10 |
Sentry Onboarding
11 |
12 |
13 |
14 |
23 |
24 |
31 |
35 |
36 |
37 |
38 | Get started by sending us a sample error:
39 | {
52 | Sentry.startSpan({
53 | name: 'Example Frontend Span',
54 | op: 'test'
55 | }, async () => {
56 | const res = await fetch("/api/sentry-example-api");
57 | if (!res.ok) {
58 | throw new Error("Sentry Example Frontend Error");
59 | }
60 | });
61 | }}
62 | >
63 | Throw error!
64 |
65 |
66 |
67 | Next, look for the error on the{" "}
68 | Issues Page .
69 |
70 |
71 | For more information, see{" "}
72 |
73 | https://docs.sentry.io/platforms/javascript/guides/nextjs/
74 |
75 |
76 |
77 |
78 | );
79 | }
80 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/components/Approach.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { AnimatePresence, motion } from "framer-motion";
3 |
4 | import { CanvasRevealEffect } from "./ui/CanvasRevealEffect";
5 |
6 | const Approach = () => {
7 | return (
8 |
9 |
10 | My approach
11 |
12 | {/* remove bg-white dark:bg-black */}
13 |
14 | {/* add des prop */}
15 |
}
18 | des="We'll collaborate to map out your website's goals, target audience,
19 | and key functionalities. We'll discuss things like site structure,
20 | navigation, and content requirements."
21 | >
22 |
rounded-3xl overflow-hidden
25 | containerClassName="bg-emerald-900 rounded-3xl overflow-hidden"
26 | />
27 |
28 | }
31 | des="Once we agree on the plan, I cue my lofi playlist and dive into
32 | coding. From initial sketches to polished code, I keep you updated
33 | every step of the way."
34 | >
35 |
46 | {/* Radial gradient for the cute fade */}
47 | {/* remove this one */}
48 | {/*
*/}
49 |
50 | }
53 | des="This is where the magic happens! Based on the approved design,
54 | I'll translate everything into functional code, building your website
55 | from the ground up."
56 | >
57 |
62 |
63 |
64 |
65 | );
66 | };
67 |
68 | export default Approach;
69 |
70 | const Card = ({
71 | title,
72 | icon,
73 | children,
74 | // add this one for the desc
75 | des,
76 | }: {
77 | title: string;
78 | icon: React.ReactNode;
79 | children?: React.ReactNode;
80 | des: string;
81 | }) => {
82 | const [hovered, setHovered] = React.useState(false);
83 | return (
84 | setHovered(true)}
86 | onMouseLeave={() => setHovered(false)}
87 | // change h-[30rem] to h-[35rem], add rounded-3xl
88 | className="border border-black/[0.2] group/canvas-card flex items-center justify-center
89 | dark:border-white/[0.2] max-w-sm w-full mx-auto p-4 relative lg:h-[35rem] rounded-3xl "
90 | style={{
91 | // add these two
92 | // you can generate the color from here https://cssgradient.io/
93 | background: "rgb(4,7,29)",
94 | backgroundColor:
95 | "linear-gradient(90deg, rgba(4,7,29,1) 0%, rgba(12,14,35,1) 100%)",
96 | }}
97 | >
98 | {/* change to h-10 w-10 , add opacity-30 */}
99 |
100 |
101 |
102 |
103 |
104 |
105 | {hovered && (
106 |
111 | {children}
112 |
113 | )}
114 |
115 |
116 |
117 |
123 | {icon}
124 |
125 |
131 | {title}
132 |
133 | {/* add this one for the description */}
134 |
140 | {des}
141 |
142 |
143 |
144 | );
145 | };
146 | // add order prop for the Phase number change
147 | const AceternityIcon = ({ order }: { order: string }) => {
148 | return (
149 |
150 | {/* this btn is from https://ui.aceternity.com/components/tailwindcss-buttons border magic */}
151 | {/* change rounded-lg, text-purple px-5 py-2 */}
152 | {/* remove focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50 cuz we don't need to focus */}
153 | {/* remove text-sm font-medium h-12 , add font-bold text-2xl */}
154 |
155 |
159 |
163 | {order}
164 |
165 |
166 |
167 | // remove the svg and add the button
168 | //
176 | //
184 | //
185 | );
186 | };
187 |
188 | export const Icon = ({ className, ...rest }: any) => {
189 | return (
190 |
199 |
200 |
201 | );
202 | };
203 |
--------------------------------------------------------------------------------
/components/Clients.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 |
5 | import { companies, testimonials } from "@/data";
6 | import { InfiniteMovingCards } from "./ui/InfiniteCards";
7 |
8 | const Clients = () => {
9 | return (
10 |
11 |
12 | Kind words from
13 | satisfied clients
14 |
15 |
16 |
17 |
21 |
26 |
27 |
28 |
29 | {companies.map((company) => (
30 |
31 |
32 |
37 |
43 |
44 |
45 | ))}
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default Clients;
53 |
--------------------------------------------------------------------------------
/components/Experience.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { workExperience } from "@/data";
4 | import { Button } from "./ui/MovingBorders";
5 |
6 | const Experience = () => {
7 | return (
8 |
9 |
10 | My work experience
11 |
12 |
13 |
14 | {workExperience.map((card) => (
15 |
32 |
33 |
38 |
39 |
40 | {card.title}
41 |
42 |
43 | {card.desc}
44 |
45 |
46 |
47 |
48 | ))}
49 |
50 |
51 | );
52 | };
53 |
54 | export default Experience;
55 |
--------------------------------------------------------------------------------
/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { FaLocationArrow } from "react-icons/fa6";
2 |
3 | import { socialMedia } from "@/data";
4 | import MagicButton from "./MagicButton";
5 |
6 | const Footer = () => {
7 | return (
8 |
52 | );
53 | };
54 |
55 | export default Footer;
56 |
--------------------------------------------------------------------------------
/components/Grid.tsx:
--------------------------------------------------------------------------------
1 | import { gridItems } from "@/data";
2 | import { BentoGrid, BentoGridItem } from "./ui/BentoGrid";
3 |
4 | const Grid = () => {
5 | return (
6 |
7 |
8 | {gridItems.map((item, i) => (
9 |
22 | ))}
23 |
24 |
25 | );
26 | };
27 |
28 | export default Grid;
29 |
--------------------------------------------------------------------------------
/components/Hero.tsx:
--------------------------------------------------------------------------------
1 | import { FaLocationArrow } from "react-icons/fa6";
2 |
3 | import MagicButton from "./MagicButton";
4 | import { Spotlight } from "./ui/Spotlight";
5 | import { TextGenerateEffect } from "./ui/TextGenerateEffect";
6 |
7 | const Hero = () => {
8 | return (
9 |
10 | {/**
11 | * UI: Spotlights
12 | * Link: https://ui.aceternity.com/components/spotlight
13 | */}
14 |
15 |
19 |
23 |
24 |
25 |
26 | {/**
27 | * UI: grid
28 | * change bg color to bg-black-100 and reduce grid color from
29 | * 0.2 to 0.03
30 | */}
31 |
35 | {/* Radial gradient for the container to give a faded look */}
36 |
41 |
42 |
43 |
44 |
45 |
46 | Dynamic Web Magic with Next.js
47 |
48 |
49 | {/**
50 | * Link: https://ui.aceternity.com/components/text-generate-effect
51 | *
52 | * change md:text-6xl, add more responsive code
53 | */}
54 |
58 |
59 |
60 | Hi! I'm Adrian, a Next.js Developer based in Croatia.
61 |
62 |
63 |
64 | }
67 | position="right"
68 | />
69 |
70 |
71 |
72 |
73 | );
74 | };
75 |
76 | export default Hero;
77 |
--------------------------------------------------------------------------------
/components/MagicButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | /**
4 | * UI: border magic from tailwind css btns
5 | * Link: https://ui.aceternity.com/components/tailwindcss-buttons
6 | *
7 | * change border radius to rounded-lg
8 | * add margin of md:mt-10
9 | * remove focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50
10 | */
11 | const MagicButton = ({
12 | title,
13 | icon,
14 | position,
15 | handleClick,
16 | otherClasses,
17 | }: {
18 | title: string;
19 | icon: React.ReactNode;
20 | position: string;
21 | handleClick?: () => void;
22 | otherClasses?: string;
23 | }) => {
24 | return (
25 |
29 |
30 |
31 | {/* remove px-3 py-1, add px-5 gap-2 */}
32 |
36 | {position === "left" && icon}
37 | {title}
38 | {position === "right" && icon}
39 |
40 |
41 | );
42 | };
43 |
44 | export default MagicButton;
45 |
--------------------------------------------------------------------------------
/components/RecentProjects.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { FaLocationArrow } from "react-icons/fa6";
4 |
5 | import { projects } from "@/data";
6 | import { PinContainer } from "./ui/Pin";
7 |
8 | const RecentProjects = () => {
9 | return (
10 |
11 |
12 | A small selection of{" "}
13 | recent projects
14 |
15 |
16 | {projects.map((item) => (
17 |
21 |
25 |
26 |
30 |
31 |
32 |
37 |
38 |
39 |
40 | {item.title}
41 |
42 |
43 |
50 | {item.des}
51 |
52 |
53 |
54 |
55 | {item.iconLists.map((icon, index) => (
56 |
63 |
64 |
65 | ))}
66 |
67 |
68 |
69 |
70 | Check Live Site
71 |
72 |
73 |
74 |
75 |
76 |
77 | ))}
78 |
79 |
80 | );
81 | };
82 |
83 | export default RecentProjects;
84 |
--------------------------------------------------------------------------------
/components/ui/BentoGrid.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { IoCopyOutline } from "react-icons/io5";
3 |
4 | // Also install this npm i --save-dev @types/react-lottie
5 | import Lottie from "react-lottie";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 |
10 | import { BackgroundGradientAnimation } from "./GradientBg";
11 | import GridGlobe from "./GridGlobe";
12 | import animationData from "@/data/confetti.json";
13 | import MagicButton from "../MagicButton";
14 |
15 | export const BentoGrid = ({
16 | className,
17 | children,
18 | }: {
19 | className?: string;
20 | children?: React.ReactNode;
21 | }) => {
22 | return (
23 |
30 | {children}
31 |
32 | );
33 | };
34 |
35 | export const BentoGridItem = ({
36 | className,
37 | id,
38 | title,
39 | description,
40 | // remove unecessary things here
41 | img,
42 | imgClassName,
43 | titleClassName,
44 | spareImg,
45 | }: {
46 | className?: string;
47 | id: number;
48 | title?: string | React.ReactNode;
49 | description?: string | React.ReactNode;
50 | img?: string;
51 | imgClassName?: string;
52 | titleClassName?: string;
53 | spareImg?: string;
54 | }) => {
55 | const leftLists = ["ReactJS", "Express", "Typescript"];
56 | const rightLists = ["VueJS", "NuxtJS", "GraphQL"];
57 |
58 | const [copied, setCopied] = useState(false);
59 |
60 | const defaultOptions = {
61 | loop: copied,
62 | autoplay: copied,
63 | animationData: animationData,
64 | rendererSettings: {
65 | preserveAspectRatio: "xMidYMid slice",
66 | },
67 | };
68 |
69 | const handleCopy = () => {
70 | const text = "hsu@jsmastery.pro";
71 | navigator.clipboard.writeText(text);
72 | setCopied(true);
73 | };
74 |
75 | return (
76 |
90 | {/* add img divs */}
91 |
92 |
93 | {img && (
94 |
99 | )}
100 |
101 |
105 | {spareImg && (
106 |
112 | )}
113 |
114 | {id === 6 && (
115 | // add background animation , remove the p tag
116 |
117 |
118 |
119 | )}
120 |
121 |
127 | {/* change the order of the title and des, font-extralight, remove text-xs text-neutral-600 dark:text-neutral-300 , change the text-color */}
128 |
129 | {description}
130 |
131 | {/* add text-3xl max-w-96 , remove text-neutral-600 dark:text-neutral-300*/}
132 | {/* remove mb-2 mt-2 */}
133 |
136 | {title}
137 |
138 |
139 | {/* for the github 3d globe */}
140 | {id === 2 &&
}
141 |
142 | {/* Tech stack list div */}
143 | {id === 3 && (
144 |
145 | {/* tech stack lists */}
146 |
147 | {leftLists.map((item, i) => (
148 |
153 | {item}
154 |
155 | ))}
156 |
157 |
158 |
159 |
160 | {rightLists.map((item, i) => (
161 |
166 | {item}
167 |
168 | ))}
169 |
170 |
171 | )}
172 | {id === 6 && (
173 |
174 | {/* button border magic from tailwind css buttons */}
175 | {/* add rounded-md h-8 md:h-8, remove rounded-full */}
176 | {/* remove focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50 */}
177 | {/* add handleCopy() for the copy the text */}
178 |
182 | {/*
*/}
183 |
184 |
185 |
186 |
}
189 | position="left"
190 | handleClick={handleCopy}
191 | otherClasses="!bg-[#161A31]"
192 | />
193 |
194 | )}
195 |
196 |
197 |
198 | );
199 | };
200 |
--------------------------------------------------------------------------------
/components/ui/CanvasRevealEffect.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { cn } from "@/lib/utils";
3 | import { Canvas, useFrame, useThree } from "@react-three/fiber";
4 | import React, { useMemo, useRef } from "react";
5 | import * as THREE from "three";
6 |
7 | export const CanvasRevealEffect = ({
8 | animationSpeed = 0.4,
9 | opacities = [0.3, 0.3, 0.3, 0.5, 0.5, 0.5, 0.8, 0.8, 0.8, 1],
10 | colors = [[0, 255, 255]],
11 | containerClassName,
12 | dotSize,
13 | showGradient = true,
14 | }: {
15 | /**
16 | * 0.1 - slower
17 | * 1.0 - faster
18 | */
19 | animationSpeed?: number;
20 | opacities?: number[];
21 | colors?: number[][];
22 | containerClassName?: string;
23 | dotSize?: number;
24 | showGradient?: boolean;
25 | }) => {
26 | return (
27 |
28 |
29 |
43 |
44 | {showGradient && (
45 |
46 | )}
47 |
48 | );
49 | };
50 |
51 | interface DotMatrixProps {
52 | colors?: number[][];
53 | opacities?: number[];
54 | totalSize?: number;
55 | dotSize?: number;
56 | shader?: string;
57 | center?: ("x" | "y")[];
58 | }
59 |
60 | const DotMatrix: React.FC = ({
61 | colors = [[0, 0, 0]],
62 | opacities = [0.04, 0.04, 0.04, 0.04, 0.04, 0.08, 0.08, 0.08, 0.08, 0.14],
63 | totalSize = 4,
64 | dotSize = 2,
65 | shader = "",
66 | center = ["x", "y"],
67 | }) => {
68 | const uniforms = React.useMemo(() => {
69 | let colorsArray = [
70 | colors[0],
71 | colors[0],
72 | colors[0],
73 | colors[0],
74 | colors[0],
75 | colors[0],
76 | ];
77 | if (colors.length === 2) {
78 | colorsArray = [
79 | colors[0],
80 | colors[0],
81 | colors[0],
82 | colors[1],
83 | colors[1],
84 | colors[1],
85 | ];
86 | } else if (colors.length === 3) {
87 | colorsArray = [
88 | colors[0],
89 | colors[0],
90 | colors[1],
91 | colors[1],
92 | colors[2],
93 | colors[2],
94 | ];
95 | }
96 |
97 | return {
98 | u_colors: {
99 | value: colorsArray.map((color) => [
100 | color[0] / 255,
101 | color[1] / 255,
102 | color[2] / 255,
103 | ]),
104 | type: "uniform3fv",
105 | },
106 | u_opacities: {
107 | value: opacities,
108 | type: "uniform1fv",
109 | },
110 | u_total_size: {
111 | value: totalSize,
112 | type: "uniform1f",
113 | },
114 | u_dot_size: {
115 | value: dotSize,
116 | type: "uniform1f",
117 | },
118 | };
119 | }, [colors, opacities, totalSize, dotSize]);
120 |
121 | return (
122 |
175 | );
176 | };
177 |
178 | type Uniforms = {
179 | [key: string]: {
180 | value: number[] | number[][] | number;
181 | type: string;
182 | };
183 | };
184 | const ShaderMaterial = ({
185 | source,
186 | uniforms,
187 | maxFps = 60,
188 | }: {
189 | source: string;
190 | hovered?: boolean;
191 | maxFps?: number;
192 | uniforms: Uniforms;
193 | }) => {
194 | const { size } = useThree();
195 | const ref = useRef();
196 | let lastFrameTime = 0;
197 |
198 | useFrame(({ clock }) => {
199 | if (!ref.current) return;
200 | const timestamp = clock.getElapsedTime();
201 | if (timestamp - lastFrameTime < 1 / maxFps) {
202 | return;
203 | }
204 | lastFrameTime = timestamp;
205 |
206 | const material: any = ref.current.material;
207 | const timeLocation = material.uniforms.u_time;
208 | timeLocation.value = timestamp;
209 | });
210 |
211 | const getUniforms = () => {
212 | const preparedUniforms: any = {};
213 |
214 | for (const uniformName in uniforms) {
215 | const uniform: any = uniforms[uniformName];
216 |
217 | switch (uniform.type) {
218 | case "uniform1f":
219 | preparedUniforms[uniformName] = { value: uniform.value, type: "1f" };
220 | break;
221 | case "uniform3f":
222 | preparedUniforms[uniformName] = {
223 | value: new THREE.Vector3().fromArray(uniform.value),
224 | type: "3f",
225 | };
226 | break;
227 | case "uniform1fv":
228 | preparedUniforms[uniformName] = { value: uniform.value, type: "1fv" };
229 | break;
230 | case "uniform3fv":
231 | preparedUniforms[uniformName] = {
232 | value: uniform.value.map((v: number[]) =>
233 | new THREE.Vector3().fromArray(v)
234 | ),
235 | type: "3fv",
236 | };
237 | break;
238 | case "uniform2f":
239 | preparedUniforms[uniformName] = {
240 | value: new THREE.Vector2().fromArray(uniform.value),
241 | type: "2f",
242 | };
243 | break;
244 | default:
245 | console.error(`Invalid uniform type for '${uniformName}'.`);
246 | break;
247 | }
248 | }
249 |
250 | preparedUniforms["u_time"] = { value: 0, type: "1f" };
251 | preparedUniforms["u_resolution"] = {
252 | value: new THREE.Vector2(size.width * 2, size.height * 2),
253 | }; // Initialize u_resolution
254 | return preparedUniforms;
255 | };
256 |
257 | // Shader material
258 | const material = useMemo(() => {
259 | const materialObject = new THREE.ShaderMaterial({
260 | vertexShader: `
261 | precision mediump float;
262 | in vec2 coordinates;
263 | uniform vec2 u_resolution;
264 | out vec2 fragCoord;
265 | void main(){
266 | float x = position.x;
267 | float y = position.y;
268 | gl_Position = vec4(x, y, 0.0, 1.0);
269 | fragCoord = (position.xy + vec2(1.0)) * 0.5 * u_resolution;
270 | fragCoord.y = u_resolution.y - fragCoord.y;
271 | }
272 | `,
273 | fragmentShader: source,
274 | uniforms: getUniforms(),
275 | glslVersion: THREE.GLSL3,
276 | blending: THREE.CustomBlending,
277 | blendSrc: THREE.SrcAlphaFactor,
278 | blendDst: THREE.OneFactor,
279 | });
280 |
281 | return materialObject;
282 | }, [size.width, size.height, source]);
283 |
284 | return (
285 |
286 |
287 |
288 |
289 | );
290 | };
291 |
292 | const Shader: React.FC = ({ source, uniforms, maxFps = 60 }) => {
293 | return (
294 |
295 |
296 |
297 | );
298 | };
299 | interface ShaderProps {
300 | source: string;
301 | uniforms: {
302 | [key: string]: {
303 | value: number[] | number[][] | number;
304 | type: string;
305 | };
306 | };
307 | maxFps?: number;
308 | }
309 |
--------------------------------------------------------------------------------
/components/ui/FloatingNavbar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import {
4 | motion,
5 | AnimatePresence,
6 | useScroll,
7 | useMotionValueEvent,
8 | } from "framer-motion";
9 | import Link from "next/link";
10 | import { cn } from "@/lib/utils";
11 |
12 | export const FloatingNav = ({
13 | navItems,
14 | className,
15 | }: {
16 | navItems: {
17 | name: string;
18 | link: string;
19 | icon?: JSX.Element;
20 | }[];
21 | className?: string;
22 | }) => {
23 | const { scrollYProgress } = useScroll();
24 |
25 | // set true for the initial state so that nav bar is visible in the hero section
26 | const [visible, setVisible] = useState(true);
27 |
28 | useMotionValueEvent(scrollYProgress, "change", (current) => {
29 | // Check if current is not undefined and is a number
30 | if (typeof current === "number") {
31 | let direction = current! - scrollYProgress.getPrevious()!;
32 |
33 | if (scrollYProgress.get() < 0.05) {
34 | // also set true for the initial state
35 | setVisible(true);
36 | } else {
37 | if (direction < 0) {
38 | setVisible(true);
39 | } else {
40 | setVisible(false);
41 | }
42 | }
43 | }
44 | });
45 |
46 | return (
47 |
48 |
74 | {navItems.map((navItem: any, idx: number) => (
75 |
82 | {navItem.icon}
83 | {/* add !cursor-pointer */}
84 | {/* remove hidden sm:block for the mobile responsive */}
85 | {navItem.name}
86 |
87 | ))}
88 | {/* remove this login btn */}
89 | {/*
90 | Login
91 |
92 | */}
93 |
94 |
95 | );
96 | };
97 |
--------------------------------------------------------------------------------
/components/ui/Globe.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useEffect, useRef, useState } from "react";
3 | import { Color, Scene, Fog, PerspectiveCamera, Vector3 } from "three";
4 | import ThreeGlobe from "three-globe";
5 | import { useThree, Object3DNode, Canvas, extend } from "@react-three/fiber";
6 | import { OrbitControls } from "@react-three/drei";
7 | import countries from "@/data/globe.json";
8 | declare module "@react-three/fiber" {
9 | interface ThreeElements {
10 | threeGlobe: Object3DNode;
11 | }
12 | }
13 |
14 | extend({ ThreeGlobe });
15 |
16 | const RING_PROPAGATION_SPEED = 3;
17 | const aspect = 1.2;
18 | const cameraZ = 300;
19 |
20 | type Position = {
21 | order: number;
22 | startLat: number;
23 | startLng: number;
24 | endLat: number;
25 | endLng: number;
26 | arcAlt: number;
27 | color: string;
28 | };
29 |
30 | export type GlobeConfig = {
31 | pointSize?: number;
32 | globeColor?: string;
33 | showAtmosphere?: boolean;
34 | atmosphereColor?: string;
35 | atmosphereAltitude?: number;
36 | emissive?: string;
37 | emissiveIntensity?: number;
38 | shininess?: number;
39 | polygonColor?: string;
40 | ambientLight?: string;
41 | directionalLeftLight?: string;
42 | directionalTopLight?: string;
43 | pointLight?: string;
44 | arcTime?: number;
45 | arcLength?: number;
46 | rings?: number;
47 | maxRings?: number;
48 | initialPosition?: {
49 | lat: number;
50 | lng: number;
51 | };
52 | autoRotate?: boolean;
53 | autoRotateSpeed?: number;
54 | };
55 |
56 | interface WorldProps {
57 | globeConfig: GlobeConfig;
58 | data: Position[];
59 | }
60 |
61 | let numbersOfRings = [0];
62 |
63 | export function Globe({ globeConfig, data }: WorldProps) {
64 | const [globeData, setGlobeData] = useState<
65 | | {
66 | size: number;
67 | order: number;
68 | color: (t: number) => string;
69 | lat: number;
70 | lng: number;
71 | }[]
72 | | null
73 | >(null);
74 |
75 | const globeRef = useRef(null);
76 |
77 | const defaultProps = {
78 | pointSize: 1,
79 | atmosphereColor: "#ffffff",
80 | showAtmosphere: true,
81 | atmosphereAltitude: 0.1,
82 | polygonColor: "rgba(255,255,255,0.7)",
83 | globeColor: "#1d072e",
84 | emissive: "#000000",
85 | emissiveIntensity: 0.1,
86 | shininess: 0.9,
87 | arcTime: 2000,
88 | arcLength: 0.9,
89 | rings: 1,
90 | maxRings: 3,
91 | ...globeConfig,
92 | };
93 |
94 | useEffect(() => {
95 | if (globeRef.current) {
96 | _buildData();
97 | _buildMaterial();
98 | }
99 | }, [globeRef.current]);
100 |
101 | const _buildMaterial = () => {
102 | if (!globeRef.current) return;
103 |
104 | const globeMaterial = globeRef.current.globeMaterial() as unknown as {
105 | color: Color;
106 | emissive: Color;
107 | emissiveIntensity: number;
108 | shininess: number;
109 | };
110 | globeMaterial.color = new Color(globeConfig.globeColor);
111 | globeMaterial.emissive = new Color(globeConfig.emissive);
112 | globeMaterial.emissiveIntensity = globeConfig.emissiveIntensity || 0.1;
113 | globeMaterial.shininess = globeConfig.shininess || 0.9;
114 | };
115 |
116 | const _buildData = () => {
117 | const arcs = data;
118 | let points = [];
119 | for (let i = 0; i < arcs.length; i++) {
120 | const arc = arcs[i];
121 | const rgb = hexToRgb(arc.color) as { r: number; g: number; b: number };
122 | points.push({
123 | size: defaultProps.pointSize,
124 | order: arc.order,
125 | color: (t: number) => `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${1 - t})`,
126 | lat: arc.startLat,
127 | lng: arc.startLng,
128 | });
129 | points.push({
130 | size: defaultProps.pointSize,
131 | order: arc.order,
132 | color: (t: number) => `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${1 - t})`,
133 | lat: arc.endLat,
134 | lng: arc.endLng,
135 | });
136 | }
137 |
138 | // remove duplicates for same lat and lng
139 | const filteredPoints = points.filter(
140 | (v, i, a) =>
141 | a.findIndex((v2) =>
142 | ["lat", "lng"].every(
143 | (k) => v2[k as "lat" | "lng"] === v[k as "lat" | "lng"]
144 | )
145 | ) === i
146 | );
147 |
148 | setGlobeData(filteredPoints);
149 | };
150 |
151 | useEffect(() => {
152 | if (globeRef.current && globeData) {
153 | globeRef.current
154 | .hexPolygonsData(countries.features)
155 | .hexPolygonResolution(3)
156 | .hexPolygonMargin(0.7)
157 | .showAtmosphere(defaultProps.showAtmosphere)
158 | .atmosphereColor(defaultProps.atmosphereColor)
159 | .atmosphereAltitude(defaultProps.atmosphereAltitude)
160 | .hexPolygonColor((e) => {
161 | return defaultProps.polygonColor;
162 | });
163 | startAnimation();
164 | }
165 | }, [globeData]);
166 |
167 | const startAnimation = () => {
168 | if (!globeRef.current || !globeData) return;
169 |
170 | globeRef.current
171 | .arcsData(data)
172 | .arcStartLat((d) => (d as { startLat: number }).startLat * 1)
173 | .arcStartLng((d) => (d as { startLng: number }).startLng * 1)
174 | .arcEndLat((d) => (d as { endLat: number }).endLat * 1)
175 | .arcEndLng((d) => (d as { endLng: number }).endLng * 1)
176 | .arcColor((e: any) => (e as { color: string }).color)
177 | .arcAltitude((e) => {
178 | return (e as { arcAlt: number }).arcAlt * 1;
179 | })
180 | .arcStroke((e) => {
181 | return [0.32, 0.28, 0.3][Math.round(Math.random() * 2)];
182 | })
183 | .arcDashLength(defaultProps.arcLength)
184 | .arcDashInitialGap((e) => (e as { order: number }).order * 1)
185 | .arcDashGap(15)
186 | .arcDashAnimateTime((e) => defaultProps.arcTime);
187 |
188 | globeRef.current
189 | .pointsData(data)
190 | .pointColor((e) => (e as { color: string }).color)
191 | .pointsMerge(true)
192 | .pointAltitude(0.0)
193 | .pointRadius(2);
194 |
195 | globeRef.current
196 | .ringsData([])
197 | .ringColor((e: any) => (t: any) => e.color(t))
198 | .ringMaxRadius(defaultProps.maxRings)
199 | .ringPropagationSpeed(RING_PROPAGATION_SPEED)
200 | .ringRepeatPeriod(
201 | (defaultProps.arcTime * defaultProps.arcLength) / defaultProps.rings
202 | );
203 | };
204 |
205 | useEffect(() => {
206 | if (!globeRef.current || !globeData) return;
207 |
208 | const interval = setInterval(() => {
209 | if (!globeRef.current || !globeData) return;
210 | numbersOfRings = genRandomNumbers(
211 | 0,
212 | data.length,
213 | Math.floor((data.length * 4) / 5)
214 | );
215 |
216 | globeRef.current.ringsData(
217 | globeData.filter((d, i) => numbersOfRings.includes(i))
218 | );
219 | }, 2000);
220 |
221 | return () => {
222 | clearInterval(interval);
223 | };
224 | }, [globeRef.current, globeData]);
225 |
226 | return (
227 | <>
228 |
229 | >
230 | );
231 | }
232 |
233 | export function WebGLRendererConfig() {
234 | const { gl, size } = useThree();
235 |
236 | useEffect(() => {
237 | gl.setPixelRatio(window.devicePixelRatio);
238 | gl.setSize(size.width, size.height);
239 | gl.setClearColor(0xffaaff, 0);
240 | }, []);
241 |
242 | return null;
243 | }
244 |
245 | export function World(props: WorldProps) {
246 | const { globeConfig } = props;
247 | const scene = new Scene();
248 | scene.fog = new Fog(0xffffff, 400, 2000);
249 | return (
250 |
251 |
252 |
253 |
257 |
261 |
266 |
267 |
277 |
278 | );
279 | }
280 |
281 | export function hexToRgb(hex: string) {
282 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
283 | hex = hex.replace(shorthandRegex, function (m, r, g, b) {
284 | return r + r + g + g + b + b;
285 | });
286 |
287 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
288 | return result
289 | ? {
290 | r: parseInt(result[1], 16),
291 | g: parseInt(result[2], 16),
292 | b: parseInt(result[3], 16),
293 | }
294 | : null;
295 | }
296 |
297 | export function genRandomNumbers(min: number, max: number, count: number) {
298 | const arr = [];
299 | while (arr.length < count) {
300 | const r = Math.floor(Math.random() * (max - min)) + min;
301 | if (arr.indexOf(r) === -1) arr.push(r);
302 | }
303 |
304 | return arr;
305 | }
306 |
--------------------------------------------------------------------------------
/components/ui/GradientBg.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { cn } from "@/lib/utils";
3 | import { useEffect, useRef, useState } from "react";
4 |
5 | export const BackgroundGradientAnimation = ({
6 | gradientBackgroundStart = "rgb(108, 0, 162)",
7 | gradientBackgroundEnd = "rgb(0, 17, 82)",
8 | firstColor = "18, 113, 255",
9 | secondColor = "221, 74, 255",
10 | thirdColor = "100, 220, 255",
11 | fourthColor = "200, 50, 50",
12 | fifthColor = "180, 180, 50",
13 | pointerColor = "140, 100, 255",
14 | size = "80%",
15 | blendingValue = "hard-light",
16 | children,
17 | className,
18 | interactive = true,
19 | containerClassName,
20 | }: {
21 | gradientBackgroundStart?: string;
22 | gradientBackgroundEnd?: string;
23 | firstColor?: string;
24 | secondColor?: string;
25 | thirdColor?: string;
26 | fourthColor?: string;
27 | fifthColor?: string;
28 | pointerColor?: string;
29 | size?: string;
30 | blendingValue?: string;
31 | children?: React.ReactNode;
32 | className?: string;
33 | interactive?: boolean;
34 | containerClassName?: string;
35 | }) => {
36 | const interactiveRef = useRef(null);
37 |
38 | const [curX, setCurX] = useState(0);
39 | const [curY, setCurY] = useState(0);
40 | const [tgX, setTgX] = useState(0);
41 | const [tgY, setTgY] = useState(0);
42 | useEffect(() => {
43 | document.body.style.setProperty(
44 | "--gradient-background-start",
45 | gradientBackgroundStart
46 | );
47 | document.body.style.setProperty(
48 | "--gradient-background-end",
49 | gradientBackgroundEnd
50 | );
51 | document.body.style.setProperty("--first-color", firstColor);
52 | document.body.style.setProperty("--second-color", secondColor);
53 | document.body.style.setProperty("--third-color", thirdColor);
54 | document.body.style.setProperty("--fourth-color", fourthColor);
55 | document.body.style.setProperty("--fifth-color", fifthColor);
56 | document.body.style.setProperty("--pointer-color", pointerColor);
57 | document.body.style.setProperty("--size", size);
58 | document.body.style.setProperty("--blending-value", blendingValue);
59 | }, []);
60 |
61 | useEffect(() => {
62 | function move() {
63 | if (!interactiveRef.current) {
64 | return;
65 | }
66 | setCurX(curX + (tgX - curX) / 20);
67 | setCurY(curY + (tgY - curY) / 20);
68 | interactiveRef.current.style.transform = `translate(${Math.round(
69 | curX
70 | )}px, ${Math.round(curY)}px)`;
71 | }
72 |
73 | move();
74 | }, [tgX, tgY]);
75 |
76 | const handleMouseMove = (event: React.MouseEvent) => {
77 | if (interactiveRef.current) {
78 | const rect = interactiveRef.current.getBoundingClientRect();
79 | setTgX(event.clientX - rect.left);
80 | setTgY(event.clientY - rect.top);
81 | }
82 | };
83 |
84 | const [isSafari, setIsSafari] = useState(false);
85 | useEffect(() => {
86 | setIsSafari(/^((?!chrome|android).)*safari/i.test(navigator.userAgent));
87 | }, []);
88 |
89 | return (
90 |
96 |
97 |
98 |
99 |
104 |
110 |
111 |
112 |
113 |
114 |
{children}
115 |
121 |
130 |
139 |
148 |
157 |
166 |
167 | {interactive && (
168 |
177 | )}
178 |
179 |
180 | );
181 | };
182 |
--------------------------------------------------------------------------------
/components/ui/GridGlobe.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { motion } from "framer-motion";
4 | import dynamic from "next/dynamic";
5 |
6 | const World = dynamic(() => import("./Globe").then((m) => m.World), {
7 | ssr: false,
8 | });
9 |
10 | const GridGlobe = () => {
11 | const globeConfig = {
12 | pointSize: 4,
13 | globeColor: "#062056",
14 | showAtmosphere: true,
15 | atmosphereColor: "#FFFFFF",
16 | atmosphereAltitude: 0.1,
17 | emissive: "#062056",
18 | emissiveIntensity: 0.1,
19 | shininess: 0.9,
20 | polygonColor: "rgba(255,255,255,0.7)",
21 | ambientLight: "#38bdf8",
22 | directionalLeftLight: "#ffffff",
23 | directionalTopLight: "#ffffff",
24 | pointLight: "#ffffff",
25 | arcTime: 1000,
26 | arcLength: 0.9,
27 | rings: 1,
28 | maxRings: 3,
29 | initialPosition: { lat: 22.3193, lng: 114.1694 },
30 | autoRotate: true,
31 | autoRotateSpeed: 0.5,
32 | };
33 | const colors = ["#06b6d4", "#3b82f6", "#6366f1"];
34 | const sampleArcs = [
35 | {
36 | order: 1,
37 | startLat: -19.885592,
38 | startLng: -43.951191,
39 | endLat: -22.9068,
40 | endLng: -43.1729,
41 | arcAlt: 0.1,
42 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
43 | },
44 | {
45 | order: 1,
46 | startLat: 28.6139,
47 | startLng: 77.209,
48 | endLat: 3.139,
49 | endLng: 101.6869,
50 | arcAlt: 0.2,
51 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
52 | },
53 | {
54 | order: 1,
55 | startLat: -19.885592,
56 | startLng: -43.951191,
57 | endLat: -1.303396,
58 | endLng: 36.852443,
59 | arcAlt: 0.5,
60 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
61 | },
62 | {
63 | order: 2,
64 | startLat: 1.3521,
65 | startLng: 103.8198,
66 | endLat: 35.6762,
67 | endLng: 139.6503,
68 | arcAlt: 0.2,
69 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
70 | },
71 | {
72 | order: 2,
73 | startLat: 51.5072,
74 | startLng: -0.1276,
75 | endLat: 3.139,
76 | endLng: 101.6869,
77 | arcAlt: 0.3,
78 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
79 | },
80 | {
81 | order: 2,
82 | startLat: -15.785493,
83 | startLng: -47.909029,
84 | endLat: 36.162809,
85 | endLng: -115.119411,
86 | arcAlt: 0.3,
87 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
88 | },
89 | {
90 | order: 3,
91 | startLat: -33.8688,
92 | startLng: 151.2093,
93 | endLat: 22.3193,
94 | endLng: 114.1694,
95 | arcAlt: 0.3,
96 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
97 | },
98 | {
99 | order: 3,
100 | startLat: 21.3099,
101 | startLng: -157.8581,
102 | endLat: 40.7128,
103 | endLng: -74.006,
104 | arcAlt: 0.3,
105 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
106 | },
107 | {
108 | order: 3,
109 | startLat: -6.2088,
110 | startLng: 106.8456,
111 | endLat: 51.5072,
112 | endLng: -0.1276,
113 | arcAlt: 0.3,
114 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
115 | },
116 | {
117 | order: 4,
118 | startLat: 11.986597,
119 | startLng: 8.571831,
120 | endLat: -15.595412,
121 | endLng: -56.05918,
122 | arcAlt: 0.5,
123 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
124 | },
125 | {
126 | order: 4,
127 | startLat: -34.6037,
128 | startLng: -58.3816,
129 | endLat: 22.3193,
130 | endLng: 114.1694,
131 | arcAlt: 0.7,
132 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
133 | },
134 | {
135 | order: 4,
136 | startLat: 51.5072,
137 | startLng: -0.1276,
138 | endLat: 48.8566,
139 | endLng: -2.3522,
140 | arcAlt: 0.1,
141 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
142 | },
143 | {
144 | order: 5,
145 | startLat: 14.5995,
146 | startLng: 120.9842,
147 | endLat: 51.5072,
148 | endLng: -0.1276,
149 | arcAlt: 0.3,
150 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
151 | },
152 | {
153 | order: 5,
154 | startLat: 1.3521,
155 | startLng: 103.8198,
156 | endLat: -33.8688,
157 | endLng: 151.2093,
158 | arcAlt: 0.2,
159 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
160 | },
161 | {
162 | order: 5,
163 | startLat: 34.0522,
164 | startLng: -118.2437,
165 | endLat: 48.8566,
166 | endLng: -2.3522,
167 | arcAlt: 0.2,
168 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
169 | },
170 | {
171 | order: 6,
172 | startLat: -15.432563,
173 | startLng: 28.315853,
174 | endLat: 1.094136,
175 | endLng: -63.34546,
176 | arcAlt: 0.7,
177 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
178 | },
179 | {
180 | order: 6,
181 | startLat: 37.5665,
182 | startLng: 126.978,
183 | endLat: 35.6762,
184 | endLng: 139.6503,
185 | arcAlt: 0.1,
186 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
187 | },
188 | {
189 | order: 6,
190 | startLat: 22.3193,
191 | startLng: 114.1694,
192 | endLat: 51.5072,
193 | endLng: -0.1276,
194 | arcAlt: 0.3,
195 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
196 | },
197 | {
198 | order: 7,
199 | startLat: -19.885592,
200 | startLng: -43.951191,
201 | endLat: -15.595412,
202 | endLng: -56.05918,
203 | arcAlt: 0.1,
204 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
205 | },
206 | {
207 | order: 7,
208 | startLat: 48.8566,
209 | startLng: -2.3522,
210 | endLat: 52.52,
211 | endLng: 13.405,
212 | arcAlt: 0.1,
213 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
214 | },
215 | {
216 | order: 7,
217 | startLat: 52.52,
218 | startLng: 13.405,
219 | endLat: 34.0522,
220 | endLng: -118.2437,
221 | arcAlt: 0.2,
222 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
223 | },
224 | {
225 | order: 8,
226 | startLat: -8.833221,
227 | startLng: 13.264837,
228 | endLat: -33.936138,
229 | endLng: 18.436529,
230 | arcAlt: 0.2,
231 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
232 | },
233 | {
234 | order: 8,
235 | startLat: 49.2827,
236 | startLng: -123.1207,
237 | endLat: 52.3676,
238 | endLng: 4.9041,
239 | arcAlt: 0.2,
240 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
241 | },
242 | {
243 | order: 8,
244 | startLat: 1.3521,
245 | startLng: 103.8198,
246 | endLat: 40.7128,
247 | endLng: -74.006,
248 | arcAlt: 0.5,
249 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
250 | },
251 | {
252 | order: 9,
253 | startLat: 51.5072,
254 | startLng: -0.1276,
255 | endLat: 34.0522,
256 | endLng: -118.2437,
257 | arcAlt: 0.2,
258 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
259 | },
260 | {
261 | order: 9,
262 | startLat: 22.3193,
263 | startLng: 114.1694,
264 | endLat: -22.9068,
265 | endLng: -43.1729,
266 | arcAlt: 0.7,
267 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
268 | },
269 | {
270 | order: 9,
271 | startLat: 1.3521,
272 | startLng: 103.8198,
273 | endLat: -34.6037,
274 | endLng: -58.3816,
275 | arcAlt: 0.5,
276 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
277 | },
278 | {
279 | order: 10,
280 | startLat: -22.9068,
281 | startLng: -43.1729,
282 | endLat: 28.6139,
283 | endLng: 77.209,
284 | arcAlt: 0.7,
285 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
286 | },
287 | {
288 | order: 10,
289 | startLat: 34.0522,
290 | startLng: -118.2437,
291 | endLat: 31.2304,
292 | endLng: 121.4737,
293 | arcAlt: 0.3,
294 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
295 | },
296 | {
297 | order: 10,
298 | startLat: -6.2088,
299 | startLng: 106.8456,
300 | endLat: 52.3676,
301 | endLng: 4.9041,
302 | arcAlt: 0.3,
303 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
304 | },
305 | {
306 | order: 11,
307 | startLat: 41.9028,
308 | startLng: 12.4964,
309 | endLat: 34.0522,
310 | endLng: -118.2437,
311 | arcAlt: 0.2,
312 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
313 | },
314 | {
315 | order: 11,
316 | startLat: -6.2088,
317 | startLng: 106.8456,
318 | endLat: 31.2304,
319 | endLng: 121.4737,
320 | arcAlt: 0.2,
321 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
322 | },
323 | {
324 | order: 11,
325 | startLat: 22.3193,
326 | startLng: 114.1694,
327 | endLat: 1.3521,
328 | endLng: 103.8198,
329 | arcAlt: 0.2,
330 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
331 | },
332 | {
333 | order: 12,
334 | startLat: 34.0522,
335 | startLng: -118.2437,
336 | endLat: 37.7749,
337 | endLng: -122.4194,
338 | arcAlt: 0.1,
339 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
340 | },
341 | {
342 | order: 12,
343 | startLat: 35.6762,
344 | startLng: 139.6503,
345 | endLat: 22.3193,
346 | endLng: 114.1694,
347 | arcAlt: 0.2,
348 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
349 | },
350 | {
351 | order: 12,
352 | startLat: 22.3193,
353 | startLng: 114.1694,
354 | endLat: 34.0522,
355 | endLng: -118.2437,
356 | arcAlt: 0.3,
357 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
358 | },
359 | {
360 | order: 13,
361 | startLat: 52.52,
362 | startLng: 13.405,
363 | endLat: 22.3193,
364 | endLng: 114.1694,
365 | arcAlt: 0.3,
366 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
367 | },
368 | {
369 | order: 13,
370 | startLat: 11.986597,
371 | startLng: 8.571831,
372 | endLat: 35.6762,
373 | endLng: 139.6503,
374 | arcAlt: 0.3,
375 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
376 | },
377 | {
378 | order: 13,
379 | startLat: -22.9068,
380 | startLng: -43.1729,
381 | endLat: -34.6037,
382 | endLng: -58.3816,
383 | arcAlt: 0.1,
384 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
385 | },
386 | {
387 | order: 14,
388 | startLat: -33.936138,
389 | startLng: 18.436529,
390 | endLat: 21.395643,
391 | endLng: 39.883798,
392 | arcAlt: 0.3,
393 | color: colors[Math.floor(Math.random() * (colors.length - 1))],
394 | },
395 | ];
396 |
397 | return (
398 | // remove dark:bg-black bg-white h-screen md:h-auto w-full flex-row py-20
399 | // change absolute -left-5 top-36, add w-full h-full md:top-40
400 |
401 | {/* remove h-full md:h-[40rem] */}
402 |
403 | {/* remove these text divs */}
404 | {/*
418 |
419 | We sell soap worldwide
420 |
421 |
422 | This globe is interactive and customizable. Have fun with it, and
423 | don't forget to share it.
424 |
425 | */}
426 |
427 | {/* remove -bottom-20 */}
428 |
429 |
430 |
431 |
432 |
433 | );
434 | };
435 | export default GridGlobe;
436 |
--------------------------------------------------------------------------------
/components/ui/HoverBorder.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useEffect, useRef } from "react";
3 |
4 | import { motion } from "framer-motion";
5 | import { cn } from "@/lib/utils";
6 |
7 | type Direction = "TOP" | "LEFT" | "BOTTOM" | "RIGHT";
8 |
9 | export function HoverBorderGradient({
10 | children,
11 | containerClassName,
12 | className,
13 | as: Tag = "button",
14 | duration = 1,
15 | clockwise = true,
16 | ...props
17 | }: React.PropsWithChildren<
18 | {
19 | as?: React.ElementType;
20 | containerClassName?: string;
21 | className?: string;
22 | duration?: number;
23 | clockwise?: boolean;
24 | } & React.HTMLAttributes
25 | >) {
26 | const [hovered, setHovered] = useState(false);
27 | const [direction, setDirection] = useState("TOP");
28 |
29 | const rotateDirection = (currentDirection: Direction): Direction => {
30 | const directions: Direction[] = ["TOP", "LEFT", "BOTTOM", "RIGHT"];
31 | const currentIndex = directions.indexOf(currentDirection);
32 | const nextIndex = clockwise
33 | ? (currentIndex - 1 + directions.length) % directions.length
34 | : (currentIndex + 1) % directions.length;
35 | return directions[nextIndex];
36 | };
37 |
38 | const movingMap: Record = {
39 | TOP: "radial-gradient(20.7% 50% at 50% 0%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
40 | LEFT: "radial-gradient(16.6% 43.1% at 0% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
41 | BOTTOM:
42 | "radial-gradient(20.7% 50% at 50% 100%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
43 | RIGHT:
44 | "radial-gradient(16.2% 41.199999999999996% at 100% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
45 | };
46 |
47 | const highlight =
48 | "radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)";
49 |
50 | useEffect(() => {
51 | if (!hovered) {
52 | const interval = setInterval(() => {
53 | setDirection((prevState) => rotateDirection(prevState));
54 | }, duration * 1000);
55 | return () => clearInterval(interval);
56 | }
57 | }, [hovered]);
58 | return (
59 | ) => {
61 | setHovered(true);
62 | }}
63 | onMouseLeave={() => setHovered(false)}
64 | className={cn(
65 | "relative flex rounded-full border content-center bg-black/20 hover:bg-black/10 transition duration-500 dark:bg-white/20 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-fit",
66 | containerClassName
67 | )}
68 | {...props}
69 | >
70 |
76 | {children}
77 |
78 |
96 |
97 |
98 | );
99 | }
100 |
--------------------------------------------------------------------------------
/components/ui/InfiniteCards.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { cn } from "@/lib/utils";
4 | import React, { useEffect, useState } from "react";
5 |
6 | export const InfiniteMovingCards = ({
7 | items,
8 | direction = "left",
9 | speed = "fast",
10 | pauseOnHover = true,
11 | className,
12 | }: {
13 | items: {
14 | quote: string;
15 | name: string;
16 | title: string;
17 | }[];
18 | direction?: "left" | "right";
19 | speed?: "fast" | "normal" | "slow";
20 | pauseOnHover?: boolean;
21 | className?: string;
22 | }) => {
23 | const containerRef = React.useRef(null);
24 | const scrollerRef = React.useRef(null);
25 |
26 | useEffect(() => {
27 | addAnimation();
28 | }, []);
29 | const [start, setStart] = useState(false);
30 | function addAnimation() {
31 | if (containerRef.current && scrollerRef.current) {
32 | const scrollerContent = Array.from(scrollerRef.current.children);
33 |
34 | scrollerContent.forEach((item) => {
35 | const duplicatedItem = item.cloneNode(true);
36 | if (scrollerRef.current) {
37 | scrollerRef.current.appendChild(duplicatedItem);
38 | }
39 | });
40 |
41 | getDirection();
42 | getSpeed();
43 | setStart(true);
44 | }
45 | }
46 | const getDirection = () => {
47 | if (containerRef.current) {
48 | if (direction === "left") {
49 | containerRef.current.style.setProperty(
50 | "--animation-direction",
51 | "forwards"
52 | );
53 | } else {
54 | containerRef.current.style.setProperty(
55 | "--animation-direction",
56 | "reverse"
57 | );
58 | }
59 | }
60 | };
61 | const getSpeed = () => {
62 | if (containerRef.current) {
63 | if (speed === "fast") {
64 | containerRef.current.style.setProperty("--animation-duration", "20s");
65 | } else if (speed === "normal") {
66 | containerRef.current.style.setProperty("--animation-duration", "40s");
67 | } else {
68 | containerRef.current.style.setProperty("--animation-duration", "80s");
69 | }
70 | }
71 | };
72 | return (
73 |
81 |
90 | {items.map((item, idx) => (
91 |
107 |
108 |
112 | {/* change text color, text-lg */}
113 |
114 | {item.quote}
115 |
116 |
117 | {/* add this div for the profile img */}
118 |
119 |
120 |
121 |
122 | {/* change text color, font-normal to font-bold, text-xl */}
123 |
124 | {item.name}
125 |
126 | {/* change text color */}
127 |
128 | {item.title}
129 |
130 |
131 |
132 |
133 |
134 | ))}
135 |
136 |
137 | );
138 | };
139 |
--------------------------------------------------------------------------------
/components/ui/LayoutGrid.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useRef, useEffect } from "react";
3 | import { motion } from "framer-motion";
4 | import Image from "next/image";
5 | import { cn } from "@/lib/utils";
6 | import { Button } from "./MovingBorders";
7 |
8 | type Card = {
9 | id: number;
10 | content: JSX.Element | React.ReactNode | string;
11 | className: string;
12 | thumbnail: string;
13 | };
14 |
15 | export const LayoutGrid = ({ cards }: { cards: Card[] }) => {
16 | const [selected, setSelected] = useState(null);
17 | const [lastSelected, setLastSelected] = useState(null);
18 |
19 | const handleClick = (card: Card) => {
20 | setLastSelected(selected);
21 | setSelected(card);
22 | };
23 |
24 | const handleOutsideClick = () => {
25 | setLastSelected(selected);
26 | setSelected(null);
27 | };
28 |
29 | return (
30 | // change md:grid-cols-3 to md:grid-cols-4, gap-4 to gap-10
31 |
32 | {cards.map((card, i) => (
33 |
44 |
50 | handleClick(card)}
52 | className={cn(
53 | card.className,
54 | "relative overflow-hidden",
55 | selected?.id === card.id
56 | ? "rounded-lg cursor-pointer absolute inset-0 h-1/2 w-full md:w-1/2 m-auto z-50 flex justify-center items-center flex-wrap flex-col"
57 | : lastSelected?.id === card.id
58 | ? "z-40 bg-white rounded-xl h-full w-full"
59 | : "bg-white rounded-xl h-full w-full"
60 | )}
61 | layout
62 | >
63 | {selected?.id === card.id && }
64 |
65 |
66 |
67 |
68 | ))}
69 |
77 |
78 | );
79 | };
80 |
81 | const BlurImage = ({ card }: { card: Card }) => {
82 | const [loaded, setLoaded] = useState(false);
83 | return (
84 | setLoaded(true)}
90 | className={cn(
91 | "object-cover object-top absolute inset-0 h-full w-full transition duration-200",
92 | loaded ? "blur-none" : "blur-md"
93 | )}
94 | alt="thumbnail"
95 | />
96 | );
97 | };
98 |
99 | const SelectedCard = ({ selected }: { selected: Card | null }) => {
100 | return (
101 |
102 |
111 |
126 | {selected?.content}
127 |
128 |
129 | );
130 | };
131 |
--------------------------------------------------------------------------------
/components/ui/MovingBorders.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import {
4 | motion,
5 | useAnimationFrame,
6 | useMotionTemplate,
7 | useMotionValue,
8 | useTransform,
9 | } from "framer-motion";
10 | import { useRef } from "react";
11 | import { cn } from "@/lib/utils";
12 |
13 | export function Button({
14 | borderRadius = "1.75rem",
15 | children,
16 | as: Component = "button",
17 | containerClassName,
18 | borderClassName,
19 | duration,
20 | className,
21 | ...otherProps
22 | }: {
23 | borderRadius?: string;
24 | children: React.ReactNode;
25 | as?: any;
26 | containerClassName?: string;
27 | borderClassName?: string;
28 | duration?: number;
29 | className?: string;
30 | [key: string]: any;
31 | }) {
32 | return (
33 |
44 |
57 |
58 |
67 | {children}
68 |
69 |
70 | );
71 | }
72 |
73 | export const MovingBorder = ({
74 | children,
75 | duration = 2000,
76 | rx,
77 | ry,
78 | ...otherProps
79 | }: {
80 | children: React.ReactNode;
81 | duration?: number;
82 | rx?: string;
83 | ry?: string;
84 | [key: string]: any;
85 | }) => {
86 | const pathRef = useRef();
87 | const progress = useMotionValue(0);
88 |
89 | useAnimationFrame((time) => {
90 | const length = pathRef.current?.getTotalLength();
91 | if (length) {
92 | const pxPerMillisecond = length / duration;
93 | progress.set((time * pxPerMillisecond) % length);
94 | }
95 | });
96 |
97 | const x = useTransform(
98 | progress,
99 | (val) => pathRef.current?.getPointAtLength(val).x
100 | );
101 | const y = useTransform(
102 | progress,
103 | (val) => pathRef.current?.getPointAtLength(val).y
104 | );
105 |
106 | const transform = useMotionTemplate`translateX(${x}px) translateY(${y}px) translateX(-50%) translateY(-50%)`;
107 |
108 | return (
109 | <>
110 |
118 |
126 |
127 |
136 | {children}
137 |
138 | >
139 | );
140 | };
141 |
--------------------------------------------------------------------------------
/components/ui/Pin.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import { motion } from "framer-motion";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | export const PinContainer = ({
8 | children,
9 | title,
10 | href,
11 | className,
12 | containerClassName,
13 | }: {
14 | children: React.ReactNode;
15 | title?: string;
16 | href?: string;
17 | className?: string;
18 | containerClassName?: string;
19 | }) => {
20 | const [transform, setTransform] = useState(
21 | "translate(-50%,-50%) rotateX(0deg)"
22 | );
23 |
24 | const onMouseEnter = () => {
25 | setTransform("translate(-50%,-50%) rotateX(40deg) scale(0.8)");
26 | };
27 | const onMouseLeave = () => {
28 | setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)");
29 | };
30 |
31 | return (
32 |
59 | );
60 | };
61 |
62 | export const PinPerspective = ({
63 | title,
64 | href,
65 | }: {
66 | title?: string;
67 | href?: string;
68 | }) => {
69 | return (
70 | // change w-96 to w-full
71 |
72 |
73 |
86 |
87 |
94 | <>
95 |
115 |
135 |
155 | >
156 |
157 |
158 | <>
159 |
160 |
161 |
162 |
163 | >
164 |
165 |
166 | );
167 | };
168 |
--------------------------------------------------------------------------------
/components/ui/Spotlight.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 | import React from "react";
3 |
4 | type SpotlightProps = {
5 | className?: string;
6 | fill?: string;
7 | };
8 |
9 | export const Spotlight = ({ className, fill }: SpotlightProps) => {
10 | return (
11 |
20 |
21 |
30 |
31 |
32 |
41 |
42 |
48 |
52 |
53 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/components/ui/TextGenerateEffect.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useEffect } from "react";
3 | import { motion, stagger, useAnimate } from "framer-motion";
4 | import { cn } from "@/lib/utils";
5 |
6 | export const TextGenerateEffect = ({
7 | words,
8 | className,
9 | }: {
10 | words: string;
11 | className?: string;
12 | }) => {
13 | const [scope, animate] = useAnimate();
14 | let wordsArray = words.split(" ");
15 | useEffect(() => {
16 | console.log(wordsArray);
17 | animate(
18 | "span",
19 | {
20 | opacity: 1,
21 | },
22 | {
23 | duration: 2,
24 | delay: stagger(0.2),
25 | }
26 | );
27 | }, [scope.current]);
28 |
29 | const renderWords = () => {
30 | return (
31 |
32 | {wordsArray.map((word, idx) => {
33 | return (
34 | 3 ? "text-purple" : "dark:text-white text-black"
38 | } opacity-0`}
39 | >
40 | {word}{" "}
41 |
42 | );
43 | })}
44 |
45 | );
46 | };
47 |
48 | return (
49 |
50 | {/* mt-4 to my-4 */}
51 |
52 | {/* remove text-2xl from the original */}
53 |
54 | {renderWords()}
55 |
56 |
57 |
58 | );
59 | };
60 |
--------------------------------------------------------------------------------
/data/index.ts:
--------------------------------------------------------------------------------
1 | export const navItems = [
2 | { name: "About", link: "#about" },
3 | { name: "Projects", link: "#projects" },
4 | { name: "Testimonials", link: "#testimonials" },
5 | { name: "Contact", link: "#contact" },
6 | ];
7 |
8 | export const gridItems = [
9 | {
10 | id: 1,
11 | title: "I prioritize client collaboration, fostering open communication ",
12 | description: "",
13 | className: "lg:col-span-3 md:col-span-6 md:row-span-4 lg:min-h-[60vh]",
14 | imgClassName: "w-full h-full",
15 | titleClassName: "justify-end",
16 | img: "/b1.svg",
17 | spareImg: "",
18 | },
19 | {
20 | id: 2,
21 | title: "I'm very flexible with time zone communications",
22 | description: "",
23 | className: "lg:col-span-2 md:col-span-3 md:row-span-2",
24 | imgClassName: "",
25 | titleClassName: "justify-start",
26 | img: "",
27 | spareImg: "",
28 | },
29 | {
30 | id: 3,
31 | title: "My tech stack",
32 | description: "I constantly try to improve",
33 | className: "lg:col-span-2 md:col-span-3 md:row-span-2",
34 | imgClassName: "",
35 | titleClassName: "justify-center",
36 | img: "",
37 | spareImg: "",
38 | },
39 | {
40 | id: 4,
41 | title: "Tech enthusiast with a passion for development.",
42 | description: "",
43 | className: "lg:col-span-2 md:col-span-3 md:row-span-1",
44 | imgClassName: "",
45 | titleClassName: "justify-start",
46 | img: "/grid.svg",
47 | spareImg: "/b4.svg",
48 | },
49 |
50 | {
51 | id: 5,
52 | title: "Currently building a JS Animation library",
53 | description: "The Inside Scoop",
54 | className: "md:col-span-3 md:row-span-2",
55 | imgClassName: "absolute right-0 bottom-0 md:w-96 w-60",
56 | titleClassName: "justify-center md:justify-start lg:justify-center",
57 | img: "/b5.svg",
58 | spareImg: "/grid.svg",
59 | },
60 | {
61 | id: 6,
62 | title: "Do you want to start a project together?",
63 | description: "",
64 | className: "lg:col-span-2 md:col-span-3 md:row-span-1",
65 | imgClassName: "",
66 | titleClassName: "justify-center md:max-w-full max-w-60 text-center",
67 | img: "",
68 | spareImg: "",
69 | },
70 | ];
71 |
72 | export const projects = [
73 | {
74 | id: 1,
75 | title: "3D Solar System Planets to Explore",
76 | des: "Explore the wonders of our solar system with this captivating 3D simulation of the planets using Three.js.",
77 | img: "/p1.svg",
78 | iconLists: ["/re.svg", "/tail.svg", "/ts.svg", "/three.svg", "/fm.svg"],
79 | link: "/ui.earth.com",
80 | },
81 | {
82 | id: 2,
83 | title: "Yoom - Video Conferencing App",
84 | des: "Simplify your video conferencing experience with Yoom. Seamlessly connect with colleagues and friends.",
85 | img: "/p2.svg",
86 | iconLists: ["/next.svg", "/tail.svg", "/ts.svg", "/stream.svg", "/c.svg"],
87 | link: "/ui.yoom.com",
88 | },
89 | {
90 | id: 3,
91 | title: "AI Image SaaS - Canva Application",
92 | des: "A REAL Software-as-a-Service app with AI features and a payments and credits system using the latest tech stack.",
93 | img: "/p3.svg",
94 | iconLists: ["/re.svg", "/tail.svg", "/ts.svg", "/three.svg", "/c.svg"],
95 | link: "/ui.aiimg.com",
96 | },
97 | {
98 | id: 4,
99 | title: "Animated Apple Iphone 3D Website",
100 | des: "Recreated the Apple iPhone 15 Pro website, combining GSAP animations and Three.js 3D effects..",
101 | img: "/p4.svg",
102 | iconLists: ["/next.svg", "/tail.svg", "/ts.svg", "/three.svg", "/gsap.svg"],
103 | link: "/ui.apple.com",
104 | },
105 | ];
106 |
107 | export const testimonials = [
108 | {
109 | quote:
110 | "Collaborating with Adrian was an absolute pleasure. His professionalism, promptness, and dedication to delivering exceptional results were evident throughout our project. Adrian's enthusiasm for every facet of development truly stands out. If you're seeking to elevate your website and elevate your brand, Adrian is the ideal partner.",
111 | name: "Michael Johnson",
112 | title: "Director of AlphaStream Technologies",
113 | },
114 | {
115 | quote:
116 | "Collaborating with Adrian was an absolute pleasure. His professionalism, promptness, and dedication to delivering exceptional results were evident throughout our project. Adrian's enthusiasm for every facet of development truly stands out. If you're seeking to elevate your website and elevate your brand, Adrian is the ideal partner.",
117 | name: "Michael Johnson",
118 | title: "Director of AlphaStream Technologies",
119 | },
120 | {
121 | quote:
122 | "Collaborating with Adrian was an absolute pleasure. His professionalism, promptness, and dedication to delivering exceptional results were evident throughout our project. Adrian's enthusiasm for every facet of development truly stands out. If you're seeking to elevate your website and elevate your brand, Adrian is the ideal partner.",
123 | name: "Michael Johnson",
124 | title: "Director of AlphaStream Technologies",
125 | },
126 | {
127 | quote:
128 | "Collaborating with Adrian was an absolute pleasure. His professionalism, promptness, and dedication to delivering exceptional results were evident throughout our project. Adrian's enthusiasm for every facet of development truly stands out. If you're seeking to elevate your website and elevate your brand, Adrian is the ideal partner.",
129 | name: "Michael Johnson",
130 | title: "Director of AlphaStream Technologies",
131 | },
132 | {
133 | quote:
134 | "Collaborating with Adrian was an absolute pleasure. His professionalism, promptness, and dedication to delivering exceptional results were evident throughout our project. Adrian's enthusiasm for every facet of development truly stands out. If you're seeking to elevate your website and elevate your brand, Adrian is the ideal partner.",
135 | name: "Michael Johnson",
136 | title: "Director of AlphaStream Technologies",
137 | },
138 | ];
139 |
140 | export const companies = [
141 | {
142 | id: 1,
143 | name: "cloudinary",
144 | img: "/cloud.svg",
145 | nameImg: "/cloudName.svg",
146 | },
147 | {
148 | id: 2,
149 | name: "appwrite",
150 | img: "/app.svg",
151 | nameImg: "/appName.svg",
152 | },
153 | {
154 | id: 3,
155 | name: "HOSTINGER",
156 | img: "/host.svg",
157 | nameImg: "/hostName.svg",
158 | },
159 | {
160 | id: 4,
161 | name: "stream",
162 | img: "/s.svg",
163 | nameImg: "/streamName.svg",
164 | },
165 | {
166 | id: 5,
167 | name: "docker.",
168 | img: "/dock.svg",
169 | nameImg: "/dockerName.svg",
170 | },
171 | ];
172 |
173 | export const workExperience = [
174 | {
175 | id: 1,
176 | title: "Frontend Engineer Intern",
177 | desc: "Assisted in the development of a web-based platform using React.js, enhancing interactivity.",
178 | className: "md:col-span-2",
179 | thumbnail: "/exp1.svg",
180 | },
181 | {
182 | id: 2,
183 | title: "Mobile App Dev - JSM Tech",
184 | desc: "Designed and developed mobile app for both iOS & Android platforms using React Native.",
185 | className: "md:col-span-2", // change to md:col-span-2
186 | thumbnail: "/exp2.svg",
187 | },
188 | {
189 | id: 3,
190 | title: "Freelance App Dev Project",
191 | desc: "Led the dev of a mobile app for a client, from initial concept to deployment on app stores.",
192 | className: "md:col-span-2", // change to md:col-span-2
193 | thumbnail: "/exp3.svg",
194 | },
195 | {
196 | id: 4,
197 | title: "Lead Frontend Developer",
198 | desc: "Developed and maintained user-facing features using modern frontend technologies.",
199 | className: "md:col-span-2",
200 | thumbnail: "/exp4.svg",
201 | },
202 | ];
203 |
204 | export const socialMedia = [
205 | {
206 | id: 1,
207 | img: "/git.svg",
208 | },
209 | {
210 | id: 2,
211 | img: "/twit.svg",
212 | },
213 | {
214 | id: 3,
215 | img: "/link.svg",
216 | },
217 | ];
218 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | import {withSentryConfig} from '@sentry/nextjs';
2 | /** @type {import('next').NextConfig} */
3 | const nextConfig = {};
4 |
5 | export default withSentryConfig(nextConfig, {
6 | // For all available options, see:
7 | // https://github.com/getsentry/sentry-webpack-plugin#options
8 |
9 | // Suppresses source map uploading logs during build
10 | silent: true,
11 | org: "javascript-mastery",
12 | project: "javascript-nextjs",
13 | }, {
14 | // For all available options, see:
15 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
16 |
17 | // Upload a larger set of source maps for prettier stack traces (increases build time)
18 | widenClientFileUpload: true,
19 |
20 | // Transpiles SDK to be compatible with IE11 (increases bundle size)
21 | transpileClientSDK: true,
22 |
23 | // Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
24 | // This can increase your server load as well as your hosting bill.
25 | // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
26 | // side errors will fail.
27 | // tunnelRoute: "/monitoring",
28 |
29 | // Hides source maps from generated client bundles
30 | hideSourceMaps: true,
31 |
32 | // Automatically tree-shake Sentry logger statements to reduce bundle size
33 | disableLogger: true,
34 |
35 | // Enables automatic instrumentation of Vercel Cron Monitors.
36 | // See the following for more information:
37 | // https://docs.sentry.io/product/crons/
38 | // https://vercel.com/docs/cron-jobs
39 | automaticVercelMonitors: true,
40 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "portfolio",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "deploy": "vercel --prod"
11 | },
12 | "dependencies": {
13 | "@react-three/drei": "^9.105.4",
14 | "@react-three/fiber": "^8.16.2",
15 | "@sentry/nextjs": "^7.105.0",
16 | "@tabler/icons-react": "^3.1.0",
17 | "@types/three": "^0.163.0",
18 | "class-variance-authority": "^0.7.0",
19 | "clsx": "^2.1.0",
20 | "framer-motion": "^11.0.25",
21 | "lucide-react": "^0.365.0",
22 | "mini-svg-data-uri": "^1.4.4",
23 | "next": "14.1.4",
24 | "next-themes": "^0.3.0",
25 | "react": "^18",
26 | "react-dom": "^18",
27 | "react-icons": "^5.0.1",
28 | "react-lottie": "^1.2.4",
29 | "tailwind-merge": "^2.2.2",
30 | "tailwindcss-animate": "^1.0.7",
31 | "three": "^0.163.0",
32 | "three-globe": "^2.31.0",
33 | "vercel": "^34.0.0"
34 | },
35 | "devDependencies": {
36 | "@types/node": "^20",
37 | "@types/react": "^18",
38 | "@types/react-dom": "^18",
39 | "@types/react-lottie": "^1.2.10",
40 | "autoprefixer": "^10.0.1",
41 | "eslint": "^8",
42 | "eslint-config-next": "14.1.4",
43 | "postcss": "^8",
44 | "tailwindcss": "^3.3.0",
45 | "typescript": "^5"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/app.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/appName.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/b4.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrianhajdin/portfolio/aa3fc6de5066925313070cbb5bdce9d85cc89241/public/bg.png
--------------------------------------------------------------------------------
/public/c.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/cloud.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/cloudName.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/public/confetti.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrianhajdin/portfolio/aa3fc6de5066925313070cbb5bdce9d85cc89241/public/confetti.gif
--------------------------------------------------------------------------------
/public/dock.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/dockerName.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/exp1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/public/exp2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/public/git.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/host.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/hostName.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/public/insta.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/jsm-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrianhajdin/portfolio/aa3fc6de5066925313070cbb5bdce9d85cc89241/public/jsm-logo.png
--------------------------------------------------------------------------------
/public/link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/re.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/s.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/stream.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/streamName.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/tail.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/three.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/public/ts.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/public/twit.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/wha.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/sentry.client.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the client.
2 | // The config you add here will be used whenever a users loads a page in their browser.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from "@sentry/nextjs";
6 |
7 | Sentry.init({
8 | dsn: "https://7b883d075caa19b41fd9b00ae313a1c6@o4506813739368448.ingest.us.sentry.io/4507222371729408",
9 |
10 | // Adjust this value in production, or use tracesSampler for greater control
11 | tracesSampleRate: 1,
12 |
13 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
14 | debug: false,
15 |
16 | replaysOnErrorSampleRate: 1.0,
17 |
18 | // This sets the sample rate to be 10%. You may want this to be 100% while
19 | // in development and sample at a lower rate in production
20 | replaysSessionSampleRate: 0.1,
21 |
22 | // You can remove this option if you're not planning to use the Sentry Session Replay feature:
23 | integrations: [
24 | Sentry.replayIntegration({
25 | // Additional Replay configuration goes in here, for example:
26 | maskAllText: true,
27 | blockAllMedia: true,
28 | }),
29 | ],
30 | });
31 |
--------------------------------------------------------------------------------
/sentry.edge.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
2 | // The config you add here will be used whenever one of the edge features is loaded.
3 | // Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
4 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
5 |
6 | import * as Sentry from "@sentry/nextjs";
7 |
8 | Sentry.init({
9 | dsn: "https://7b883d075caa19b41fd9b00ae313a1c6@o4506813739368448.ingest.us.sentry.io/4507222371729408",
10 |
11 | // Adjust this value in production, or use tracesSampler for greater control
12 | tracesSampleRate: 1,
13 |
14 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
15 | debug: false,
16 | });
17 |
--------------------------------------------------------------------------------
/sentry.server.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the server.
2 | // The config you add here will be used whenever the server handles a request.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from "@sentry/nextjs";
6 |
7 | Sentry.init({
8 | dsn: "https://7b883d075caa19b41fd9b00ae313a1c6@o4506813739368448.ingest.us.sentry.io/4507222371729408",
9 |
10 | // Adjust this value in production, or use tracesSampler for greater control
11 | tracesSampleRate: 1,
12 |
13 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
14 | debug: false,
15 |
16 | // uncomment the line below to enable Spotlight (https://spotlightjs.com)
17 | // spotlight: process.env.NODE_ENV === 'development',
18 |
19 | });
20 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const svgToDataUri = require("mini-svg-data-uri");
4 |
5 | const colors = require("tailwindcss/colors");
6 | const {
7 | default: flattenColorPalette,
8 | } = require("tailwindcss/lib/util/flattenColorPalette");
9 |
10 | const config = {
11 | darkMode: ["class"],
12 | content: [
13 | "./pages/**/*.{ts,tsx}",
14 | "./components/**/*.{ts,tsx}",
15 | "./app/**/*.{ts,tsx}",
16 | "./src/**/*.{ts,tsx}",
17 | "./data/**/*.{ts,tsx}",
18 | ],
19 | prefix: "",
20 | theme: {
21 | container: {
22 | center: true,
23 | padding: "2rem",
24 | screens: {
25 | "2xl": "1400px",
26 | },
27 | },
28 | extend: {
29 | colors: {
30 | black: {
31 | DEFAULT: "#000",
32 | 100: "#000319",
33 | 200: "rgba(17, 25, 40, 0.75)",
34 | 300: "rgba(255, 255, 255, 0.125)",
35 | },
36 | white: {
37 | DEFAULT: "#FFF",
38 | 100: "#BEC1DD",
39 | 200: "#C1C2D3",
40 | },
41 | blue: {
42 | "100": "#E4ECFF",
43 | },
44 | purple: "#CBACF9",
45 | border: "hsl(var(--border))",
46 | input: "hsl(var(--input))",
47 | ring: "hsl(var(--ring))",
48 | background: "hsl(var(--background))",
49 | foreground: "hsl(var(--foreground))",
50 | primary: {
51 | DEFAULT: "hsl(var(--primary))",
52 | foreground: "hsl(var(--primary-foreground))",
53 | },
54 | secondary: {
55 | DEFAULT: "hsl(var(--secondary))",
56 | foreground: "hsl(var(--secondary-foreground))",
57 | },
58 | destructive: {
59 | DEFAULT: "hsl(var(--destructive))",
60 | foreground: "hsl(var(--destructive-foreground))",
61 | },
62 | muted: {
63 | DEFAULT: "hsl(var(--muted))",
64 | foreground: "hsl(var(--muted-foreground))",
65 | },
66 | accent: {
67 | DEFAULT: "hsl(var(--accent))",
68 | foreground: "hsl(var(--accent-foreground))",
69 | },
70 | popover: {
71 | DEFAULT: "hsl(var(--popover))",
72 | foreground: "hsl(var(--popover-foreground))",
73 | },
74 | card: {
75 | DEFAULT: "hsl(var(--card))",
76 | foreground: "hsl(var(--card-foreground))",
77 | },
78 | },
79 | borderRadius: {
80 | lg: "var(--radius)",
81 | md: "calc(var(--radius) - 2px)",
82 | sm: "calc(var(--radius) - 4px)",
83 | },
84 | keyframes: {
85 | "accordion-down": {
86 | from: { height: "0" },
87 | to: { height: "var(--radix-accordion-content-height)" },
88 | },
89 | "accordion-up": {
90 | from: { height: "var(--radix-accordion-content-height)" },
91 | to: { height: "0" },
92 | },
93 | spotlight: {
94 | "0%": {
95 | opacity: "0",
96 | transform: "translate(-72%, -62%) scale(0.5)",
97 | },
98 | "100%": {
99 | opacity: "1",
100 | transform: "translate(-50%,-40%) scale(1)",
101 | },
102 | },
103 | shimmer: {
104 | from: {
105 | backgroundPosition: "0 0",
106 | },
107 | to: {
108 | backgroundPosition: "-200% 0",
109 | },
110 | },
111 | moveHorizontal: {
112 | "0%": {
113 | transform: "translateX(-50%) translateY(-10%)",
114 | },
115 | "50%": {
116 | transform: "translateX(50%) translateY(10%)",
117 | },
118 | "100%": {
119 | transform: "translateX(-50%) translateY(-10%)",
120 | },
121 | },
122 | moveInCircle: {
123 | "0%": {
124 | transform: "rotate(0deg)",
125 | },
126 | "50%": {
127 | transform: "rotate(180deg)",
128 | },
129 | "100%": {
130 | transform: "rotate(360deg)",
131 | },
132 | },
133 | moveVertical: {
134 | "0%": {
135 | transform: "translateY(-50%)",
136 | },
137 | "50%": {
138 | transform: "translateY(50%)",
139 | },
140 | "100%": {
141 | transform: "translateY(-50%)",
142 | },
143 | },
144 | scroll: {
145 | to: {
146 | transform: "translate(calc(-50% - 0.5rem))",
147 | },
148 | },
149 | },
150 | animation: {
151 | "accordion-down": "accordion-down 0.2s ease-out",
152 | "accordion-up": "accordion-up 0.2s ease-out",
153 | spotlight: "spotlight 2s ease .75s 1 forwards",
154 | shimmer: "shimmer 2s linear infinite",
155 | first: "moveVertical 30s ease infinite",
156 | second: "moveInCircle 20s reverse infinite",
157 | third: "moveInCircle 40s linear infinite",
158 | fourth: "moveHorizontal 40s ease infinite",
159 | fifth: "moveInCircle 20s ease infinite",
160 | scroll:
161 | "scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite",
162 | },
163 | },
164 | },
165 | plugins: [
166 | require("tailwindcss-animate"),
167 | addVariablesForColors,
168 | function ({ matchUtilities, theme }: any) {
169 | matchUtilities(
170 | {
171 | "bg-grid": (value: any) => ({
172 | backgroundImage: `url("${svgToDataUri(
173 | ` `
174 | )}")`,
175 | }),
176 | "bg-grid-small": (value: any) => ({
177 | backgroundImage: `url("${svgToDataUri(
178 | ` `
179 | )}")`,
180 | }),
181 | "bg-dot": (value: any) => ({
182 | backgroundImage: `url("${svgToDataUri(
183 | ` `
184 | )}")`,
185 | }),
186 | },
187 | { values: flattenColorPalette(theme("backgroundColor")), type: "color" }
188 | );
189 | },
190 | ],
191 | } satisfies Config;
192 |
193 | function addVariablesForColors({ addBase, theme }: any) {
194 | let allColors = flattenColorPalette(theme("colors"));
195 | let newVars = Object.fromEntries(
196 | Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
197 | );
198 |
199 | addBase({
200 | ":root": newVars,
201 | });
202 | }
203 |
204 | export default config;
205 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------