Full Code of Alfinpratamaa/HWID-SPOOFER for AI

main 47125543225a cached
39 files
79.1 KB
20.7k tokens
24 symbols
1 requests
Download .txt
Repository: Alfinpratamaa/HWID-SPOOFER
Branch: main
Commit: 47125543225a
Files: 39
Total size: 79.1 KB

Directory structure:
gitextract_riayleb2/

├── .gitignore
├── .nvmrc
├── README.md
├── app/
│   ├── about/
│   │   └── page.tsx
│   ├── api/
│   │   └── contact/
│   │       └── route.ts
│   ├── blogs/
│   │   ├── [slug]/
│   │   │   ├── PostContent.tsx
│   │   │   └── page.tsx
│   │   └── page.tsx
│   ├── contact/
│   │   └── page.tsx
│   ├── globals.css
│   ├── layout.tsx
│   ├── page.tsx
│   ├── robots.ts
│   └── sitemap.ts
├── components/
│   ├── AboutSection.tsx
│   ├── BackgroundStar.tsx
│   ├── Banner.tsx
│   ├── BlogClientPage.tsx
│   ├── ClientEffects.tsx
│   ├── CursorBacground.tsx
│   ├── LinksCard.tsx
│   ├── Navbar.tsx
│   ├── NebulaEffect.tsx
│   ├── ProjectCard.tsx
│   ├── ProjectSection.tsx
│   ├── Skills.tsx
│   ├── about.tsx
│   └── contact.tsx
├── libs/
│   ├── NavbarLinks.ts
│   ├── constant.ts
│   └── sanity.client.ts
├── next.config.mjs
├── package.json
├── postcss.config.mjs
├── provider/
│   ├── ClientProvicer.tsx
│   └── SkillData.tsx
├── tailwind.config.js
├── tsconfig.json
└── types/
    └── index.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build


# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

.env.local


# typescript
*.tsbuildinfo
next-env.d.ts


================================================
FILE: .nvmrc
================================================
20.19.1

================================================
FILE: README.md
================================================
hello world

================================================
FILE: app/about/page.tsx
================================================
import AboutSection from "@/components/AboutSection";
import { Metadata } from "next";
import React from "react";

export const metadata: Metadata = {
  title: "About | evrea",
  description:
    "Frontendwith 1+ years of expertise. Junior Software Engineer. Specializing web apps, UX, and JavaScript technologies.",
  keywords: [
    "Developer",
    "Portfolio",
    "Developer Portflio",
    "Muhamad Alfin Pratama",
    "Next.js",
    "React",
    "ReactNative",
    "Android",
    "nodejs",
    "alfin",
    "alfin pratama",
    "muhamad alfin",
    "muhamad alfin pratama",
    "muhammad alfin pratama",
    "frontend web",
    "Frontend Developer",
    "Front-end Developer",
    "Front End Developer",
    "Frontend Engineer",
    "Front-end Engineer",
    "Front End Engineer",
    "Muhamad Alfin",
    "Alfin Pratama",
    "Muhamad",
    "Alfin",
    "Pratama",
    "evrea",
  ],
};

const AboutPage = () => {
  return (
    <main className="w-full min-h-screen bg-transparent pt-28 pb-12 px-4 md:px-8">
      <AboutSection />
    </main>
  );
};

export default AboutPage;


================================================
FILE: app/api/contact/route.ts
================================================
export const runtime = "edge";

export const POST = async (req: Request) => {
  try {
    const { name, email, subject, message } = await req.json();

    if (!name || !email || !subject || !message) {
      return Response.json(
        { error: "Please fill in all fields" },
        { status: 400 }
      );
    }

    const res = await fetch(
      `https://api.mailgun.net/v3/${process.env.MAILGUN_DOMAIN}/messages`,
      {
        method: "POST",
        headers: {
          Authorization: `Basic ${btoa(`api:${process.env.MAILGUN_PASSWORD}`)}`,
          "Content-Type": "application/x-www-form-urlencoded",
        },
        body: new URLSearchParams({
          from: process.env.MAILGUN_EMAIL!,
          to: "muhamadalfinpratamaa@gmail.com",
          subject,
          text: message,
        }),
      }
    );

    if (!res.ok) throw new Error("Failed to send email");

    return Response.json({ message: "Email sent" }, { status: 200 });
  } catch (error) {
    console.error(error);
    return Response.json({ error: "Error sending email" }, { status: 500 });
  }
};


================================================
FILE: app/blogs/[slug]/PostContent.tsx
================================================
import Image from "next/image";
import { PortableText } from "@portabletext/react";

export default function PostContent({ post, ptComponents, urlFor }: any) {
  return (
    <section className="min-h-screen py-24 px-4 text-white">
      <div className="max-w-5xl mx-auto grid gap-6 md:grid-cols-3">
        <div className="col-span-full bg-neutral-900/50 border border-neutral-700 rounded-xl p-8">
          <h1 className="text-4xl md:text-5xl font-bold mb-6 text-white">
            {post.title}
          </h1>
          {post.mainImage && (
            <div className="relative w-full h-72 md:h-96 rounded-xl overflow-hidden border border-neutral-700">
              <Image
                src={urlFor(post.mainImage).url()}
                alt={post.title}
                fill
                className="object-cover"
                priority
              />
            </div>
          )}
        </div>

        <div className="col-span-full md:col-span-2 bg-neutral-900/50 border border-neutral-700 rounded-xl p-8 prose prose-invert max-w-none">
          <PortableText value={post.body} components={ptComponents} />
        </div>
      </div>
    </section>
  );
}


================================================
FILE: app/blogs/[slug]/page.tsx
================================================
import { client } from "@/libs/sanity.client";
import imageUrlBuilder from "@sanity/image-url";
import { groq } from "next-sanity";
import Image from "next/image";
import { notFound } from "next/navigation";
import { PortableText } from "@portabletext/react";
import { Metadata } from "next";

export const runtime = "edge";

const builder = imageUrlBuilder(client);
function urlFor(source: any) {
  return builder.image(source);
}

const postQuery = groq`*[_type == "post" && slug.current == $slug][0] {
  ...,
  author-> { 
    name,
    image,
    bio
  },
  "categories": categories[]->title 
}`;

const ptComponents = {
  types: {
    image: ({ value }: { value: any }) => {
      if (!value?.asset?._ref) return null;
      return (
        <div className="relative w-full h-96 my-8">
          <Image
            src={urlFor(value).url()}
            alt={value.alt || "Gambar dari artikel"}
            fill
            priority
            className="object-contain"
          />
        </div>
      );
    },
  },
  block: {
    h1: ({ children }: any) => (
      <h1 className="text-4xl font-bold text-white my-6">{children}</h1>
    ),
    h2: ({ children }: any) => (
      <h2 className="text-3xl font-semibold text-white my-4">{children}</h2>
    ),
    h3: ({ children }: any) => (
      <h3 className="text-2xl font-semibold text-white my-3">{children}</h3>
    ),
    normal: ({ children }: any) => <p className="leading-7 my-4">{children}</p>,
    blockquote: ({ children }: any) => (
      <blockquote className="border-l-4 border-purple-400 pl-4 italic my-4">
        {children}
      </blockquote>
    ),
  },
  marks: {
    strong: ({ children }: any) => (
      <strong className="font-bold text-white">{children}</strong>
    ),
    em: ({ children }: any) => (
      <em className="italic text-white">{children}</em>
    ),
    link: ({ value, children }: any) => {
      const target = (value?.href || "").startsWith("http")
        ? "_blank"
        : undefined;
      return (
        <a
          href={value?.href}
          target={target}
          rel={target === "_blank" ? "noopener noreferrer" : undefined}
          className="text-purple-400 hover:text-purple-300 underline"
        >
          {children}
        </a>
      );
    },
  },
  list: {
    bullet: ({ children }: any) => (
      <ul className="list-disc ml-6 space-y-2">{children}</ul>
    ),
    number: ({ children }: any) => (
      <ol className="list-decimal ml-6 space-y-2">{children}</ol>
    ),
  },
};

type PageProps = {
  params: Promise<{ slug: string }>;
};

// Generate dynamic metadata
export async function generateMetadata({
  params,
}: PageProps): Promise<Metadata> {
  const { slug } = await params;
  const post = await client.fetch(postQuery, { slug });

  if (!post) {
    return {
      title: "Post Not Found",
      description: "The blog post you're looking for doesn't exist.",
    };
  }

  const getPlainTextFromBody = (body: any[]): string => {
    if (!body || !Array.isArray(body)) return "";

    return body
      .filter((block) => block._type === "block" && block.style === "normal")
      .map((block) => block.children?.map((child: any) => child.text).join(""))
      .join(" ")
      .slice(0, 160);
  };

  const description =
    post.excerpt || getPlainTextFromBody(post.body) || "Read this blog post";
  const imageUrl = post.mainImage
    ? urlFor(post.mainImage).width(1200).height(630).url()
    : null;

  return {
    title: `${post.title} - Blog Alfin Pratama`,
    description,
    keywords: post.categories?.join(", "),
    authors: [{ name: post.author?.name || "Anonymous" }],
    openGraph: {
      title: post.title,
      description,
      type: "article",
      publishedTime: post.publishedAt,
      authors: [post.author?.name || "Anonymous"],
      images: imageUrl
        ? [
            {
              url: imageUrl,
              width: 1200,
              height: 630,
              alt: post.title,
            },
          ]
        : [],
    },
    twitter: {
      card: "summary_large_image",
      title: post.title,
      description,
      images: imageUrl ? [imageUrl] : [],
    },
    alternates: {
      canonical: `/blogs/${slug}`,
    },
  };
}

export default async function PostPage({ params }: PageProps) {
  const { slug } = await params;
  const post = await client.fetch(postQuery, { slug });

  if (!post) {
    notFound();
  }

  return (
    <article className="min-h-screen py-28  text-white">
      <div className="max-w-5xl rounded-xl mx-auto bg-neutral-900/50 border border-neutral-700 p-10">
        {/* Header Artikel */}
        <div className="text-center mb-12">
          <h1 className="text-4xl md:text-5xl font-extrabold tracking-tight mb-4">
            {post.title}
          </h1>
          <div className="flex items-center justify-center space-x-4 text-gray-400">
            <div className="flex items-center space-x-2">
              {post.author?.image && (
                <Image
                  src={
                    post.authorImage?.asset
                      ? urlFor(post.authorImage).width(40).height(40).url()
                      : "/default-avatar.jpg"
                  }
                  alt={`Foto ${post.authorName}`}
                  width={40}
                  height={40}
                  className="rounded-full"
                />
              )}
              <span>{post.author?.name || "Anonim"}</span>
            </div>
            <span>&bull;</span>
            <span>{new Date(post.publishedAt).toLocaleDateString()}</span>
          </div>
        </div>

        <div className="relative w-full h-64 md:h-96 rounded-lg overflow-hidden mb-12 shadow-lg">
          <Image
            src={urlFor(post.mainImage).url()}
            alt={`Gambar utama untuk ${post.title}`}
            layout="fill"
            objectFit="cover"
            priority
          />
        </div>

        <div className="prose prose-invert prose-lg max-w-none prose-h1:text-purple-400 prose-a:text-purple-400 hover:prose-a:text-purple-300">
          <PortableText value={post.body} components={ptComponents} />
        </div>
      </div>
    </article>
  );
}


================================================
FILE: app/blogs/page.tsx
================================================
import { client } from "@/libs/sanity.client";
import { groq } from "next-sanity";
import BlogClientPage from "@/components/BlogClientPage"; // Kita akan buat komponen ini
import { Metadata } from "next";

export const runtime = 'edge';

export const metadata: Metadata = {
  title: "Blog - Alfin Pratama",
  description:
    "Sharing my thoughts, experiences, and insights on web development, programming, and technology.",
  openGraph: {
    title: "Blog - Alfin Pratama",
    description:
      "Sharing my thoughts, experiences, and insights on web development, programming, and technology.",
    url: "https://evrea.tech/blogs",
    siteName: "Alfin Pratama",
    images: [
      {
        url: "https://evrea.tech/avatar2_.webp",
        width: 1200,
        height: 630,
        alt: "Blog - Alfin Pratama",
      },
    ],
    locale: "id_ID",
    type: "website",
  },
  keywords: [
    "blog",
    "web development",
    "programming",
    "technology",
    "javascript",
    "react",
    "next.js",
    "sanity.io",
    "tailwindcss",
    "personal blog",
    "blog alfin",
    "coding",
    "software development",
    "frontend",
    "backend",
    "fullstack",
    "tutorials",
    "tips and tricks",
    "web design",
    "developer insights",
    "blog evrea",
  ],
};

export interface Post {
  _id: string;
  title: string;
  slug: { current: string };
  mainImage: any;
  publishedAt: string;
  authorName: string;
  authorImage: any;
  categories: Category[];
}

export interface Category {
  _id: string;
  title: string;
  slug: { current: string };
}

const postsQuery = groq`*[_type == "post"] | order(publishedAt desc) {
  _id,
  title,
  slug,
  mainImage,
  publishedAt,
  "authorName": author->name, 
  "authorImage": author->image,
  "categories": categories[]->{ _id, title, slug }
}`;

const categoriesQuery = groq`*[_type == "category"] | order(title asc) {
  _id,
  title,
  slug
}`;

export default async function BlogPage() {
  const posts: Post[] = await client.fetch(
    postsQuery,
    {},
    { cache: "no-store" }
  );

  const categories: Category[] = await client.fetch(
    categoriesQuery,
    {},
    {
      cache: "no-store",
    }
  );

  return <BlogClientPage posts={posts} categories={categories} />;
}


================================================
FILE: app/contact/page.tsx
================================================
import { type Metadata } from "next";
import ContactForm from "@/components/contact";

export const metadata: Metadata = {
  title: "Contact | evrea",
  description:
    "Contact me via email to start a conversation or ask me anything",
  keywords: [
    "Contact",
    "Portfolio",
    "Developer Portflio",
    "Muhamad Alfin Pratama",
    "Next.js",
    "React",
    "ReactNative",
    "Android",
    "nodejs",
    "alfin",
    "alfin pratama",
    "muhamad alfin",
    "muhamad alfin pratama",
    "muhammad alfin pratama",
    "frontend web",
    "Frontend Developer",
    "Front-end Developer",
    "Front End Developer",
    "Frontend Engineer",
    "Front-end Engineer",
    "Front End Engineer",
    "Muhamad Alfin",
    "Alfin Pratama",
    "Muhamad",
    "Alfin",
    "Pratama",
    "evrea",
  ],
};

const ContactPage = () => {
  return <ContactForm />;
};
export default ContactPage;


================================================
FILE: app/globals.css
================================================
@import "tailwindcss";

:root {
  --foreground-rgb: 0, 0, 0;
  --background-start-rgb: 214, 219, 220;
  --background-end-rgb: 255, 255, 255;
}

@layer components {
  .banner-heading {
    @apply text-4xl font-bold text-white md:text-5xl;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --foreground-rgb: 255, 255, 255;
    --background-start-rgb: 0, 0, 0;
    --background-end-rgb: 0, 0, 0;
  }
}

body {
  color: rgb(var(--foreground-rgb));
  background: linear-gradient(
      to bottom,
      transparent,
      rgb(var(--background-end-rgb))
    )
    rgb(var(--background-start-rgb));
}

@layer utilities {
  .text-balance {
    text-wrap: balance;
  }
}

.custom-scrollbar {
  scrollbar-width: thin; /* Firefox */
  scrollbar-color: #6b21a8 #262626; /* thumb, track */
}

/* Chrome, Edge, Safari */
.custom-scrollbar::-webkit-scrollbar {
  width: 6px; /* lebar scrollbar */
}

.custom-scrollbar::-webkit-scrollbar-track {
  background: #262626; /* warna track */
  border-radius: 10px;
}

.custom-scrollbar::-webkit-scrollbar-thumb {
  background-color: #6b21a8; /* warna thumb */
  border-radius: 10px;
}

.custom-scrollbar::-webkit-scrollbar-thumb:hover {
  background-color: #9333ea; /* hover state */
}


================================================
FILE: app/layout.tsx
================================================
import type { Metadata } from "next";
import { Poppins } from "next/font/google";
import "./globals.css";
import Navbar from "@/components/Navbar";
import ClientEffects from "@/components/ClientEffects";
import { ClientProvider } from "@/provider/ClientProvicer";
import CursorBackground from "@/components/CursorBacground";
const poppins = Poppins({
  subsets: ["latin"],
  weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
  variable: "--font-poppins",
});

export const metadata: Metadata = {
  metadataBase: new URL("https://alfinpratamaa.github.io/"),
  title: "Muhamad Alfin Pratama",
  description:
    "Fullstack Developer & DevOps Engineer with 1+ years of expertise in JavaScript, TypeScript, Golang, and PHP. Experienced with Next.js, React.js, Laravel frameworks, Docker containerization, VPS hosting, and CI/CD pipelines.",
  keywords: [
    "Fullstack Developer",
    "DevOps Engineer",
    "Portfolio",
    "Developer Portfolio",
    "Muhamad Alfin Pratama",
    "Next.js",
    "React",
    "ReactNative",
    "Laravel",
    "JavaScript",
    "TypeScript",
    "Golang",
    "PHP",
    "Docker",
    "VPS Hosting",
    "CI/CD",
    "DevOps",
    "Backend Developer",
    "Frontend Developer",
    "Fullstack",
    "Web Developer",
    "Software Engineer",
    "Software Developer",
    "Devops Engineer",
    "web development",
    "programming",
    "alfin",
    "alfin pratama",
    "muhamad alfin",
    "muhamad alfin pratama",
    "muhammad alfin pratama",
    "Muhamad Alfin",
    "Alfin Pratama",
    "Muhamad",
    "Alfin",
    "Pratama",
    "evrea",
  ],
  creator: "Muhamad Alfin Pratama",
  applicationName: "evrea",
  icons: "/avatar_2.webp",
  openGraph: {
    title: "Muhamad Alfin Pratama",
    description:
      "Fullstack Developer & DevOps Engineer with 1+ years of expertise in JavaScript, TypeScript, Golang, and PHP. Specializing in Next.js, React.js, Laravel, Docker, VPS hosting, and CI/CD pipelines.",
    images: "/Avatar_2.webp",
  },
  alternates: {
    canonical: "https://evrea.tech",
  },
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className="dark">
      <head>
        <link
          rel="preload"
          as="image"
          href="/avatar_2.webp"
          type="image/webp"
        />
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="shortcut icon" href="/avatar_2.jpeg" />
      </head>
      <body
        className={`${poppins.className} min-h-screen overflow-y-scroll overflow-x-hidden relative bg-black`}
      >
        <CursorBackground>
          <ClientProvider />
          <ClientEffects />
          <div className="relative z-[2]">
            <div className="mb-[10px]">
              <Navbar />
            </div>
            {children}
          </div>
        </CursorBackground>
      </body>
    </html>
  );
}


================================================
FILE: app/page.tsx
================================================
import Banner from "@/components/Banner";

export default function Home() {
  return (
    <main>
      <Banner />
    </main>
  );
}


================================================
FILE: app/robots.ts
================================================
import { MetadataRoute } from "next";

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: "*",
      allow: "/",
    },
    sitemap: "https://evrea.tech/sitemap.xml",
  };
}


================================================
FILE: app/sitemap.ts
================================================
// app/sitemap.ts

import { MetadataRoute } from "next";
import { client } from "@/libs/sanity.client";
import { groq } from "next-sanity";

// Definisikan tipe untuk data post yang diambil
interface Post {
  slug: {
    current: string;
  };
  _updatedAt: string; // Gunakan _updatedAt untuk lastModified
}

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = "https://evrea.tech";

  // 1. Ambil semua slug post dari Sanity
  const query = groq`*[_type == "post"]{ "slug": slug.current, _updatedAt }`;
  const posts: Post[] = await client.fetch(query);

  const postUrls = posts.map((post) => ({
    url: `${baseUrl}/blogs/${post.slug}`,
    lastModified: new Date(post._updatedAt),
    changeFrequency: "daily" as "daily",
    priority: 0.8,
  }));

  // 2. Gabungkan dengan URL statis Anda
  return [
    {
      url: baseUrl,
      lastModified: new Date(),
      changeFrequency: "daily",
      priority: 1,
    },
    {
      url: `${baseUrl}/about`,
      lastModified: new Date(),
      changeFrequency: "monthly",
      priority: 0.7,
    },
    {
      url: `${baseUrl}/blogs`,
      lastModified: new Date(),
      changeFrequency: "daily",
      priority: 0.9,
    },
    ...postUrls,
  ];
}


================================================
FILE: components/AboutSection.tsx
================================================
"use client";

import Image from "next/image";
import Link from "next/link";
import { motion } from "framer-motion";
import {
  FaGithub,
  FaInstagram,
  FaLinkedin,
  FaLocationDot,
  FaTelegram,
  FaWhatsapp,
  FaCalendar,
} from "react-icons/fa6";
import { MdOutlineWork } from "react-icons/md";
import Skills from "./Skills";
import { FaExternalLinkAlt } from "react-icons/fa";
import { ProjectsSection } from "./ProjectSection";

// Data untuk media sosial
const socials = [
  { icon: <FaInstagram />, href: "https://www.instagram.com/visfiveor5" },
  { icon: <FaLinkedin />, href: "https://www.linkedin.com/in/alfinpr" },
  { icon: <FaGithub />, href: "https://www.github.com/Alfinpratamaa" },
  { icon: <FaWhatsapp />, href: "https://wa.me/6285175369960" },
  { icon: <FaTelegram />, href: "https://t.me/visfiveor5" },
];

const AboutSection = () => {
  // Varian animasi untuk container utama (stagger effect)
  const containerVariants = {
    hidden: { opacity: 0 },
    visible: {
      opacity: 1,
      transition: {
        staggerChildren: 0.1, // Setiap anak akan muncul dengan jeda 0.1 detik
      },
    },
  };

  // Varian animasi untuk setiap item di dalam grid
  const itemVariants = {
    hidden: { opacity: 0, y: 20 },
    visible: { opacity: 1, y: 0, transition: { duration: 0.5 } },
  };

  return (
    <motion.div
      className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 max-w-5xl mx-auto"
      variants={containerVariants}
      initial="hidden"
      animate="visible"
    >
      {/* ======================================= */}
      {/* KARTU 1: PROFIL UTAMA                 */}
      {/* ======================================= */}
      <motion.div
        className="col-span-full md:col-span-2 row-span-2 bg-neutral-900/50 border border-neutral-700 rounded-xl p-6 flex flex-col md:flex-row items-center gap-6"
        variants={itemVariants}
      >
        <div className="flex-shrink-0">
          <Image
            src="/Me.png"
            alt="Muhamad Alfin Pratama"
            width={120}
            height={120}
            className="rounded-full object-cover grayscale hover:grayscale-0 transition-all duration-300"
          />
        </div>
        <div className="text-center md:text-left">
          <h1 className="text-2xl font-bold text-white">
            Muhamad Alfin Pratama
          </h1>
          <div className="relative mx-auto md:mx-0 w-3/4 mt-2 mb-3">
            <div className="absolute inset-0 h-0.5 bg-gradient-to-r from-purple-500 to-orange-400"></div>
          </div>
          <div className="flex items-center justify-center md:justify-start space-x-2 mt-4">
            <MdOutlineWork className="text-purple-400" />
            <p className="text-sm font-semibold text-white">
              Student | Frontend Web
            </p>
          </div>
          <div className="flex items-center justify-center md:justify-start space-x-2 mt-2">
            <FaLocationDot className="text-orange-400" />
            <p className="text-xs text-gray-400">
              Bandung, West Java, Indonesia
            </p>
          </div>
        </div>
      </motion.div>

      {/* ======================================= */}
      {/* KARTU 2: DESKRIPSI DIRI               */}
      {/* ======================================= */}
      <motion.div
        className="col-span-full md:col-span-1 lg:col-span-2 bg-neutral-900/50 border border-neutral-700 rounded-xl p-6"
        variants={itemVariants}
      >
        <p className="text-sm text-gray-300 leading-relaxed">
          I am a frontend developer with expertise in React, Next.js, and
          Tailwind CSS. Passionate about creating user-friendly interfaces and
          delivering high-quality code. Let's work together!
        </p>
      </motion.div>

      {/* ======================================= */}
      {/* KARTU 3: MEDIA SOSIAL                 */}
      {/* ======================================= */}
      <motion.div
        className="col-span-full md:col-span-1 lg:col-span-2 row-span-1 bg-neutral-900/50 border border-neutral-700 rounded-xl p-6 flex items-center justify-center"
        variants={itemVariants}
      >
        <div className="flex items-center justify-center gap-4">
          {socials.map((social, index) => (
            <Link
              href={social.href}
              key={index}
              target="_blank"
              rel="noopener noreferrer"
              className="text-gray-400 hover:text-white transition-colors text-2xl"
            >
              {social.icon}
            </Link>
          ))}
        </div>
      </motion.div>

      {/* ======================================= */}
      {/* KARTU 4: EXPERIENCE                   */}
      {/* ======================================= */}
      <motion.div
        className="col-span-full lg:col-span-2 bg-neutral-900/50 border border-neutral-700 rounded-xl p-6"
        variants={itemVariants}
      >
        <div className="flex items-center gap-3 mb-4">
          <MdOutlineWork className="text-purple-400 text-xl" />
          <h3 className="text-lg font-bold text-white">Experience</h3>
        </div>
        <div className="space-y-4">
          <div className="border-l-2 border-purple-400 pl-4">
            <div className="flex items-center gap-2 mb-2">
              <h4 className="text-white font-semibold">
                Frontend Developer Intern
              </h4>
            </div>
            <div className="flex items-center gap-2 mb-2">
              <Image
                src="https://pilihjurusan.id/favicon.ico"
                alt="Pilih Jurusan Logo"
                width={16}
                height={16}
                className="rounded"
              />
              <Link
                href="https://pilihjurusan.id"
                target="_blank"
                rel="noopener noreferrer"
                className="text-orange-400 text-sm font-medium hover:text-orange-300 transition-colors"
              >
                Pilih Jurusan
              </Link>
            </div>
            <div className="flex items-center gap-1 mb-3">
              <FaCalendar className="text-gray-400 text-xs" />
              <p className="text-gray-400 text-xs">
                September 2024 - December 2024
              </p>
            </div>
            <ul className="text-gray-300 text-sm space-y-2">
              <li>
                • Reorganized assets and components to improve code cleanliness
                and reusability
              </li>
              <li>
                • Adapted and implemented unit testing using Cypress according
                to the latest code
              </li>
              <li>
                • Developed new sections and pages based on project manager
                requests
              </li>
              <li>
                • Built CMS with Firebase integration and data interaction using
                server actions in Next.js
              </li>
            </ul>
          </div>
        </div>
      </motion.div>

      {/* ======================================= */}
      {/* KARTU 5: PROJECTS                     */}
      {/* ======================================= */}
      <ProjectsSection />

      {/* ======================================= */}
      {/* KARTU 6: SKILLS                       */}
      {/* ======================================= */}
      <motion.div
        className="col-span-full row-span-2 bg-neutral-900/50 border border-neutral-700 rounded-xl p-6"
        variants={itemVariants}
      >
        <Skills />
      </motion.div>
    </motion.div>
  );
};

export default AboutSection;


================================================
FILE: components/BackgroundStar.tsx
================================================
"use client";

import React, { useMemo, useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { Points, PointMaterial } from "@react-three/drei";
// @ts-ignore
import * as random from "maath/random/dist/maath-random.esm";

const StarBackground = () => {
  const ref = useRef<THREE.Points>(null!);

  const positions = useMemo(
    () => random.inSphere(new Float32Array(5000 * 3), { radius: 1.2 }),
    []
  );
  const posRef = useRef<Float32Array>(positions.slice());

  useFrame((state, delta) => {
    if (!ref.current) return;

    const { mouse } = state;
    const pos = posRef.current;

    for (let i = 0; i < pos.length; i += 3) {
      const x = pos[i];
      const y = pos[i + 1];

      const mx = mouse.x * 1.2;
      const my = mouse.y * 1.2;

      const dx = x - mx;
      const dy = y - my;
      const dist = Math.sqrt(dx * dx + dy * dy);

      if (dist < 0.25) {
        const force = (0.25 - dist) * 0.03;
        pos[i] += dx * force;
        pos[i + 1] += dy * force;
      }
    }

    // kasih tau three.js kalau posisi berubah
    ref.current.geometry.attributes.position.needsUpdate = true;

    // rotasi background biar tetap hidup
    ref.current.rotation.x -= delta / 20;
    ref.current.rotation.y -= delta / 30;
  });

  return (
    <group rotation={[0, 0, Math.PI / 4]}>
      <Points ref={ref} positions={posRef.current} stride={3} frustumCulled>
        <PointMaterial
          transparent
          color="#ffffff"
          size={0.002}
          sizeAttenuation
          depthWrite={false}
        />
      </Points>
    </group>
  );
};

const StarsCanvas: React.FC = () => (
  <div className="w-full h-screen fixed inset-0 z-[1]">
    <Canvas camera={{ position: [0, 0, 1] }}>
      <StarBackground />
    </Canvas>
  </div>
);

export default StarsCanvas;


================================================
FILE: components/Banner.tsx
================================================
"use client";

import React from "react";
import Image from "next/image";
import Link from "next/link";
import dynamic from "next/dynamic";
import { motion } from "framer-motion";

const TypeAnimation = dynamic(
  () => import("react-type-animation").then((mod) => mod.TypeAnimation),
  { ssr: false }
);
const FaCloudDownloadAlt = dynamic(
  () => import("react-icons/fa").then((mod) => mod.FaCloudDownloadAlt),
  { ssr: false }
);

const Banner: React.FC = () => {
  const containerVariants = {
    hidden: { opacity: 0 },
    visible: {
      opacity: 1,
      transition: {
        delay: 0.2,
        staggerChildren: 0.2,
      },
    },
  };
  const itemVariants = {
    hidden: { opacity: 0, y: 30 },
    visible: { opacity: 1, y: 0, transition: { duration: 0.6 } },
  };
  const cardVariants = {
    hidden: { opacity: 0, scale: 0.9 },
    visible: { opacity: 1, scale: 1, transition: { duration: 0.7 } },
  };
  return (
    <motion.section
      className="flex items-center justify-center min-h-screen px-3 sm:px-4 py-6 sm:py-10"
      initial="hidden"
      animate="visible"
      variants={containerVariants}
    >
      <motion.div
        className="bg-neutral-900/40 backdrop-blur-sm border border-neutral-700/30 rounded-2xl sm:rounded-3xl p-4 sm:p-6 md:p-8 lg:p-12 max-w-4xl w-full shadow-2xl"
        variants={cardVariants}
      >
        <div className="flex flex-col lg:flex-row items-center gap-6 sm:gap-8 lg:gap-12">
          <motion.div className="flex-shrink-0" variants={itemVariants}>
            <div className="bg-neutral-700/50 p-3 sm:p-4 md:p-6 rounded-xl sm:rounded-2xl backdrop-blur-sm border border-neutral-600/30">
              <Image
                priority
                src="/avatar_2.webp"
                height={160}
                width={160}
                alt="Alfin"
                className="rounded-lg sm:rounded-xl hover:scale-105 transition-transform duration-300 w-32 h-32 sm:w-40 sm:h-40 md:w-[220px] md:h-[220px] object-cover"
              />
            </div>
          </motion.div>
          <motion.div
            className="flex-1 space-y-4 sm:space-y-6 text-center lg:text-left w-full"
            variants={itemVariants}
          >
            <motion.div
              className="space-y-1 sm:space-y-2"
              variants={itemVariants}
            >
              <h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold leading-tight">
                <span className="text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-orange-400">
                  Hi,
                </span>
                <br className="sm:hidden" />
                <span className="sm:ml-2">I'm Alfin</span>
              </h1>
            </motion.div>

            {/* Typing Effect Card */}
            <motion.div
              className="bg-neutral-700/30 rounded-lg sm:rounded-xl p-3 sm:p-4 border border-neutral-600/20"
              variants={itemVariants}
            >
              <div className="flex items-center justify-center lg:justify-start">
                <div className="text-lg sm:text-xl md:text-2xl font-medium text-gray-200 text-center lg:text-left">
                  <TypeAnimation
                    sequence={[
                      "Fullstack Developer",
                      1000,
                      "DevOps Engineer",
                      1000,
                    ]}
                    wrapper="span"
                    speed={50}
                    repeat={Infinity}
                  />
                </div>
              </div>
            </motion.div>

            <motion.div
              className="bg-neutral-700/20 rounded-lg sm:rounded-xl p-3 sm:p-4 md:p-5 border border-neutral-600/20"
              initial={{ opacity: 1, y: 0 }}
              animate={{ opacity: 1, y: 0 }}
            >
              <h2 className="text-sm sm:text-base md:text-lg text-gray-200 leading-relaxed">
                Passionate Fullstack Developer with a focus on{" "}
                <span className="text-transparent font-semibold bg-clip-text bg-gradient-to-r from-purple-400 to-orange-400">
                  React.js, php, golang, nodejs
                </span>{" "}
                development using{" "}
                <span className="text-transparent font-semibold bg-clip-text bg-gradient-to-r from-purple-400 to-orange-400">
                  Next.js, laravel, gofiber, expressjs
                </span>{" "}
                framework, dedicated to crafting elegant and user-friendly web
                applications.
              </h2>
            </motion.div>

            {/* Action Buttons */}
            <motion.div
              className="flex flex-col sm:flex-row gap-3 sm:gap-4 pt-2 sm:pt-4 w-full"
              variants={itemVariants}
            >
              <motion.a
                href={"/cv-alfin.pdf"}
                target="_blank"
                rel="noopener noreferrer"
                className="group flex items-center justify-center gap-2 relative overflow-hidden bg-gradient-to-r from-purple-500 to-orange-400 text-white font-semibold px-4 sm:px-6 py-3 rounded-lg sm:rounded-xl transition-all duration-300 hover:shadow-lg hover:shadow-purple-500/25 min-h-[44px] sm:min-h-[48px] text-sm sm:text-base w-full sm:w-auto"
                variants={itemVariants}
                whileHover={{ scale: 1.05 }}
                whileTap={{ scale: 0.95 }}
              >
                Download CV{" "}
                <FaCloudDownloadAlt className="text-sm sm:text-base" />
              </motion.a>

              <motion.div
                variants={itemVariants}
                whileHover={{ scale: 1.05 }}
                whileTap={{ scale: 0.95 }}
                className="w-full sm:w-auto"
              >
                <Link
                  href={"/blogs"}
                  className="flex items-center justify-center bg-neutral-700/50 backdrop-blur-sm border border-neutral-600/30 text-white font-semibold px-4 sm:px-6 py-3 rounded-lg sm:rounded-xl hover:bg-neutral-600/50 transition-all duration-300 min-h-[44px] sm:min-h-[48px] text-sm sm:text-base w-full"
                >
                  See My blogs
                </Link>
              </motion.div>
            </motion.div>
          </motion.div>
        </div>
      </motion.div>
    </motion.section>
  );
};

export default Banner;


================================================
FILE: components/BlogClientPage.tsx
================================================
"use client";

import Link from "next/link";
import Image from "next/image";
import { useState } from "react";
import { client } from "@/libs/sanity.client";
import imageUrlBuilder from "@sanity/image-url";
import { Post, Category } from "@/app/blogs/page";

const builder = imageUrlBuilder(client);
function urlFor(source: any) {
  return builder.image(source);
}

interface BlogClientPageProps {
  posts: Post[];
  categories: Category[];
}

export default function BlogClientPage({
  posts,
  categories,
}: BlogClientPageProps) {
  const [filteredPosts, setFilteredPosts] = useState<Post[]>(posts);
  const [activeCategory, setActiveCategory] = useState<string>("all");

  const filterPosts = (categoryId: string) => {
    setActiveCategory(categoryId);

    if (categoryId === "all") {
      setFilteredPosts(posts);
    } else {
      const filtered = posts.filter((post) =>
        post.categories?.some((cat) => cat._id === categoryId)
      );
      setFilteredPosts(filtered);
    }
  };

  return (
    <div className="min-h-screen py-28 text-white">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        {/* Header */}
        <div className="text-center mb-8 bg-neutral-900/50 p-6 border border-neutral-700 rounded-xl shadow-lg">
          <h1 className="text-3xl md:text-5xl font-extrabold tracking-tight">
            My Blog
          </h1>
          <p className="mt-4 text-lg text-gray-400">
            Sharing my thoughts, experiences, and insights on web development,
            programming, and technology.
          </p>
        </div>

        {/* Category Tabs */}
        <div className="mb-8">
          <div className="bg-neutral-900/50 border border-neutral-700 rounded-xl p-6 shadow-lg">
            <h3 className="text-lg font-semibold mb-4 text-gray-300">
              Filter by category :
            </h3>
            <div className="flex flex-wrap gap-3">
              {/* Tab Semua */}
              <button
                onClick={() => filterPosts("all")}
                className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-300 ${
                  activeCategory === "all"
                    ? "bg-purple-600 text-white shadow-lg shadow-purple-500/30"
                    : "bg-neutral-800 text-gray-300 hover:bg-neutral-700 hover:text-white"
                }`}
              >
                All Topics ({posts.length})
              </button>

              {/* Tab Categories */}
              {categories.map((category) => {
                const categoryPostCount = posts.filter((post) =>
                  post.categories?.some((cat) => cat._id === category._id)
                ).length;

                return (
                  <button
                    key={category._id}
                    onClick={() => filterPosts(category._id)}
                    className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-300 ${
                      activeCategory === category._id
                        ? "bg-purple-600 text-white shadow-lg shadow-purple-500/30"
                        : "bg-neutral-800 text-gray-300 hover:bg-neutral-700 hover:text-white"
                    }`}
                  >
                    {category.title} ({categoryPostCount})
                  </button>
                );
              })}
            </div>
          </div>
        </div>

        {/* Posts Grid */}
        {filteredPosts.length === 0 ? (
          <div className="text-center py-12">
            <div className="bg-neutral-900/50 border border-neutral-700 rounded-xl p-8">
              <p className="text-gray-400 text-lg">
                Tidak ada artikel untuk kategori ini.
              </p>
            </div>
          </div>
        ) : (
          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
            {filteredPosts.map((post) => (
              <Link key={post._id} href={`/blogs/${post.slug.current}`}>
                <div className="group block p-3 bg-neutral-900/50 border border-neutral-700 rounded-xl overflow-hidden shadow-lg hover:shadow-purple-500/30 transition-all duration-300 hover:scale-105">
                  <div className="relative w-full h-48 rounded-lg overflow-hidden">
                    <Image
                      src={
                        post.mainImage
                          ? urlFor(post.mainImage).url()
                          : "https://via.placeholder.com/400x300?text=No+Image"
                      }
                      alt={`Gambar untuk ${post.title}`}
                      fill
                      className="object-cover group-hover:scale-110 transition-transform duration-300"
                    />
                  </div>

                  <div className="p-6">
                    {/* Categories Badge */}
                    {post.categories && post.categories.length > 0 && (
                      <div className="flex flex-wrap gap-2 mb-3">
                        {post.categories.slice(0, 2).map((category) => (
                          <span
                            key={category._id}
                            className="px-2 py-1 bg-purple-600/20 text-purple-300 text-xs rounded-full border border-purple-500/30"
                          >
                            {category.title}
                          </span>
                        ))}
                        {post.categories.length > 2 && (
                          <span className="px-2 py-1 bg-gray-600/20 text-gray-400 text-xs rounded-full">
                            +{post.categories.length - 2}
                          </span>
                        )}
                      </div>
                    )}

                    <h2 className="text-xl font-bold mb-2 line-clamp-2 group-hover:text-purple-400 transition-colors">
                      {post.title}
                    </h2>

                    <div className="flex items-center text-sm text-gray-400 mt-4">
                      <div className="relative w-8 h-8 rounded-full overflow-hidden mr-3">
                        {post.authorImage && (
                          <Image
                            src={
                              post.authorImage?.asset
                                ? urlFor(post.authorImage)
                                    .width(40)
                                    .height(40)
                                    .url()
                                : "/default-avatar.jpg"
                            }
                            alt={`Foto ${post.authorName}`}
                            fill
                            className="object-cover"
                          />
                        )}
                      </div>
                      <span>{post.authorName}</span>
                      <span className="mx-2">&bull;</span>
                      <span>
                        {new Date(post.publishedAt).toLocaleDateString("id-ID")}
                      </span>
                    </div>
                  </div>
                </div>
              </Link>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}


================================================
FILE: components/ClientEffects.tsx
================================================
"use client";
import dynamic from "next/dynamic";

const StarsCanvas = dynamic(() => import("./BackgroundStar"), {
  ssr: false,
});

const NebulaEffect = dynamic(
  () =>
    import("./NebulaEffect").then((mod) => ({
      default: mod.NebulaEffect,
    })),
  {
    ssr: false,
  }
);

export default function ClientEffects() {
  return (
    <>
      <StarsCanvas />
      <NebulaEffect />
    </>
  );
}


================================================
FILE: components/CursorBacground.tsx
================================================
"use client";

import { useState } from "react";

export default function CursorBackground({
  children,
}: {
  children: React.ReactNode;
}) {
  const [pos, setPos] = useState({ x: 0, y: 0 });

  return (
    <div
      className="relative min-h-screen bg-neutral-900/50 verflow-hidden"
      onMouseMove={(e) => setPos({ x: e.clientX, y: e.clientY })}
    >
      {/* Layer gradasi mengikuti mouse */}
      <div
        className="pointer-events-none fixed inset-0 z-0"
        style={{
          background: `radial-gradient(
            600px at ${pos.x}px ${pos.y}px,
            rgba(147, 51, 234, 0.35),   /* purple-600 */
            rgba(251, 146, 60, 0.15),   /* orange-400 */
            transparent 80%
          )`,
          transition: "background 0.1s ease",
        }}
      />

      {/* Konten utama di atas */}
      <div className="relative z-10">{children}</div>
    </div>
  );
}


================================================
FILE: components/LinksCard.tsx
================================================
import Image from "next/image";
interface LinksCardProps {
    name: string;
    icon?: any;
}

const LinksCard = ({ name, icon }: LinksCardProps) => {
    return (
        <div className=" w-[500px] z-[1] bg-gray-700 rounded-lg cursor-pointer hover:bg-gray-800 hover:-translate-y-3 transition-all ease-in-out duration-300">
            <div className="flex justify-center h-[60px] z-[1] cursor-pointer items-center gap-3">
                <h3 className="text-white">{name}</h3>
                <Image src={icon} alt={name} width={30} height={30} />
            </div>

        </div>
    )
}

export default LinksCard

================================================
FILE: components/Navbar.tsx
================================================
"use client";
import { NavbarLinks } from "@/libs/NavbarLinks";
import Link from "next/link";
import React, { useState } from "react";
import { motion } from "framer-motion";
import { AiOutlineClose, AiOutlineMenu } from "react-icons/ai";

const Navbar: React.FC<{}> = () => {
  const navItemVariants = {
    hidden: { opacity: 0, y: -20 },
    visible: { opacity: 1, y: 0, transition: { duration: 0.5 } },
  };

  const containerVariants = {
    hidden: { opacity: 0 },
    visible: {
      opacity: 1,
      transition: {
        delayChildren: 0.2,
        staggerChildren: 0.1,
      },
    },
  };

  const [nav, setNav] = useState(false);

  const handleNav = () => {
    setNav(!nav);
  };

  return (
    <div className="w-full h-[5px] bg-['#111'] fixed backdrop-blur-sm py-12 z-50 px-10">
      <motion.div
        variants={containerVariants}
        className="w-full h-full flex flex-row items-center justify-between px-[10px]"
      >
        <Link
          title="Evrea logo"
          href="/"
          className="h-auto w-auto flex flex-row items-center"
        >
          <motion.h1
            variants={navItemVariants}
            className=" bg-clip-text bg-gradient-to-r from-purple-500 to-orange-500 text-transparent text-2xl font-bold"
          >
            Evrea
          </motion.h1>
        </Link>

        {/* Navbar for desktop (sudah benar) */}
        <div className="md:flex hidden">
          <motion.ul
            initial="hidden"
            animate="visible"
            variants={containerVariants}
            className="flex gap-5 items-center"
          >
            {NavbarLinks.map((nav) => (
              <motion.li key={nav.href} variants={navItemVariants}>
                <Link
                  className="group text-white transition-all duration-300 ease-in-out"
                  href={nav.href}
                >
                  <span className="bg-left-bottom bg-gradient-to-r from-purple-500 to-orange-500 font-bold bg-[length:0%_2px] bg-no-repeat group-hover:bg-[length:100%_2px] transition-all duration-500 ease-out">
                    {nav.text}
                  </span>
                </Link>
              </motion.li>
            ))}
          </motion.ul>
        </div>

        {/* Navbar for mobile (bagian yang diperbaiki) */}
        <div className="block md:hidden">
          {/* PERBAIKAN 1: Mengubah div menjadi button untuk aksesibilitas */}
          <button
            onClick={handleNav}
            className="cursor-pointer"
            aria-label={nav ? "Close menu" : "Open menu"} // Memberi label yang jelas
            aria-expanded={nav} // Menandakan status menu (terbuka/tertutup)
            aria-controls="mobile-menu" // Menghubungkan tombol dengan menu
          >
            {nav ? (
              <AiOutlineClose className="text-white" size={20} />
            ) : (
              <AiOutlineMenu size={20} className="text-white" />
            )}
          </button>

          <div
            className={
              nav
                ? "fixed left-0 top-0 w-[60%] min-h-screen bg-gradient-to-r from-purple-500 to-orange-500 z-[1] ease-in-out duration-500"
                : "fixed left-[-100%] top-0 ease-in-out duration-500"
            }
          >
            {/* PERBAIKAN 2: Judul dipindahkan ke luar <ul> */}
            <h1 className="p-4 text-center text-2xl font-bold text-white">
              Evrea
            </h1>

            <ul id="mobile-menu">
              {" "}
              {/* Menambahkan ID untuk dihubungkan dengan aria-controls */}
              {/* PERBAIKAN 3: Struktur <li> -> <Link> dibenarkan */}
              {NavbarLinks.map((item) => (
                <li
                  key={item.href}
                  className="border-b border-b-white rounded-xl top-10 hover:bg-black duration-300 border-gray-600"
                >
                  <Link
                    href={item.href}
                    onClick={() => setNav(false)}
                    className="block p-4 text-black hover:text-white cursor-pointer" // 'block' agar link mengisi seluruh area <li>
                  >
                    {item.text}
                  </Link>
                </li>
              ))}
            </ul>
          </div>
        </div>
      </motion.div>
    </div>
  );
};

export default Navbar;


================================================
FILE: components/NebulaEffect.tsx
================================================
export const NebulaEffect = () => {
  return (
    <>
      <div className="fixed inset-0 z-[0]">
        <div className="w-full h-full bg-[radial-gradient(circle_at_25%_40%,rgba(59,130,246,0.6),transparent_70%)] blur-3xl"></div>
      </div>
      <div className="fixed inset-0 z-[0]">
        <div className="w-full h-full bg-[radial-gradient(circle_at_65%_50%,rgba(175,170,224,0.6),transparent_70%)] blur-3xl"></div>
      </div>
      <div className="fixed inset-0 z-[0]">
        <div className="w-full h-full bg-[radial-gradient(circle_at_50%_50%,rgba(255,255,255,0.35),transparent_60%)] blur-2xl"></div>
      </div>
    </>
  );
};


================================================
FILE: components/ProjectCard.tsx
================================================
'use client';
import Image from 'next/image';
import Link from 'next/link';
import { FaGithub, FaLink } from 'react-icons/fa';
import { motion } from 'framer-motion';

interface ProjectCardProps {
    srcImg: string;
    demoUrl: string;
    srcGithub: string;
    title: string;
    description: string;
    date: string;
}

const ProjectCard = ({
    srcImg,
    demoUrl,
    srcGithub,
    title,
    description,
    date
}: ProjectCardProps) => {

    // Variants for the container
    const containerVariants = {
        hidden: { opacity: 0 },
        visible: {
            opacity: 1,
            transition: {
                delayChildren: 0.3,
                staggerChildren: 0.2,
            },
        },
    };

    // Variants for the image
    const imageVariants = {
        hidden: { opacity: 0, x: -50 },
        visible: { opacity: 1, x: 0, transition: { duration: 0.5 } },
    };

    // Variants for the text
    const textVariants = {
        hidden: { opacity: 0, x: 50 },
        visible: { opacity: 1, x: 0, transition: { duration: 0.5 } },
    };

    return (
        <motion.div
            className="bg-transparent border border-white p-6 rounded-lg shadow-lg  text-white max-w-md mx-auto flex flex-col justify-between h-full"
            variants={containerVariants}
            initial="hidden"
            animate="visible"
        >
            <motion.div className='mb-5' variants={containerVariants}>
                < motion.div className="relative w-full h-56 z-[1]" variants={imageVariants} >
                    <Image src={srcImg} alt={title} layout="fill" objectFit="cover" className="rounded-lg cursor-pointer object-contain hover:scale-105 transition-all duration-500" />
                </ motion.div>
                <motion.div className="flex justify-between items-center mt-4" variants={textVariants}>
                    <span className="text-sm text-gray-400">{date}</span>
                </motion.div>
                <motion.h3 className="mt-2 text-xl font-semibold text-white" variants={textVariants}>{title}</motion.h3>
                <motion.p className="mt-2 text-gray-300" variants={textVariants}>{description}</motion.p>
            </motion.div >
            <motion.div className="flex justify-center items-center z-[1] gap-4 mt-auto" variants={textVariants}>
                <Link href={srcGithub}>
                    <div className="w-6 h-6 rounded-full">
                        <FaGithub className='w-full h-full' />
                    </div>
                </Link>
                <Link href={demoUrl}>
                    <div className="w-6 h-6 rounded-full">
                        <FaLink className='w-full h-full' />
                    </div>
                </Link>
            </motion.div>
        </motion.div >
    );
};

export default ProjectCard;


================================================
FILE: components/ProjectSection.tsx
================================================
"use client";

import { project_data } from "@/libs/constant";
import { motion } from "framer-motion";
import { useState } from "react";
import { FaGithub, FaExternalLinkAlt, FaCalendar } from "react-icons/fa";
import Image from "next/image";
import Link from "next/link";

export function ProjectsSection() {
  const [activeIndex, setActiveIndex] = useState(0);

  return (
    <motion.div
      className="col-span-full lg:col-span-2 bg-neutral-900/50 border border-neutral-700 rounded-xl p-6"
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5 }}
    >
      {/* Header */}
      <div className="flex items-center gap-3 mb-6">
        <FaGithub className="text-orange-400 text-xl" />
        <h3 className="text-lg font-bold text-white">Projects</h3>
      </div>

      <div className="grid grid-cols-4 gap-6">
        {/* Sidebar Project List */}
        <div className="col-span-1 flex flex-col space-y-3 text-sm">
          {project_data.map((proj, index) => (
            <button
              key={index}
              onClick={() => setActiveIndex(index)}
              className={`text-left transition-colors truncate ${
                activeIndex === index
                  ? "text-orange-400 border-l-2 border-orange-400 pl-2"
                  : "text-gray-400 hover:text-white pl-2"
              }`}
            >
              {proj.title}
            </button>
          ))}
        </div>

        {/* Project Details */}
        <div className="col-span-3">
          <div className="flex items-center gap-3 mb-3">
            <Image
              src={project_data[activeIndex].image}
              alt={project_data[activeIndex].title}
              width={60}
              height={40}
              className="rounded-md border border-neutral-600 object-cover"
            />
            <h4 className="text-white font-semibold text-base">
              {project_data[activeIndex].title}
            </h4>
          </div>

          {/* Date */}
          <div className="flex items-center gap-2 text-gray-400 text-xs mb-3">
            <FaCalendar className="text-xs" />
            {project_data[activeIndex].date}
          </div>

          {/* Description */}
          <p className="text-gray-300 text-sm leading-relaxed mb-4">
            {project_data[activeIndex].description}
          </p>

          {/* Links */}
          <div className="flex gap-3">
            {project_data[activeIndex].linkDemo && (
              <Link
                href={project_data[activeIndex].linkDemo}
                target="_blank"
                rel="noopener noreferrer"
                className="flex items-center gap-1 text-purple-400 hover:text-purple-300 text-sm"
              >
                <FaExternalLinkAlt className="text-xs" /> Demo
              </Link>
            )}
            {project_data[activeIndex].linkGithub && (
              <Link
                href={project_data[activeIndex].linkGithub}
                target="_blank"
                rel="noopener noreferrer"
                className="flex items-center gap-1 text-orange-400 hover:text-orange-300 text-sm"
              >
                <FaGithub className="text-xs" /> Code
              </Link>
            )}
          </div>
        </div>
      </div>
    </motion.div>
  );
}


================================================
FILE: components/Skills.tsx
================================================
"use client";

import { motion } from "framer-motion";
import Image from "next/image";
import { Frontend_skill, Backend_Skill, tools } from "@/libs/constant";

const categories = [
  {
    title: "Frontend",
    data: Frontend_skill,
    color: "from-green-500/20 to-teal-500/20",
  },
  {
    title: "Backend",
    data: Backend_Skill,
    color: "from-blue-500/20 to-purple-500/20",
  },
  { title: "Tools", data: tools, color: "from-orange-500/20 to-red-500/20" },
];

const Skills = () => {
  return (
    <div className="w-full max-w-7xl mx-auto px-4">
      <div className="text-center mb-12">
        <motion.h2
          initial={{ opacity: 0, y: -20 }}
          whileInView={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.6 }}
          viewport={{ once: true }}
          className="text-3xl md:text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-600 bg-clip-text text-transparent mb-4"
        >
          Technical Skills
        </motion.h2>
        <motion.p
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.6, delay: 0.2 }}
          viewport={{ once: true }}
          className="text-gray-400 text-lg max-w-2xl mx-auto"
        >
          Technologies and tools I work with
        </motion.p>
      </div>

      <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-8">
        {categories.map((cat, i) => (
          <motion.div
            key={i}
            initial={{ opacity: 0, y: 30, scale: 0.9 }}
            whileInView={{ opacity: 1, y: 0, scale: 1 }}
            transition={{
              duration: 0.6,
              delay: i * 0.1,
              type: "spring",
              stiffness: 100,
            }}
            viewport={{ once: true }}
            whileHover={{
              scale: 1.02,
              transition: { duration: 0.2 },
            }}
            className={`
                            relative overflow-hidden
                            bg-gradient-to-br ${cat.color}
                            backdrop-blur-xl 
                            border border-white/10
                            rounded-3xl p-8
                            shadow-2xl shadow-black/20
                            hover:shadow-3xl hover:shadow-black/30
                            transition-all duration-300
                        `}
          >
            {/* Background Pattern */}
            <div className="absolute inset-0 opacity-5">
              <div className="absolute top-0 right-0 w-32 h-32 bg-white rounded-full -translate-y-16 translate-x-16"></div>
              <div className="absolute bottom-0 left-0 w-24 h-24 bg-white rounded-full translate-y-12 -translate-x-12"></div>
            </div>

            {/* Content */}
            <div className="relative z-10">
              {/* Category Header */}
              <div className="flex items-center justify-between mb-6">
                <h3 className="text-xl font-bold text-white">{cat.title}</h3>
                <div className="w-8 h-1 bg-gradient-to-r from-white/50 to-transparent rounded-full"></div>
              </div>

              {/* Skills Grid */}
              <div className="grid grid-cols-3 sm:grid-cols-4 gap-6">
                {cat.data.map((skill, idx) => (
                  <motion.div
                    key={idx}
                    initial={{ opacity: 0, scale: 0 }}
                    whileInView={{ opacity: 1, scale: 1 }}
                    transition={{
                      duration: 0.4,
                      delay: i * 0.1 + idx * 0.05,
                      type: "spring",
                      stiffness: 200,
                    }}
                    viewport={{ once: true }}
                    whileHover={{
                      scale: 1.1,
                      y: -5,
                      transition: { duration: 0.2 },
                    }}
                    className="group"
                  >
                    <div
                      className="
                                            flex flex-col items-center text-center
                                            p-3 rounded-2xl
                                            
                                            transition-all duration-300
                                            cursor-pointer
                                        "
                    >
                      <div
                        className="
                                                relative w-12 h-12 mb-3
                                                flex items-center justify-center
                                                bg-white/10 rounded-xl
                                                group-hover:bg-white/20
                                                transition-all duration-300
                                            "
                      >
                        <Image
                          src={skill.Image}
                          width={32}
                          height={32}
                          alt={skill.skill_name}
                          className="object-contain filter group-hover:scale-110 transition-transform duration-300"
                        />
                      </div>
                      <p
                        className="
                                                text-xs font-medium text-gray-300 
                                                group-hover:text-white
                                                transition-colors duration-300
                                                leading-tight
                                            "
                      >
                        {skill.skill_name}
                      </p>
                    </div>
                  </motion.div>
                ))}
              </div>

              {/* Skills Count */}
              <div className="mt-6 pt-4 border-t border-white/10">
                <p className="text-xs text-gray-400 text-center">
                  {cat.data.length} skills
                </p>
              </div>
            </div>
          </motion.div>
        ))}
      </div>
    </div>
  );
};

export default Skills;


================================================
FILE: components/about.tsx
================================================
'use client'
import { Link as ScrollLink, Element } from 'react-scroll';
import Skills from '@/components/Skills';
import AboutSection from '@/components/AboutSection';
import { FaArrowDownLong } from "react-icons/fa6";
export const About = () => {
    return (
        <main className='bg-black'>
            <AboutSection />
            <div className='flex justify-center items-center h-5 z-[1] cursor-pointer'>
                <ScrollLink to="skills" smooth={true} duration={1000} className='z-[1] -mt-8'>
                    <FaArrowDownLong className='animate-bounce text-4xl text-white' />
                </ScrollLink>
            </div>
            <Element name="skills" className='lg:min-h-screen h-screen'>
                <Skills />
            </Element>
        </main>
    );
};

================================================
FILE: components/contact.tsx
================================================
"use client";

import { FaMapMarkerAlt, FaPhoneAlt, FaEnvelope } from "react-icons/fa";
import { motion } from "framer-motion";
import { useState } from "react";

const ContactForm = () => {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [subject, setSubject] = useState("");
  const [message, setMessage] = useState("");
  const [status, setStatus] = useState("");

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setStatus("Sending...");

    try {
      const response = await fetch("/api/contact", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name, email, subject, message }),
      });

      const data = await response.json();
      setStatus(data.message);
    } catch (error) {
      console.error("Error sending email:", error);
      setStatus("Error sending email");
    } finally {
      setName("");
      setEmail("");
      setSubject("");
      setMessage("");
    }
  };

  return (
    <main className="min-h-screen flex justify-center items-center py-24 px-6 md:px-20">
      <div className="w-full max-w-6xl">
        <motion.div
          className="bg-neutral-900/80 backdrop-blur-sm rounded-2xl shadow-2xl border border-neutral-700/50 overflow-hidden"
          initial={{ opacity: 0, y: 50 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.8 }}
        >
          <div className="grid grid-cols-1 lg:grid-cols-2">
            {/* Left side - Contact Info */}
            <motion.div
              className="p-8 lg:p-12 bg-gradient-to-br from-neutral-800/50 to-neutral-900/50"
              initial={{ x: -50, opacity: 0 }}
              animate={{ x: 0, opacity: 1 }}
              transition={{ duration: 0.8, delay: 0.2 }}
            >
              <div className="h-full flex flex-col justify-center">
                <h1 className="text-4xl lg:text-5xl text-white font-extrabold tracking-tight mb-4">
                  Get in touch
                </h1>
                <p className="text-lg lg:text-xl font-medium text-gray-300 mb-8">
                  Fill in the form to start a conversation
                </p>

                <div className="space-y-6">
                  <div className="flex items-center text-gray-300">
                    <div className="w-12 h-12 bg-neutral-700/50 rounded-full flex items-center justify-center mr-4">
                      <FaMapMarkerAlt className="w-5 h-5 text-purple-400" />
                    </div>
                    <p className="text-base font-medium">
                      Bandung City, West Java, Indonesia
                    </p>
                  </div>

                  <div className="flex items-center text-gray-300">
                    <div className="w-12 h-12 bg-neutral-700/50 rounded-full flex items-center justify-center mr-4">
                      <FaPhoneAlt className="w-5 h-5 text-purple-400" />
                    </div>
                    <p className="text-base font-medium">+62 851 7536 9960</p>
                  </div>

                  <div className="flex items-center text-gray-300">
                    <div className="w-12 h-12 bg-neutral-700/50 rounded-full flex items-center justify-center mr-4">
                      <FaEnvelope className="w-5 h-5 text-purple-400" />
                    </div>
                    <p className="text-base font-medium">
                      muhamadalfinpratamaa@gmail.com
                    </p>
                  </div>
                </div>
              </div>
            </motion.div>

            {/* Right side - Contact Form */}
            <motion.div
              className="p-8 lg:p-12"
              initial={{ x: 50, opacity: 0 }}
              animate={{ x: 0, opacity: 1 }}
              transition={{ duration: 0.8, delay: 0.4 }}
            >
              <form onSubmit={handleSubmit} className="space-y-6">
                <div>
                  <label
                    htmlFor="name"
                    className="block text-sm font-medium text-gray-300 mb-2"
                  >
                    Full Name
                  </label>
                  <input
                    type="text"
                    id="name"
                    value={name}
                    onChange={(e) => setName(e.target.value)}
                    placeholder="Enter your full name"
                    className="w-full py-3 px-4 rounded-lg bg-neutral-700/50 border border-neutral-600 text-white placeholder-gray-400 font-medium focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-300"
                    required
                  />
                </div>

                <div>
                  <label
                    htmlFor="email"
                    className="block text-sm font-medium text-gray-300 mb-2"
                  >
                    Email
                  </label>
                  <input
                    type="email"
                    id="email"
                    value={email}
                    onChange={(e) => setEmail(e.target.value)}
                    placeholder="Enter your email"
                    className="w-full py-3 px-4 rounded-lg bg-neutral-700/50 border border-neutral-600 text-white placeholder-gray-400 font-medium focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-300"
                    required
                  />
                </div>

                <div>
                  <label
                    htmlFor="subject"
                    className="block text-sm font-medium text-gray-300 mb-2"
                  >
                    Subject
                  </label>
                  <input
                    type="text"
                    id="subject"
                    value={subject}
                    onChange={(e) => setSubject(e.target.value)}
                    placeholder="Enter email subject"
                    className="w-full py-3 px-4 rounded-lg bg-neutral-700/50 border border-neutral-600 text-white placeholder-gray-400 font-medium focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-300"
                    required
                  />
                </div>

                <div>
                  <label
                    htmlFor="message"
                    className="block text-sm font-medium text-gray-300 mb-2"
                  >
                    Message
                  </label>
                  <textarea
                    id="message"
                    value={message}
                    onChange={(e) => setMessage(e.target.value)}
                    placeholder="Enter your message"
                    rows={5}
                    className="w-full py-3 px-4 rounded-lg bg-neutral-700/50 border border-neutral-600 text-white placeholder-gray-400 font-medium focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-300 resize-none"
                    required
                  />
                </div>

                <motion.button
                  type="submit"
                  className="w-full bg-gradient-to-r from-purple-500 to-pink-500 text-white font-bold py-3 px-6 rounded-lg hover:from-purple-600 hover:to-pink-600 transition-all duration-300 shadow-lg hover:shadow-purple-500/25"
                  whileHover={{ scale: 1.02 }}
                  whileTap={{ scale: 0.98 }}
                  disabled={status === "Sending..."}
                >
                  {status || "Send Message"}
                </motion.button>

                {status && status !== "Sending..." && (
                  <motion.p
                    initial={{ opacity: 0 }}
                    animate={{ opacity: 1 }}
                    className={`text-center text-sm font-medium ${
                      status.includes("Error")
                        ? "text-red-400"
                        : "text-green-400"
                    }`}
                  >
                    {status}
                  </motion.p>
                )}
              </form>
            </motion.div>
          </div>
        </motion.div>
      </div>
    </main>
  );
};

export default ContactForm;


================================================
FILE: libs/NavbarLinks.ts
================================================
export const NavbarLinks = [
  {
    text: "Home",
    href: "/",
  },
  {
    text: "About Me",
    href: "/about",
  },
  {
    text: "Blogs",
    href: "/blogs",
  },
  {
    text: "Contact Me",
    href: "/contact",
  },
];


================================================
FILE: libs/constant.ts
================================================
export const Social_Icons = [
  {
    link: "https://github.com/alfinpratamaa",
    image: "/Github.svg",
    alt: "Github",
  },
  {
    link: "https://www.instagram.com/visfiveor5",
    image: "/Instagram.svg",
    alt: "Instagram",
  },
  {
    link: "https://www.linkedin.com/in/alfinpr/",
    image: "/LinkedIn.svg",
    alt: "LinkedIn",
  },
  {
    link: "https://t.me/visfiveor5",
    image: "/telegram.svg",
    alt: "Telegram",
  },
  {
    link: "https://wa.me/6285175369960",
    image: "/whatsapp.svg",
    alt: "Whatsapp",
  },
];

export const Backend_Skill = [
  {
    skill_name: "Node.js",
    Image:
      "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nodejs/nodejs-original.svg",
    width: 80,
    height: 80,
  },
  {
    skill_name: "Express.js",
    Image:
      "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/express/express-original.svg",
    width: 80,
    height: 80,
  },
  {
    skill_name: "NestJS",
    Image: "https://img.jsdelivr.com/github.com/nestjs.png",
    width: 80,
    height: 80,
  },
  {
    skill_name: "Golang",
    Image:
      "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/go/go-original.svg",
    width: 80,
    height: 80,
  },
  {
    skill_name: "PHP",
    Image:
      "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/php/php-original.svg",
    width: 80,
    height: 80,
  },
  {
    skill_name: "Laravel",
    Image: "https://www.svgrepo.com/show/353985/laravel.svg",
    width: 80,
    height: 80,
  },
  {
    skill_name: "PostgreSQL",
    Image:
      "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/postgresql/postgresql-original.svg",
    width: 80,
    height: 80,
  },
  {
    skill_name: "MySQL",
    Image:
      "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/mysql/mysql-original.svg",
    width: 80,
    height: 80,
  },
  {
    skill_name: "MongoDB",
    Image:
      "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/mongodb/mongodb-original.svg",
    width: 80,
    height: 80,
  },
];

export const Frontend_skill = [
  {
    skill_name: "Html 5",
    Image: "/html.png",
    width: 80,
    height: 80,
  },
  {
    skill_name: "Css",
    Image: "/css.png",
    width: 80,
    height: 80,
  },
  {
    skill_name: "Java Script",
    Image: "/js.png",
    width: 65,
    height: 65,
  },
  {
    skill_name: "Tailwind Css",
    Image: "/tailwind.png",
    width: 80,
    height: 80,
  },
  {
    skill_name: "React",
    Image: "/react.png",
    width: 80,
    height: 80,
  },
  {
    skill_name: "React Query",
    Image: "/reactquery.png",
    width: 80,
    height: 80,
  },
  {
    skill_name: "Type Script",
    Image: "/ts.png",
    width: 80,
    height: 80,
  },
  {
    skill_name: "Next js",
    Image: "/Next.js.png",
    width: 80,
    height: 80,
  },
];

export const tools = [
  {
    skill_name: "Figma",
    Image: "/Figma.svg",
    width: 80,
    height: 80,
  },
  {
    skill_name: "Git",
    Image: "/git.png",
    width: 80,
    height: 80,
  },
  {
    skill_name: "Github",
    Image: "/Github.svg",
    width: 80,
    height: 80,
  },
  {
    skill_name: "VS Code",
    Image: "/vscode.png",
    width: 80,
    height: 80,
  },
  {
    skill_name: "Postman",
    Image: "/postman.png",
    width: 80,
    height: 80,
  },
];

export const project_data = [
  {
    title: "Mangaloomp (Comics Reader)",
    description:
      "Mangaloom is a web application for reading manga online. Built with Next.js and Tailwind CSS, it features a clean UI, search functionality, and a vast collection of manga titles.",
    image: "/mangaloom.png",
    linkDemo: "https://mangaloom.app",
    linkGithub: "https://github.com/Mangaloom/web",
    date: "March 2024 - Present",
  },
  {
    title: "E-Commerce Book Store",
    description:
      "An e-commerce website for books built with Laravel + Livewire and Tailwind CSS. Features include product browsing, shopping cart, user authentication, and admin panel for managing products and orders.",
    image: "/ngabaca.png",
    linkDemo: "https://ngabaca.me",
    linkGithub: "https://github.com/Alfinpratamaa/ngabaca",
    date: "June 2025 - August 2025",
  },
  {
    title: "Selfgalery Web Apps",
    description:
      "Photo gallery application built with Next.js and Tailwind CSS for storing photos. Features upload, delete, and download functionality.",
    image: "/selfgalery.png",
    linkDemo: "https://selfgalery.vercel.app/",
    linkGithub: "https://github.com/Alfinpratamaa/selfgalery",
    date: "January 2024 - February 2024",
  },
  {
    title: "Portfolio",
    description: "Personal portfolio built with Next.js and Tailwind CSS",
    image: "/portfolio.png",
    linkDemo: "https://muhamadalfinpratama.vercel.app/",
    linkGithub: "https://github.com/Alfinpratamaa/portfolio",
    date: "June 2024",
  },
];


================================================
FILE: libs/sanity.client.ts
================================================
import { createClient } from 'next-sanity'

const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET
const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION

export const client = createClient({
  projectId,
  dataset,
  apiVersion,
  useCdn: true, 
})

================================================
FILE: next.config.mjs
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "**",
      },
      {
        protocol: "http",
        hostname: "**",
      },
    ],
  },
  output: "standalone",
};

export default nextConfig;
  

================================================
FILE: package.json
================================================
{
  "name": "out",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build --turbopack",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@portabletext/react": "^4.0.1",
    "@sanity/image-url": "^1.1.0",
    "@types/mailgun-js": "^0.22.18",
    "@types/nodemailer": "^7.0.1",
    "@types/nodemailer-mailgun-transport": "^1.4.6",
    "@types/react-scroll": "^1.8.10",
    "@types/react-typing-effect": "^2.0.7",
    "@vercel/analytics": "^1.5.0",
    "clsx": "^2.1.1",
    "framer-motion": "^12.23.12",
    "mailgun-js": "^0.22.0",
    "next": "^15.5.2",
    "next-sanity": "^10.0.15",
    "next-sitemap": "^4.2.3",
    "nextjs-toploader": "^3.8.16",
    "nodemailer": "^7.0.5",
    "nodemailer-mailgun-transport": "^2.1.5",
    "react": "^19.1.1",
    "react-dom": "^19.1.1",
    "react-icons": "^5.5.0",
    "react-scroll": "^1.9.3",
    "react-tippy": "^1.4.0",
    "react-type-animation": "^3.2.0",
    "sharp": "^0.34.3"
  },
  "devDependencies": {
    "@react-three/drei": "^10.7.4",
    "@react-three/fiber": "^9.3.0",
    "@tailwindcss/postcss": "^4.1.12",
    "@types/node": "^24.3.0",
    "@types/react": "^19.1.11",
    "@types/react-dom": "^19.1.8",
    "autoprefixer": "^10.4.21",
    "eslint": "^9.34.0",
    "eslint-config-next": "^15.5.2",
    "postcss": "^8.5.6",
    "react-intersection-observer": "^9.16.0",
    "tailwindcss": "^4.1.12",
    "three": "^0.179.1",
    "typescript": "^5.9.2"
  },
  "engines": {
    "node": ">=20.x"
  }
}


================================================
FILE: postcss.config.mjs
================================================
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};


================================================
FILE: provider/ClientProvicer.tsx
================================================
"use client";

import NextTopLoader from "nextjs-toploader";

function ClientProvider() {
  return (
    <>
      <NextTopLoader
        showSpinner={false}
        crawlSpeed={200}
        height={3}
        crawl={true}
        color="transparent"
      />

      <style jsx global>{`
        #nprogress .bar {
          background: linear-gradient(to right, #9333ea, #fb923c) !important;
        }
      `}</style>
    </>
  );
}

export { ClientProvider };


================================================
FILE: provider/SkillData.tsx
================================================
'use client';
import { useInView } from 'react-intersection-observer';
import { motion } from 'framer-motion';
import Image from 'next/image';
interface Props {
    src: string;
    width: number;
    height: number;
    index: number;
    name?: string;
}
const SkillData = ({ src, width, height, index, name }: Props) => {
    const [ref, inView] = useInView({
        triggerOnce: true
    })

    const imageVariant = {
        hidden: {
            opacity: 0,
        },
        visible: {
            opacity: 1
        }
    }

    const animationDelay = 0.3
    return (
        <motion.div
            ref={ref}
            initial="hidden"
            variants={imageVariant}
            animate={inView ? "visible" : "hidden"}
            custom={index}
            transition={{ delay: index * animationDelay }}

        >
            <Image src={src} width={width} height={height} alt={name || "image err"} />
        </motion.div>
    )
}

export default SkillData

================================================
FILE: tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: "class",
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",

    // Or if using `src` directory:
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    container: {
      padding: {
        DEFAULT: "1rem",
        sm: "2rem",
        lg: "4rem",
        xl: "15rem",
        "2xl": "22rem",
      },
    },
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
      backgroundColor: {
        primary: "#DD7C7F",
      },
    },
  },
  plugins: [],
};


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": [
        "./*"
      ]
    },
    "target": "ES2017"
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}


================================================
FILE: types/index.ts
================================================
export type ProjectFrontmatter = {
  slug?: string;
  title: string;
  publishedAt?: string;
  lastUpdated?: string;
  description: string;
  category?: string;
  techs?: string;
  banner?: string;
  link?: string;
  github?: string;
  youtube?: string;
};
Download .txt
gitextract_riayleb2/

├── .gitignore
├── .nvmrc
├── README.md
├── app/
│   ├── about/
│   │   └── page.tsx
│   ├── api/
│   │   └── contact/
│   │       └── route.ts
│   ├── blogs/
│   │   ├── [slug]/
│   │   │   ├── PostContent.tsx
│   │   │   └── page.tsx
│   │   └── page.tsx
│   ├── contact/
│   │   └── page.tsx
│   ├── globals.css
│   ├── layout.tsx
│   ├── page.tsx
│   ├── robots.ts
│   └── sitemap.ts
├── components/
│   ├── AboutSection.tsx
│   ├── BackgroundStar.tsx
│   ├── Banner.tsx
│   ├── BlogClientPage.tsx
│   ├── ClientEffects.tsx
│   ├── CursorBacground.tsx
│   ├── LinksCard.tsx
│   ├── Navbar.tsx
│   ├── NebulaEffect.tsx
│   ├── ProjectCard.tsx
│   ├── ProjectSection.tsx
│   ├── Skills.tsx
│   ├── about.tsx
│   └── contact.tsx
├── libs/
│   ├── NavbarLinks.ts
│   ├── constant.ts
│   └── sanity.client.ts
├── next.config.mjs
├── package.json
├── postcss.config.mjs
├── provider/
│   ├── ClientProvicer.tsx
│   └── SkillData.tsx
├── tailwind.config.js
├── tsconfig.json
└── types/
    └── index.ts
Download .txt
SYMBOL INDEX (24 symbols across 16 files)

FILE: app/blogs/[slug]/PostContent.tsx
  function PostContent (line 4) | function PostContent({ post, ptComponents, urlFor }: any) {

FILE: app/blogs/[slug]/page.tsx
  function urlFor (line 12) | function urlFor(source: any) {
  type PageProps (line 93) | type PageProps = {
  function generateMetadata (line 98) | async function generateMetadata({
  function PostPage (line 161) | async function PostPage({ params }: PageProps) {

FILE: app/blogs/page.tsx
  type Post (line 54) | interface Post {
  type Category (line 65) | interface Category {
  function BlogPage (line 88) | async function BlogPage() {

FILE: app/layout.tsx
  function RootLayout (line 72) | function RootLayout({

FILE: app/page.tsx
  function Home (line 3) | function Home() {

FILE: app/robots.ts
  function robots (line 3) | function robots(): MetadataRoute.Robots {

FILE: app/sitemap.ts
  type Post (line 8) | interface Post {
  function sitemap (line 15) | async function sitemap(): Promise<MetadataRoute.Sitemap> {

FILE: components/BlogClientPage.tsx
  function urlFor (line 11) | function urlFor(source: any) {
  type BlogClientPageProps (line 15) | interface BlogClientPageProps {
  function BlogClientPage (line 20) | function BlogClientPage({

FILE: components/ClientEffects.tsx
  function ClientEffects (line 18) | function ClientEffects() {

FILE: components/CursorBacground.tsx
  function CursorBackground (line 5) | function CursorBackground({

FILE: components/LinksCard.tsx
  type LinksCardProps (line 2) | interface LinksCardProps {

FILE: components/ProjectCard.tsx
  type ProjectCardProps (line 7) | interface ProjectCardProps {

FILE: components/ProjectSection.tsx
  function ProjectsSection (line 10) | function ProjectsSection() {

FILE: provider/ClientProvicer.tsx
  function ClientProvider (line 5) | function ClientProvider() {

FILE: provider/SkillData.tsx
  type Props (line 5) | interface Props {

FILE: types/index.ts
  type ProjectFrontmatter (line 1) | type ProjectFrontmatter = {
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (87K chars).
[
  {
    "path": ".gitignore",
    "chars": 405,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": ".nvmrc",
    "chars": 7,
    "preview": "20.19.1"
  },
  {
    "path": "README.md",
    "chars": 11,
    "preview": "hello world"
  },
  {
    "path": "app/about/page.tsx",
    "chars": 1084,
    "preview": "import AboutSection from \"@/components/AboutSection\";\nimport { Metadata } from \"next\";\nimport React from \"react\";\n\nexpor"
  },
  {
    "path": "app/api/contact/route.ts",
    "chars": 1087,
    "preview": "export const runtime = \"edge\";\n\nexport const POST = async (req: Request) => {\n  try {\n    const { name, email, subject, "
  },
  {
    "path": "app/blogs/[slug]/PostContent.tsx",
    "chars": 1178,
    "preview": "import Image from \"next/image\";\nimport { PortableText } from \"@portabletext/react\";\n\nexport default function PostContent"
  },
  {
    "path": "app/blogs/[slug]/page.tsx",
    "chars": 6198,
    "preview": "import { client } from \"@/libs/sanity.client\";\nimport imageUrlBuilder from \"@sanity/image-url\";\nimport { groq } from \"ne"
  },
  {
    "path": "app/blogs/page.tsx",
    "chars": 2255,
    "preview": "import { client } from \"@/libs/sanity.client\";\nimport { groq } from \"next-sanity\";\nimport BlogClientPage from \"@/compone"
  },
  {
    "path": "app/contact/page.tsx",
    "chars": 897,
    "preview": "import { type Metadata } from \"next\";\nimport ContactForm from \"@/components/contact\";\n\nexport const metadata: Metadata ="
  },
  {
    "path": "app/globals.css",
    "chars": 1224,
    "preview": "@import \"tailwindcss\";\n\n:root {\n  --foreground-rgb: 0, 0, 0;\n  --background-start-rgb: 214, 219, 220;\n  --background-end"
  },
  {
    "path": "app/layout.tsx",
    "chars": 2927,
    "preview": "import type { Metadata } from \"next\";\nimport { Poppins } from \"next/font/google\";\nimport \"./globals.css\";\nimport Navbar "
  },
  {
    "path": "app/page.tsx",
    "chars": 134,
    "preview": "import Banner from \"@/components/Banner\";\n\nexport default function Home() {\n  return (\n    <main>\n      <Banner />\n    <"
  },
  {
    "path": "app/robots.ts",
    "chars": 221,
    "preview": "import { MetadataRoute } from \"next\";\n\nexport default function robots(): MetadataRoute.Robots {\n  return {\n    rules: {\n"
  },
  {
    "path": "app/sitemap.ts",
    "chars": 1245,
    "preview": "// app/sitemap.ts\n\nimport { MetadataRoute } from \"next\";\nimport { client } from \"@/libs/sanity.client\";\nimport { groq } "
  },
  {
    "path": "components/AboutSection.tsx",
    "chars": 7682,
    "preview": "\"use client\";\n\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { motion } from \"framer-motion\";\nimp"
  },
  {
    "path": "components/BackgroundStar.tsx",
    "chars": 1864,
    "preview": "\"use client\";\n\nimport React, { useMemo, useRef } from \"react\";\nimport { Canvas, useFrame } from \"@react-three/fiber\";\nim"
  },
  {
    "path": "components/Banner.tsx",
    "chars": 6378,
    "preview": "\"use client\";\n\nimport React from \"react\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport dynamic f"
  },
  {
    "path": "components/BlogClientPage.tsx",
    "chars": 7185,
    "preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { useState } from \"react\";\nimport { "
  },
  {
    "path": "components/ClientEffects.tsx",
    "chars": 408,
    "preview": "\"use client\";\nimport dynamic from \"next/dynamic\";\n\nconst StarsCanvas = dynamic(() => import(\"./BackgroundStar\"), {\n  ssr"
  },
  {
    "path": "components/CursorBacground.tsx",
    "chars": 904,
    "preview": "\"use client\";\n\nimport { useState } from \"react\";\n\nexport default function CursorBackground({\n  children,\n}: {\n  children"
  },
  {
    "path": "components/LinksCard.tsx",
    "chars": 618,
    "preview": "import Image from \"next/image\";\ninterface LinksCardProps {\n    name: string;\n    icon?: any;\n}\n\nconst LinksCard = ({ nam"
  },
  {
    "path": "components/Navbar.tsx",
    "chars": 4348,
    "preview": "\"use client\";\nimport { NavbarLinks } from \"@/libs/NavbarLinks\";\nimport Link from \"next/link\";\nimport React, { useState }"
  },
  {
    "path": "components/NebulaEffect.tsx",
    "chars": 640,
    "preview": "export const NebulaEffect = () => {\n  return (\n    <>\n      <div className=\"fixed inset-0 z-[0]\">\n        <div className"
  },
  {
    "path": "components/ProjectCard.tsx",
    "chars": 2837,
    "preview": "'use client';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport { FaGithub, FaLink } from 'react-icon"
  },
  {
    "path": "components/ProjectSection.tsx",
    "chars": 3353,
    "preview": "\"use client\";\n\nimport { project_data } from \"@/libs/constant\";\nimport { motion } from \"framer-motion\";\nimport { useState"
  },
  {
    "path": "components/Skills.tsx",
    "chars": 6253,
    "preview": "\"use client\";\n\nimport { motion } from \"framer-motion\";\nimport Image from \"next/image\";\nimport { Frontend_skill, Backend_"
  },
  {
    "path": "components/about.tsx",
    "chars": 794,
    "preview": "'use client'\nimport { Link as ScrollLink, Element } from 'react-scroll';\nimport Skills from '@/components/Skills';\nimpor"
  },
  {
    "path": "components/contact.tsx",
    "chars": 8414,
    "preview": "\"use client\";\n\nimport { FaMapMarkerAlt, FaPhoneAlt, FaEnvelope } from \"react-icons/fa\";\nimport { motion } from \"framer-m"
  },
  {
    "path": "libs/NavbarLinks.ts",
    "chars": 228,
    "preview": "export const NavbarLinks = [\n  {\n    text: \"Home\",\n    href: \"/\",\n  },\n  {\n    text: \"About Me\",\n    href: \"/about\",\n  }"
  },
  {
    "path": "libs/constant.ts",
    "chars": 4792,
    "preview": "export const Social_Icons = [\n  {\n    link: \"https://github.com/alfinpratamaa\",\n    image: \"/Github.svg\",\n    alt: \"Gith"
  },
  {
    "path": "libs/sanity.client.ts",
    "chars": 316,
    "preview": "import { createClient } from 'next-sanity'\n\nconst projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID\nconst dataset = "
  },
  {
    "path": "next.config.mjs",
    "chars": 300,
    "preview": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  images: {\n    remotePatterns: [\n      {\n        protocol"
  },
  {
    "path": "package.json",
    "chars": 1548,
    "preview": "{\n  \"name\": \"out\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbopack\",\n    \"build"
  },
  {
    "path": "postcss.config.mjs",
    "chars": 70,
    "preview": "export default {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n"
  },
  {
    "path": "provider/ClientProvicer.tsx",
    "chars": 461,
    "preview": "\"use client\";\n\nimport NextTopLoader from \"nextjs-toploader\";\n\nfunction ClientProvider() {\n  return (\n    <>\n      <NextT"
  },
  {
    "path": "provider/SkillData.tsx",
    "chars": 979,
    "preview": "'use client';\nimport { useInView } from 'react-intersection-observer';\nimport { motion } from 'framer-motion';\nimport Im"
  },
  {
    "path": "tailwind.config.js",
    "chars": 787,
    "preview": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  darkMode: \"class\",\n  content: [\n    \"./app/**/*.{js,ts,"
  },
  {
    "path": "tsconfig.json",
    "chars": 666,
    "preview": "{\n  \"compilerOptions\": {\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    "
  },
  {
    "path": "types/index.ts",
    "chars": 257,
    "preview": "export type ProjectFrontmatter = {\n  slug?: string;\n  title: string;\n  publishedAt?: string;\n  lastUpdated?: string;\n  d"
  }
]

About this extraction

This page contains the full source code of the Alfinpratamaa/HWID-SPOOFER GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (79.1 KB), approximately 20.7k tokens, and a symbol index with 24 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!