Showing preview only (524K chars total). Download the full file or copy to clipboard to get everything.
Repository: j471n/j471n.in
Branch: main
Commit: 7f76fee83374
Files: 177
Total size: 481.1 KB
Directory structure:
gitextract_2ou2nl1y/
├── .eslintrc.json
├── .gitignore
├── .vercelignore
├── LICENSE
├── README.md
├── components/
│ ├── Blog.tsx
│ ├── BookCard.tsx
│ ├── Contact/
│ │ ├── Contact.tsx
│ │ ├── ContactForm.tsx
│ │ └── index.tsx
│ ├── CreateAnIssue.tsx
│ ├── EpigraphCard.tsx
│ ├── Footer.tsx
│ ├── FramerMotion/
│ │ ├── AnimatedDiv.tsx
│ │ ├── AnimatedHeading.tsx
│ │ └── AnimatedText.tsx
│ ├── GitHubActivityGraph.tsx
│ ├── Home/
│ │ ├── BlogsSection.tsx
│ │ ├── EpigraphsSection.tsx
│ │ ├── HeroSection.tsx
│ │ ├── SkillSection.tsx
│ │ └── StatsSection.tsx
│ ├── Instagram/
│ │ ├── InstagramPost.tsx
│ │ ├── InstagramPostLoading.tsx
│ │ └── InstagramSection.tsx
│ ├── MDXComponents/
│ │ ├── Code.tsx
│ │ ├── CodeSandbox.tsx
│ │ ├── CodeTitle.tsx
│ │ ├── Codepen.tsx
│ │ ├── Danger.tsx
│ │ ├── EmbedBlog.tsx
│ │ ├── Figcaption.tsx
│ │ ├── LinkedInEmbed.tsx
│ │ ├── NextAndPreviousButton.tsx
│ │ ├── Pre.tsx
│ │ ├── Step.tsx
│ │ ├── Tip.tsx
│ │ ├── UrlMetaInfo.tsx
│ │ ├── Warning.tsx
│ │ ├── YouTube.tsx
│ │ └── index.tsx
│ ├── MetaData.tsx
│ ├── MovieCard.tsx
│ ├── Newsletter.tsx
│ ├── OgImage.tsx
│ ├── PageHeader.tsx
│ ├── PageNotFound.tsx
│ ├── PageTop.tsx
│ ├── Project.tsx
│ ├── QRCodeContainer.tsx
│ ├── SVG/
│ │ ├── Ditto.tsx
│ │ ├── Flameshot.tsx
│ │ ├── Flux.tsx
│ │ ├── Logo.tsx
│ │ ├── MicrosoftToDo.tsx
│ │ ├── RainDrop.tsx
│ │ ├── ShareX.tsx
│ │ ├── UPI.tsx
│ │ ├── Zip7.tsx
│ │ └── index.tsx
│ ├── ScrollProgressBar.tsx
│ ├── ScrollToTopButton.tsx
│ ├── ShareOnSocialMedia.tsx
│ ├── SnippetCard.tsx
│ ├── SnowfallCanvas.tsx
│ ├── StaticPage.tsx
│ ├── Stats/
│ │ ├── Artist.tsx
│ │ ├── MonkeyTypeStats.tsx
│ │ ├── StatsCard.tsx
│ │ └── Track.tsx
│ ├── Support.tsx
│ ├── TableOfContents.tsx
│ └── TopNavbar.tsx
├── content/
│ ├── FramerMotionVariants.ts
│ ├── meta.ts
│ ├── siteConfig.ts
│ ├── skillsData.ts
│ ├── socialMedia.ts
│ ├── support.ts
│ ├── user.ts
│ └── utilitiesData.ts
├── context/
│ └── darkModeContext.tsx
├── docs/
│ └── sanity-deploy.md
├── hooks/
│ ├── useBookmarkBlogs.ts
│ ├── useDebounce.ts
│ ├── useFetchWithSWR.ts
│ ├── useScrollPercentage.ts
│ ├── useShare.ts
│ ├── useWindowLocation.ts
│ └── useWindowSize.ts
├── layout/
│ ├── BlogLayout.tsx
│ ├── Layout.tsx
│ └── SnippetLayout.tsx
├── lib/
│ ├── MDXContent.ts
│ ├── devto.ts
│ ├── fetcher.ts
│ ├── generateRSS.ts
│ ├── github.ts
│ ├── hardcover.ts
│ ├── instaposts.ts
│ ├── interface/
│ │ └── sanity.ts
│ ├── interface.ts
│ ├── sanityClient.ts
│ ├── sanityContent.ts
│ ├── sitemap.ts
│ ├── spotify.ts
│ ├── supabase.ts
│ ├── tmdb.ts
│ ├── toc.ts
│ ├── types.ts
│ └── windowsAnimation.ts
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages/
│ ├── 404.tsx
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── about.tsx
│ ├── api/
│ │ ├── books.ts
│ │ ├── ga.ts
│ │ ├── now-playing.ts
│ │ ├── posts/
│ │ │ └── insta.ts
│ │ ├── revalidate.ts
│ │ ├── stats/
│ │ │ ├── artists.ts
│ │ │ ├── devto.ts
│ │ │ ├── github-contribution.ts
│ │ │ ├── github.ts
│ │ │ ├── monkeytype.ts
│ │ │ └── tracks.ts
│ │ ├── validate/
│ │ │ └── email.ts
│ │ └── views/
│ │ ├── [slug].ts
│ │ └── index.ts
│ ├── blogs/
│ │ ├── [slug].tsx
│ │ ├── bookmark.tsx
│ │ └── index.tsx
│ ├── books.tsx
│ ├── certificates.tsx
│ ├── epigraphs.tsx
│ ├── index.tsx
│ ├── privacy.tsx
│ ├── projects.tsx
│ ├── snippets/
│ │ ├── [slug].tsx
│ │ └── index.tsx
│ ├── stats.tsx
│ ├── tet.json
│ └── utilities.tsx
├── postcss.config.js
├── public/
│ ├── manifest.json
│ └── robots.txt
├── sanity/
│ ├── .eslintrc
│ ├── .gitignore
│ ├── README.md
│ ├── gist.md.ndjson
│ ├── package.json
│ ├── sanity.cli.ts
│ ├── sanity.config.ts
│ ├── schemas/
│ │ ├── author.ts
│ │ ├── blockContent.ts
│ │ ├── category.ts
│ │ ├── epigraph.ts
│ │ ├── index.ts
│ │ ├── language.ts
│ │ ├── organization.ts
│ │ ├── post.ts
│ │ ├── snippet.ts
│ │ └── static_page.ts
│ ├── static/
│ │ └── .gitkeep
│ ├── tailwind.config.js
│ └── tsconfig.json
├── sanity.cli.ts
├── styles/
│ └── globals.css
├── tailwind.config.js
├── tsconfig.json
├── utils/
│ ├── date.ts
│ ├── functions.ts
│ └── utils.ts
└── vercel.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": "next/core-web-vitals",
"rules": {
"react/no-unescaped-entities": 0,
"@next/next/no-img-element": "off"
}
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# private markdown
meta.md
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# rss
/public/rss
/public/feed.xml
public/sitemap.xml
# pwa
/public/sw.js
/public/workbox-*.js
/public/worker-*.js
/public/sw.js.map
/public/workbox-*.js.map
/public/worker-*.js.map
/store
tsconfig.tsbuildinfo
.env*.local
.vscode/
================================================
FILE: .vercelignore
================================================
/sanity
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Jatin Sharma
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<div align="center">





</div>
## Tools Used
- **Framework**: [Next.js](https://nextjs.org/)
- **Styling**: [Tailwind CSS](https://tailwindcss.com/)
- **Content**: [MDX](https://github.com/mdx-js/mdx)
- **Database**: [Supabase](https://supabase.com/)
- **Animations**: [Framer Motion](https://framer.com/motion)
- **Deployment**: [Vercel](https://vercel.com)
- **Icons**: [React Icons](https://react-icons.github.io/react-icons/)
- **Plugins**: [rehype](https://github.com/rehypejs/rehype)
- **Analytics**: [Google Analytics](https://analytics.google.com/analytics/web/)
- [SWR](https://swr.vercel.app/)
- [Email.js](https://www.emailjs.com/)
- [React Toastify](https://github.com/fkhadra/react-toastify)
## Run Locally
Clone the project:
```bash
git clone https://github.com/j471n/j471n.in.git
```
Go to the project directory:
```bash
cd j471n.in
```
Install dependencies
```bash
yarn
# or
npm install
```
Start the server:
```bash
yarn dev
# or
npm run dev
```
After that server should be running on [localhost:3000](http://localhost:3000)
> I am using [yarn](https://yarnpkg.com/) you can use [pnpm](https://pnpm.io/) or [npm](https://www.npmjs.com/)
> Warning: You could run into errors if you don't populate the `.env.local` with the correct values
## Setting up the Environment
Rename [`.env.example`](/.env.example) to `.env.local` and then you need to populate that with the respective values.
### Email Service Integration
- `NEXT_PUBLIC_YOUR_SERVICE_ID`: Go to the [Admin Panel](https://dashboard.emailjs.com/admin) of [emailjs.com](https://emailjs.com). If you haven't already added a service then Click on the **Add Service** Button as shown in the image

Then choose any method you want I am using **Gmail**

- Then first click on the **Connect Account and log** in with your Gmail account that you want to use to get the emails from.
- In the second step click on **Create Service** and then copy the **Service ID** and add this ID to `NEXT_PUBLIC_YOUR_SERVICE_ID` in `.env.local`

- `NEXT_PUBLIC_YOUR_TEMPLATE_ID`: To get the Template ID visit the [Email Templates](https://dashboard.emailjs.com/admin/templates) section and click on **Create New Template**.

And then you will see a window where you can edit your email template after you are satisfied with your template then click on the Save button in the top right corner.

After that you will have your Template ID as shown in the image below:

- `NEXT_PUBLIC_YOUR_USER_ID`: To get your User ID, Go to [Account](https://dashboard.emailjs.com/admin/account) and then you will be able to see it:

### dev\.to Integration
- `NEXT_PUBLIC_BLOGS_API`: I am using [Dev.to API](https://developers.forem.com/api) to fetch all the blog stats. You can get this API at the bottom of the [Extensions](https://dev.to/settings/extensions) section.

### Google Analytics
- `NEXT_PUBLIC_GA_MEASUREMENT_ID`: You can follow this [guide](https://support.google.com/analytics/answer/9539598?hl=en) to get your Google Analytics ID and then you will be able to use Google Analytics in this project.
- [**Google Analytics Data API**](https://developers.google.com/analytics/devguides/reporting/data/v1): I am using this API to get the analytics of this website so that I can show how many user visit this site in the last 7 days. In this you will need the value of the following properties:
- `GA_PROPERTY_ID`
- `GA_CLIENT_EMAIL`
- `GA_PRIVATE_KEY`
I have written a [blog](https://j471n.in/blogs/google-analytics-data-api) that shows how you can get these properties and guides to use them.
### Spotify Integration
I have used [Spotify API](https://developer.spotify.com/documentation/web-api/). So, you need three Environment Variable values-
- `SPOTIFY_CLIENT_ID`
- `SPOTIFY_CLIENT_SECRET`
- `SPOTIFY_REFRESH_TOKEN`
You need to follow this [blog](https://j471n.in/blogs/spotify-api-nextjs) to get these variables' values.
### Supabase Integration
I am using [Supabase](https://supabase.com/) with ISR to store all my projects and certificates for now. It provides an API that helps you to access the data. To access that data you need two things:
- `SUPABASE_URL`: Database URL.
- `SUPABASE_KEY`: It is safe to be used in a browser context.
**Steps-**
- To get these go to [Supabase](https://app.supabase.com/sign-in) and log in with your account.
- Click on **New Project** and fill all the fields.
- Click on **Create New Project**.
- Go to the [Settings](https://app.supabase.com/project/_/settings/general) page in the Dashboard.
- Click **API** in the sidebar.
- Find your API **URL** and **anon** key on this page.
- Now you can [Create table](https://app.supabase.com/project/_/editor) and start using it.
But before you use this there was one issue I had when I was using this it was returning the empty array ([]). It was because of project policies. By default, no-one has access to the data. To fix that you can do the following:
- Go to [Policies](https://app.supabase.com/project/_/auth/policies).
- Select your Project.
- Click on **New Policy**.

- You will be presented with two options. You can choose either one. I chose the 1st option:

- After that, you will have four options as shown in the following image. You can choose according to your need. I only need the read access so I went with 1st option.

- Click on **Use this template**.
- Click on **Review**.
- Click on **Save Policy**
After that, you will be able to access the data using [@supabase/supabase-js](https://www.npmjs.com/package/@supabase/supabase-js). Install it and you just set up your project with Supabase.
- `REVALIDATE_SECRET`: As I am using [Supabase](https://supabase.com/), It has a feature called [webhooks](https://supabase.com/docs/guides/database/webhooks) which allow you to send real-time data from your database to another system whenever a table event occurs. So I am using it to revalidate my `projects` and `certificates` page. For that I am providing a custom secret value to verify that request is coming from authenticated source. Let's create webhook:
- Go to [webhooks](https://app.supabase.com/project/_/database/hooks) page.
- Click on **Create a new hook**
- Enter the name of the function hook (example: `update_projects`)

- Choose your table from the dropdown list

- Select events which will trigger this function hook

- Now Choose POST method and enter the revalidate URL (request will be sent to this URL)

- Then add two HTTP Params `secret` and `revalidateUrl`

- Now add this secret to your `env.local` and it will update the page when you made some changes to your supabase database.
- `pages/api/revalidate.ts` is using `revalidateUrl` to update the page with new data.
### GitHub Integration
To get `GITHUB_TOKEN` Follow these steps to generate a GitHub token that I am using fetch my GitHub details:
**Step 1: Accessing GitHub Developer Settings**
- Log in to your GitHub account.
- Click on your profile picture in the top-right corner of the page.
- From the drop-down menu, select Settings.

**Step 2: Navigating to Developer Settings**
In the left sidebar, scroll down and click on Developer settings.

**Step 3: Creating a New Personal Access Token**
- In the Developer settings page, click on Personal access tokens and then Click on Tokens (Classic).

- Next, click on the Generate new token button.

- After selecting the necessary permissions, click on the Generate token button at the bottom of the page.
- GitHub will generate a new token for you. Make sure to copy the token value.
- **Important**: Treat this token like a password and keep it secure. Do not share it publicly or commit it to a version control repository.
### Email Validation Integration
To get `EMAIL_VALIDATION_API` follow the following steps to get the `API_KEY` to validate the email address for the newsletter:
- You need to have an account on [RapidAPI](https://rapidapi.com/).
- If you have an account then you can just [subscribe](https://rapidapi.com/Top-Rated/api/e-mail-check-invalid-or-disposable-domain/pricing) the free version of [E-mail Check Invalid or Disposable Domain](https://rapidapi.com/Top-Rated/api/e-mail-check-invalid-or-disposable-domain/). Which will give you the 1000 request/month.

- Then you'll get the `API_KEY`, which you can store in your `.env.local`.

### Sanity Integration
- `SANITY_PROJECT_ID`:
- Go to the [Sanity.io](<(https://www.sanity.io/)>) website using your web browser.
- Login with you account/Create a new account.
- After logging in, you'll be redirected to the Sanity.io dashboard.
- If you have an existing project, you'll see it listed on the dashboard. Click on the project's name to access it.
- Once you're inside the project, look at the browser's address bar. The URL should look something like this: `https://www.sanity.io/manage/project/your-project-id`
- The your-project-id in the URL is your Sanity project ID. It's a unique identifier for your specific project.
That's it! You've now obtained your Sanity project ID, which you can use for interacting with your Sanity project via its API or other integrations.
### TMDB Integration
- `TMDB_ACCOUNT_ID` and `TMDB_ACCESS_TOKEN`: To enable seamless integration of movie and TV show data, we will use the TMDB API, which offers comprehensive information about media content. The following steps will guide you:
**1. Overview of TMDB Integration**
Previously, movie and TV show data were manually stored using Supabase, requiring tedious manual work. To streamline the process and automatically update ratings, we have switched to TMDB (The Movie Database).
**2. Creating or Logging into Your TMDB Account**
If you already have a TMDB account, log in with your existing credentials. Otherwise, visit TMDB's website and create a new account.
**3. Generating API Key**
After logging in, navigate to the API section in your account settings. Here, you can generate a new API key to access TMDB's data and services.

**Completing the API Key Request Form**
Fill in all the required details in the API key request form, and make sure to accept the terms and conditions.

**Obtaining API Key and Access Token**
Once you have completed the application registration, you will receive an API key and an access token. Assign the access token to the `TMDB_ACCESS_TOKEN` variable.

**Finding Your TMDB Account ID**
To get the `TMDB_ACCOUNT_ID`, log in to the TMDB system and visit the developer website. There, you will find your account ID associated with your account.

With the `TMDB_ACCOUNT_ID` and `TMDB_ACCESS_TOKEN` acquired from the steps above, you can now seamlessly access and update movie and TV show data through TMDB's API, automating the process and making it significantly more efficient. Enjoy your improved movie and TV show list management experience!
## Supabase Database Schema
### Table: certificates
| Column Name | Data Type | Constraints |
| ------------- | --------------------------- | ------------------------------------------------- |
| `id` | UUID | NOT NULL, PRIMARY KEY, DEFAULT uuid_generate_v4() |
| `title` | TEXT | NOT NULL |
| `issued_date` | DATE | - |
| `org_name` | TEXT | - |
| `org_logo` | TEXT | - |
| `url` | TEXT | - |
| `pinned` | BOOLEAN | - |
| `created_at` | TIMESTAMP WITHOUT TIME ZONE | NOT NULL, DEFAULT now() |
### Table: movies
| Column Name | Data Type | Constraints |
| ------------ | ------------------------ | ------------------------------------------------------- |
| `id` | BIGINT | GENERATED BY DEFAULT AS IDENTITY, NOT NULL, PRIMARY KEY |
| `created_at` | TIMESTAMP WITH TIME ZONE | NOT NULL, DEFAULT (now() AT TIME ZONE 'utc'::text) |
| `name` | TEXT | - |
| `image` | TEXT | - |
| `url` | TEXT | - |
| `year` | INTEGER | - |
| `watched` | BOOLEAN | DEFAULT true |
| `rating` | SMALLINT | - |
### Table: projects
| Column Name | Data Type | Constraints |
| ------------- | --------------------------- | ------------------------------------------------- |
| `id` | UUID | NOT NULL, PRIMARY KEY, DEFAULT uuid_generate_v4() |
| `created_at` | TIMESTAMP WITHOUT TIME ZONE | - |
| `name` | TEXT | - |
| `description` | TEXT | - |
| `githubURL` | TEXT | - |
| `previewURL` | TEXT | - |
| `tools` | TEXT[] | - |
| `pinned` | BOOLEAN | DEFAULT false |
| `coverImage` | TEXT | - |
### Table: views
| Column Name | Data Type | Constraints |
| ----------- | --------- | --------------------- |
| `slug` | TEXT | NOT NULL, PRIMARY KEY |
| `views` | BIGINT | - |
### Table: user_data
| Column Name | Data Type | Constraints |
| ------------ | ------------------------ | ------------------------------------------------ |
| `id` | UUID | NOT NULL, PRIMARY KEY, DEFAULT gen_random_uuid() |
| `key` | TEXT | - |
| `value` | TEXT | - |
| `created_at` | TIMESTAMP WITH TIME ZONE | DEFAULT now() |
This table is unique because it accommodates various types of miscellaneous data. I'm sharing the current keys in my database, which are utilized in my project. This will guide you on the types of data you need to add to avoid errors during implementation."
| Keys | Sample Data |
| ---------------------- | ---------------------------------------- |
| `linkedin` | [Sample JSON](https://traff.co/K6wi1Q3p) |
| `instagram_user_token` | [Sample TEXT](https://traff.co/mgFnU8vN) |
| `devto_stats` | [Sample JSON](https://traff.co/Ff99Gv2h) |
## Documentation
I have written an in-depth blog on [How I Made My Portfolio with Next.js](https://dev.to/j471n/how-i-made-my-portfolio-with-nextjs-2mn3). You can visit there to look at the detailed guide about this portfolio.
================================================
FILE: components/Blog.tsx
================================================
import { BlogPost } from "@lib/interface/sanity";
import Image from "next/image";
import Link from "next/link";
import { getFormattedDate } from "@utils/date";
import { motion } from "framer-motion";
import { FiArrowRight } from "react-icons/fi";
const cardVariants = {
hidden: { opacity: 0, y: 14 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring" as const, stiffness: 120, damping: 20 },
},
};
export default function Blog({
blog,
animate = false,
index,
}: {
blog: BlogPost;
animate?: boolean;
index?: number;
}) {
return (
<motion.article
variants={cardVariants}
initial={animate ? "hidden" : false}
whileInView={animate ? "visible" : undefined}
viewport={{ once: true }}
className="group relative border-b border-gray-300 dark:border-neutral-700 last:border-0"
>
{/* Left accent bar */}
<div className="absolute left-0 inset-y-0 w-0.5 bg-gray-900 dark:bg-white origin-center scale-y-0 group-hover:scale-y-100 transition-transform duration-200 rounded-sm" />
<Link
href={`/blogs/${blog.slug.current}`}
className="flex items-center gap-4 sm:gap-6 py-6 pl-4 pr-2 hover:bg-gray-50/70 dark:hover:bg-gray-900/20 transition-colors duration-150 rounded-r-xl"
>
{/* Article index */}
{index !== undefined && (
<span className="text-[10px] font-mono text-gray-300 dark:text-gray-700 w-5 flex-shrink-0 select-none self-start mt-1.5">
{String(index + 1).padStart(2, "0")}
</span>
)}
{/* Content */}
<div className="flex-1 min-w-0 space-y-2">
<h3 className="font-bold text-lg leading-snug text-gray-900 dark:text-white line-clamp-2 group-hover:text-gray-700 dark:group-hover:text-gray-300 transition-colors">
{blog.title}
</h3>
<p className="text-sm text-gray-500 dark:text-gray-500 line-clamp-2 leading-relaxed hidden sm:block">
{blog.excerpt}
</p>
{/* Meta row */}
<div className="flex items-center gap-2 pt-0.5 flex-wrap">
<div className="relative w-4 h-4 rounded-full overflow-hidden flex-shrink-0">
<Image
alt={blog.author.name}
fill
src={
blog.organization
? blog.organization.image.asset.url
: blog.author.image.asset.url
}
className="object-cover"
/>
</div>
<span className="text-[11px] font-mono text-gray-400 dark:text-gray-600">
{blog.author.name}
</span>
{blog.organization && (
<>
<span className="text-gray-200 dark:text-neutral-700">·</span>
<span className="text-[11px] font-mono text-gray-400 dark:text-gray-600">
{blog.organization.name}
</span>
</>
)}
<span className="text-gray-200 dark:text-neutral-700">·</span>
<span className="text-[11px] font-mono text-gray-400 dark:text-gray-600">
{getFormattedDate(new Date(blog.publishedAt))}
</span>
</div>
</div>
{/* Thumbnail — grayscale at rest, color on hover */}
{/* <div className="relative w-20 h-16 sm:w-28 sm:h-20 rounded-xl overflow-hidden flex-shrink-0 border border-gray-100 dark:border-neutral-700 shadow-sm">
<Image
title={blog.title}
alt={blog.title}
src={blog.mainImage.asset.url}
fill
quality={80}
className="object-cover grayscale group-hover:grayscale-0 transition-all duration-500"
/>
</div> */}
{/* Arrow */}
<FiArrowRight className="w-4 h-4 text-gray-300 dark:text-gray-700 group-hover:text-gray-900 dark:group-hover:text-white group-hover:translate-x-0.5 transition-all flex-shrink-0 hidden sm:block" />
</Link>
</motion.article>
);
}
================================================
FILE: components/BookCard.tsx
================================================
import Image from "next/image";
import Link from "next/link";
import { motion } from "framer-motion";
import { HiOutlineExternalLink } from "react-icons/hi";
import { HardcoverBook } from "@lib/types";
const itemVariants = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring", stiffness: 260, damping: 24 },
},
};
const HARDCOVER_BOOK_URL = "https://hardcover.app/books/";
const FALLBACK_COVER = "https://imgur.com/5dYYce8.png";
const STAR_PATH =
"M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z";
function Stars({
rating,
size = "w-3 h-3",
}: {
rating: number;
size?: string;
}) {
const filled = Math.round(rating);
return (
<>
{[1, 2, 3, 4, 5].map((i) => (
<svg
key={i}
className={`${size} ${i <= filled ? "text-amber-400" : "text-gray-300 dark:text-neutral-600"}`}
fill="currentColor"
viewBox="0 0 20 20"
aria-hidden="true"
>
<path d={STAR_PATH} />
</svg>
))}
</>
);
}
function formatFinishedDate(iso: string): string {
const d = new Date(iso);
return d.toLocaleDateString("en-US", { month: "short", year: "numeric" });
}
export default function BookCard({ book }: { book: HardcoverBook }) {
const href = book.slug
? `${HARDCOVER_BOOK_URL}${book.slug}`
: "https://hardcover.app";
const authorLine =
book.authors.length > 0 ? book.authors.join(", ") : "Unknown author";
const finishedDate =
book.statusId === 3
? book.finishedAt
? formatFinishedDate(book.finishedAt)
: book.updatedAt
? formatFinishedDate(book.updatedAt)
: null
: null;
const hasMyRating = book.userRating != null && book.userRating > 0;
const hasCommunityRating = book.rating != null && book.rating > 0;
return (
<motion.div variants={itemVariants}>
<Link
href={href}
target="_blank"
rel="noopener noreferrer"
className="relative flex gap-3 p-3 border border-gray-200 dark:border-neutral-700 bg-white dark:bg-darkSecondary/10 hover:bg-gray-50 dark:hover:bg-darkSecondary/30 transition-colors group"
aria-label={`${book.title} by ${authorLine} — view on Hardcover`}
>
{/* External link icon */}
<span className="absolute top-2.5 right-2.5 opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 dark:text-gray-500">
<HiOutlineExternalLink className="w-4 h-4" />
</span>
{/* Cover */}
<div className="relative flex-shrink-0 w-[88px] h-[132px] overflow-hidden shadow-md">
<Image
src={book.coverUrl ?? FALLBACK_COVER}
alt={`Cover of ${book.title}`}
fill
sizes="88px"
quality={85}
className="object-cover group-hover:scale-105 transition-transform duration-500"
/>
</div>
{/* Details */}
<div className="flex flex-col min-w-0 flex-1 py-0.5 pr-5">
{/* Author */}
<p className="text-[10px] font-mono text-gray-400 dark:text-gray-500 uppercase tracking-[0.12em] truncate">
{authorLine}
</p>
{/* Title */}
<h3 className="mt-1 text-sm font-semibold text-gray-900 dark:text-white leading-snug line-clamp-2 group-hover:text-gray-600 dark:group-hover:text-gray-300 transition-colors">
{book.title}
</h3>
{/* Spacer */}
<div className="flex-1" />
{/* Bottom meta */}
<div className="flex flex-col gap-1.5 mt-2">
{/* Ratings row — personal stars + community avg on one line */}
{(hasMyRating || hasCommunityRating) && (
<div className="flex items-center gap-2 flex-wrap">
{hasMyRating && (
<span
className="flex items-center gap-0.5"
title={`My rating: ${book.userRating!.toFixed(1)}`}
>
<Stars rating={book.userRating!} />
<span className="ml-1 text-[11px] font-medium text-amber-500 dark:text-amber-400 tabular-nums">
{book.userRating!.toFixed(1)}
</span>
</span>
)}
{hasMyRating && hasCommunityRating && (
<span className="text-gray-300 dark:text-neutral-600 select-none">
·
</span>
)}
{hasCommunityRating && (
<span
className="flex items-center gap-0.5 text-[11px] text-gray-400 dark:text-gray-500 tabular-nums"
title="Community average rating"
>
<svg
className="w-2.5 h-2.5 text-gray-400 dark:text-gray-500"
fill="currentColor"
viewBox="0 0 20 20"
aria-hidden="true"
>
<path d={STAR_PATH} />
</svg>
<span>{book.rating!.toFixed(1)}</span>
<span className="text-[10px] ml-0.5 text-gray-400 dark:text-gray-600">
avg
</span>
</span>
)}
</div>
)}
{/* Year · pages */}
<div className="flex items-center gap-1.5 text-[11px] text-gray-500 dark:text-gray-400 tabular-nums">
{book.releaseYear != null && <span>{book.releaseYear}</span>}
{book.releaseYear != null && book.pages != null && (
<span className="text-gray-300 dark:text-neutral-600">·</span>
)}
{book.pages != null && (
<span>{book.pages.toLocaleString()} pages</span>
)}
</div>
{/* Finished date */}
{finishedDate && (
<div className="flex items-center gap-1 text-[11px] text-gray-500 dark:text-gray-400">
<svg
className="w-3 h-3 flex-shrink-0 text-green-500 dark:text-green-400"
fill="none"
strokeWidth="2"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M4.5 12.75l6 6 9-13.5"
/>
</svg>
<span>Finished</span>
<span className="text-gray-400 dark:text-gray-500">·</span>
<span className="font-medium text-gray-700 dark:text-gray-300">
{finishedDate}
</span>
</div>
)}
</div>
</div>
</Link>
</motion.div>
);
}
================================================
FILE: components/Contact/Contact.tsx
================================================
import React from "react";
import { motion } from "framer-motion";
import ContactForm from "./ContactForm";
import siteConfig from "@content/siteConfig";
const infoRows = [
{ label: "Response Time", value: "< 24 hours" },
{ label: "Timezone", value: "IST · UTC +5:30" },
{ label: "Preferred", value: "Email / LinkedIn" },
{ label: "Work Type", value: "Remote / Contract" },
];
export default function Contact() {
const { contact } = siteConfig;
return (
<div id="contact" className="relative pt-20 sm:pt-24 overflow-hidden">
<div className="max-w-7xl mx-auto px-6 sm:px-8 lg:px-12">
{/* Section number watermark */}
<div
className="absolute -right-2 top-6 font-black select-none pointer-events-none leading-none tracking-tighter bg-gradient-to-b from-gray-200 to-gray-50 dark:from-[#232628] dark:to-darkPrimary bg-clip-text text-transparent"
style={{ fontSize: "clamp(5rem, 16vw, 13rem)" }}
aria-hidden="true"
>
05
</div>
{/* ── Header ── */}
<div className="relative z-10 flex flex-col lg:flex-row lg:items-end lg:justify-between gap-8 mb-12">
<div className="space-y-3 max-w-xl">
<motion.div
initial={{ opacity: 0, x: -16 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
className="flex items-center gap-3"
>
<div className="h-px w-5 bg-gray-400 dark:bg-gray-600 flex-shrink-0" />
<span className="font-mono text-[10px] tracking-[0.45em] uppercase text-gray-400 dark:text-gray-600">
{contact.eyebrow}
</span>
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white"
>
{contact.title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15 }}
className="text-base text-gray-600 dark:text-gray-400 border-l-2 border-gray-300 dark:border-gray-700 pl-4 py-0.5"
>
{contact.description}
</motion.p>
</div>
{/* Email quick-link */}
<motion.div
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15 }}
className="flex flex-col items-start lg:items-end gap-1 flex-shrink-0"
>
<span className="text-[10px] font-mono uppercase tracking-[0.3em] text-gray-400 dark:text-gray-600">
Or reach me directly
</span>
<a
href={`mailto:${siteConfig.person.email}`}
className="text-sm font-semibold text-gray-900 dark:text-white hover:underline underline-offset-4 decoration-gray-400 font-mono"
>
{siteConfig.person.email}
</a>
</motion.div>
</div>
{/* ── Divider ── */}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
className="flex items-center gap-4 mb-12"
>
<div className="h-px flex-1 bg-gray-300 dark:bg-neutral-700" />
<span className="font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-700">
Send a Message
</span>
<div className="h-px flex-1 bg-gray-300 dark:bg-neutral-700" />
</motion.div>
{/* ── Two-column body ── */}
<div className="grid lg:grid-cols-5 gap-12 lg:gap-16 items-start">
{/* Form — 3 cols */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="lg:col-span-3"
>
<ContactForm />
</motion.div>
{/* Info sidebar — 2 cols */}
<motion.div
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.3 }}
className="lg:col-span-2 flex flex-col gap-0"
>
{/* Status */}
<div className="pb-6">
<span className="font-mono text-[9px] tracking-[0.4em] uppercase text-gray-400 dark:text-gray-600 mb-3 block">
Current Status
</span>
<div className="flex items-center gap-2.5">
<span className="relative flex h-2.5 w-2.5 flex-shrink-0">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-60" />
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" />
</span>
<span className="text-sm font-semibold text-gray-900 dark:text-white">
Available for Work
</span>
</div>
</div>
<div className="h-px bg-gray-200 dark:bg-neutral-700" />
{/* Info rows */}
<div className="divide-y divide-gray-100 dark:divide-neutral-700">
{infoRows.map(({ label, value }, i) => (
<motion.div
key={label}
initial={{ opacity: 0, y: 8 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.35 + i * 0.06 }}
className="flex justify-between items-center py-4"
>
<span className="font-mono text-[9px] tracking-[0.35em] uppercase text-gray-400 dark:text-gray-500">
{label}
</span>
<span className="text-sm font-medium text-gray-900 dark:text-white">
{value}
</span>
</motion.div>
))}
</div>
<div className="h-px bg-gray-200 dark:bg-neutral-700" />
{/* Decorative type */}
<div className="pt-8 select-none" aria-hidden="true">
<p
className="font-black leading-none tracking-tighter bg-gradient-to-b from-gray-300 to-gray-100 dark:from-[#2e3133] dark:to-darkPrimary bg-clip-text text-transparent"
style={{ fontSize: "clamp(2.8rem, 6vw, 4.5rem)" }}
>
LET'S
<br />
BUILD
<br />
TOGETHER
</p>
</div>
</motion.div>
</div>
</div>
</div>
);
}
================================================
FILE: components/Contact/ContactForm.tsx
================================================
import React, { useRef, useState } from "react";
import { ToastContainer, toast } from "react-toastify";
import { useDarkMode } from "@context/darkModeContext";
import emailjs from "@emailjs/browser";
import { motion, AnimatePresence } from "framer-motion";
import { FormInput } from "@lib/types";
import siteConfig from "@content/siteConfig";
import { FiSend } from "react-icons/fi";
const inputVariants = {
hidden: { opacity: 0, x: -20 },
visible: {
opacity: 1,
x: 0,
transition: {
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
},
},
};
export default function Form() {
const { isDarkMode } = useDarkMode();
const sendButtonRef = useRef<HTMLButtonElement>(null!);
const formRef = useRef<HTMLFormElement>(null!);
const [isSubmitting, setIsSubmitting] = useState(false);
const [focusedField, setFocusedField] = useState<string | null>(null);
const { contact } = siteConfig;
const FailToastId: string = "failed";
function sendEmail(e: React.SyntheticEvent) {
e.preventDefault();
const target = e.target as typeof e.target & {
first_name: { value: string };
last_name: { value: string };
email: { value: string };
subject: { value: string };
message: { value: string };
};
const emailData = {
to_name: siteConfig.person.name,
first_name: target.first_name.value.trim(),
last_name: target.last_name.value.trim(),
email: target.email.value.trim(),
subject: target.subject.value.trim(),
message: target.message.value.trim(),
};
if (!validateForm(emailData) && !toast.isActive(FailToastId))
return toast.error("Please fill in all required fields", {
toastId: FailToastId,
});
setIsSubmitting(true);
sendButtonRef.current.setAttribute("disabled", "true");
const toastId = toast.loading("Sending your message...");
emailjs
.send(
process.env.NEXT_PUBLIC_YOUR_SERVICE_ID!,
process.env.NEXT_PUBLIC_YOUR_TEMPLATE_ID!,
emailData!,
process.env.NEXT_PUBLIC_YOUR_USER_ID,
)
.then(() => {
formRef.current.reset();
toast.update(toastId, {
render: "Message sent successfully!",
type: "success",
isLoading: false,
autoClose: 4000,
});
setIsSubmitting(false);
sendButtonRef.current.removeAttribute("disabled");
})
.catch(() => {
toast.update(toastId, {
render: "Failed to send message. Please try again.",
type: "error",
isLoading: false,
autoClose: 4000,
});
setIsSubmitting(false);
sendButtonRef.current.removeAttribute("disabled");
});
}
function validateForm(data: FormInput): boolean {
for (const key in data) {
if (data[key as keyof FormInput] === "") return false;
}
return true;
}
/* Shared field class — borderless top/sides, only bottom rule */
const fieldCls = (name: string) =>
`block w-full bg-transparent border-0 border-b py-3 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-600 focus:outline-none transition-colors duration-200 ${
focusedField === name
? "border-gray-900 dark:border-white"
: "border-gray-300 dark:border-gray-700"
}`;
return (
<>
<motion.form
ref={formRef}
initial="hidden"
animate="visible"
className="space-y-0"
onSubmit={sendEmail}
>
{/* Name row */}
<div className="grid sm:grid-cols-2 gap-x-10">
{/* First Name */}
<motion.div variants={inputVariants} className="py-2">
<label
htmlFor="first_name"
className="block font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-500 mb-1"
>
First Name
</label>
<input
type="text"
name="first_name"
id="first_name"
onFocus={() => setFocusedField("first_name")}
onBlur={() => setFocusedField(null)}
className={fieldCls("first_name")}
placeholder="John"
required
/>
</motion.div>
{/* Last Name */}
<motion.div variants={inputVariants} className="py-2">
<label
htmlFor="last_name"
className="block font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-500 mb-1"
>
Last Name
</label>
<input
type="text"
name="last_name"
id="last_name"
onFocus={() => setFocusedField("last_name")}
onBlur={() => setFocusedField(null)}
className={fieldCls("last_name")}
placeholder="Doe"
required
/>
</motion.div>
</div>
{/* Email */}
<motion.div variants={inputVariants} className="py-2">
<label
htmlFor="email"
className="block font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-500 mb-1"
>
Email Address
</label>
<input
type="email"
name="email"
id="email"
onFocus={() => setFocusedField("email")}
onBlur={() => setFocusedField(null)}
className={fieldCls("email")}
placeholder="john.doe@example.com"
required
/>
</motion.div>
{/* Subject */}
<motion.div variants={inputVariants} className="py-2">
<label
htmlFor="subject"
className="block font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-500 mb-1"
>
Subject
</label>
<input
type="text"
name="subject"
id="subject"
onFocus={() => setFocusedField("subject")}
onBlur={() => setFocusedField(null)}
className={fieldCls("subject")}
placeholder="Project Discussion"
required
/>
</motion.div>
{/* Message */}
<motion.div variants={inputVariants} className="py-2">
<label
htmlFor="message"
className="block font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-500 mb-1"
>
Message
</label>
<textarea
name="message"
id="message"
rows={5}
onFocus={() => setFocusedField("message")}
onBlur={() => setFocusedField(null)}
className={`${fieldCls("message")} resize-none`}
placeholder="Tell me about your project or idea..."
required
/>
</motion.div>
{/* Submit */}
<motion.div variants={inputVariants} className="pt-8">
<button
ref={sendButtonRef}
type="submit"
disabled={isSubmitting}
className="group relative inline-flex items-center gap-3 px-7 py-3 border border-gray-900 dark:border-white text-gray-900 dark:text-white font-semibold text-sm tracking-wide overflow-hidden hover:text-white dark:hover:text-gray-900 focus:outline-none transition-colors duration-300 disabled:opacity-40 disabled:cursor-not-allowed"
>
{/* fill layer */}
<span className="absolute inset-0 bg-gray-900 dark:bg-white translate-y-full group-hover:translate-y-0 transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]" />
<span className="relative z-10 flex items-center gap-2">
<AnimatePresence>
{isSubmitting ? (
<motion.span
key="submitting"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
className="flex items-center gap-2"
>
<svg
className="animate-spin h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
Sending...
</motion.span>
) : (
<motion.span
key="send"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
className="flex items-center gap-2"
>
Send Message
<FiSend className="w-4 h-4 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform" />
</motion.span>
)}
</AnimatePresence>
</span>
</button>
</motion.div>
{/* Privacy note */}
<motion.p
variants={inputVariants}
className="pt-5 font-mono text-[9px] tracking-[0.3em] uppercase text-gray-400 dark:text-gray-600"
>
{contact.privacyNote}
</motion.p>
</motion.form>
<ToastContainer
theme={isDarkMode ? "dark" : "light"}
position="bottom-right"
style={{ zIndex: 1000 }}
toastClassName="!rounded-xl !shadow-2xl"
/>
</>
);
}
================================================
FILE: components/Contact/index.tsx
================================================
export { default } from "./Contact";
================================================
FILE: components/CreateAnIssue.tsx
================================================
import Link from "next/link";
import React from "react";
export default function CreateAnIssue() {
return (
<div className="grid w-full h-screen px-10 sm:px-20 place-items-center dark:text-gray-200">
<p>
Something went wrong. I know you don't know what's the problem. So Let
me know by{" "}
<Link
href="https://github.com/j471n/j471n.in/issues/new"
target="_blank"
rel="noopener noreferrer"
className="font-bold underline hover:text-blue-500 "
>
creating an issue
</Link>{" "}
on GitHub.
</p>
</div>
);
}
================================================
FILE: components/EpigraphCard.tsx
================================================
import { useState } from "react";
import { IEpigraph, EpigraphSourceType } from "@lib/interface/sanity";
import { motion } from "framer-motion";
import ReactMarkdown from "react-markdown";
import { FiCopy, FiCheck, FiSearch } from "react-icons/fi";
const SOURCE_LABELS: Record<EpigraphSourceType, string> = {
book: "Book",
movie: "Film",
tvShow: "Series",
person: "Person",
song: "Song",
podcast: "Podcast",
other: "Other",
};
const rowVariants = {
hidden: { opacity: 0, y: 8 },
visible: { opacity: 1, y: 0, transition: { duration: 0.4, ease: "easeOut" } },
};
const quoteComponents = {
p: ({ children }: { children: React.ReactNode }) => (
<p className="text-gray-900 dark:text-white text-base sm:text-lg leading-[1.85] font-normal italic mb-2 last:mb-0">
{children}
</p>
),
strong: ({ children }: { children: React.ReactNode }) => (
<strong className="not-italic font-semibold">{children}</strong>
),
em: ({ children }: { children: React.ReactNode }) => (
<em className="not-italic text-gray-500 dark:text-gray-400">{children}</em>
),
};
export default function EpigraphCard({
epigraph,
index,
}: {
epigraph: IEpigraph;
index: number;
}) {
const [copied, setCopied] = useState(false);
const sourceLabel = SOURCE_LABELS[epigraph.sourceType] ?? epigraph.sourceType;
const handleCopy = () => {
navigator.clipboard.writeText(epigraph.quote);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<motion.article
variants={rowVariants}
className="group py-6 sm:py-8"
style={
{
contentVisibility: "auto",
containIntrinsicSize: "0 120px",
} as React.CSSProperties
}
>
{/* ── Mobile layout ── */}
<div className="flex flex-col gap-3 sm:hidden">
{/* Header: index left, meta right */}
<div className="flex items-center gap-3">
<span className="font-mono text-[10px] text-gray-300 dark:text-neutral-700 tabular-nums select-none shrink-0">
{String(index + 1).padStart(3, "0")}
</span>
<div className="flex items-center gap-2 ml-auto">
{epigraph.year && (
<span className="font-mono text-[9px] text-gray-400 dark:text-neutral-600 select-none">
{epigraph.year}
</span>
)}
<span className="font-mono text-[8px] tracking-[0.3em] uppercase text-gray-600 dark:text-gray-500 border border-gray-200 dark:border-neutral-700 px-1.5 py-0.5 leading-none">
{sourceLabel}
</span>
</div>
</div>
{/* Quote */}
<blockquote className="border-l-2 border-gray-200 dark:border-neutral-700 group-hover:border-gray-900 dark:group-hover:border-white transition-colors duration-200 pl-3">
<ReactMarkdown components={quoteComponents}>
{epigraph.quote}
</ReactMarkdown>
</blockquote>
{/* Attribution */}
<Attribution epigraph={epigraph} />
{/* Footer: tags + copy */}
<Footer
tags={epigraph.tags}
copied={copied}
onCopy={handleCopy}
searchQuery={epigraph.sourceTitle}
/>
</div>
{/* ── Desktop layout: three-column ledger ── */}
<div className="hidden sm:grid grid-cols-[5rem_1px_1fr] gap-0">
{/* Left: index + rotated source + year */}
<div className="flex flex-col items-end gap-3 pr-6 pt-0.5">
<span className="font-mono text-[10px] text-gray-300 dark:text-neutral-700 tabular-nums select-none leading-none">
{String(index + 1).padStart(3, "0")}
</span>
<span
className="font-mono text-[8px] tracking-[0.4em] uppercase text-gray-400 dark:text-neutral-600 select-none flex-1 flex items-center"
style={
{
writingMode: "vertical-lr",
transform: "rotate(180deg)",
} as React.CSSProperties
}
>
{sourceLabel}
</span>
{epigraph.year && (
<span className="font-mono text-[9px] text-gray-400 dark:text-neutral-600 select-none">
{epigraph.year}
</span>
)}
</div>
{/* Vertical rule */}
<div className="w-px bg-gray-200 dark:bg-neutral-800 group-hover:bg-gray-900 dark:group-hover:bg-white transition-colors duration-200" />
{/* Right: quote + attribution + footer */}
<div className="flex flex-col gap-3 pl-7">
<blockquote>
<ReactMarkdown components={quoteComponents}>
{epigraph.quote}
</ReactMarkdown>
</blockquote>
<Attribution epigraph={epigraph} />
<Footer
tags={epigraph.tags}
copied={copied}
onCopy={handleCopy}
searchQuery={epigraph.sourceTitle}
/>
</div>
</div>
</motion.article>
);
}
function Attribution({ epigraph }: { epigraph: IEpigraph }) {
return (
<div className="flex flex-wrap items-baseline gap-x-1.5 gap-y-0.5">
<span className="text-gray-400 dark:text-neutral-600 text-sm select-none leading-none">
—
</span>
{epigraph.speaker && (
<>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 tracking-tight">
{epigraph.speaker}
</span>
{(epigraph.sourceTitle || epigraph.sourceMeta) && (
<span className="text-gray-300 dark:text-neutral-700 text-xs select-none">
,
</span>
)}
</>
)}
<p className="text-sm text-gray-600 dark:text-gray-400 italic hover:text-gray-900 dark:hover:text-white decoration-gray-300 dark:decoration-neutral-700 hover:decoration-gray-900 dark:hover:decoration-white transition-colors duration-150">
{epigraph.sourceTitle}
</p>
{epigraph.sourceMeta && (
<>
<span className="text-gray-300 dark:text-neutral-700 text-xs select-none">
/
</span>
<span className="text-xs text-gray-500 dark:text-gray-500 tracking-wide">
{epigraph.sourceMeta}
</span>
</>
)}
</div>
);
}
function Footer({
tags,
copied,
onCopy,
searchQuery,
}: {
tags?: string[];
copied: boolean;
onCopy: () => void;
searchQuery: string;
}) {
return (
<div className="flex items-center gap-4 pt-0.5">
{/* Copy button */}
<button
onClick={onCopy}
aria-label={copied ? "Copied" : "Copy quote"}
className={`flex items-center gap-1.5 shrink-0 transition-colors duration-150 ${
copied
? "text-gray-900 dark:text-white"
: "text-gray-400 dark:text-neutral-600 hover:text-gray-900 dark:hover:text-white"
}`}
>
{copied ? <FiCheck size={11} /> : <FiCopy size={11} />}
<span className="font-mono text-[9px] tracking-wider uppercase">
{copied ? "Copied" : "Copy"}
</span>
</button>
{/* Google search */}
<a
href={`https://www.google.com/search?q=${encodeURIComponent(searchQuery)}`}
target="_blank"
rel="noopener noreferrer"
aria-label="Search on Google"
className="flex items-center gap-1.5 shrink-0 text-gray-400 dark:text-neutral-600 hover:text-gray-900 dark:hover:text-white transition-colors duration-150"
>
<FiSearch size={11} />
<span className="font-mono text-[9px] tracking-wider uppercase">
Search
</span>
</a>
{/* Tags */}
<div className="flex flex-wrap gap-3">
{tags?.map((tag) => (
<span
key={tag}
className="font-mono text-[9px] tracking-widest uppercase text-gray-400 dark:text-neutral-600"
>
#{tag}
</span>
))}
</div>
</div>
);
}
================================================
FILE: components/Footer.tsx
================================================
import Link from "next/link";
import Image from "next/image";
import socialMedia from "@content/socialMedia";
import siteConfig from "@content/siteConfig";
import { navigationRoutes } from "../utils/utils";
import { motion } from "framer-motion";
import { SiSpotify } from "react-icons/si";
import useSWR from "swr";
import fetcher from "../lib/fetcher";
import { HiOutlineQrcode } from "react-icons/hi";
import { Song } from "@lib/types";
export default function Footer({
setShowQR,
showQR,
}: {
setShowQR: (value: boolean) => void;
showQR: boolean;
}) {
const { data: currentSong } = useSWR("/api/now-playing", fetcher);
const { data: visitors } = useSWR("/api/ga", fetcher);
const navLinks = navigationRoutes.slice(0, 5);
const moreLinks = navigationRoutes.slice(5);
return (
<footer className="w-screen font-inter print:hidden border-t border-gray-200 dark:border-neutral-700 mt-20 relative overflow-hidden">
<div className="max-w-7xl mx-auto px-6 sm:px-8 lg:px-12 py-16">
{/* ── Top: Brand + Now Playing ── */}
<div className="grid lg:grid-cols-2 gap-10 mb-10">
{/* Brand */}
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
>
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
{siteConfig.person.name}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-6 leading-relaxed max-w-xs">
{siteConfig.footer.description}
</p>
{/* Visitor stat */}
<div className="flex items-center gap-3">
<span className="font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-500">
Visitors
</span>
<div className="h-px w-6 bg-gray-200 dark:bg-neutral-700" />
<div className="flex items-center gap-2">
<span className="relative flex h-2 w-2 flex-shrink-0">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-60" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500" />
</span>
<span className="text-sm font-semibold text-gray-900 dark:text-white font-mono">
{visitors?.totalVisitors ?? "—"}
</span>
</div>
</div>
</motion.div>
{/* Now Playing */}
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
>
<span className="font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-500 mb-4 block">
Now Playing
</span>
{currentSong?.isPlaying ? (
<WhenPlaying song={currentSong} />
) : (
<NotPlaying />
)}
</motion.div>
</div>
{/* ── Divider ── */}
<div className="h-px bg-gray-200 dark:bg-neutral-700 mb-10" />
{/* ── Links grid ── */}
<div className="grid grid-cols-2 md:grid-cols-3 gap-x-12 gap-y-8 mb-10">
{/* Navigation */}
<motion.div
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
>
<span className="font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-500 mb-3 block">
Navigation
</span>
<div className="divide-y divide-gray-200 dark:divide-neutral-700">
{navLinks.map((text, i) => (
<FooterLink
key={i}
href={`/${text}`}
text={text}
isExternal={false}
/>
))}
</div>
</motion.div>
{/* More */}
<motion.div
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15 }}
>
<span className="font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-500 mb-3 block">
More
</span>
<div className="divide-y divide-gray-200 dark:divide-neutral-700">
{moreLinks.map((route, i) => (
<FooterLink
key={i}
href={`/${route}`}
text={route === "rss" ? "RSS" : route}
isExternal={false}
/>
))}
</div>
</motion.div>
{/* Connect */}
<motion.div
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
>
<span className="font-mono text-[9px] tracking-[0.4em] uppercase text-gray-500 dark:text-gray-500 mb-3 block">
Connect
</span>
<div className="divide-y divide-gray-200 dark:divide-neutral-700">
{socialMedia.slice(0, 5).map((platform, i) => (
<FooterLink
key={i}
href={platform.url}
text={platform.title}
isExternal={true}
/>
))}
</div>
</motion.div>
</div>
{/* ── Bottom bar ── */}
<div className="h-px bg-gray-200 dark:bg-neutral-700 mb-8" />
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4"
>
<div className="flex flex-wrap items-center gap-x-3 gap-y-1">
<span className="font-mono text-[10px] tracking-[0.3em] uppercase text-gray-600 dark:text-gray-400">
© {new Date().getFullYear()} Jatin Sharma
</span>
<span className="text-gray-300 dark:text-gray-700 text-xs hidden sm:inline">
·
</span>
<span className="font-mono text-[10px] tracking-[0.2em] uppercase text-gray-500 dark:text-gray-500">
Built with{" "}
<Link
href="https://nextjs.org"
target="_blank"
rel="noreferrer"
className="text-neutral-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
>
Next.js
</Link>{" "}
&{" "}
<Link
href="https://vercel.com"
target="_blank"
rel="noreferrer"
className="text-neutral-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
>
Vercel
</Link>
</span>
</div>
{/* QR button — matches contact CTA invert style */}
<motion.button
whileTap={{ scale: 0.96 }}
onClick={() => setShowQR(!showQR)}
className="group relative inline-flex items-center gap-2 px-4 py-2 border border-gray-900 dark:border-white text-gray-900 dark:text-white text-sm font-semibold overflow-hidden hover:text-white dark:hover:text-gray-900 focus:outline-none transition-colors duration-300"
aria-label="Show QR Code"
>
<span className="absolute inset-0 bg-gray-900 dark:bg-white translate-y-full group-hover:translate-y-0 transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]" />
<span className="relative z-10 flex items-center gap-2">
<HiOutlineQrcode className="w-4 h-4" />
QR Code
</span>
</motion.button>
</motion.div>
</div>
</footer>
);
}
function FooterLink({
href,
text,
isExternal,
}: {
href: string;
text: string;
isExternal: boolean;
}) {
return (
<Link
href={href}
target={isExternal ? "_blank" : undefined}
rel={isExternal ? "noopener noreferrer" : undefined}
className="flex items-center justify-between py-2.5 group text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors capitalize"
>
<span className="relative">
<span className="absolute bottom-0 left-0 h-px w-0 group-hover:w-full bg-current transition-all duration-300" />
{text}
</span>
<span className="opacity-0 group-hover:opacity-100 transition-opacity text-xs">
{isExternal ? "↗" : "→"}
</span>
</Link>
);
}
function NotPlaying() {
return (
<div className="flex items-center gap-3 py-1">
<SiSpotify className="w-5 h-5 text-gray-400 dark:text-gray-600 flex-shrink-0" />
<div>
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">
Not Playing
</p>
<p className="text-xs font-mono text-gray-500 dark:text-gray-500">
Spotify
</p>
</div>
</div>
);
}
function WhenPlaying({ song }: { song: Song }) {
return (
<Link
href={song.songUrl}
target="_blank"
rel="noopener noreferrer"
className="group flex items-center gap-4 py-1"
>
<div className="relative flex-shrink-0">
<Image
alt={song.title}
src={song.albumImageUrl}
width={40}
height={40}
quality={50}
className="rounded-sm grayscale group-hover:grayscale-0 transition-all duration-500"
/>
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-semibold text-gray-900 dark:text-white truncate group-hover:underline underline-offset-2 decoration-gray-400">
{song.title}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400 font-mono truncate mt-0.5">
{song.artist}
</p>
</div>
{/* Equaliser bars */}
<div className="flex items-end gap-0.5 flex-shrink-0 h-5">
{[3, 4, 2, 5].map((h, i) => (
<motion.span
key={i}
animate={{ height: [`${h * 3}px`, `${h * 4.5}px`, `${h * 3}px`] }}
transition={{
duration: 0.8,
repeat: Infinity,
delay: i * 0.15,
ease: "easeInOut",
}}
className="w-0.5 bg-emerald-500 rounded-full inline-block"
/>
))}
</div>
</Link>
);
}
================================================
FILE: components/FramerMotion/AnimatedDiv.tsx
================================================
import { AnimatedTAGProps } from "@lib/types";
import { motion } from "framer-motion";
export default function AnimatedDiv({
variants,
className,
children,
infinity,
}: AnimatedTAGProps) {
return (
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: !infinity }}
variants={variants}
className={className}
// style={style}
transition={{ staggerChildren: 0.5 }}
>
{children}
</motion.div>
);
}
================================================
FILE: components/FramerMotion/AnimatedHeading.tsx
================================================
import { motion } from "framer-motion";
import { AnimatedTAGProps } from "@lib/types";
export default function AnimatedHeading({
variants,
className,
children,
infinity,
}: AnimatedTAGProps) {
return (
<motion.h1
initial="hidden"
whileInView="visible"
viewport={{ once: !infinity }}
variants={variants}
className={className}
>
{children}
</motion.h1>
);
}
================================================
FILE: components/FramerMotion/AnimatedText.tsx
================================================
import { AnimatedTAGProps } from "@lib/types";
import { motion } from "framer-motion";
export default function AnimatedText({
variants,
className,
children,
infinity,
}: AnimatedTAGProps) {
return (
<motion.p
initial="hidden"
whileInView="visible"
viewport={{ once: !infinity }}
variants={variants}
className={className}
>
{children}
</motion.p>
);
}
================================================
FILE: components/GitHubActivityGraph.tsx
================================================
import {
Area,
AreaChart,
Bar,
BarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
TooltipProps,
XAxis,
YAxis,
} from "recharts";
import {
NameType,
ValueType,
} from "recharts/types/component/DefaultTooltipContent";
import { motion } from "framer-motion";
import React from "react";
import fetcher from "@lib/fetcher";
import { getFormattedDate } from "@utils/date";
import { useDarkMode } from "@context/darkModeContext";
import useSWR from "swr";
function ChartSectionHeading({
title,
description,
}: {
title: string;
description: string;
}) {
return (
<div className="mb-6 space-y-2">
<motion.div
initial={{ opacity: 0, x: -16 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
className="flex items-center gap-3"
>
<div className="h-px w-5 bg-gray-400 dark:bg-gray-600 flex-shrink-0" />
<span className="font-mono text-[10px] tracking-[0.45em] uppercase text-gray-500 dark:text-gray-500">
Activity
</span>
</motion.div>
<motion.h3
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.06 }}
className="text-xl sm:text-2xl font-bold text-gray-900 dark:text-white"
>
{title}
</motion.h3>
<motion.p
initial={{ opacity: 0, y: 8 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.12 }}
className="text-sm text-gray-600 dark:text-gray-400"
>
{description}
</motion.p>
</div>
);
}
export default function GitHubActivityGraph() {
const { isDarkMode } = useDarkMode();
const { data: githubActivity } = useSWR(
"/api/stats/github-contribution",
fetcher,
);
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div className="max-w-full">
<ChartSectionHeading
title="Activity Graph"
description="GitHub contributions over the last 31 days."
/>
<ResponsiveContainer width="100%" height={300}>
{githubActivity?.contributions ? (
<AreaChart
width={730}
height={250}
data={githubActivity?.contributions}
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor={isDarkMode ? "#26a64160" : "#26a641"}
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor={isDarkMode ? "#26a64160" : "#26a641"}
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis dataKey="shortDate" />
<YAxis />
<CartesianGrid
strokeDasharray="2 3"
stroke={isDarkMode ? "#ffffff20" : "#00000020"}
/>
<Tooltip content={<ContributionsToolTip />} />
<Area
dot
activeDot
strokeWidth={3}
type="monotone"
dataKey="contributionCount"
aria-label="count"
stroke={isDarkMode ? "#26a641" : "#216e39"}
fillOpacity={1}
fill="url(#colorUv)"
/>
</AreaChart>
) : (
<LoadingAreaChart />
)}
</ResponsiveContainer>
</div>
<div className="max-w-full">
<ChartSectionHeading
title="Productivity by Day"
description="Contributions by day of the week."
/>
<ResponsiveContainer width="100%" height={300}>
{githubActivity?.contributionCountByDayOfWeek ? (
<BarChart
width={730}
height={250}
data={githubActivity?.contributionCountByDayOfWeek}
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
>
<CartesianGrid
strokeDasharray="2 3"
stroke={isDarkMode ? "#ffffff20" : "#00000020"}
/>
<XAxis dataKey="day" />
<YAxis />
<Tooltip content={<ContributionCountByDayOfWeekToolTip />} />
<Bar dataKey="count" fill="#26a641" />
</BarChart>
) : (
<LoadingBarChart />
)}
</ResponsiveContainer>
</div>
</div>
);
}
const ContributionsToolTip = ({
active,
payload,
}: TooltipProps<ValueType, NameType>) => {
if (active && payload && payload.length) {
return (
<div className="p-5 rounded-md bg-white dark:bg-darkSecondary text-black dark:text-gray-200 text-sm max-w-[250px] w-fit shadow-lg">
<p className="label">
<span className="font-medium">Date :</span>{" "}
{getFormattedDate(new Date(payload[0].payload.date))}
</p>
<p className="desc">
<span className="font-medium">Commit Count :</span> {payload[0].value}
</p>
</div>
);
}
return null;
};
const ContributionCountByDayOfWeekToolTip = ({
active,
payload,
}: TooltipProps<ValueType, NameType>) => {
if (active && payload && payload.length) {
return (
<div className="p-5 rounded-md bg-white dark:bg-darkSecondary text-black dark:text-gray-200 text-sm max-w-[250px] w-fit shadow-lg">
<p className="label">
<span className="font-medium">Day :</span> {payload[0].payload.day}
</p>
<p className="desc">
<span className="font-medium">Commit Count :</span> {payload[0].value}
</p>
</div>
);
}
return null;
};
function LoadingBarChart() {
const { isDarkMode } = useDarkMode();
const barGraphLoadingData = [
{ day: "Monday", count: 3 },
{ day: "Tuesday", count: 5 },
{ day: "Wednesday", count: 7 },
{ day: "Thursday", count: 9 },
{ day: "Friday", count: 11 },
{ day: "Saturday", count: 13 },
{ day: "Sunday", count: 15 },
];
return (
<div className="pointer-events-none relative">
<div className="grid place-items-center font-semibold text-base sm:text-lg absolute inset-0 z-1">
Loading Data...
</div>
<BarChart
width={730}
height={250}
data={barGraphLoadingData}
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
style={{
opacity: "0.25",
}}
>
<CartesianGrid
strokeDasharray="2 3"
stroke={isDarkMode ? "#ffffff20" : "#00000020"}
/>
<XAxis dataKey="day" />
<YAxis />
<Tooltip
cursor={{ fill: "transparent" }}
content={<ContributionCountByDayOfWeekToolTip />}
/>
<Bar dataKey="count" fill={isDarkMode ? "#404040" : "#ababab"} />
</BarChart>
</div>
);
}
function LoadingAreaChart() {
const { isDarkMode } = useDarkMode();
const areaChartLoadingData = [
{ shortDate: "09", contributionCount: 3 },
{ shortDate: "10", contributionCount: 5 },
{ shortDate: "11", contributionCount: 15 },
{ shortDate: "12", contributionCount: 9 },
{ shortDate: "13", contributionCount: 4 },
{ shortDate: "14", contributionCount: 13 },
{ shortDate: "15", contributionCount: 5 },
{ shortDate: "16", contributionCount: 4 },
{ shortDate: "17", contributionCount: 9 },
{ shortDate: "18", contributionCount: 2 },
{ shortDate: "19", contributionCount: 5 },
{ shortDate: "21", contributionCount: 6 },
{ shortDate: "22", contributionCount: 7 },
{ shortDate: "23", contributionCount: 3 },
{ shortDate: "24", contributionCount: 21 },
{ shortDate: "25", contributionCount: 4 },
{ shortDate: "26", contributionCount: 9 },
{ shortDate: "27", contributionCount: 2 },
];
return (
<div className="pointer-events-none relative">
<div className="grid place-items-center font-semibold text-base sm:text-lg absolute inset-0 z-1">
Loading Data...
</div>
<AreaChart
width={730}
height={250}
data={areaChartLoadingData}
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
style={{
opacity: "0.25",
}}
>
<defs>
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor={isDarkMode ? "#404040" : "#ababab"}
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor={isDarkMode ? "#404040" : "#ababab"}
stopOpacity={0}
/>
</linearGradient>
</defs>
<XAxis dataKey="shortDate" />
<YAxis />
<CartesianGrid
strokeDasharray="2 3"
stroke={isDarkMode ? "#ffffff20" : "#00000020"}
/>
<Tooltip content={<ContributionsToolTip />} />
<Area
dot
activeDot
strokeWidth={3}
type="monotone"
dataKey="contributionCount"
aria-label="count"
stroke={isDarkMode ? "#404040" : "#ababab"}
fillOpacity={1}
fill="url(#colorUv)"
/>
</AreaChart>
</div>
);
}
================================================
FILE: components/Home/BlogsSection.tsx
================================================
import Blog from "../Blog";
import { BlogPost } from "@lib/interface/sanity";
import Link from "next/link";
import { motion } from "framer-motion";
import { FiArrowRight } from "react-icons/fi";
import siteConfig from "@content/siteConfig";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.1 } },
};
export default function BlogsSection({
blogs,
totalBlogs,
}: {
blogs: BlogPost[];
totalBlogs: number;
}) {
const { blogsSection } = siteConfig.home;
return (
<section className="pt-20 sm:pt-24 relative overflow-hidden">
{/* Section number watermark */}
<div
className="absolute -right-2 top-6 font-black select-none pointer-events-none leading-none tracking-tighter bg-gradient-to-b from-gray-200 to-gray-50 dark:from-[#232628] dark:to-darkPrimary bg-clip-text text-transparent"
style={{ fontSize: "clamp(5rem, 16vw, 13rem)" }}
aria-hidden="true"
>
03
</div>
{/* ── Header ── */}
<div className="relative z-10 flex flex-col lg:flex-row lg:items-end lg:justify-between gap-8 mb-12">
<div className="space-y-3 max-w-xl">
{/* Section label */}
<motion.div
initial={{ opacity: 0, x: -16 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
className="flex items-center gap-3"
>
<div className="h-px w-5 bg-gray-400 dark:bg-gray-600 flex-shrink-0" />
<span className="font-mono text-[10px] tracking-[0.45em] uppercase text-gray-400 dark:text-gray-600">
{blogsSection.eyebrow}
</span>
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white"
>
{blogsSection.title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15 }}
className="text-base text-gray-600 dark:text-gray-400 border-l-2 border-gray-300 dark:border-gray-700 pl-4 py-0.5"
>
{blogsSection.description}
</motion.p>
</div>
{/* Article count + CTA */}
<motion.div
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15 }}
className="flex flex-col items-start lg:items-end gap-3 flex-shrink-0"
>
<div className="flex items-end gap-2">
<span className="text-5xl sm:text-6xl font-black text-gray-900 dark:text-white leading-none">
{totalBlogs}
</span>
<span className="text-xs font-mono uppercase tracking-widest text-gray-500 dark:text-gray-500 mb-2">
Articles
</span>
</div>
<Link
href="/blogs"
className="group inline-flex items-center gap-2 text-[10px] font-mono uppercase tracking-[0.25em] text-gray-500 dark:text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors"
>
{blogsSection.ctaLabel}
<FiArrowRight className="w-3 h-3 group-hover:translate-x-1 transition-transform" />
</Link>
</motion.div>
</div>
{/* ── Blog list ── */}
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="flex flex-col"
>
{blogs.map((blog, index) => (
<Blog key={`home-blog-${index}`} blog={blog} index={index} />
))}
</motion.div>
</section>
);
}
================================================
FILE: components/Home/EpigraphsSection.tsx
================================================
import { IEpigraph } from "@lib/interface/sanity";
import Link from "next/link";
import EpigraphCard from "@components/EpigraphCard";
import { motion } from "framer-motion";
import { FiArrowRight } from "react-icons/fi";
import siteConfig from "@content/siteConfig";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.08 } },
};
export default function EpigraphsSection({
epigraphs,
totalEpigraphs,
}: {
epigraphs: IEpigraph[];
totalEpigraphs: number;
}) {
const { epigraphsSection } = siteConfig.home;
return (
<section className="pt-20 sm:pt-24 relative overflow-hidden">
{/* Section number watermark */}
<div
className="absolute -right-2 top-6 font-black select-none pointer-events-none leading-none tracking-tighter bg-gradient-to-b from-gray-200 to-gray-50 dark:from-[#232628] dark:to-darkPrimary bg-clip-text text-transparent"
style={{ fontSize: "clamp(5rem, 16vw, 13rem)" }}
aria-hidden="true"
>
04
</div>
{/* ── Header ── */}
<div className="relative z-10 flex flex-col lg:flex-row lg:items-end lg:justify-between gap-8 mb-12">
<div className="space-y-3 max-w-xl">
{/* Section label */}
<motion.div
initial={{ opacity: 0, x: -16 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
className="flex items-center gap-3"
>
<div className="h-px w-5 bg-gray-400 dark:bg-gray-600 flex-shrink-0" />
<span className="font-mono text-[10px] tracking-[0.45em] uppercase text-gray-400 dark:text-gray-600">
{epigraphsSection.eyebrow}
</span>
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white"
>
{epigraphsSection.title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15 }}
className="text-base text-gray-600 dark:text-gray-400 border-l-2 border-gray-300 dark:border-gray-700 pl-4 py-0.5"
>
{epigraphsSection.description}
</motion.p>
</div>
{/* Count + CTA */}
<motion.div
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15 }}
className="flex flex-col items-start lg:items-end gap-3 flex-shrink-0"
>
<div className="flex items-end gap-2">
<span className="text-5xl sm:text-6xl font-black text-gray-900 dark:text-white leading-none">
{totalEpigraphs}
</span>
<span className="text-xs font-mono uppercase tracking-widest text-gray-500 dark:text-gray-500 mb-2">
Epigraphs
</span>
</div>
<Link
href="/epigraphs"
className="group inline-flex items-center gap-2 text-[10px] font-mono uppercase tracking-[0.25em] text-gray-500 dark:text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors"
>
{epigraphsSection.ctaLabel}
<FiArrowRight className="w-3 h-3 group-hover:translate-x-1 transition-transform" />
</Link>
</motion.div>
</div>
{/* ── Epigraphs list ── */}
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="divide-y divide-gray-100 dark:divide-neutral-800"
>
{epigraphs.map((epigraph, i) => (
<EpigraphCard key={epigraph._id} epigraph={epigraph} index={i} />
))}
</motion.div>
</section>
);
}
================================================
FILE: components/Home/HeroSection.tsx
================================================
import React from "react";
import Image from "next/image";
import Link from "next/link";
import { motion } from "framer-motion";
import {
FiDownload,
FiArrowRight,
FiGithub,
FiLinkedin,
FiTwitter,
} from "react-icons/fi";
import { opacityVariant, popUp } from "@content/FramerMotionVariants";
import { featuredSocialLinks } from "@content/siteConfig";
import siteConfig from "@content/siteConfig";
const socialIconMap = {
github: FiGithub,
linkedin: FiLinkedin,
twitter: FiTwitter,
};
export default function HeroSection() {
const { hero } = siteConfig.home;
return (
<section className="relative min-h-screen flex items-center py-24 lg:py-0 px-6 sm:px-8 lg:px-12 overflow-hidden">
{/* ── Dot grid ── */}
<div
className="absolute inset-0 pointer-events-none select-none"
style={{
backgroundImage:
"radial-gradient(circle, #6b7280 1px, transparent 1px)",
backgroundSize: "28px 28px",
opacity: 0.12,
}}
aria-hidden="true"
/>
{/* ── Diagonal stripe — right half ── */}
<div
className="absolute top-0 right-0 w-1/2 h-full hero-stripe pointer-events-none"
aria-hidden="true"
/>
{/* ── Bottom fade ── */}
<div className="absolute bottom-0 inset-x-0 h-56 bg-gradient-to-t from-white dark:from-darkPrimary to-transparent pointer-events-none" />
{/* ── JS initials — gradient emboss ── */}
<div
className="absolute -right-4 top-1/2 -translate-y-1/2 font-black select-none pointer-events-none leading-none tracking-tighter bg-gradient-to-b from-gray-200 to-gray-50 dark:from-[#232628] dark:to-darkPrimary bg-clip-text text-transparent"
style={{ fontSize: "clamp(8rem, 24vw, 22rem)" }}
aria-hidden="true"
>
JS
</div>
<div className="relative max-w-7xl mx-auto w-full z-10">
{/* ── Corner cross-tick marks ── */}
{(
[
"top-0 left-0",
"top-0 right-0",
"bottom-8 left-0",
"bottom-8 right-0",
] as const
).map((pos) => (
<div
key={pos}
className={`absolute ${pos} w-5 h-5 pointer-events-none`}
aria-hidden="true"
>
<div className="absolute top-1/2 -translate-y-1/2 w-full h-px bg-gray-300 dark:bg-gray-700" />
<div className="absolute left-1/2 -translate-x-1/2 h-full w-px bg-gray-300 dark:bg-gray-700" />
</div>
))}
<div className="grid lg:grid-cols-5 gap-12 lg:gap-8 items-center py-16 lg:py-20">
{/* ── Left content (3 cols) ── */}
<div className="lg:col-span-3 flex flex-col gap-7 text-center lg:text-left">
{/* Availability badge — inverted */}
<motion.div variants={opacityVariant}>
<span className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-gray-900 dark:bg-white text-[11px] font-semibold tracking-[0.1em] uppercase text-white dark:text-gray-900">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse flex-shrink-0" />
{hero.availabilityBadge}
</span>
</motion.div>
{/* Name */}
<div>
<motion.p
variants={opacityVariant}
className="text-xs sm:text-sm font-light tracking-[0.5em] uppercase text-gray-500 dark:text-gray-500 mb-4"
>
{hero.greeting}
</motion.p>
<motion.h1
variants={opacityVariant}
className="font-black leading-[0.9] tracking-tight"
style={{ fontSize: "clamp(3.2rem, 9vw, 6.5rem)" }}
>
<span className="text-neutral-700 dark:text-gray-200">
Jatin
</span>
<br />
<span className="font-sarina font-normal text-gray-900 dark:text-white">
Sharma
</span>
</motion.h1>
</div>
{/* Role line */}
<motion.div
variants={opacityVariant}
className="flex items-center gap-4 justify-center lg:justify-start"
>
<div className="h-px w-8 bg-gray-400 dark:bg-gray-600 flex-shrink-0" />
<p className="text-xs font-mono uppercase tracking-widest text-gray-500 dark:text-gray-500 whitespace-nowrap">
{hero.rolePrefix}{" "}
<Link
href={hero.companyUrl}
target="_blank"
rel="noopener noreferrer"
className="text-neutral-700 dark:text-gray-200 font-semibold hover:underline underline-offset-4 decoration-gray-400 dark:decoration-gray-600"
>
{hero.companyName}
</Link>
</p>
<div className="h-px flex-1 bg-gray-200 dark:bg-neutral-700 hidden sm:block" />
</motion.div>
{/* Bio — left accent border */}
<motion.p
variants={opacityVariant}
className="text-base sm:text-lg text-gray-600 dark:text-gray-400 leading-relaxed max-w-lg mx-auto lg:mx-0 border-l-2 border-gray-300 dark:border-gray-700 pl-5 py-1"
>
{hero.roleSuffix.replace(/^\.\s*/, "")}
</motion.p>
{/* CTAs */}
<motion.div
variants={opacityVariant}
className="flex flex-col sm:flex-row gap-3 justify-center lg:justify-start"
>
{/* Primary — solid fill */}
<Link
href={hero.primaryCta.url}
target="_blank"
rel="noopener noreferrer"
className="group relative inline-flex items-center justify-center gap-2 px-7 py-3.5 bg-gray-900 dark:bg-white text-white dark:text-gray-900 font-semibold text-sm overflow-hidden transition-colors duration-300 hover:bg-neutral-700 dark:hover:bg-gray-100 active:scale-95"
>
<FiDownload className="w-4 h-4 transition-transform group-hover:-translate-y-0.5 duration-200 relative z-10" />
<span className="relative z-10">{hero.primaryCta.label}</span>
</Link>
{/* Secondary — invert-fill on hover */}
<Link
href={hero.secondaryCta.url}
className="group relative inline-flex items-center justify-center gap-2 px-7 py-3.5 border border-gray-400 dark:border-gray-600 text-gray-900 dark:text-white font-semibold text-sm overflow-hidden hover:text-white dark:hover:text-gray-900 transition-colors duration-300 active:scale-95"
>
<span className="absolute inset-0 bg-gray-900 dark:bg-white translate-y-full group-hover:translate-y-0 transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]" />
<span className="relative z-10">{hero.secondaryCta.label}</span>
<FiArrowRight className="w-4 h-4 relative z-10 transition-transform group-hover:translate-x-0.5 duration-200" />
</Link>
</motion.div>
{/* Social links */}
<motion.div
variants={opacityVariant}
className="flex items-center gap-3 justify-center lg:justify-start"
>
<span className="text-[10px] font-mono uppercase tracking-[0.3em] text-gray-500 dark:text-gray-500">
{hero.socialLabel}
</span>
<div className="w-px h-4 bg-gray-300 dark:bg-gray-700 flex-shrink-0" />
{featuredSocialLinks.map((socialLink) => {
const Icon =
socialIconMap[socialLink.icon as keyof typeof socialIconMap];
if (!Icon) return null;
return (
<a
key={socialLink.title}
href={socialLink.url}
target="_blank"
rel="noopener noreferrer"
aria-label={socialLink.title}
className="p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white border border-gray-300 dark:border-gray-700 hover:border-gray-700 dark:hover:border-gray-400 hover:bg-gray-50 dark:hover:bg-darkSecondary transition-all"
>
<Icon className="w-5 h-5" />
</a>
);
})}
</motion.div>
</div>
{/* ── Right image (2 cols) ── */}
<div className="lg:col-span-2 relative order-first lg:order-last">
<div className="relative mx-auto max-w-[260px] sm:max-w-[290px] lg:max-w-[240px] xl:max-w-[350px]">
{/* Diagonal stripe behind image */}
<div
className="absolute -inset-8 hero-stripe rounded-3xl pointer-events-none"
aria-hidden="true"
/>
{/* Outer dashed ring +3° */}
<div
className="absolute inset-0 rounded-2xl border-2 border-dashed border-gray-400 dark:border-gray-600 rotate-3 scale-[1.06] pointer-events-none"
aria-hidden="true"
/>
{/* Inner solid ring -1° */}
<div
className="absolute inset-0 rounded-2xl border border-gray-300 dark:border-gray-700 -rotate-1 scale-[1.02] pointer-events-none"
aria-hidden="true"
/>
{/* Profile image */}
<div className="relative aspect-[3/4] rounded-2xl overflow-hidden border border-gray-200 dark:border-gray-700 shadow-2xl shadow-gray-300 dark:shadow-black/60">
<Image
src={siteConfig.person.profileImage}
className="object-cover object-top w-full h-full grayscale hover:grayscale-0 transition-all duration-700"
fill
alt={siteConfig.person.name}
quality={95}
priority
/>
<div className="absolute inset-x-0 bottom-0 h-2/5 bg-gradient-to-t from-black/35 to-transparent pointer-events-none" />
</div>
{/* Floating card — years */}
<motion.div
variants={popUp}
className="absolute -left-5 sm:-left-10 top-10 bg-white dark:bg-darkSecondary border border-gray-200 dark:border-gray-700 border-t-[3px] border-t-neutral-700 dark:border-t-gray-200 rounded-2xl px-4 py-3.5 shadow-[0_12px_40px_-8px_rgba(0,0,0,0.18)] dark:shadow-[0_12px_40px_-8px_rgba(0,0,0,0.6)] flex flex-col items-center min-w-[76px]"
>
<span className="text-3xl font-black text-gray-900 dark:text-white leading-none">
{hero.experienceBadge.value}
</span>
<span className="text-[10px] uppercase tracking-[0.14em] text-gray-700 dark:text-gray-300 mt-1.5 font-bold">
{hero.experienceBadge.title}
</span>
<span className="text-[10px] text-gray-500 dark:text-gray-500">
{hero.experienceBadge.description}
</span>
</motion.div>
{/* Floating card — projects */}
<motion.div
variants={popUp}
className="absolute -right-5 sm:-right-10 bottom-16 bg-white dark:bg-darkSecondary border border-gray-200 dark:border-gray-700 border-t-[3px] border-t-neutral-700 dark:border-t-gray-200 rounded-2xl px-4 py-3.5 shadow-[0_12px_40px_-8px_rgba(0,0,0,0.18)] dark:shadow-[0_12px_40px_-8px_rgba(0,0,0,0.6)] flex flex-col items-center min-w-[76px]"
>
<span className="text-3xl font-black text-gray-900 dark:text-white leading-none">
100+
</span>
<span className="text-[10px] uppercase tracking-[0.14em] text-gray-700 dark:text-gray-300 mt-1.5 font-bold">
Articles
</span>
</motion.div>
</div>
</div>
</div>
{/* Scroll indicator */}
<motion.div
variants={opacityVariant}
className="hidden lg:flex flex-col items-center gap-2 absolute -bottom-4 left-1/2 -translate-x-1/2"
>
<span className="text-[9px] font-mono uppercase tracking-[0.4em] text-gray-500 dark:text-gray-500">
Scroll
</span>
<motion.div
animate={{ scaleY: [0.4, 1, 0.4], opacity: [0.3, 0.9, 0.3] }}
transition={{ duration: 2.5, repeat: Infinity, ease: "easeInOut" }}
className="w-px h-10 bg-gradient-to-b from-gray-500 dark:from-gray-400 to-transparent origin-top"
/>
</motion.div>
</div>
</section>
);
}
================================================
FILE: components/Home/SkillSection.tsx
================================================
import { motion } from "framer-motion";
import siteConfig from "@content/siteConfig";
import skills from "@content/skillsData";
import React, { useState, useMemo } from "react";
const containerVariants = {
hidden: {},
visible: { transition: { staggerChildren: 0.04 } },
};
const itemVariants = {
hidden: { opacity: 0, y: 12, scale: 0.97 },
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: { type: "spring" as const, stiffness: 160, damping: 20 },
},
exit: { opacity: 0, y: 6, scale: 0.96, transition: { duration: 0.12 } },
};
export default function SkillSection() {
const [selectedCategory, setSelectedCategory] = useState<string>("All");
const { skillsSection } = siteConfig.home;
const categories = useMemo(() => {
const set = new Set(skills.map((s) => s.category || "Other"));
return ["All", ...Array.from(set)];
}, []);
const filteredSkills = useMemo(
() =>
selectedCategory === "All"
? skills
: skills.filter((s) => s.category === selectedCategory),
[selectedCategory],
);
const categoryCounts = useMemo(() => {
const counts: Record<string, number> = { All: skills.length };
for (const s of skills) {
const cat = s.category || "Other";
counts[cat] = (counts[cat] ?? 0) + 1;
}
return counts;
}, []);
return (
<section className="pt-20 sm:pt-24 relative overflow-hidden">
{/* Section number watermark */}
<div
className="absolute -right-2 top-6 font-black select-none pointer-events-none leading-none tracking-tighter bg-gradient-to-b from-gray-200 to-gray-50 dark:from-[#232628] dark:to-darkPrimary bg-clip-text text-transparent"
style={{ fontSize: "clamp(5rem, 16vw, 13rem)" }}
aria-hidden="true"
>
02
</div>
{/* ── Header ── */}
<div className="relative z-10 flex flex-col lg:flex-row lg:items-end lg:justify-between gap-8 mb-12">
<div className="space-y-3 max-w-xl">
{/* Section label */}
<motion.div
initial={{ opacity: 0, x: -16 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
className="flex items-center gap-3"
>
<div className="h-px w-5 bg-gray-400 dark:bg-gray-600 flex-shrink-0" />
<span className="font-mono text-[10px] tracking-[0.45em] uppercase text-gray-400 dark:text-gray-500">
{skillsSection.eyebrow}
</span>
</motion.div>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 dark:text-white"
>
{skillsSection.title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15 }}
className="text-base text-gray-600 dark:text-gray-400 border-l-2 border-gray-300 dark:border-gray-700 pl-4 py-0.5"
>
{skillsSection.description}
</motion.p>
</div>
{/* Tech count + category breakdown */}
<motion.div
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15 }}
className="flex flex-col items-start lg:items-end gap-3 flex-shrink-0"
>
<div className="flex items-end gap-2">
<span className="text-5xl sm:text-6xl font-black text-gray-900 dark:text-white leading-none">
{skills.length}
</span>
<span className="text-xs font-mono uppercase tracking-widest text-gray-500 dark:text-gray-500 mb-2">
Technologies
</span>
</div>
<div className="flex flex-wrap gap-x-4 gap-y-1.5 max-w-xs lg:justify-end">
{categories.slice(1).map((cat) => (
<button
key={cat}
onClick={() => setSelectedCategory(cat)}
className="inline-flex items-center gap-1.5 text-[10px] font-mono uppercase tracking-[0.2em] text-gray-500 dark:text-gray-500 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
>
<span
className={`w-1 h-1 rounded-full transition-colors ${
selectedCategory === cat
? "bg-gray-900 dark:bg-white"
: "bg-gray-400 dark:bg-gray-600"
}`}
/>
{cat}
<span className="opacity-60">({categoryCounts[cat]})</span>
</button>
))}
</div>
</motion.div>
</div>
{/* ── Marquee divider ── */}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
className="relative my-10 overflow-hidden border-y border-gray-200 dark:border-neutral-700 py-3.5"
>
<div className="flex animate-marquee gap-10 w-max">
{[...skills, ...skills].map((skill, i) => {
const Icon = skill.Icon;
return (
<div
key={i}
className="inline-flex items-center gap-2 text-gray-600 dark:text-gray-400 select-none"
>
{/* @ts-ignore */}
<Icon className="w-3.5 h-3.5 flex-shrink-0" />
<span className="text-[10px] font-mono uppercase tracking-[0.25em] whitespace-nowrap">
{skill.name}
</span>
</div>
);
})}
</div>
{/* Side fades */}
<div className="absolute inset-y-0 left-0 w-16 bg-gradient-to-r from-darkWhite dark:from-darkPrimary to-transparent pointer-events-none" />
<div className="absolute inset-y-0 right-0 w-16 bg-gradient-to-l from-darkWhite dark:from-darkPrimary to-transparent pointer-events-none" />
</motion.div>
{/* ── Category filter ── */}
<motion.div
initial={{ opacity: 0, y: -8 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="flex flex-wrap gap-2 mb-8"
>
{categories.map((cat) => (
<button
key={cat}
onClick={() => setSelectedCategory(cat)}
className={`px-4 py-2 text-xs font-semibold font-mono uppercase tracking-wide transition-all duration-200 ${
selectedCategory === cat
? "bg-gray-900 dark:bg-white text-white dark:text-gray-900"
: "bg-transparent text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-700 hover:border-gray-500 dark:hover:border-gray-500 hover:text-gray-900 dark:hover:text-white"
}`}
>
{cat}
<span className="ml-1.5 opacity-50 text-[9px]">
{categoryCounts[cat]}
</span>
</button>
))}
</motion.div>
{/* ── Skills grid — keyed so the whole grid stagger-enters on every filter change ── */}
<motion.div
key={selectedCategory}
variants={containerVariants}
initial="hidden"
animate="visible"
className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3"
>
{filteredSkills.map((skill) => {
const Icon = skill.Icon;
return (
<motion.div
key={skill.name}
variants={itemVariants}
whileHover={{ y: -4 }}
className="group relative flex items-center gap-3 px-4 py-3.5 bg-white dark:bg-darkSecondary border border-gray-200 dark:border-gray-700 hover:border-gray-400 dark:hover:border-gray-600 transition-all duration-200 overflow-hidden cursor-default"
>
{/* Hover background */}
<div className="absolute inset-0 bg-gray-50 dark:bg-darkPrimary opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
{/* Left accent bar */}
<div className="absolute left-0 inset-y-0 w-0.5 bg-gray-900 dark:bg-white origin-center scale-y-0 group-hover:scale-y-100 transition-transform duration-250 rounded-sm" />
{/* Icon */}
<div className="relative flex-shrink-0 w-8 h-8 bg-gray-100 dark:bg-white/10 group-hover:bg-gray-900 dark:group-hover:bg-white flex items-center justify-center transition-colors duration-200">
{/* @ts-ignore */}
<Icon className="w-4 h-4 text-gray-700 dark:text-gray-300 group-hover:text-white dark:group-hover:text-gray-900 transition-colors duration-200" />
</div>
{/* Name */}
<span className="relative text-sm font-semibold text-gray-700 dark:text-gray-300 group-hover:text-gray-900 dark:group-hover:text-white transition-colors duration-200 truncate leading-tight">
{skill.name}
</span>
</motion.div>
);
})}
</motion.div>
</section>
);
}
================================================
FILE: components/Home/StatsSection.tsx
================================================
import { motion } from "framer-motion";
import { FiCode, FiUsers, FiCoffee, FiAward } from "react-icons/fi";
import siteConfig, { StatIconKey } from "@content/siteConfig";
const iconMap: Record<StatIconKey, typeof FiCode> = {
code: FiCode,
users: FiUsers,
coffee: FiCoffee,
award: FiAward,
};
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
},
},
};
export default function StatsSection() {
return (
<section className="py-20 sm:py-24">
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="grid grid-cols-2 lg:grid-cols-4 gap-6 sm:gap-8"
>
{siteConfig.home.stats.map((stat) => {
const Icon = iconMap[stat.icon];
return (
<motion.div
key={stat.label}
variants={itemVariants}
whileHover={{ y: -8 }}
className="relative p-6 sm:p-8 rounded-2xl bg-white dark:bg-darkSecondary border-2 border-gray-100 dark:border-neutral-700 hover:border-gray-300 dark:hover:border-gray-700 transition-all duration-300 group overflow-hidden"
>
{/* Background Gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-gray-50 to-gray-100 dark:from-neutral-700 dark:to-gray-900 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
{/* Content */}
<div className="relative z-10 text-center space-y-3">
{/* Icon */}
<div className="inline-flex p-3 rounded-xl bg-gray-100 dark:bg-gray-900 group-hover:bg-gray-900 dark:group-hover:bg-white transition-all duration-300">
<Icon className="w-6 h-6 text-gray-700 dark:text-gray-300 group-hover:text-white dark:group-hover:text-gray-900 transition-colors duration-300" />
</div>
{/* Value */}
<div className="text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white">
{stat.value}
</div>
{/* Label */}
<div className="space-y-1">
<div className="text-sm font-semibold text-gray-700 dark:text-gray-300">
{stat.label}
</div>
<div className="text-xs text-gray-500 dark:text-gray-500">
{stat.description}
</div>
</div>
</div>
</motion.div>
);
})}
</motion.div>
</section>
);
}
================================================
FILE: components/Instagram/InstagramPost.tsx
================================================
import { DetailedInstagramPost, MediaType } from "@lib/interface";
import { FaPlay } from "react-icons/fa";
import Link from "next/link";
import React from "react";
import { TbBoxMultiple } from "react-icons/tb";
export default function InstagramPost({
post,
}: {
post: DetailedInstagramPost;
}) {
const previewURL =
post.media_type === MediaType.Video ? post.thumbnail_url! : post.media_url;
return (
<Link
href={post.permalink}
target="_blank"
rel="noopener noreferrer"
className="w-full aspect-square relative group"
>
<div className="absolute hidden inset-0 bg-gradient-to-t from-black to-black/30 p-5 lg:group-hover:flex ">
<div className="text-white text-sm line-clamp-2 mt-auto">
{post.caption}
</div>
</div>
{post.media_type !== MediaType.Image && (
<div
title={post.media_type.replace("_", " ").toLowerCase()}
className="absolute right-1 top-1 sm:right-2 sm:top-2 hover:bg-black/50 p-1 sm:p-2 rounded-full"
>
{post.media_type === MediaType.Video && (
<FaPlay className="text-white drop-shadow-md" />
)}
{post.media_type === MediaType.CarouselAlbum && (
<TbBoxMultiple className="text-white drop-shadow-md" />
)}
</div>
)}
<img
src={previewURL}
width={300}
height={300}
alt={post.caption ?? ""}
className="object-cover h-full w-full"
/>
</Link>
);
}
================================================
FILE: components/Instagram/InstagramPostLoading.tsx
================================================
import React from "react";
export default function InstagramPostLoading({ count }: { count: number }) {
return (
<>
{Array.from(Array(count).keys()).map((item) => {
return (
<div
key={item}
style={{
animationDelay: `calc(${item} * 200ms)`,
}}
className="aspect-square bg-neutral-300 dark:bg-neutral-700 animate-pulse "
/>
);
})}
</>
);
}
================================================
FILE: components/Instagram/InstagramSection.tsx
================================================
import { HomeHeading } from "pages";
import { InstagramData } from "@lib/interface";
import InstagramPost from "./InstagramPost";
import InstagramPostLoading from "./InstagramPostLoading";
import Link from "next/link";
import React from "react";
import fetcher from "@lib/fetcher";
import socialMedia from "@content/socialMedia";
import useSWR from "swr";
export default function InstagramSection() {
const { data: instaData } = useSWR<InstagramData>(
"/api/posts/insta",
fetcher
);
return (
<section className="mx-5 mb-5">
<HomeHeading title="Recent Instagram Posts" />
<div className="grid grid-cols-1 gap-4 mx-auto mt-5">
<div className="grid grid-cols-3 gap-0.5">
{instaData === undefined ? (
<InstagramPostLoading count={9} />
) : (
instaData?.data.slice(0, 9).map((post) => {
return <InstagramPost key={post.id} post={post} />;
})
)}
</div>
<Link
href={socialMedia.find((item) => item.title === "Instagram")?.url!}
className="flex items-center justify-center gap-1 font-medium transition border-transparent font-inter active:scale-95 active:border-black w-fit group"
>
<span className="group-hover:underline">View More on Instagram</span>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
className="w-6 h-6 ml-1 transition group-hover:translate-x-2"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M17.5 12h-15m11.667-4l3.333 4-3.333-4zm3.333 4l-3.333 4 3.333-4z"
></path>
</svg>
</Link>
</div>
</section>
);
}
================================================
FILE: components/MDXComponents/Code.tsx
================================================
type Props = {
children?: string | React.ReactNode;
};
export default function Code(props: Props) {
return (
<>
{typeof props.children === "string" ? (
<code className="bg-gray-300 dark:bg-darkSecondary text-black dark:text-white p-0.5 rounded before:text-gray-500 after:text-gray-500 ">
{props.children}
</code>
) : (
<code>{props.children}</code>
)}
</>
);
}
================================================
FILE: components/MDXComponents/CodeSandbox.tsx
================================================
export default function CodeSandbox({
id,
hideNavigation = true,
}: {
id: string;
hideNavigation: boolean;
}) {
return (
<div className="my-3 print:hidden">
<h3>Code Sandbox</h3>
<iframe
className="w-full h-[500px] border-0 rounded overflow-hidden"
src={`https://codesandbox.io/embed/${id}?fontsize=14&theme=dark&hidenavigation=${
hideNavigation ? 1 : 0
}`}
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
</div>
);
}
================================================
FILE: components/MDXComponents/CodeTitle.tsx
================================================
import { BsFileEarmarkCodeFill } from "react-icons/bs";
import {
SiCss3,
SiPython,
SiGnubash,
SiHtml5,
SiReact,
SiMarkdown,
SiNextdotjs,
SiVercel,
SiTypescript,
} from "react-icons/si";
import { VscJson } from "react-icons/vsc";
import { IoLogoJavascript } from "react-icons/io5";
import { AiOutlineFileText, AiOutlineFolderOpen } from "react-icons/ai";
type Props = {
title?: string;
lang: string;
};
export default function CodeTitle({ title, lang }: Props) {
let Icon;
switch (lang) {
case "html":
Icon = SiHtml5;
break;
case "css":
Icon = SiCss3;
break;
case "js":
Icon = IoLogoJavascript;
break;
case "bash":
Icon = SiGnubash;
break;
case "py":
Icon = SiPython;
break;
case "json":
Icon = VscJson;
break;
case "jsx":
Icon = SiReact;
break;
case "text":
Icon = AiOutlineFileText;
break;
case "md":
Icon = SiMarkdown;
break;
case "next":
Icon = SiNextdotjs;
break;
case "directory":
Icon = AiOutlineFolderOpen;
break;
case "vercel":
Icon = SiVercel;
break;
case "ts":
case "tsx":
Icon = SiTypescript;
break;
default:
Icon = BsFileEarmarkCodeFill;
break;
}
return (
<div className="!mt-4 mb-[14px]">
<div className="h-0.5 w-full bg-gray-900 dark:bg-white" />
<div className="bg-white dark:bg-darkSecondary border border-b-0 border-gray-200 dark:border-neutral-700 px-4 py-2 flex items-center gap-2 font-mono overflow-x-auto">
<Icon className="w-3.5 h-3.5 text-gray-400 dark:text-gray-500 flex-shrink-0" />
<span className="text-[10px] tracking-[0.35em] uppercase text-gray-600 dark:text-gray-400 whitespace-nowrap">
{title || lang}
</span>
</div>
</div>
);
}
================================================
FILE: components/MDXComponents/Codepen.tsx
================================================
export default function Codepen({ id }: { id: string }) {
return (
<div className="my-3 print:hidden">
<iframe
height="600"
style={{ marginTop: "10px" }}
className="w-full"
src={`https://codepen.io/j471n/embed/${id}`}
loading="lazy"
allowFullScreen={true}
></iframe>
</div>
);
}
================================================
FILE: components/MDXComponents/Danger.tsx
================================================
type Props = { title?: string; text: string };
export default function Danger({ title, text }: Props) {
return (
<div className="w-full p-6 my-4 bg-red-100 border-l-4 border-red-700 dark:border-red-500 dark:bg-red-700/30">
<div className="flex items-center gap-2 mb-2 text-2xl font-medium leading-tight text-red-700 dark:text-red-500">
<svg
aria-hidden="true"
focusable="false"
data-icon="times-circle"
className="w-4 h-4 mr-2 fill-current"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"
></path>
</svg>
{title || "Danger"}
</div>
<p className="mt-4 text-red-700/80 dark:text-red-400/50">{text}</p>
</div>
);
}
================================================
FILE: components/MDXComponents/EmbedBlog.tsx
================================================
import Image from "next/image";
import Link from "next/link";
export default function EmbedBlog({
img,
text,
url,
}: {
img: string;
text: string;
url: string;
}) {
return (
<Link
href={url}
className="flex items-center gap-3 my-5 px-2 py-2 sm:py-0 border-black dark:border-white border-2 shadow-[5px_5px_black] dark:rounded-md dark:shadow-none select-none"
target="_blank"
rel="noopener noreferrer"
>
<div className="flex">
<Image
src={img}
width={100}
height={55}
placeholder="blur"
blurDataURL={img}
alt="blog image"
style={{
maxWidth: "100%",
height: "auto",
}}
/>
</div>
<p className="font-bold sm:text-lg">{text}</p>
</Link>
);
}
================================================
FILE: components/MDXComponents/Figcaption.tsx
================================================
type Props = {
src: string;
caption?: string;
alt?: string;
};
export default function figcaption({ src, caption, alt }: Props) {
if (caption !== undefined) {
return (
<figure>
<img src={src} alt={alt} />
<figcaption>{caption}</figcaption>
</figure>
);
} else {
return <img src={src} alt={alt} />;
}
}
================================================
FILE: components/MDXComponents/LinkedInEmbed.tsx
================================================
export default function LinkedInEmbed({ id }: { id: string }) {
return (
<div
style={{
position: "relative",
overflow: "hidden",
paddingTop: 56.25 + "%",
}}
>
<iframe
style={{
position: "absolute",
top: 0,
left: 0,
width: 100 + "%",
height: 100 + "%",
border: 0,
}}
src={`https://www.linkedin.com/embed/feed/update/urn:li:activity:${id}`}
/>
</div>
);
}
================================================
FILE: components/MDXComponents/NextAndPreviousButton.tsx
================================================
import Link from "next/link";
import { IoArrowForwardSharp } from "react-icons/io5";
type Props = {
prevHref: string;
prevTitle: string;
nextHref: string;
nextTitle: string;
};
export default function NextAndPreviousButton({
prevHref,
prevTitle,
nextHref,
nextTitle,
}: Props) {
return (
<div className="flex flex-col gap-2 lg:flex-row ">
{prevHref && prevTitle && (
<BlogPageButton href={prevHref} title={prevTitle} type="previous" />
)}
{nextHref && nextTitle && (
<BlogPageButton href={nextHref} title={nextTitle} type="next" />
)}
</div>
);
}
function BlogPageButton({
href,
title,
type,
}: {
href: string;
title: string;
type: "previous" | "next";
}) {
return (
<Link
title={title}
href={href}
className={`flex ${
type === "previous" && "flex-row-reverse"
} justify-between bg-neutral-800 hover:bg-black !no-underline p-3 rounded-md active:scale-95 transition w-full shadow dark:hover:ring-1 dark:ring-white`}
>
<div
className={`flex flex-col gap-1 ${type === "previous" && "text-right"}`}
>
<p className="text-gray-300 !my-0 capitalize text-sm sm:font-light">
{type} Article
</p>
<p className="text-white font-bold sm:font-medium !my-0 text-base">
{title}
</p>
</div>
<IoArrowForwardSharp
className={`bg-white text-black p-2 rounded-full w-8 h-8 self-center ${
type === "previous" && "rotate-180"
}`}
/>
</Link>
);
}
================================================
FILE: components/MDXComponents/Pre.tsx
================================================
import { ReactNode, useRef, useState } from "react";
import { MdCheck, MdContentCopy } from "react-icons/md";
const Pre = ({
children,
"data-theme": dataTheme,
}: {
children?: ReactNode;
"data-theme"?: string;
}) => {
const textInput = useRef<HTMLDivElement>(null);
const [copied, setCopied] = useState(false);
const onCopy = () => {
if (textInput.current !== null) {
setCopied(true);
navigator.clipboard.writeText(textInput.current.textContent!);
setTimeout(() => setCopied(false), 2000);
}
};
return (
<div
className="relative mb-3 -mt-[14px]"
data-theme={dataTheme}
ref={textInput}
>
<button
aria-label="Copy code"
type="button"
onClick={onCopy}
className={`absolute top-2 right-2 z-10 flex items-center gap-1.5 px-2.5 py-1.5 border font-mono text-[10px] tracking-[0.3em] uppercase transition-colors ${
copied
? "bg-gray-900 dark:bg-white text-white dark:text-gray-900 border-gray-900 dark:border-white"
: "bg-transparent border-gray-400 dark:border-gray-600 text-gray-400 dark:text-gray-500 hover:bg-gray-900 dark:hover:bg-white hover:text-white dark:hover:text-gray-900 hover:border-gray-900 dark:hover:border-white"
}`}
>
{copied ? (
<MdCheck className="w-3 h-3 flex-shrink-0" />
) : (
<MdContentCopy className="w-3 h-3 flex-shrink-0" />
)}
<span className="hidden sm:inline">{copied ? "Copied" : "Copy"}</span>
</button>
<pre className="blog-pre !my-0 !w-full !p-0 !py-3 border border-gray-200 dark:border-neutral-700">
{children}
</pre>
</div>
);
};
export default Pre;
================================================
FILE: components/MDXComponents/Step.tsx
================================================
import React from "react";
export default function Step({
id,
children,
}: {
id: string;
children?: JSX.Element;
}) {
return (
<div
className={`flex items-center gap-3 ${
children?.type === undefined && "my-5"
}`}
>
<div className="flex items-center justify-center w-10 h-10 p-5 font-bold text-black bg-gray-300 rounded-full dark:border-neutral-700 g-gray-300 ring dark:bg-darkSecondary dark:text-white ">
{id}
</div>
<div className="flex-grow-0 text-lg font-semibold tracking-tight text-black dark:text-white w-fit">
{children}
</div>
</div>
);
}
================================================
FILE: components/MDXComponents/Tip.tsx
================================================
export default function Tip({
id,
children,
}: {
id: string;
children?: React.ReactNode;
}) {
return (
<div className="relative flex items-center w-full gap-2 px-5 pt-4 my-5 bg-yellow-100 rounded-md dark:bg-neutral-800">
<div className="absolute top-0 left-0 px-4 py-0 font-bold text-black uppercase bg-yellow-400 font-barlow rounded-tl-md rounded-br-md dark:bg-yellow-500">
Tip {id && `#${id}`}
</div>
{children}
</div>
);
}
================================================
FILE: components/MDXComponents/UrlMetaInfo.tsx
================================================
import React, { useEffect, useState } from "react";
import Image from "next/image";
import Link from "next/link";
interface MetaData {
title: string;
description: string;
image: string;
}
interface UrlMetaInfoProps {
url: string;
}
function UrlMetaInfo({ url }: UrlMetaInfoProps) {
const [metaData, setMetaData] = useState<MetaData | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const data = await response.text();
const parser = new DOMParser();
const htmlDoc = parser.parseFromString(data, "text/html");
const metaTags = htmlDoc.querySelectorAll("meta");
const metaInfo: MetaData = {
title: (htmlDoc.querySelector("title")?.textContent || "")
.replaceAll(" - Jatin Sharma", "")
.replaceAll(" - DEV Community", ""),
description: (
Array.from(metaTags)
.find((tag) => tag.getAttribute("name") === "description")
?.getAttribute("content") || ""
).split("...")[0],
image:
Array.from(metaTags)
.find((tag) => tag.getAttribute("property") === "og:image")
?.getAttribute("content") || "",
};
setMetaData(metaInfo);
} catch (error) {
console.error("Error fetching URL data:", error);
}
};
fetchData();
}, [url]);
if (!metaData) {
return <div>Loading...</div>;
}
return (
<Link
target="_blank"
rel="noopener noreferrer"
href={url}
passHref
className="!no-underline"
>
<div className="flex gap-2 ring-2 rounded-lg ring-gray-500 relative items-center dark:hover:bg-darkSecondary cursor-pointer unset my-4">
<div className="flex flex-col gap-2 !m-0 px-5 py-5 sm:py-0">
<h4 className="!m-0 line-clamp-1">{metaData.title}</h4>
<p className="line-clamp-2 !m-0 text-sm !text-gray-400">
{metaData.description}
</p>
</div>
<div className="w-[184px] shrink-0 hidden sm:flex">
<Image
width={300}
height={200}
className="h-full w-full !m-0 rounded-lg object-contain"
src={metaData.image}
alt={metaData.title}
/>
</div>
</div>
</Link>
);
}
export default UrlMetaInfo;
================================================
FILE: components/MDXComponents/Warning.tsx
================================================
type Props = {
text?: string;
title?: string;
children?: React.ReactNode;
};
export default function Warning({ text, title, children }: Props) {
return (
<div className="w-full p-6 my-4 bg-yellow-100 border-l-4 border-yellow-700 dark:border-yellow-500 dark:bg-yellow-900">
<div className="flex items-center gap-2 mb-2 text-2xl font-medium leading-tight text-yellow-700 dark:text-yellow-500">
<svg
aria-hidden="true"
className="w-6 h-6 mr-2 fill-current"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512"
>
<path
fill="currentColor"
d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
></path>
</svg>
{title || "Warning"}
</div>
<p className="mt-4 text-yellow-700/80 dark:text-yellow-400/50">
{text || children}
</p>
</div>
);
}
================================================
FILE: components/MDXComponents/YouTube.tsx
================================================
export default function YouTube({ id }: { id: string }) {
return (
<div className="max-w-full overflow-hidden relative pb-[56.25%] h-0 ">
<iframe
className="absolute top-0 left-0 w-full h-full"
src={`https://www.youtube.com/embed/${id}`}
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
);
}
================================================
FILE: components/MDXComponents/index.tsx
================================================
import Code from "./Code";
import CodeSandbox from "./CodeSandbox";
import CodeTitle from "./CodeTitle";
import Codepen from "./Codepen";
import Danger from "./Danger";
import EmbedBlog from "./EmbedBlog";
import Figcaption from "./Figcaption";
import LinkedInEmbed from "./LinkedInEmbed";
import NextAndPreviousButton from "./NextAndPreviousButton";
import Pre from "./Pre";
import Step from "./Step";
import Tip from "./Tip";
import UrlMetaInfo from "./UrlMetaInfo";
import Warning from "./Warning";
import YouTube from "./YouTube";
const MDXComponents = {
Codepen,
Figcaption,
Warning,
Danger,
CodeTitle,
Tip,
Step,
CodeSandbox,
NextAndPreviousButton,
YouTube,
EmbedBlog,
UrlMetaInfo,
LinkedInEmbed,
pre: Pre,
code: Code,
};
export default MDXComponents;
================================================
FILE: components/MetaData.tsx
================================================
import { useEffect, useState } from "react";
import { NextSeo } from "next-seo";
import useWindowLocation from "@hooks/useWindowLocation";
// import Head from "next/head";
type Props = {
title: string;
description: string;
previewImage?: string;
keywords?: string;
suffix?: string;
};
const getFaviconPath = (isDarkMode: boolean = true): string => {
return `/favicon-${isDarkMode ? "dark" : "light"}.ico`;
};
export default function MetaData({
title,
description,
previewImage,
keywords,
suffix,
}: Props) {
const { currentURL } = useWindowLocation();
const [faviconHref, setFaviconHref] = useState("/favicon-dark.ico");
useEffect(() => {
// Get current color scheme.
const matcher = window.matchMedia("(prefers-color-scheme: dark)");
// Set favicon initially.
setFaviconHref(getFaviconPath(matcher.matches));
// Change favicon if the color scheme changes.
matcher.onchange = () => setFaviconHref(getFaviconPath(matcher.matches));
}, [faviconHref]);
return (
<NextSeo
title={title + (suffix ? ` - ${suffix}` : "")}
description={description || "Jatin Sharma"}
canonical={currentURL}
additionalLinkTags={[
{
rel: "icon",
href: faviconHref,
},
{
rel: "manifest",
href: "/manifest.json",
},
{
rel: "apple-touch-icon",
href: "/icons/icon-192x192.png",
sizes: "192x192",
},
]}
openGraph={{
type: "website",
url: currentURL,
title: title + (suffix ? ` - ${suffix}` : ""),
description: description || "Jatin Sharma",
profile: {
firstName: "Jatin",
lastName: "Sharma",
gender: "Male",
username: "j471n",
},
article: {
tags: keywords?.split(","),
authors: ["https://linkedin.com/in/j471n"],
},
images: [
{
url: previewImage ?? "",
width: 1200,
height: 630,
alt: title,
type: "image/jpeg",
},
],
siteName: "j471n.in",
}}
twitter={{
handle: "@j471n_",
site: "@j471n_",
cardType: "summary_large_image",
}}
/>
// <Head>
// <meta charSet="utf-8" />
// <meta
// name="viewport"
// content="width=device-width,initial-scale=1,minimum-scale=1"
// />
// <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
// <meta name="description" content={description || "Jatin Sharma"} />
// <title>{title + (suffix ? ` - ${suffix}` : "")}</title>
// <meta name="theme-color" content="#000" />
// <link rel="shortcut icon" href={faviconHref} sizes="any" />
// <link rel="manifest" href="/manifest.json" />
// <link rel="apple-touch-icon" href="/icons/icon-192x192.png"></link>
// <meta httpEquiv="Content-Type" content="text/html; charset=UTF-8" />
// <meta name="author" content="Jatin Sharma"></meta>
// <meta name="robots" content="index,follow" />
// <meta
// name="keywords"
// content={`${keywords || ""} Jatin, Jatin sharma, j471n, j471n_`}
// />
// {/* Og */}
// <meta property="og:title" content={`${title || ""} Jatin Sharma`} />
// <meta property="og:description" content={description || "Jatin Sharma"} />
// <meta property="og:site_name" content="Jatin Sharma" />
// <meta property="og:url" content={currentURL} key="ogurl" />
// <meta property="og:image" content={previewImage || ""} />
// {/* Twitter */}
// <meta name="twitter:card" content="summary_large_image" />
// <meta name="twitter:creator" content="@j471n_" />
// <meta name="twitter:title" content={`${title || ""} Jatin Sharma`} />
// <meta name="twitter:description" content={description} />
// <meta name="twitter:image" content={previewImage || ""} />
// <meta name="twitter:image:alt" content={title || "Jatin Sharma"}></meta>
// <meta name="twitter:domain" content={currentURL} />
// </Head>
);
}
================================================
FILE: components/MovieCard.tsx
================================================
import React, { useState } from "react";
import { AiFillStar } from "react-icons/ai";
import { ITMDBData } from "@lib/interface";
import Image from "next/image";
import { motion } from "framer-motion";
const TMDB_IMAGE_PREFIX = "https://image.tmdb.org/t/p/w780";
const cardVariants = {
hidden: { opacity: 0, y: 12 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring" as const, stiffness: 160, damping: 22 },
},
};
export default function MovieCard({ movie }: { movie: ITMDBData }) {
const [loading, setLoading] = useState(true);
const title = movie?.original_title ?? movie?.original_name ?? "";
return (
<motion.div
variants={cardVariants}
className="group flex-shrink-0 w-[148px] flex flex-col bg-white dark:bg-darkPrimary border border-gray-200 dark:border-neutral-700 hover:border-gray-400 dark:hover:border-gray-600 transition-colors duration-200"
>
{/* Poster */}
<div className="relative w-full aspect-[2/3] overflow-hidden bg-gray-100 dark:bg-darkSecondary">
{loading && (
<div className="absolute inset-0 bg-gray-200 dark:bg-neutral-700 animate-pulse" />
)}
<Image
className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-500"
src={TMDB_IMAGE_PREFIX + movie.poster_path}
alt={title}
width={296}
height={444}
onLoadingComplete={() => setLoading(false)}
/>
</div>
{/* Info */}
<div className="p-3 flex flex-col gap-2 flex-1">
<WatchStatus rating={movie.rating} />
<p
className="text-xs font-medium text-gray-900 dark:text-white leading-snug line-clamp-2"
title={title}
>
{title}
</p>
</div>
</motion.div>
);
}
function WatchStatus({ rating }: { rating?: number }) {
return (
<div className="flex items-center justify-between gap-1">
{rating ? (
<>
<span className="font-mono text-[9px] tracking-[0.3em] uppercase text-emerald-700 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-900/30 px-2 py-0.5">
Watched
</span>
<span className="flex items-center gap-0.5 text-[10px] font-mono text-gray-600 dark:text-gray-400">
<AiFillStar className="w-3 h-3 text-amber-500" />
{rating}
</span>
</>
) : (
<span className="font-mono text-[9px] tracking-[0.3em] uppercase text-amber-700 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 animate-pulse">
Watching
</span>
)}
</div>
);
}
================================================
FILE: components/Newsletter.tsx
================================================
import { useState } from "react";
import { ToastContainer, toast } from "react-toastify";
import { CgSpinnerTwo } from "react-icons/cg";
import { useDarkMode } from "@context/darkModeContext";
export default function Newsletter() {
const { isDarkMode } = useDarkMode();
const [email, setEmail] = useState("");
const [validationLoading, setValidationLoading] = useState(false);
async function subscribeNewsLetter(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setValidationLoading(true);
fetch("/api/validate/email", {
method: "POST",
body: JSON.stringify({ email }),
})
.then((res) => res.json())
.then((res) => {
if (res.status === "success" && res.valid === true) {
try {
fetch(process.env.NEXT_PUBLIC_EMAIL_LIST + email, {
mode: "no-cors",
});
} catch (error) {
console.error(error);
}
toast.success("You have been added to my mailing list.");
setEmail("");
} else {
toast.error("Please enter a valid email address.");
}
setValidationLoading(false);
})
.catch((error) => {
console.error(error);
setValidationLoading(false);
});
}
return (
<>
<div className="not-prose my-12 border border-gray-200 dark:border-neutral-700 bg-white dark:bg-darkPrimary print:hidden">
{/* Header */}
<div className="px-6 pt-6 pb-5 border-b border-gray-200 dark:border-neutral-700">
<span className="font-mono text-[10px] tracking-[0.45em] uppercase text-gray-500 dark:text-gray-500 block mb-2">
Newsletter
</span>
<p className="text-sm font-semibold text-gray-900 dark:text-white">
Monthly digest on web dev, tech, and productivity.
</p>
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
No spam. Unsubscribe any time.
</p>
</div>
{/* Form */}
<form onSubmit={subscribeNewsLetter} className="flex p-4 gap-2">
<input
type="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
required
className="flex-1 min-w-0 px-3 py-2 text-sm bg-gray-50 dark:bg-darkSecondary border border-gray-200 dark:border-neutral-700 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-600 outline-none focus:border-gray-400 dark:focus:border-gray-600 transition-colors font-mono"
/>
<button
type="submit"
disabled={validationLoading}
className="flex-shrink-0 flex items-center gap-2 px-4 py-2 bg-gray-900 dark:bg-white text-white dark:text-gray-900 font-mono text-[10px] tracking-[0.35em] uppercase hover:bg-gray-700 dark:hover:bg-gray-200 transition-colors disabled:opacity-50"
>
{validationLoading ? (
<CgSpinnerTwo className="w-3.5 h-3.5 animate-spin" />
) : (
"Subscribe"
)}
</button>
</form>
</div>
<ToastContainer
theme={isDarkMode ? "dark" : "light"}
style={{ zIndex: 1000 }}
autoClose={3000}
/>
</>
);
}
================================================
FILE: components/OgImage.tsx
================================================
import Image from "next/image";
function OgImage({ src, alt }: { src: string; alt: string }) {
return (
<div className="relative -mt-[35%] sm:-mt-0 md:-ml-[35%] w-full sm:w-1/2 md:w-8/12 shrink-0 rounded-xl overflow-hidden shadow-2xl before:absolute before:inset-0 dark:before:bg-black/20 before:z-10">
<Image
title={alt}
alt={alt}
src={src}
width={1200}
height={630}
placeholder="blur"
blurDataURL={src}
quality={25}
className="transition-all duration-300 lg:group-hover:scale-110 backdrop-blur-xl"
style={{
width: "100%",
height: "auto",
objectFit: "cover",
}}
/>
</div>
);
}
export default OgImage;
================================================
FILE: components/PageHeader.tsx
================================================
import React from "react";
import { motion } from "framer-motion";
interface PageHeaderProps {
/** Large background watermark text, e.g. "about" or "/uses" */
watermark: string;
/** Mono eyebrow label above the title, e.g. "Profile — 001" */
eyebrow: string;
/** Page title rendered as h1 */
title: string;
/** Description rendered with left-border accent below the title */
description: string;
/** Content rendered inside the max-w-7xl container below the header */
children?: React.ReactNode;
/** Extra classes on the outermost wrapper — use for padding overrides */
className?: string;
}
export default function PageHeader({
watermark,
eyebrow,
title,
description,
children,
className = "",
}: PageHeaderProps) {
return (
<div
className={`relative w-full pt-20 px-6 sm:px-8 lg:px-12 overflow-hidden ${className}`}
>
{/* Watermark */}
<div
className="absolute -right-2 top-8 font-black select-none pointer-events-none leading-none tracking-tighter bg-gradient-to-b from-gray-300 to-gray-50 dark:from-[#232628] dark:to-darkPrimary bg-clip-text text-transparent"
style={{ fontSize: "clamp(5rem, 16vw, 13rem)" }}
aria-hidden="true"
>
{watermark}
</div>
<div className="relative max-w-7xl mx-auto z-10">
{/* Header */}
<div className="space-y-4 mb-12 max-w-2xl">
<motion.div
initial={{ opacity: 0, x: -16 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4 }}
className="flex items-center gap-3"
>
<div className="h-px w-5 bg-gray-400 dark:bg-gray-600 flex-shrink-0" />
<span className="font-mono text-[10px] tracking-[0.45em] uppercase text-gray-500 dark:text-gray-500">
{eyebrow}
</span>
</motion.div>
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.08 }}
className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-white"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, delay: 0.14 }}
className="text-base text-gray-600 dark:text-gray-400 border-l-2 border-gray-300 dark:border-gray-700 pl-4 py-0.5"
>
{description}
</motion.p>
</div>
{children}
</div>
</div>
);
}
================================================
FILE: components/PageNotFound.tsx
================================================
import Link from "next/link";
import MetaData from "@components/MetaData";
import { motion } from "framer-motion";
export default function PageNotFound() {
return (
<>
<MetaData
title="404"
suffix="Page Not Found"
description="You are lost in Space !!!"
/>
<div className="relative w-full min-h-screen pt-20 px-6 sm:px-8 lg:px-12 overflow-hidden flex items-center">
{/* Watermark */}
<div
className="absolute -right-2 top-8 font-black select-none pointer-events-none leading-none tracking-tighter bg-gradient-to-b from-gray-300 to-gray-50 dark:from-[#232628] dark:to-darkPrimary bg-clip-text text-transparent"
style={{ fontSize: "clamp(5rem, 20vw, 16rem)" }}
aria-hidden="true"
>
404
</div>
<div className="relative mx-auto z-10 max-w-2xl space-y-6">
<motion.div
initial={{ opacity: 0, x: -16 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4 }}
className="flex items-center gap-3"
>
<div className="h-px w-5 bg-gray-400 dark:bg-gray-600 flex-shrink-0" />
<span className="font-mono text-[10px] tracking-[0.45em] uppercase text-gray-500 dark:text-gray-500">
Error — 404
</span>
</motion.div>
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.08 }}
className="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-white"
>
Page not found.
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, delay: 0.14 }}
className="text-base text-gray-600 dark:text-gray-400 border-l-2 border-gray-300 dark:border-gray-700 pl-4 py-0.5"
>
You didn't break the internet, but I can't find what
you're looking for. Visit the homepage to get back on track.
</motion.p>
<motion.div
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.22 }}
>
<Link
href="/"
className="inline-flex items-center gap-2 px-5 py-2.5 bg-gray-900 dark:bg-white text-white dark:text-gray-900 font-mono text-[11px] tracking-[0.35em] uppercase transition-opacity hover:opacity-80"
>
Take me home
</Link>
</motion.div>
</div>
</div>
</>
);
}
================================================
FILE: components/PageTop.tsx
================================================
import {
fromLeftVariant,
opacityVariant,
} from "../content/FramerMotionVariants";
import AnimatedHeading from "./FramerMotion/AnimatedHeading";
import AnimatedText from "./FramerMotion/AnimatedText";
export default function PageTop({
pageTitle,
headingClass,
containerClass,
children,
}: {
pageTitle: string;
headingClass?: string;
containerClass?: string;
children?: React.ReactNode;
}) {
return (
<div
className={`w-full flex flex-col gap-3 py-5 select-none mb-10 ${containerClass}`}
>
<AnimatedHeading
variants={fromLeftVariant}
className={`text-4xl md:text-5xl font-bold text-neutral-900 dark:text-neutral-200 ${headingClass}`}
>
{pageTitle}
</AnimatedHeading>
<AnimatedText
variants={opacityVariant}
className="text-lg text-gray-600 dark:text-gray-400"
>
{children}
</AnimatedText>
</div>
);
}
================================================
FILE: components/Project.tsx
================================================
import { BsGithub } from "react-icons/bs";
import { MdOutlineLink } from "react-icons/md";
import Link from "next/link";
import Image from "next/image";
import { motion } from "framer-motion";
import { ProjectType } from "@lib/types";
const itemVariants = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring", stiffness: 260, damping: 24 },
},
};
export default function Project({
project,
featured = false,
}: {
project: ProjectType;
featured?: boolean;
}) {
return (
<motion.div
variants={itemVariants}
className={`flex border border-gray-200 dark:border-neutral-700 bg-white dark:bg-darkPrimary hover:bg-gray-50 dark:hover:bg-darkSecondary transition-colors group ${
featured
? "sm:col-span-2 lg:col-span-3 flex-col md:flex-row"
: "flex-col"
}`}
>
{/* Cover image */}
{project.coverImage && (
<div
className={`relative overflow-hidden flex-shrink-0 border-gray-200 dark:border-neutral-700 ${
featured
? "w-full h-56 md:h-auto md:w-80 lg:w-[420px] border-b md:border-b-0 md:border-l order-first md:order-last"
: "w-full h-40 border-b"
}`}
>
<Image
src={project.coverImage}
alt={project.name}
fill
quality={50}
placeholder="blur"
blurDataURL={project.coverImage}
className="object-cover transition-transform duration-500 group-hover:scale-105"
/>
</div>
)}
{/* Content */}
<div
className={`flex flex-col flex-1 gap-3 ${
featured ? "p-6 lg:p-8" : "p-4"
}`}
>
{featured && (
<span className="font-mono text-[10px] tracking-[0.45em] uppercase text-gray-400 dark:text-gray-600">
Featured
</span>
)}
<div className="flex-1 space-y-2">
<h2
className={`font-bold text-gray-900 dark:text-white leading-snug ${
featured ? "text-xl sm:text-2xl" : "text-sm"
}`}
>
{project.name}
</h2>
<p
className={`text-sm text-gray-600 dark:text-gray-400 ${
featured ? "line-clamp-4 max-w-xl" : "line-clamp-2"
}`}
>
{project.description}
</p>
</div>
{/* Tech tags */}
{project.tools && project.tools.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{(featured ? project.tools : project.tools.slice(0, 4)).map(
(tool, index) => (
<span
key={`${tool}-${index}`}
className="font-mono text-[9px] tracking-[0.3em] uppercase px-2 py-0.5 border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-500"
>
{tool}
</span>
),
)}
</div>
)}
{/* Links */}
<div className="flex items-center gap-4 pt-2 border-t border-gray-100 dark:border-neutral-700 mt-auto">
<Link
href={project.githubURL}
title="Source Code on GitHub"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 font-mono text-[10px] tracking-[0.3em] uppercase text-gray-500 dark:text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors"
>
<BsGithub className="w-3.5 h-3.5" />
Code
</Link>
{project.previewURL && (
<Link
href={project.previewURL}
title="Live Preview"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 font-mono text-[10px] tracking-[0.3em] uppercase text-gray-500 dark:text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors"
>
<MdOutlineLink className="w-3.5 h-3.5" />
Preview
</Link>
)}
</div>
</div>
</motion.div>
);
}
================================================
FILE: components/QRCodeContainer.tsx
================================================
import QRCode from "react-qr-code";
import useWindowLocation from "@hooks/useWindowLocation";
import { MdClose } from "react-icons/md";
import { AnimatePresence, motion } from "framer-motion";
import { useDarkMode } from "@context/darkModeContext";
export default function QRCodeContainer({
showQR,
setShowQR,
}: {
showQR: boolean;
setShowQR: (value: boolean) => void;
}) {
const { currentURL } = useWindowLocation();
const { isDarkMode } = useDarkMode();
function downloadQRCode() {
const svg = document.getElementById("QRCode");
const svgData = new XMLSerializer().serializeToString(svg!);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx?.drawImage(img, 0, 0);
const pngFile = canvas.toDataURL("image/png");
const downloadLink = document.createElement("a");
downloadLink.download = "QRCode";
downloadLink.href = `${pngFile}`;
downloadLink.click();
};
img.src = `data:image/svg+xml;base64,${btoa(svgData)}`;
}
return (
<AnimatePresence>
{showQR && (
<>
{/* Backdrop */}
<motion.div
key="qr-backdrop"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setShowQR(false)}
className="fixed inset-0 bg-black/50 z-[9999]"
/>
{/* Panel */}
<motion.div
key="qr-panel"
initial={{ opacity: 0, scale: 0.96, y: 12 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.96, y: 12 }}
transition={{ type: "spring", stiffness: 340, damping: 30 }}
className="fixed inset-0 flex items-center justify-center z-[10000] pointer-events-none"
>
<div className="pointer-events-auto w-full max-w-xs bg-white dark:bg-darkPrimary border border-gray-200 dark:border-neutral-700">
{/* Header */}
<div className="h-0.5 w-full bg-gray-900 dark:bg-white" />
<div className="flex items-center justify-between px-5 py-4 border-b border-gray-200 dark:border-neutral-700">
<div>
<span className="font-mono text-[10px] tracking-[0.45em] uppercase text-gray-400 dark:text-gray-500 block leading-none mb-1">
Share
</span>
<span className="text-sm font-semibold text-gray-900 dark:text-white">
Scan QR Code
</span>
</div>
<button
onClick={() => setShowQR(false)}
aria-label="Close"
className="w-8 h-8 flex items-center justify-center border border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-900 dark:hover:border-white transition-colors"
>
<MdClose className="w-4 h-4" />
</button>
</div>
{/* QR code */}
<div className="p-6 flex justify-center border-b border-gray-200 dark:border-neutral-700">
<QRCode
id="QRCode"
value={currentURL}
size={180}
bgColor={isDarkMode ? "#1a1d1f" : "#ffffff"}
fgColor={isDarkMode ? "#ffffff" : "#111827"}
/>
</div>
{/* URL + download */}
<div className="px-5 py-4 space-y-3">
<p className="font-mono text-[10px] tracking-[0.2em] text-gray-400 dark:text-gray-500 truncate">
{currentURL}
</p>
<button
onClick={downloadQRCode}
className="w-full py-2.5 bg-gray-900 dark:bg-white text-white dark:text-gray-900 font-mono text-[10px] tracking-[0.35em] uppercase hover:opacity-80 transition-opacity"
>
Download PNG
</button>
</div>
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
}
================================================
FILE: components/SVG/Ditto.tsx
================================================
import { useDarkMode } from "@context/darkModeContext";
export default function Ditto() {
const { isDarkMode } = useDarkMode();
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 32 32"
fill="currentColor"
className="utilities-svg"
>
<rect width="32" height="32" rx="8" fill="currentColor" />
<g clipPath="url(#clip0_739_15)">
<path
d="M18.691 17.0001C18.1726 17.0001 17.6542 17.0001 17.1357 17.0001C16.6173 17.0001 16.2007 17.0001 15.8859 17.0001C16.1081 16.0373 16.3303 15.0837 16.5525 14.1394C16.7747 13.2136 16.9968 12.2786 17.219 11.3343C17.4412 10.4085 17.6634 9.47347 17.8856 8.52917C18.2744 8.52917 18.6355 8.52917 18.9688 8.52917C19.3021 8.52917 19.6076 8.52917 19.8853 8.52917C20.163 8.52917 20.45 8.52917 20.7463 8.52917C21.024 8.52917 21.3388 8.52917 21.6906 8.52917C21.7646 8.52917 21.8295 8.53843 21.885 8.55694C21.922 8.59397 21.9405 8.631 21.9405 8.66804C21.9405 8.72358 21.9313 8.78839 21.9128 8.86245C21.598 9.69566 21.274 10.5566 20.9407 11.4454C20.6074 12.3342 20.2741 13.2322 19.9408 14.1394C19.5891 15.0652 19.265 15.954 18.9688 16.8057C18.9317 16.8983 18.8947 16.9538 18.8577 16.9723C18.8206 16.9909 18.7651 17.0001 18.691 17.0001ZM12.314 17.0001C11.7955 17.0001 11.2771 17.0001 10.7586 17.0001C10.2402 17.0001 9.8236 17.0001 9.50883 17.0001C9.73102 16.0373 9.95321 15.0837 10.1754 14.1394C10.3976 13.2136 10.6198 12.2786 10.842 11.3343C11.0642 10.4085 11.2863 9.47347 11.5085 8.52917C11.8974 8.52917 12.2584 8.52917 12.5917 8.52917C12.925 8.52917 13.2305 8.52917 13.5082 8.52917C13.786 8.52917 14.073 8.52917 14.3692 8.52917C14.6469 8.52917 14.9617 8.52917 15.3135 8.52917C15.3876 8.52917 15.4524 8.53843 15.5079 8.55694C15.545 8.59397 15.5635 8.631 15.5635 8.66804C15.5635 8.72358 15.5542 8.78839 15.5357 8.86245C15.2209 9.69566 14.8969 10.5566 14.5636 11.4454C14.2303 12.3342 13.8971 13.2322 13.5638 14.1394C13.212 15.0652 12.8879 15.954 12.5917 16.8057C12.5547 16.8983 12.5176 16.9538 12.4806 16.9723C12.4436 16.9909 12.388 17.0001 12.314 17.0001Z"
fill={isDarkMode ? "#25282a" : "#f2f5fa"}
/>
</g>
<defs>
<clipPath id="clip0_739_15">
<rect
x="1.96216"
y="3.16992"
width="27.9245"
height="24.4528"
rx="0.754717"
fill={isDarkMode ? "#25282a" : "#f2f5fa"}
/>
</clipPath>
</defs>
</svg>
);
}
================================================
FILE: components/SVG/Flameshot.tsx
================================================
import { useDarkMode } from "@context/darkModeContext";
export default function Flameshot({ className }: { className?: string }) {
const { isDarkMode } = useDarkMode();
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={32}
height={32}
viewBox="0 0 32 32"
fill="none"
className={className}
>
<g clipPath="url(#clip0_1404_14)">
<path
d="M16 0C24.85 0 32 7.15 32 16C32 24.85 24.85 32 16 32C7.15 32 0 24.85 0 16C0 7.15 7.15 0 16 0Z"
fill="currentColor"
/>
<path
d="M17.1508 13.8819C26.4278 12.0912 26.7842 8.04529 26.7842 8.04529C26.7842 8.04529 26.0536 20.5842 19.2738 21.2476C10.6823 22.0882 9.39868 26.1413 9.39868 26.1413C9.39868 26.1413 11.6963 14.9347 17.1508 13.8819Z"
fill={isDarkMode ? "#666" : "#777"}
/>
<path
d="M11.5533 13.3286C19.414 9.99951 18.6941 4.56873 18.6941 4.56873C18.6941 4.56873 23.034 15.6479 16.5279 18.2308C9.25981 21.1161 9.58321 26.0448 9.58321 26.0448C9.58321 26.0448 6.93658 15.2838 11.5533 13.3286V13.3286Z"
fill={isDarkMode ? "#444" : "#999"}
/>
<path
d="M6.92249 16.2738C11.3389 10.4502 10.3455 6.36566 10.3455 6.36566C10.3455 6.36566 16.0039 13.4608 12.1536 17.8553C7.85232 22.7645 9.72553 26.1798 9.72553 26.1798C9.72553 26.1798 4.3894 19.6141 6.92249 16.2738V16.2738Z"
fill={isDarkMode ? "#000" : "#fff"}
/>
</g>
<defs>
<clipPath id="clip0_1404_14">
<rect width={32} height={32} fill="white" />
</clipPath>
</defs>
</svg>
);
}
================================================
FILE: components/SVG/Flux.tsx
================================================
import { useDarkMode } from "@context/darkModeContext";
export default function Flux() {
const { isDarkMode } = useDarkMode();
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 32 32"
fill="currentColor"
className="utilities-svg"
>
<g clipPath="url(#clip0_741_34)">
<path
d="M0 16C0 7.16344 7.16344 0 16 0V0C24.8366 0 32 7.16344 32 16V16C32 24.8366 24.8366 32 16 32V32C7.16344 32 0 24.8366 0 16V16Z"
fill="currentColor"
/>
<circle
cx="22.6455"
cy="17.5662"
r="4.65609"
fill="#9B9B9B"
stroke={isDarkMode ? "black" : "white"}
strokeWidth="1"
/>
<path
d="M30.9635 28.1791C30.9635 34.0935 17.838 31.8614 11.9236 31.8614C6.00918 31.8614 1.3739 28.8871 1.3739 22.9727C-3.07865 20.8706 4.19449 12.6984 10.3075 12.6984C20.0853 12.6984 30.2863 23.5295 30.9635 28.1791Z"
fill="#454545"
/>
</g>
<defs>
<clipPath id="clip0_741_34">
<path
d="M0 16C0 7.16344 7.16344 0 16 0V0C24.8366 0 32 7.16344 32 16V16C32 24.8366 24.8366 32 16 32V32C7.16344 32 0 24.8366 0 16V16Z"
fill="white"
/>
</clipPath>
</defs>
</svg>
);
}
================================================
FILE: components/SVG/Logo.tsx
================================================
import { motion } from "framer-motion";
export default function Logo({ className }: { className: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={512}
height={512}
viewBox="0 0 512 512"
fill="none"
className={className}
>
<motion.path
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 3, ease: "easeInOut" }}
d="M18 0V380.671L260.5 495.5L498 385.055V0M67 17.5H431V126H85V81"
stroke="currentColor"
strokeWidth="35"
/>
<motion.path
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 3, ease: "easeInOut" }}
d="M67.5 327.5L258 416L431.5 341.5V264H84.5V183.5H449"
stroke="currentColor"
strokeWidth="35"
/>
</svg>
);
}
================================================
FILE: components/SVG/MicrosoftToDo.tsx
================================================
export default function MicrosoftToDo() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="41"
height="33"
viewBox="0 0 41 33"
fill="none"
className="utilities-svg"
>
<rect
width="22.3455"
height="11.2534"
rx="1.96723"
transform="matrix(0.707135 0.707078 -0.707135 0.707078 8.14673 8.92383)"
fill="#545454"
/>
<g filter="url(#filter0_d_744_7)">
<rect
width="33.904"
height="11.3527"
rx="1.96723"
transform="matrix(0.707135 -0.707078 0.707135 0.707078 8.07495 24.8127)"
fill="currentColor"
/>
</g>
<defs>
<filter
id="filter0_d_744_7"
x="8.103"
y="0.474446"
width="31.9466"
height="31.9441"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feMorphology
radius="0.786893"
operator="erode"
in="SourceAlpha"
result="effect1_dropShadow_744_7"
/>
<feOffset dy="-0.393446" />
<feGaussianBlur stdDeviation="0.786893" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_744_7"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_744_7"
result="shape"
/>
</filter>
</defs>
</svg>
);
}
================================================
FILE: components/SVG/RainDrop.tsx
================================================
export default function RainDrop() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="35"
viewBox="0 0 32 35"
fill="currentColor"
className="utilities-svg"
>
<path
d="M13.3433 13.6108C17.1634 17.0095 15.8846 24.5064 15.8846 27.8684C10.8766 27.8684 7.307 28.3185 4.09012 26.3952C0.217283 24.0796 0.108804 18.6472 2.664 15.1169C5.21919 11.5867 10.1254 10.7479 13.3433 13.6108Z"
fill="#454545"
/>
<path
d="M18.5482 13.7231C14.7622 17.0326 15.9116 24.5606 15.8844 27.8684C20.8117 27.9137 24.3683 28.2484 27.5489 26.3851C31.3781 24.1418 31.5288 18.7979 29.0434 15.3014C26.5579 11.8049 21.7374 10.9354 18.5482 13.7231Z"
fill="#9F9F9F"
/>
<g filter="url(#filter0_d_745_19)">
<path
d="M25.8466 13.5213C25.8466 20.3855 18.6544 24.6615 15.884 27.8685C12.4211 23.5925 5.92139 20.2167 5.92139 13.5213C5.92139 7.71055 10.3818 3 15.884 3C21.3862 3 25.8466 7.71055 25.8466 13.5213Z"
fill="currentColor"
/>
</g>
<defs>
<filter
id="filter0_d_745_19"
x="0.921387"
y="0"
width="29.9253"
height="34.8687"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="2" />
<feGaussianBlur stdDeviation="2.5" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_745_19"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_745_19"
result="shape"
/>
</filter>
</defs>
</svg>
);
}
================================================
FILE: components/SVG/ShareX.tsx
================================================
import { useDarkMode } from "@context/darkModeContext";
export default function ShareX() {
const { isDarkMode } = useDarkMode();
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
className="utilities-svg"
>
<g clipPath="url(#clip0_736_2)">
<path
d="M25.0435 10.8953C24.9357 6.27203 21.657 2.33881 17.0616 0.753423C15.6008 0.251362 14.0664 -0.00326983 12.5217 3.16972e-05C5.60626 3.16972e-05 5.88007e-08 4.98299 5.88007e-08 11.1305C-0.000141637 12.438 0.255807 13.7328 0.753391 14.9419C2.34226 10.3465 6.272 7.06786 10.8953 6.95655C10.9732 6.95655 11.0518 6.95655 11.1304 6.95655C13.8101 6.95655 16.2692 8.02229 18.1899 9.7976C18.6346 10.2098 19.0472 10.6554 19.424 11.1305C20.9489 13.0456 21.9576 15.4922 22.2031 18.1934C23.9777 16.2692 25.0435 13.8101 25.0435 11.1305C25.0435 11.0519 25.0435 10.9739 25.0435 10.8953Z"
fill="currentColor"
/>
<path
d="M22.2024 17.842C21.9569 15.1408 20.9503 12.6942 19.4233 10.7791C19.0465 10.3053 18.6342 9.86083 18.1899 9.44969C16.2692 7.67438 13.8101 6.60864 11.1304 6.60864C11.0518 6.60864 10.9739 6.60864 10.8953 6.60864C6.272 6.71647 2.33878 9.99508 0.753391 14.5906C0.279533 13.4404 0.0240464 12.212 5.88007e-08 10.9683C5.88007e-08 11.0226 5.88007e-08 11.0761 5.88007e-08 11.1304C-0.000141637 12.4379 0.255807 13.7327 0.753391 14.9419C2.34226 10.3464 6.272 7.06777 10.8953 6.95647C10.9732 6.95647 11.0518 6.95647 11.1304 6.95647C13.8101 6.95647 16.2692 8.02221 18.1899 9.79751C18.6346 10.2097 19.0472 10.6553 19.424 11.1304C20.9489 13.0455 21.9576 15.4921 22.2031 18.1933C23.9777 16.2692 25.0435 13.81 25.0435 11.1304C25.0435 11.0761 25.0435 11.0226 25.0435 10.9683C24.9934 13.5763 23.9367 15.9659 22.2024 17.842Z"
fill={isDarkMode ? "#25282a" : "#f2f5fa"}
/>
<path
d="M6.95655 21.1047C6.95655 21.0268 6.95655 20.9482 6.95655 20.8696C6.95655 18.1899 8.02229 15.7308 9.7976 13.8101C11.8379 11.6021 14.8153 10.1044 18.1899 9.79759C16.2692 8.02228 13.8101 6.95654 11.1305 6.95654C11.0519 6.95654 10.9739 6.95654 10.8953 6.95654C6.27203 7.06437 2.33881 10.343 0.753423 14.9385C0.251362 16.3993 -0.00326983 17.9336 3.16972e-05 19.4783C3.16972e-05 26.3938 4.98299 32 11.1305 32C12.438 32.0002 13.7328 31.7442 14.9419 31.2466C10.3465 29.6578 7.06786 25.728 6.95655 21.1047Z"
fill="currentColor"
/>
<path
d="M6.95647 21.1047C6.95647 21.0268 6.95647 20.9482 6.95647 20.8696C6.95647 18.1899 8.02221 15.7308 9.79751 13.8101C11.8379 11.6021 14.8153 10.1044 18.1899 9.79759C16.2692 8.02228 13.81 6.95654 11.1304 6.95654C11.0761 6.95654 11.0226 6.95654 10.9683 6.95654C13.5763 7.00454 15.9659 8.06124 17.842 9.7948C14.4674 10.1016 11.49 11.5993 9.44969 13.8073C7.67438 15.7308 6.60864 18.1899 6.60864 20.8696C6.60864 20.9482 6.60864 21.0261 6.60864 21.1047C6.71647 25.728 9.99508 29.6612 14.5906 31.2466C13.4404 31.7205 12.212 31.976 10.9683 32C11.0226 32 11.0761 32 11.1304 32C12.4379 32.0002 13.7327 31.7442 14.9419 31.2466C10.3464 29.6578 7.06777 25.728 6.95647 21.1047Z"
fill={isDarkMode ? "#25282a" : "#f2f5fa"}
/>
<path
d="M31.2466 17.0581C29.6578 21.6535 25.728 24.9321 21.1047 25.0434C21.0268 25.0434 20.9482 25.0434 20.8696 25.0434C18.1899 25.0434 15.7308 23.9777 13.8101 22.2024C11.6021 20.1621 10.1044 17.1847 9.79759 13.8101C8.02228 15.7308 6.95654 18.1899 6.95654 20.8695C6.95654 20.9481 6.95654 21.0261 6.95654 21.1047C7.06437 25.728 10.343 29.6612 14.9385 31.2466C16.3993 31.7486 17.9336 32.0033 19.4783 32C26.3938 32 32 27.017 32 20.8695C32.0002 19.562 31.7442 18.2672 31.2466 17.0581Z"
fill="currentColor"
/>
<path
d="M9.79759 14.1579C10.1044 17.5325 11.6021 20.5099 13.8101 22.5502C15.7308 24.3255 18.1899 25.3913 20.8696 25.3913C20.9482 25.3913 21.0261 25.3913 21.1047 25.3913C25.728 25.2834 29.6612 22.0048 31.2466 17.4094C31.7205 18.5595 31.976 19.7879 32 21.0316C32 20.9774 32 20.9238 32 20.8695C32.0002 19.562 31.7442 18.2672 31.2466 17.0581C29.6578 21.6535 25.728 24.9321 21.1047 25.0434C21.0268 25.0434 20.9482 25.0434 20.8696 25.0434C18.1899 25.0434 15.7308 23.9777 13.8101 22.2024C11.6021 20.1621 10.1044 17.1847 9.79759 13.8101C8.02228 15.7308 6.95654 18.1899 6.95654 20.8695C6.95654 20.9238 6.95654 20.9774 6.95654 21.0316C7.00663 18.4236 8.06333 16.0341 9.79759 14.1579Z"
fill={isDarkMode ? "#25282a" : "#f2f5fa"}
/>
<path
d="M20.8695 5.88007e-08C19.562 -0.000141637 18.2672 0.255807 17.0581 0.753391C21.6535 2.34226 24.9321 6.272 25.0434 10.8953C25.0434 10.9732 25.0434 11.0518 25.0434 11.1304C25.0434 13.8101 23.9777 16.2692 22.2024 18.1899C20.1621 20.3979 17.1847 21.8957 13.8101 22.2024C15.7308 23.9777 18.1899 25.0435 20.8695 25.0435C20.9481 25.0435 21.0261 25.0435 21.1047 25.0435C25.728 24.9357 29.6612 21.657 31.2466 17.0616C31.7486 15.6008 32.0033 14.0664 32 12.5217C32 5.60626 27.017 5.88007e-08 20.8695 5.88007e-08Z"
fill="currentColor"
/>
<path
d="M14.1579 22.2024C17.5325 21.8957 20.5099 20.3979 22.5502 18.1899C24.3255 16.2692 25.3913 13.8101 25.3913 11.1304C25.3913 11.0518 25.3913 10.9739 25.3913 10.8953C25.2834 6.272 22.0048 2.33878 17.4094 0.753391C18.5595 0.279533 19.7879 0.0240464 21.0316 5.88007e-08C20.9774 5.88007e-08 20.9238 5.88007e-08 20.8695 5.88007e-08C19.562 -0.000141637 18.2672 0.255807 17.0581 0.753391C21.6535 2.34226 24.9321 6.272 25.0434 10.8953C25.0434 10.9732 25.0434 11.0518 25.0434 11.1304C25.0434 13.8101 23.9777 16.2692 22.2024 18.1899C20.1621 20.3979 17.1847 21.8957 13.8101 22.2024C15.7308 23.9777 18.1899 25.0435 20.8695 25.0435C20.9238 25.0435 20.9774 25.0435 21.0316 25.0435C18.4236 24.9934 16.0341 23.9367 14.1579 22.2024Z"
fill={isDarkMode ? "#25282a" : "#f2f5fa"}
/>
</g>
<defs>
<clipPath id="clip0_736_2">
<rect width="32" height="32" fill="currentColor" />
</clipPath>
</defs>
</svg>
);
}
================================================
FILE: components/SVG/UPI.tsx
================================================
import React from "react";
export default function UPI({ className }: { className: string }) {
return (
<svg
className={className}
width="80"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1024 466"
>
<path
fill="#3d3d3c"
d="M98.1 340.7h6.3l-5.9 24.5c-.9 3.6-.7 6.4.5 8.2 1.2 1.8 3.4 2.7 6.7 2.7 3.2 0 5.9-.9 8-2.7 2.1-1.8 3.5-4.6 4.4-8.2l5.9-24.5h6.4l-6 25.1c-1.3 5.4-3.6 9.5-7 12.2-3.3 2.7-7.7 4.1-13.1 4.1-5.4 0-9.1-1.3-11.1-4s-2.4-6.8-1.1-12.2l6-25.2zm31.4 40.3 10-41.9 19 24.6c.5.7 1 1.4 1.5 2.2.5.8 1 1.7 1.6 2.7l6.7-27.9h5.9l-10 41.8-19.4-25.1-1.5-2.1c-.5-.8-.9-1.5-1.2-2.4l-6.7 28h-5.9zm44.2 0 9.6-40.3h6.4l-9.6 40.3h-6.4zm15.5 0 9.6-40.3h21.9l-1.3 5.6h-15.5l-2.4 10H217l-1.4 5.7h-15.5l-4.5 18.9h-6.4zm29 0 9.6-40.3h6.4l-9.6 40.3h-6.4zm15.5 0 9.6-40.3h21.9l-1.3 5.6h-15.5l-2.4 10.1h15.5l-1.4 5.7h-15.5l-3.1 13H257l-1.4 5.9h-21.9zm29.3 0 9.6-40.3h8.6c5.6 0 9.5.3 11.6.9 2.1.6 3.9 1.5 5.3 2.9 1.8 1.8 3 4.1 3.5 6.8.5 2.8.3 6-.5 9.5-.9 3.6-2.2 6.7-4 9.5-1.8 2.8-4.1 5-6.8 6.8-2 1.4-4.2 2.3-6.6 2.9-2.3.6-5.8.9-10.4.9H263zm7.8-6h5.4c2.9 0 5.2-.2 6.8-.6 1.6-.4 3-1.1 4.3-2 1.8-1.3 3.3-2.9 4.5-4.9 1.2-1.9 2.1-4.2 2.7-6.8.6-2.6.8-4.8.5-6.7-.3-1.9-1-3.6-2.2-4.9-.9-1-2-1.6-3.5-2-1.5-.4-3.8-.6-7.1-.6h-4.6l-6.8 28.5zm59.7-12.1-4.3 18.1h-6l9.6-40.3h9.7c2.9 0 4.9.2 6.2.5 1.3.3 2.3.8 3.1 1.6 1 .9 1.7 2.2 2 3.8.3 1.6.2 3.3-.2 5.2-.5 1.9-1.2 3.7-2.3 5.3-1.1 1.6-2.4 2.9-3.8 3.8-1.2.7-2.5 1.3-3.9 1.6-1.4.3-3.6.5-6.4.5h-3.7zm1.7-5.4h1.6c3.5 0 6-.4 7.4-1.2 1.4-.8 2.3-2.2 2.8-4.2.5-2.1.2-3.7-.8-4.5-1.1-.9-3.3-1.3-6.6-1.3H335l-2.8 11.2zm40.1 23.5-2-10.4h-15.6l-7 10.4H341l29-41.9 9 41.9h-6.7zm-13.8-15.9h10.9l-1.8-9.2c-.1-.6-.2-1.3-.2-2-.1-.8-.1-1.6-.1-2.5-.4.9-.8 1.7-1.3 2.5-.4.8-.8 1.5-1.2 2.1l-6.3 9.1zm29.7 15.9 4.4-18.4-8-21.8h6.7l5 13.7c.1.4.2.8.4 1.4.2.6.3 1.2.5 1.8l1.2-1.8c.4-.6.8-1.1 1.2-1.6l11.7-13.5h6.4L399 362.5l-4.4 18.4h-6.4zm60.9-19.9c0-.3.1-1.2.3-2.6.1-1.2.2-2.1.3-2.9-.4.9-.8 1.8-1.3 2.8-.5.9-1.1 1.9-1.8 2.8l-15.4 21.5-5-21.9c-.2-.9-.4-1.8-.5-2.6-.1-.8-.2-1.7-.2-2.5-.2.8-.5 1.7-.8 2.7-.3.9-.7 1.9-1.2 2.9l-9 19.8h-5.9l19.3-42 5.5 25.4c.1.4.2 1.1.3 2 .1.9.3 2.1.5 3.5.7-1.2 1.6-2.6 2.8-4.4.3-.5.6-.8.7-1.1l17.4-25.4-.6 42h-5.9l.5-20zm10.6 19.9 9.6-40.3h21.9l-1.3 5.6h-15.5l-2.4 10.1h15.5l-1.4 5.7h-15.5l-3.1 13H483l-1.4 5.9h-21.9zm29.2 0 10-41.9 19 24.6c.5.7 1 1.4 1.5 2.2.5.8 1 1.7 1.6 2.7l6.7-27.9h5.9l-10 41.8-19.4-25.1-1.5-2.1c-.5-.8-.9-1.5-1.2-2.4l-6.7 28h-5.9zm65.1-34.8-8.3 34.7h-6.4l8.3-34.7h-10.4l1.3-5.6h27.2l-1.3 5.6H554zm6.7 26.7 5.7-2.4c.1 1.8.6 3.2 1.7 4.1 1.1.9 2.6 1.4 4.6 1.4 1.9 0 3.5-.5 4.9-1.6 1.4-1.1 2.3-2.5 2.7-4.3.6-2.4-.8-4.5-4.2-6.3-.5-.3-.8-.5-1.1-.6-3.8-2.2-6.2-4.1-7.2-5.9-1-1.8-1.2-3.9-.6-6.4.8-3.3 2.5-5.9 5.2-8 2.7-2 5.7-3.1 9.3-3.1 2.9 0 5.2.6 6.9 1.7 1.7 1.1 2.6 2.8 2.9 4.9l-5.6 2.6c-.5-1.3-1.1-2.2-1.9-2.8-.8-.6-1.8-.9-3-.9-1.7 0-3.2.5-4.4 1.4-1.2.9-2 2.1-2.4 3.7-.6 2.4 1.1 4.7 5 6.8.3.2.5.3.7.4 3.4 1.8 5.7 3.6 6.7 5.4 1 1.8 1.2 3.9.6 6.6-.9 3.8-2.8 6.8-5.7 9.1-2.9 2.2-6.3 3.4-10.3 3.4-3.3 0-5.9-.8-7.7-2.4-2-1.6-2.9-3.9-2.8-6.8zm47.1 8.1 9.6-40.3h6.4l-9.6 40.3h-6.4zm15.6 0 10-41.9 19 24.6c.5.7 1 1.4 1.5 2.2.5.8 1 1.7 1.6 2.7l6.7-27.9h5.9l-10 41.8-19.4-25.1-1.5-2.1c-.5-.8-.9-1.5-1.2-2.4l-6.7 28h-5.9zm65.1-34.8-8.3 34.7h-6.4l8.3-34.7h-10.4l1.3-5.6h27.2l-1.3 5.6h-10.4zm6.9 34.8 9.6-40.3h22l-1.3 5.6h-15.5l-2.4 10.1h15.5l-1.4 5.7h-15.5l-3.1 13h15.5l-1.4 5.9h-22zm39.5-18.1-4.3 18h-6l9.6-40.3h8.9c2.6 0 4.6.2 5.9.5 1.4.3 2.5.9 3.3 1.7 1 1 1.6 2.2 1.9 3.8.3 1.5.2 3.2-.2 5.1-.8 3.2-2.1 5.8-4.1 7.6-2 1.8-4.5 2.9-7.5 3.3l9.1 18.3h-7.2l-8.7-18h-.7zm1.6-5.1h1.2c3.4 0 5.7-.4 7-1.2 1.3-.8 2.2-2.2 2.7-4.3.5-2.2.3-3.8-.7-4.7-1-.9-3.1-1.4-6.3-1.4h-1.2l-2.7 11.6zm18.9 23.2 9.6-40.3h21.9l-1.3 5.6h-15.5l-2.4 10h15.5l-1.4 5.7h-15.5l-4.5 18.9h-6.4zm52.8 0-2-10.4h-15.6l-7 10.4h-6.7l29-41.9 9 41.9h-6.7zm-13.9-15.9h10.9l-1.8-9.2c-.1-.6-.2-1.3-.2-2-.1-.8-.1-1.6-.1-2.5-.4.9-.8 1.7-1.3 2.5-.4.8-.8 1.5-1.2 2.1l-6.3 9.1zm62.2-14.6c-1.4-1.6-3.1-2.8-4.9-3.5-1.8-.8-3.8-1.2-6.1-1.2-4.3 0-8.1 1.4-11.5 4.2-3.4 2.8-5.6 6.5-6.7 11-1 4.3-.6 7.9 1.4 10.8 1.9 2.8 4.9 4.2 8.9 4.2 2.3 0 4.6-.4 6.9-1.3 2.3-.8 4.6-2.1 7-3.8l-1.8 7.4c-2 1.3-4.1 2.2-6.3 2.8-2.2.6-4.4.9-6.8.9-3 0-5.7-.5-8-1.5s-4.2-2.5-5.7-4.5c-1.5-1.9-2.4-4.2-2.8-6.8-.4-2.6-.3-5.4.5-8.4.7-3 1.9-5.7 3.5-8.3 1.6-2.6 3.7-4.9 6.1-6.8 2.4-2 5-3.5 7.8-4.5s5.6-1.5 8.5-1.5c2.3 0 4.4.3 6.4 1 1.9.7 3.7 1.7 5.3 3.1l-1.7 6.7zm.6 30.5 9.6-40.3h21.9l-1.3 5.6h-15.5l-2.4 10.1h15.5l-1.4 5.7H868l-3.1 13h15.5L879 381h-21.9z"
/>
<path
fill="#70706e"
d="M740.7 305.6h-43.9l61-220.3h43.9l-61 220.3zM717.9 92.2c-3-4.2-7.7-6.3-14.1-6.3H462.6l-11.9 43.2h219.4l-12.8 46.1H481.8v-.1h-43.9l-36.4 131.5h43.9l24.4-88.2h197.3c6.2 0 12-2.1 17.4-6.3 5.4-4.2 9-9.4 10.7-15.6l24.4-88.2c1.9-6.6 1.3-11.9-1.7-16.1zm-342 199.6c-2.4 8.7-10.4 14.8-19.4 14.8H130.2c-6.2 0-10.8-2.1-13.8-6.3-3-4.2-3.7-9.4-1.9-15.6l55.2-198.8h43.9l-49.3 177.6h175.6l49.3-177.6h43.9l-57.2 205.9z"
/>
<path fill="#098041" d="M877.5 85.7 933 196.1 816.3 306.5z" />
<path fill="#e97626" d="M838.5 85.7 894 196.1 777.2 306.5z" />
</svg>
);
}
================================================
FILE: components/SVG/Zip7.tsx
================================================
export default function Zip7() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
className="utilities-svg"
>
<g clipPath="url(#clip0_1332_2)">
<path d="M32 4H0V29H32V4Z" fill="none" />
<path
d="M20.3636 13.5588V15.7647H24.2182L20.3636 21.4265V23.8529H26.9091V21.6471H23.0545L26.9091 15.9853V13.5588H20.3636ZM8 11.3529V13.5588H13.4545L10.1818 16.8676V21.6471H13.0909V16.8676L16 13.9265V11.3529M5.09091 9.14706H18.1818V23.8529H5.09091V9.14706ZM0 4V29H32V4H0ZM2.18182 6.20588H29.8182V26.7941H2.18182"
fill="currentColor"
/>
</g>
<defs>
<clipPath id="clip0_1332_2">
<rect
width="32"
height="25"
fill="white"
transform="translate(0 4)"
/>
</clipPath>
</defs>
</svg>
);
}
================================================
FILE: components/SVG/index.tsx
================================================
import Ditto from "./Ditto";
import Flux from "./Flux";
import MicrosoftToDo from "./MicrosoftToDo";
import RainDrop from "./RainDrop";
import ShareX from "./ShareX";
import Zip7 from "./Zip7";
import Flameshot from "./Flameshot";
const SVG = {
Ditto,
Flux,
MicrosoftToDo,
RainDrop,
ShareX,
Zip7,
Flameshot,
};
export default SVG;
================================================
FILE: components/ScrollProgressBar.tsx
================================================
import { useCallback, useEffect, useState } from "react";
export default function ScrollProgressBar() {
const [scroll, setScroll] = useState("0");
const progressBarHandler = useCallback(() => {
const totalScroll = document.documentElement.scrollTop;
const windowHeight =
document.documentElement.scrollHeight -
document.documentElement.clientHeight;
const scroll = `${totalScroll / windowHeight}`;
setScroll(scroll);
}, []);
useEffect(() => {
window.addEventListener("scroll", progressBarHandler);
return () => window.removeEventListener("scroll", progressBarHandler);
}, [progressBarHandler]);
return (
<div
className="!fixed left-0 w-full h-1 bg-black dark:bg-white origin-top-left transform duration-300 top-[57px]"
style={{
transform: `scale(${scroll},1)`,
zIndex: 100,
}}
/>
);
}
================================================
FILE: components/ScrollToTopButton.tsx
================================================
import { IoIosArrowUp } from "react-icons/io";
import { useEffect, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import useScrollPercentage from "@hooks/useScrollPercentage";
export default function ScrollToTopButton() {
const [showButton, setShowButton] = useState(false);
const scrollPercentage = useScrollPercentage();
useEffect(() => {
setShowButton(scrollPercentage > 10 && scrollPercentage < 95);
}, [scrollPercentage]);
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
return (
<AnimatePresence>
{showButton && (
<motion.button
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 8 }}
transition={{ duration: 0.2 }}
onClick={scrollToTop}
aria-label="Scroll to top"
className="fixed bottom-20 right-6 z-40 w-9 h-9 flex items-center justify-center bg-gray-900 dark:bg-white text-white dark:text-gray-900 hover:opacity-80 transition-opacity print:hidden"
>
<IoIosArrowUp className="w-4 h-4" />
</motion.button>
)}
</AnimatePresence>
);
}
================================================
FILE: components/ShareOnSocialMedia.tsx
================================================
import "react-toastify/dist/ReactToastify.css";
import {
FacebookShareButton,
LinkedinShareButton,
TwitterShareButton,
WhatsappShareButton,
} from "react-share";
import { FiCopy, FiLinkedin } from "react-icons/fi";
import { GrFacebookOption, GrTwitter } from "react-icons/gr";
import { BsThreeDots } from "react-icons/bs";
import { FaWhatsapp } from "react-icons/fa";
import { toast } from "react-toastify";
import useShare from "../hooks/useShare";
type Props = {
className?: string;
title: string;
url: string;
summary: string;
cover_image: string;
children?: React.ReactNode;
};
export default function ShareOnSocialMedia({
title,
url,
summary,
cover_image,
children,
}: Props) {
const { isShareSupported } = useShare();
async function handleShare() {
const blob = await fetch(cover_image).then((res) => res.blob());
const file = new File([blob], "image.png", { type: "image/png" });
if (window.navigator.share) {
window.navigator
.share({ title, text: summary, url, files: [file] })
.catch(console.error);
}
}
function copyTextToClipboard(text: string) {
if (!navigator.clipboard) {
toast.error("Clipboard not supported on this device.");
return;
}
navigator.clipboard.writeText(text).then(
() => toast.success("Link copied!"),
() => toast.error("Failed to copy link."),
);
}
const btnClass =
"flex items-center gap-1.5 font-mono text-[10px] tracking-[0.3em] uppercase text-gray-500 dark:text-gray-500 hover:text-gray-900 dark:hover:text-white transition-colors px-3 py-2 border-r border-gray-200 dark:border-neutral-700 last:border-r-0";
return (
<div className="not-prose flex flex-wrap border border-gray-200 dark:border-neutral-700">
<FacebookShareButton quote={title} url={url} className="contents">
<span className={btnClass}>
<GrFacebookOption className="w-3.5 h-3.5" />
Facebook
</span>
</FacebookShareButton>
<TwitterShareButton
title={title}
url={url}
related={["@j471n_"]}
className="contents"
>
<span className={btnClass}>
<GrTwitter className="w-3.5 h-3.5" />
Twitter
</span>
</TwitterShareButton>
<LinkedinShareButton
title={title}
summary={summary}
url={url}
source={url}
className="contents"
>
<span className={btnClass}>
<FiLinkedin className="w-3.5 h-3.5" />
LinkedIn
</span>
</LinkedinShareButton>
<WhatsappShareButton title={title} url={url} className="contents">
<span className={btnClass}>
<FaWhatsapp className="w-3.5 h-3.5" />
WhatsApp
</span>
</WhatsappShareButton>
<button onClick={() => copyTextToClipboard(url)} className={btnClass}>
<FiCopy className="w-3.5 h-3.5" />
Copy link
</button>
{isShareSupported && (
<button onClick={handleShare} className={btnClass}>
<BsThreeDots className="w-3.5 h-3.5" />
More
</button>
)}
{children}
</div>
);
}
================================================
FILE: components/SnippetCard.tsx
================================================
import { motion } from "framer-motion";
import { ISnippet } from "@lib/interface/sanity";
import Image from "next/image";
import Link from "next/link";
const itemVariants = {
hidden: { opacity: 0, y: 12 },
visible: { opacity: 1, y: 0, transition: { duration: 0.35 } },
};
export default function SnippetCard({ snippet }: { snippet: ISnippet }) {
return (
<motion.div variants={itemVariants}>
<Link
href={"/snippets/" + snippet.slug.current}
className="group flex flex-col gap-3 p-5 bg-white dark:bg-darkPrimary hover:bg-gray-50 dark:hover:bg-darkSecondary transition-colors h-full"
>
{/* Language icon */}
<div className="w-9 h-9 flex items-center justify-center border border-gray-200 dark:border-neutral-700 group-hover:border-gray-400 dark:group-hover:border-gray-600 transition-colors flex-shrink-0">
<Image
src={snippet.language.image.asset.url}
alt={snippet.language.name}
width={22}
height={22}
className="object-contain"
/>
</div>
{/* Language label */}
<span className="font-mono text-[10px] tracking-[0.4em] uppercase text-gray-400 dark:text-gray-500">
{snippet.language.name}
</span>
{/* Title */}
<h2 className="text-sm font-semibold text-gray-900 dark:text-white leading-snug group-hover:underline underline-offset-2">
{snippet.title}
</h2>
{/* Excerpt */}
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed line-clamp-2">
{snippet.excerpt}
</p>
</Link>
</motion.div>
);
}
================================================
FILE: components/SnowfallCanvas.tsx
================================================
// @ts-nocheck
import React, { useEffect, useRef } from "react";
import { useDarkMode } from "@context/darkModeContext";
import useWindowSize from "@hooks/useWindowSize";
const SnowfallCanvas: React.FC = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const { width } = useWindowSize();
// useEffect(() => {
// if (canvasRef.current) {
// const c = canvasRef.current;
// const $ = c.getContext("2d");
// let w = window.innerWidth;
// let h = window.innerHeight;
// const Snowy = () => {
// let snow;
// const arr = [];
// const num =
// width > 1000 ? 1000 : width < 1000 && width > 500 ? 500 : 100,
// tsc = 1,
// sp = 1;
// const sc = 1.3,
// mv = 20,
// min = 1;
// const Flake = function () {
// this.draw = function () {
// this.g = $.createRadialGradient(
// this.x,
// this.y,
// 0,
// this.x,
// this.y,
// this.sz
// );
// this.g.addColorStop(0, "rgba(255,255,255,1)");
// this.g.addColorStop(1, "rgba(255,255,255,0)");
// $.moveTo(this.x, this.y);
// $.fillStyle = this.g;
// $.beginPath();
// $.arc(this.x, this.y, this.sz, 0, Math.PI * 2, true);
// $.fill();
// };
// };
// for (let i = 0; i < num; ++i) {
// snow = new Flake();
// snow.y = Math.random() * (h + 50);
// snow.x = Math.random() * w;
// snow.t = Math.random() * (Math.PI * 2);
// snow.sz = (100 / (10 + Math.random() * 100)) * sc;
// snow.sp = Math.pow(snow.sz * 0.8, 2) * 0.15 * sp;
// snow.sp = snow.sp < min ? min : snow.sp;
// arr.push(snow);
// }
// const go = () => {
// window.requestAnimationFrame(go);
// $.clearRect(0, 0, w, h);
// // $.fillStyle = "rgba(0,0,0, 1)"; // - add background color
// $.fillRect(0, 0, w, h);
// $.fill();
// for (let i = 0; i < arr.length; ++i) {
// const f = arr[i];
// f.t += 0.05;
// f.t = f.t >= Math.PI * 2 ? 0 : f.t;
// f.y += f.sp;
// f.x += Math.sin(f.t * tsc) * (f.sz * 0.3);
// if (f.y > h + 50) f.y = -10 - Math.random() * mv;
// if (f.x > w + mv) f.x = -mv;
// if (f.x < -mv) f.x = w + mv;
// f.draw();
// }
// };
// go();
// };
// window.addEventListener(
// "resize",
// () => {
// if (c) {
// w = window.innerWidth;
// h = window.innerHeight;
// c.width = w;
// c.height = h;
// }
// },
// false
// );
// Snowy();
// }
// }, []);
useEffect(() => {
let animationFrameId: number;
let ctx: CanvasRenderingContext2D | null;
const Snowy = () => {
if (canvasRef.current) {
ctx = canvasRef.current.getContext("2d");
if (!ctx) return;
const w = window.innerWidth;
const h = window.innerHeight;
canvasRef.current.width = w;
canvasRef.current.height = h;
let snow;
const arr: any[] = [];
const num =
width > 1000 ? 1000 : width < 1000 && width > 500 ? 500 : 100;
const tsc = 1;
const sp = 1;
const sc = 1.3,
mv = 20,
min = 1;
const Flake = function () {
this.draw = function () {
if (!ctx) return;
this.g = ctx.createRadialGradient(
this.x,
this.y,
0,
this.x,
this.y,
this.sz,
);
this.g.addColorStop(0, "hsla(255,255%,255%,1)");
this.g.addColorStop(1, "hsla(255,255%,255%,0)");
ctx.moveTo(this.x, this.y);
ctx.fillStyle = this.g;
ctx.beginPath();
ctx.arc(this.x, this.y, this.sz, 0, Math.PI * 2, true);
ctx.fill();
};
};
for (let i = 0; i < num; ++i) {
snow = new Flake();
snow.y = Math.random() * (h + 50);
snow.x = Math.random() * w;
snow.t = Math.random() * (Math.PI * 2);
snow.sz = (100 / (10 + Math.random() * 100)) * sc;
snow.sp = Math.pow(snow.sz * 0.8, 2) * 0.15 * sp;
snow.sp = snow.sp < min ? min : snow.sp;
arr.push(snow);
}
const go = () => {
animationFrameId = window.requestAnimationFrame(go);
if (!ctx) return;
ctx.clearRect(0, 0, w, h);
// ctx.fillStyle = "hsla(242, 95%, 3%, 1)";
ctx.fillRect(0, 0, w, h);
ctx.fill();
for (let i = 0; i < arr.length; ++i) {
const f = arr[i];
f.t += 0.05;
f.t = f.t >= Math.PI * 2 ? 0 : f.t;
f.y += f.sp;
f.x += Math.sin(f.t * tsc) * (f.sz * 0.3);
if (f.y > h + 50) f.y = -10 - Math.random() * mv;
if (f.x > w + mv) f.x = -mv;
if (
gitextract_2ou2nl1y/ ├── .eslintrc.json ├── .gitignore ├── .vercelignore ├── LICENSE ├── README.md ├── components/ │ ├── Blog.tsx │ ├── BookCard.tsx │ ├── Contact/ │ │ ├── Contact.tsx │ │ ├── ContactForm.tsx │ │ └── index.tsx │ ├── CreateAnIssue.tsx │ ├── EpigraphCard.tsx │ ├── Footer.tsx │ ├── FramerMotion/ │ │ ├── AnimatedDiv.tsx │ │ ├── AnimatedHeading.tsx │ │ └── AnimatedText.tsx │ ├── GitHubActivityGraph.tsx │ ├── Home/ │ │ ├── BlogsSection.tsx │ │ ├── EpigraphsSection.tsx │ │ ├── HeroSection.tsx │ │ ├── SkillSection.tsx │ │ └── StatsSection.tsx │ ├── Instagram/ │ │ ├── InstagramPost.tsx │ │ ├── InstagramPostLoading.tsx │ │ └── InstagramSection.tsx │ ├── MDXComponents/ │ │ ├── Code.tsx │ │ ├── CodeSandbox.tsx │ │ ├── CodeTitle.tsx │ │ ├── Codepen.tsx │ │ ├── Danger.tsx │ │ ├── EmbedBlog.tsx │ │ ├── Figcaption.tsx │ │ ├── LinkedInEmbed.tsx │ │ ├── NextAndPreviousButton.tsx │ │ ├── Pre.tsx │ │ ├── Step.tsx │ │ ├── Tip.tsx │ │ ├── UrlMetaInfo.tsx │ │ ├── Warning.tsx │ │ ├── YouTube.tsx │ │ └── index.tsx │ ├── MetaData.tsx │ ├── MovieCard.tsx │ ├── Newsletter.tsx │ ├── OgImage.tsx │ ├── PageHeader.tsx │ ├── PageNotFound.tsx │ ├── PageTop.tsx │ ├── Project.tsx │ ├── QRCodeContainer.tsx │ ├── SVG/ │ │ ├── Ditto.tsx │ │ ├── Flameshot.tsx │ │ ├── Flux.tsx │ │ ├── Logo.tsx │ │ ├── MicrosoftToDo.tsx │ │ ├── RainDrop.tsx │ │ ├── ShareX.tsx │ │ ├── UPI.tsx │ │ ├── Zip7.tsx │ │ └── index.tsx │ ├── ScrollProgressBar.tsx │ ├── ScrollToTopButton.tsx │ ├── ShareOnSocialMedia.tsx │ ├── SnippetCard.tsx │ ├── SnowfallCanvas.tsx │ ├── StaticPage.tsx │ ├── Stats/ │ │ ├── Artist.tsx │ │ ├── MonkeyTypeStats.tsx │ │ ├── StatsCard.tsx │ │ └── Track.tsx │ ├── Support.tsx │ ├── TableOfContents.tsx │ └── TopNavbar.tsx ├── content/ │ ├── FramerMotionVariants.ts │ ├── meta.ts │ ├── siteConfig.ts │ ├── skillsData.ts │ ├── socialMedia.ts │ ├── support.ts │ ├── user.ts │ └── utilitiesData.ts ├── context/ │ └── darkModeContext.tsx ├── docs/ │ └── sanity-deploy.md ├── hooks/ │ ├── useBookmarkBlogs.ts │ ├── useDebounce.ts │ ├── useFetchWithSWR.ts │ ├── useScrollPercentage.ts │ ├── useShare.ts │ ├── useWindowLocation.ts │ └── useWindowSize.ts ├── layout/ │ ├── BlogLayout.tsx │ ├── Layout.tsx │ └── SnippetLayout.tsx ├── lib/ │ ├── MDXContent.ts │ ├── devto.ts │ ├── fetcher.ts │ ├── generateRSS.ts │ ├── github.ts │ ├── hardcover.ts │ ├── instaposts.ts │ ├── interface/ │ │ └── sanity.ts │ ├── interface.ts │ ├── sanityClient.ts │ ├── sanityContent.ts │ ├── sitemap.ts │ ├── spotify.ts │ ├── supabase.ts │ ├── tmdb.ts │ ├── toc.ts │ ├── types.ts │ └── windowsAnimation.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages/ │ ├── 404.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── about.tsx │ ├── api/ │ │ ├── books.ts │ │ ├── ga.ts │ │ ├── now-playing.ts │ │ ├── posts/ │ │ │ └── insta.ts │ │ ├── revalidate.ts │ │ ├── stats/ │ │ │ ├── artists.ts │ │ │ ├── devto.ts │ │ │ ├── github-contribution.ts │ │ │ ├── github.ts │ │ │ ├── monkeytype.ts │ │ │ └── tracks.ts │ │ ├── validate/ │ │ │ └── email.ts │ │ └── views/ │ │ ├── [slug].ts │ │ └── index.ts │ ├── blogs/ │ │ ├── [slug].tsx │ │ ├── bookmark.tsx │ │ └── index.tsx │ ├── books.tsx │ ├── certificates.tsx │ ├── epigraphs.tsx │ ├── index.tsx │ ├── privacy.tsx │ ├── projects.tsx │ ├── snippets/ │ │ ├── [slug].tsx │ │ └── index.tsx │ ├── stats.tsx │ ├── tet.json │ └── utilities.tsx ├── postcss.config.js ├── public/ │ ├── manifest.json │ └── robots.txt ├── sanity/ │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── gist.md.ndjson │ ├── package.json │ ├── sanity.cli.ts │ ├── sanity.config.ts │ ├── schemas/ │ │ ├── author.ts │ │ ├── blockContent.ts │ │ ├── category.ts │ │ ├── epigraph.ts │ │ ├── index.ts │ │ ├── language.ts │ │ ├── organization.ts │ │ ├── post.ts │ │ ├── snippet.ts │ │ └── static_page.ts │ ├── static/ │ │ └── .gitkeep │ ├── tailwind.config.js │ └── tsconfig.json ├── sanity.cli.ts ├── styles/ │ └── globals.css ├── tailwind.config.js ├── tsconfig.json ├── utils/ │ ├── date.ts │ ├── functions.ts │ └── utils.ts └── vercel.json
SYMBOL INDEX (315 symbols across 130 files)
FILE: components/Blog.tsx
function Blog (line 17) | function Blog({
FILE: components/BookCard.tsx
constant HARDCOVER_BOOK_URL (line 16) | const HARDCOVER_BOOK_URL = "https://hardcover.app/books/";
constant FALLBACK_COVER (line 17) | const FALLBACK_COVER = "https://imgur.com/5dYYce8.png";
constant STAR_PATH (line 19) | const STAR_PATH =
function Stars (line 22) | function Stars({
function formatFinishedDate (line 47) | function formatFinishedDate(iso: string): string {
function BookCard (line 52) | function BookCard({ book }: { book: HardcoverBook }) {
FILE: components/Contact/Contact.tsx
function Contact (line 13) | function Contact() {
FILE: components/Contact/ContactForm.tsx
function Form (line 22) | function Form() {
FILE: components/CreateAnIssue.tsx
function CreateAnIssue (line 4) | function CreateAnIssue() {
FILE: components/EpigraphCard.tsx
constant SOURCE_LABELS (line 7) | const SOURCE_LABELS: Record<EpigraphSourceType, string> = {
function EpigraphCard (line 36) | function EpigraphCard({
function Attribution (line 149) | function Attribution({ epigraph }: { epigraph: IEpigraph }) {
function Footer (line 184) | function Footer({
FILE: components/Footer.tsx
function Footer (line 13) | function Footer({
function FooterLink (line 207) | function FooterLink({
function NotPlaying (line 234) | function NotPlaying() {
function WhenPlaying (line 250) | function WhenPlaying({ song }: { song: Song }) {
FILE: components/FramerMotion/AnimatedDiv.tsx
function AnimatedDiv (line 4) | function AnimatedDiv({
FILE: components/FramerMotion/AnimatedHeading.tsx
function AnimatedHeading (line 4) | function AnimatedHeading({
FILE: components/FramerMotion/AnimatedText.tsx
function AnimatedText (line 4) | function AnimatedText({
FILE: components/GitHubActivityGraph.tsx
function ChartSectionHeading (line 25) | function ChartSectionHeading({
function GitHubActivityGraph (line 67) | function GitHubActivityGraph() {
function LoadingBarChart (line 200) | function LoadingBarChart() {
function LoadingAreaChart (line 244) | function LoadingAreaChart() {
FILE: components/Home/BlogsSection.tsx
function BlogsSection (line 13) | function BlogsSection({
FILE: components/Home/EpigraphsSection.tsx
function EpigraphsSection (line 13) | function EpigraphsSection({
FILE: components/Home/HeroSection.tsx
function HeroSection (line 22) | function HeroSection() {
FILE: components/Home/SkillSection.tsx
function SkillSection (line 22) | function SkillSection() {
FILE: components/Home/StatsSection.tsx
function StatsSection (line 34) | function StatsSection() {
FILE: components/Instagram/InstagramPost.tsx
function InstagramPost (line 8) | function InstagramPost({
FILE: components/Instagram/InstagramPostLoading.tsx
function InstagramPostLoading (line 3) | function InstagramPostLoading({ count }: { count: number }) {
FILE: components/Instagram/InstagramSection.tsx
function InstagramSection (line 11) | function InstagramSection() {
FILE: components/MDXComponents/Code.tsx
type Props (line 1) | type Props = {
function Code (line 5) | function Code(props: Props) {
FILE: components/MDXComponents/CodeSandbox.tsx
function CodeSandbox (line 1) | function CodeSandbox({
FILE: components/MDXComponents/CodeTitle.tsx
type Props (line 17) | type Props = {
function CodeTitle (line 22) | function CodeTitle({ title, lang }: Props) {
FILE: components/MDXComponents/Codepen.tsx
function Codepen (line 1) | function Codepen({ id }: { id: string }) {
FILE: components/MDXComponents/Danger.tsx
type Props (line 1) | type Props = { title?: string; text: string };
function Danger (line 3) | function Danger({ title, text }: Props) {
FILE: components/MDXComponents/EmbedBlog.tsx
function EmbedBlog (line 4) | function EmbedBlog({
FILE: components/MDXComponents/Figcaption.tsx
type Props (line 1) | type Props = {
function figcaption (line 7) | function figcaption({ src, caption, alt }: Props) {
FILE: components/MDXComponents/LinkedInEmbed.tsx
function LinkedInEmbed (line 1) | function LinkedInEmbed({ id }: { id: string }) {
FILE: components/MDXComponents/NextAndPreviousButton.tsx
type Props (line 4) | type Props = {
function NextAndPreviousButton (line 11) | function NextAndPreviousButton({
function BlogPageButton (line 29) | function BlogPageButton({
FILE: components/MDXComponents/Step.tsx
function Step (line 3) | function Step({
FILE: components/MDXComponents/Tip.tsx
function Tip (line 1) | function Tip({
FILE: components/MDXComponents/UrlMetaInfo.tsx
type MetaData (line 6) | interface MetaData {
type UrlMetaInfoProps (line 12) | interface UrlMetaInfoProps {
function UrlMetaInfo (line 16) | function UrlMetaInfo({ url }: UrlMetaInfoProps) {
FILE: components/MDXComponents/Warning.tsx
type Props (line 1) | type Props = {
function Warning (line 7) | function Warning({ text, title, children }: Props) {
FILE: components/MDXComponents/YouTube.tsx
function YouTube (line 1) | function YouTube({ id }: { id: string }) {
FILE: components/MetaData.tsx
type Props (line 8) | type Props = {
function MetaData (line 20) | function MetaData({
FILE: components/MovieCard.tsx
constant TMDB_IMAGE_PREFIX (line 7) | const TMDB_IMAGE_PREFIX = "https://image.tmdb.org/t/p/w780";
function MovieCard (line 18) | function MovieCard({ movie }: { movie: ITMDBData }) {
function WatchStatus (line 56) | function WatchStatus({ rating }: { rating?: number }) {
FILE: components/Newsletter.tsx
function Newsletter (line 6) | function Newsletter() {
FILE: components/OgImage.tsx
function OgImage (line 2) | function OgImage({ src, alt }: { src: string; alt: string }) {
FILE: components/PageHeader.tsx
type PageHeaderProps (line 4) | interface PageHeaderProps {
function PageHeader (line 19) | function PageHeader({
FILE: components/PageNotFound.tsx
function PageNotFound (line 5) | function PageNotFound() {
FILE: components/PageTop.tsx
function PageTop (line 8) | function PageTop({
FILE: components/Project.tsx
function Project (line 17) | function Project({
FILE: components/QRCodeContainer.tsx
function QRCodeContainer (line 7) | function QRCodeContainer({
FILE: components/SVG/Ditto.tsx
function Ditto (line 3) | function Ditto() {
FILE: components/SVG/Flameshot.tsx
function Flameshot (line 3) | function Flameshot({ className }: { className?: string }) {
FILE: components/SVG/Flux.tsx
function Flux (line 3) | function Flux() {
FILE: components/SVG/Logo.tsx
function Logo (line 3) | function Logo({ className }: { className: string }) {
FILE: components/SVG/MicrosoftToDo.tsx
function MicrosoftToDo (line 1) | function MicrosoftToDo() {
FILE: components/SVG/RainDrop.tsx
function RainDrop (line 1) | function RainDrop() {
FILE: components/SVG/ShareX.tsx
function ShareX (line 3) | function ShareX() {
FILE: components/SVG/UPI.tsx
function UPI (line 3) | function UPI({ className }: { className: string }) {
FILE: components/SVG/Zip7.tsx
function Zip7 (line 1) | function Zip7() {
FILE: components/SVG/index.tsx
constant SVG (line 9) | const SVG = {
FILE: components/ScrollProgressBar.tsx
function ScrollProgressBar (line 3) | function ScrollProgressBar() {
FILE: components/ScrollToTopButton.tsx
function ScrollToTopButton (line 6) | function ScrollToTopButton() {
FILE: components/ShareOnSocialMedia.tsx
type Props (line 17) | type Props = {
function ShareOnSocialMedia (line 26) | function ShareOnSocialMedia({
FILE: components/SnippetCard.tsx
function SnippetCard (line 11) | function SnippetCard({ snippet }: { snippet: ISnippet }) {
FILE: components/StaticPage.tsx
function StaticPage (line 8) | function StaticPage({
FILE: components/Stats/Artist.tsx
type ArtistProps (line 4) | type ArtistProps = {
function Artist (line 12) | function Artist({
FILE: components/Stats/MonkeyTypeStats.tsx
type PersonalBest (line 25) | interface PersonalBest {
type TrendPoint (line 35) | interface TrendPoint {
type MonkeyTypeData (line 44) | interface MonkeyTypeData {
function formatTime (line 70) | function formatTime(seconds: number): string {
function formatDate (line 77) | function formatDate(ts: number): string {
function MonkeyStatsCard (line 101) | function MonkeyStatsCard({
function SectionHeading (line 126) | function SectionHeading({
function ChartSectionHeading (line 173) | function ChartSectionHeading({
function PersonalBestCard (line 216) | function PersonalBestCard({
function LoadingBarChart (line 363) | function LoadingBarChart({
function LoadingAreaChart (line 393) | function LoadingAreaChart() {
function MonkeyTypeStats (line 429) | function MonkeyTypeStats() {
FILE: components/Stats/StatsCard.tsx
function StatsCard (line 12) | function StatsCard({
FILE: components/Stats/Track.tsx
type TrackProps (line 4) | type TrackProps = {
function Track (line 12) | function Track({
FILE: components/Support.tsx
function Support (line 19) | function Support() {
function UPIPaymentForm (line 79) | function UPIPaymentForm({ close }: { close: () => void }) {
FILE: components/TableOfContents.tsx
function TableOfContents (line 11) | function TableOfContents({
FILE: components/TopNavbar.tsx
function TopNavbar (line 18) | function TopNavbar() {
function NavItem (line 139) | function NavItem({ href, text }: { href: string; text: string }) {
function HamBurger (line 164) | function HamBurger({
FILE: content/siteConfig.ts
type SocialIconKey (line 3) | type SocialIconKey =
type StatIconKey (line 13) | type StatIconKey = "code" | "users" | "coffee" | "award";
FILE: content/user.ts
type AuthorInfo (line 3) | type AuthorInfo = {
function getAuthorData (line 11) | function getAuthorData(org: string | null = null): AuthorInfo {
FILE: context/darkModeContext.tsx
type DarkModeContextType (line 3) | interface DarkModeContextType {
function DarkModeProvider (line 10) | function DarkModeProvider({ children }: { children: React.ReactNode }) {
FILE: hooks/useBookmarkBlogs.ts
function getValue (line 19) | function getValue() {
function addToBookmark (line 28) | function addToBookmark(blogToBookmark: FrontMatter) {
function removeFromBookmark (line 36) | function removeFromBookmark(blogToRemove: string) {
function isAlreadyBookmarked (line 43) | function isAlreadyBookmarked(searchBySlug: string) {
FILE: hooks/useDebounce.ts
function useDebounce (line 3) | function useDebounce<T>(value: T, delay = 300): T {
FILE: hooks/useFetchWithSWR.ts
function useFetchWithSWR (line 7) | function useFetchWithSWR(url: string) {
FILE: hooks/useScrollPercentage.ts
function useScrollPercentage (line 2) | function useScrollPercentage() {
FILE: hooks/useShare.ts
function useShare (line 2) | function useShare() {
FILE: hooks/useWindowLocation.ts
type URL (line 4) | type URL = string;
function useWindowLocation (line 6) | function useWindowLocation() {
FILE: hooks/useWindowSize.ts
function useWindowSize (line 2) | function useWindowSize() {
FILE: layout/BlogLayout.tsx
function BlogLayout (line 10) | function BlogLayout({
FILE: layout/Layout.tsx
function Layout (line 10) | function Layout({ children }: { children: React.ReactNode }) {
FILE: layout/SnippetLayout.tsx
function SnippetLayout (line 7) | function SnippetLayout({
FILE: lib/MDXContent.ts
method onVisitLine (line 22) | onVisitLine(node: any) {
method onVisitHighlightedLine (line 27) | onVisitHighlightedLine(node: any) {
method onVisitHighlightedWord (line 30) | onVisitHighlightedWord(node: any) {
class MDXContent (line 37) | class MDXContent {
method constructor (line 39) | constructor(folderName: string) {
method getSlugs (line 43) | getSlugs() {
method getFrontMatter (line 53) | getFrontMatter(slug: string): FrontMatter | null {
method getPostFromSlug (line 73) | async getPostFromSlug(slug: string, force: boolean = false) {
method getAllPosts (line 95) | getAllPosts(length?: number | undefined) {
method getTableOfContents (line 110) | getTableOfContents(markdown: string) {
FILE: lib/devto.ts
constant PER_PAGE (line 1) | const PER_PAGE: number = 1000;
constant DEV_API (line 2) | const DEV_API = process.env.NEXT_PUBLIC_BLOGS_API;
FILE: lib/fetcher.ts
function fetcher (line 4) | async function fetcher(url: string) {
FILE: lib/generateRSS.ts
function getRSS (line 5) | async function getRSS() {
FILE: lib/github.ts
function fetchGithub (line 19) | async function fetchGithub(): Promise<IGitHubProfileResponse> {
function getGithubStarsAndForks (line 42) | async function getGithubStarsAndForks() {
function getGithubContribution (line 79) | async function getGithubContribution() {
function calculateMostProductiveDayOfWeek (line 140) | function calculateMostProductiveDayOfWeek(
FILE: lib/hardcover.ts
constant HARDCOVER_GRAPHQL_URL (line 3) | const HARDCOVER_GRAPHQL_URL = "https://api.hardcover.app/v1/graphql";
function hardcoverQuery (line 7) | async function hardcoverQuery<T>(
function getMyUserId (line 35) | async function getMyUserId(): Promise<number> {
type RawUserBook (line 46) | type RawUserBook = {
function getMyBooks (line 66) | async function getMyBooks(): Promise<HardcoverBook[]> {
type RawGoal (line 118) | type RawGoal = {
type RawProfile (line 127) | type RawProfile = {
function getMyProfile (line 136) | async function getMyProfile(): Promise<HardcoverProfile> {
FILE: lib/instaposts.ts
function generateNewAccessTokenInstagram (line 6) | async function generateNewAccessTokenInstagram(
function getInstagramPosts (line 36) | async function getInstagramPosts(
FILE: lib/interface.ts
type IExternalUrls (line 1) | interface IExternalUrls {
type IFollowers (line 4) | interface IFollowers {
type IImagesEntity (line 8) | interface IImagesEntity {
type IArtistsAPIResponse (line 14) | interface IArtistsAPIResponse {
type ISpotifyArtist (line 27) | interface ISpotifyArtist {
type ISpotifyAlbum (line 36) | interface ISpotifyAlbum {
type ITracksAPIResponse (line 52) | interface ITracksAPIResponse {
type IGitHubProfileResponse (line 71) | interface IGitHubProfileResponse {
type IGitHubOwner (line 106) | interface IGitHubOwner {
type IGitHubLicense (line 127) | interface IGitHubLicense {
type IGitHubRepositoriesAPIResponse (line 135) | interface IGitHubRepositoriesAPIResponse {
type IContributionDay (line 217) | interface IContributionDay {
type IWeek (line 223) | interface IWeek {
type IUserContributionDetails (line 227) | interface IUserContributionDetails {
type IContributionCalendar (line 232) | interface IContributionCalendar {
type IContributionCountByDay (line 236) | interface IContributionCountByDay {
type IEmailValidation (line 240) | interface IEmailValidation {
type ILinkedinResponse (line 256) | interface ILinkedinResponse {
type ILinkedInExperience (line 299) | interface ILinkedInExperience {
type ILinkedInStartsAt (line 312) | interface ILinkedInStartsAt {
type ILinkedInEndsAt (line 318) | interface ILinkedInEndsAt {
type ILinkedInEducation (line 324) | interface ILinkedInEducation {
type ILinkedInVolunteerWork (line 337) | interface ILinkedInVolunteerWork {
type ILinkedInCertification (line 348) | interface ILinkedInCertification {
type ITMDBData (line 358) | interface ITMDBData {
type MediaType (line 381) | enum MediaType {
type DetailedInstagramPost (line 386) | interface DetailedInstagramPost {
type Cursors (line 396) | interface Cursors {
type Paging (line 400) | interface Paging {
type InstagramData (line 406) | interface InstagramData {
FILE: lib/interface/sanity.ts
type ISanityImage (line 5) | interface ISanityImage {
type BlogPost (line 12) | interface BlogPost extends SanityDocument {
type ISnippet (line 37) | interface ISnippet extends SanityDocument {
type IStaticPage (line 57) | interface IStaticPage extends SanityDocument {
type EpigraphSourceType (line 70) | type EpigraphSourceType =
type IEpigraph (line 79) | interface IEpigraph extends SanityDocument {
FILE: lib/sanityContent.ts
method onVisitLine (line 22) | onVisitLine(node: any) {
method onVisitHighlightedLine (line 27) | onVisitHighlightedLine(node: any) {
method onVisitHighlightedWord (line 30) | onVisitHighlightedWord(node: any) {
function getPostCount (line 37) | async function getPostCount(): Promise<number> {
function getAllPostsMeta (line 42) | async function getAllPostsMeta(limit?: number): Promise<BlogPost[]> {
function getAllSnippetsMeta (line 66) | async function getAllSnippetsMeta(limit?: number): Promise<ISnippet[]> {
function getAllSlugs (line 82) | async function getAllSlugs({
function getPostFromSlug (line 100) | async function getPostFromSlug(slug: string) {
function getSnippetFromSlug (line 136) | async function getSnippetFromSlug(slug: string) {
function getStaticPageFromSlug (line 163) | async function getStaticPageFromSlug(slug: string) {
function getTableOfContents (line 191) | function getTableOfContents(markdown: string) {
function getMarkdownSource (line 219) | async function getMarkdownSource(content: string) {
function getEpigraphCount (line 228) | async function getEpigraphCount(): Promise<number> {
function getAllEpigraphs (line 233) | async function getAllEpigraphs(limit?: number): Promise<IEpigraph[]> {
FILE: lib/sitemap.ts
function generate (line 5) | async function generate() {
FILE: lib/supabase.ts
function getProjects (line 13) | async function getProjects() {
function getCertificates (line 30) | async function getCertificates() {
function addView (line 48) | async function addView(slug: string) {
function getViewBySlug (line 72) | async function getViewBySlug(slug: string) {
function getAllViews (line 90) | async function getAllViews() {
function getUserDataValue (line 107) | async function getUserDataValue(key: string) {
function setUserDataValue (line 127) | async function setUserDataValue(key: string, value1: any) {
FILE: lib/tmdb.ts
function fetchData (line 11) | async function fetchData(url: string): Promise<ITMDBData[]> {
function fetchTMDBData (line 31) | async function fetchTMDBData(): Promise<ITMDBData[]> {
FILE: lib/toc.ts
function stringToSlug (line 6) | function stringToSlug(str: string) {
FILE: lib/types.ts
type AnimatedTAGProps (line 8) | type AnimatedTAGProps = {
type SpotifyTrack (line 16) | type SpotifyTrack = {
type SpotifyArtist (line 27) | type SpotifyArtist = {
type ProjectType (line 37) | type ProjectType = {
type SkillType (line 48) | type SkillType = {
type CertificateType (line 55) | type CertificateType = {
type SocialPlatform (line 65) | type SocialPlatform = {
type UtilityType (line 71) | type UtilityType = {
type Utilities (line 81) | type Utilities = {
type FrontMatter (line 88) | type FrontMatter = {
type PostType (line 99) | type PostType = {
type TableOfContents (line 105) | type TableOfContents = {
type SupportMe (line 111) | type SupportMe = {
type Song (line 117) | type Song = {
type FormInput (line 126) | type FormInput = {
type SpotifyAccessToken (line 135) | type SpotifyAccessToken = {
type GithubRepo (line 139) | type GithubRepo = {
type PageData (line 145) | type PageData = {
type PageMeta (line 152) | type PageMeta = {
type BookStatusId (line 167) | type BookStatusId = 1 | 2 | 3;
type HardcoverBook (line 169) | type HardcoverBook = {
type HardcoverProfile (line 185) | type HardcoverProfile = {
type Snippet (line 198) | type Snippet = {
type MovieType (line 206) | type MovieType = {
FILE: lib/windowsAnimation.ts
function showHoverAnimation (line 2) | function showHoverAnimation(e: any, isDarkMode: boolean) {
function removeHoverAnimation (line 14) | function removeHoverAnimation(e: any) {
FILE: pages/_app.tsx
function MyApp (line 18) | function MyApp({ Component, pageProps }: AppProps) {
FILE: pages/_document.tsx
function Document (line 3) | function Document() {
FILE: pages/about.tsx
function About (line 31) | function About({
function getStaticProps (line 250) | async function getStaticProps() {
FILE: pages/api/books.ts
function handler (line 5) | async function handler(
FILE: pages/api/ga.ts
constant DAYS (line 5) | const DAYS = 7;
function handler (line 14) | async function handler(
FILE: pages/api/now-playing.ts
function handler (line 4) | async function handler(
FILE: pages/api/posts/insta.ts
function handler (line 5) | async function handler(
FILE: pages/api/revalidate.ts
type ExtendedNextApiRequest (line 3) | interface ExtendedNextApiRequest extends NextApiRequest {
function handler (line 9) | async function handler(
FILE: pages/api/stats/artists.ts
function handler (line 4) | async function handler(
FILE: pages/api/stats/devto.ts
function handler (line 9) | async function handler(_req: NextRequest) {
FILE: pages/api/stats/github-contribution.ts
function handler (line 4) | async function handler(
FILE: pages/api/stats/github.ts
function handler (line 4) | async function handler(
FILE: pages/api/stats/monkeytype.ts
constant BASE_URL (line 3) | const BASE_URL = "https://api.monkeytype.com";
function monkeyFetch (line 5) | async function monkeyFetch(endpoint: string) {
function handler (line 17) | async function handler(
FILE: pages/api/stats/tracks.ts
function handler (line 4) | async function handler(
FILE: pages/api/validate/email.ts
function handler (line 11) | async function handler(
FILE: pages/api/views/[slug].ts
type ExtendedNextApiRequest (line 5) | interface ExtendedNextApiRequest extends NextApiRequest {
function viewsSlug (line 24) | async function viewsSlug(
FILE: pages/api/views/index.ts
function views (line 15) | async function views(req: NextRequest) {
FILE: pages/blogs/[slug].tsx
function Post (line 13) | function Post({
type StaticProps (line 60) | type StaticProps = GetStaticPropsContext & {
function getStaticProps (line 66) | async function getStaticProps({ params }: StaticProps) {
function getStaticPaths (line 89) | async function getStaticPaths() {
FILE: pages/blogs/bookmark.tsx
function Blogs (line 13) | function Blogs() {
FILE: pages/blogs/index.tsx
function Blogs (line 21) | function Blogs({ blogs }: { blogs: BlogPost[] }) {
function getStaticProps (line 122) | async function getStaticProps() {
FILE: pages/books.tsx
constant TABS (line 14) | const TABS = [
type TabId (line 20) | type TabId = (typeof TABS)[number]["id"];
function BooksStats (line 43) | function BooksStats({
constant STATUS_LABEL (line 163) | const STATUS_LABEL: Record<number, string> = {
function BooksPage (line 170) | function BooksPage({
function getStaticProps (line 344) | async function getStaticProps() {
FILE: pages/certificates.tsx
function Certificates (line 27) | function Certificates({
function getStaticProps (line 106) | async function getStaticProps() {
FILE: pages/epigraphs.tsx
constant SOURCE_TYPE_LABELS (line 12) | const SOURCE_TYPE_LABELS: Record<EpigraphSourceType | "all", string> = {
function Epigraphs (line 23) | function Epigraphs({ epigraphs }: { epigraphs: IEpigraph[] }) {
function getStaticProps (line 162) | async function getStaticProps() {
FILE: pages/index.tsx
function Home (line 26) | function Home({
function HomeHeading (line 65) | function HomeHeading({ title }: { title: React.ReactNode | string }) {
function getStaticProps (line 76) | async function getStaticProps() {
FILE: pages/privacy.tsx
function Privacy (line 7) | function Privacy({
function getStaticProps (line 15) | async function getStaticProps() {
FILE: pages/projects.tsx
function Projects (line 17) | function Projects({
function getStaticProps (line 66) | async function getStaticProps() {
FILE: pages/snippets/[slug].tsx
function SnippetPage (line 13) | function SnippetPage({
type StaticProps (line 49) | type StaticProps = GetStaticPropsContext & {
function getStaticProps (line 55) | async function getStaticProps({ params }: StaticProps) {
function getStaticPaths (line 79) | async function getStaticPaths() {
FILE: pages/snippets/index.tsx
function Snippets (line 15) | function Snippets({ snippets }: { snippets: ISnippet[] }) {
function getStaticProps (line 48) | async function getStaticProps() {
FILE: pages/stats.tsx
type Stats (line 19) | type Stats = {
function SectionHeading (line 30) | function SectionHeading({
function Stats (line 76) | function Stats() {
FILE: pages/utilities.tsx
function Utilities (line 25) | function Utilities() {
function UtilitySection (line 63) | function UtilitySection({
FILE: sanity/schemas/epigraph.ts
constant SOURCE_TYPES (line 3) | const SOURCE_TYPES = [
method prepare (line 86) | prepare({title, subtitle, sourceType}) {
FILE: sanity/schemas/post.ts
method prepare (line 77) | prepare(selection) {
FILE: sanity/schemas/snippet.ts
method prepare (line 52) | prepare(selection) {
FILE: sanity/schemas/static_page.ts
method prepare (line 57) | prepare(selection) {
FILE: utils/date.ts
function getFormattedDate (line 17) | function getFormattedDate(date: Date): string {
FILE: utils/functions.ts
function lockScroll (line 5) | function lockScroll() {
function removeScrollLock (line 13) | function removeScrollLock() {
function debounce (line 22) | function debounce(fn: Function, time: number = 300): Function {
type QueryParams (line 32) | interface QueryParams {
function generateUrl (line 35) | function generateUrl(baseUrl: string, queryParams: QueryParams): string {
FILE: utils/utils.ts
constant DEFAULT_IMAGE_URL (line 1) | const DEFAULT_IMAGE_URL: string = "https://imgur.com/5dYYce8.png";
constant TIME_IN_SECONDS (line 29) | const TIME_IN_SECONDS = {
Condensed preview — 177 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (530K chars).
[
{
"path": ".eslintrc.json",
"chars": 134,
"preview": "{\n \"extends\": \"next/core-web-vitals\",\n \"rules\": {\n \"react/no-unescaped-entities\": 0,\n \"@next/next/no-img-element"
},
{
"path": ".gitignore",
"chars": 652,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": ".vercelignore",
"chars": 7,
"preview": "/sanity"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2021 Jatin Sharma\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 17336,
"preview": "<div align=\"center\">\n\n\n\n {\n return (\n <div "
},
{
"path": "components/EpigraphCard.tsx",
"chars": 7989,
"preview": "import { useState } from \"react\";\nimport { IEpigraph, EpigraphSourceType } from \"@lib/interface/sanity\";\nimport { motion"
},
{
"path": "components/Footer.tsx",
"chars": 10831,
"preview": "import Link from \"next/link\";\nimport Image from \"next/image\";\nimport socialMedia from \"@content/socialMedia\";\nimport sit"
},
{
"path": "components/FramerMotion/AnimatedDiv.tsx",
"chars": 482,
"preview": "import { AnimatedTAGProps } from \"@lib/types\";\nimport { motion } from \"framer-motion\";\n\nexport default function Animated"
},
{
"path": "components/FramerMotion/AnimatedHeading.tsx",
"chars": 417,
"preview": "import { motion } from \"framer-motion\";\nimport { AnimatedTAGProps } from \"@lib/types\";\n\nexport default function Animated"
},
{
"path": "components/FramerMotion/AnimatedText.tsx",
"chars": 412,
"preview": "import { AnimatedTAGProps } from \"@lib/types\";\nimport { motion } from \"framer-motion\";\n\nexport default function Animated"
},
{
"path": "components/GitHubActivityGraph.tsx",
"chars": 9451,
"preview": "import {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n ResponsiveContainer,\n Tooltip,\n TooltipProps,\n XA"
},
{
"path": "components/Home/BlogsSection.tsx",
"chars": 3948,
"preview": "import Blog from \"../Blog\";\nimport { BlogPost } from \"@lib/interface/sanity\";\nimport Link from \"next/link\";\nimport { mot"
},
{
"path": "components/Home/EpigraphsSection.tsx",
"chars": 4065,
"preview": "import { IEpigraph } from \"@lib/interface/sanity\";\nimport Link from \"next/link\";\nimport EpigraphCard from \"@components/E"
},
{
"path": "components/Home/HeroSection.tsx",
"chars": 12893,
"preview": "import React from \"react\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { motion } from \"framer-"
},
{
"path": "components/Home/SkillSection.tsx",
"chars": 9242,
"preview": "import { motion } from \"framer-motion\";\nimport siteConfig from \"@content/siteConfig\";\nimport skills from \"@content/skill"
},
{
"path": "components/Home/StatsSection.tsx",
"chars": 2840,
"preview": "import { motion } from \"framer-motion\";\nimport { FiCode, FiUsers, FiCoffee, FiAward } from \"react-icons/fi\";\nimport site"
},
{
"path": "components/Instagram/InstagramPost.tsx",
"chars": 1522,
"preview": "import { DetailedInstagramPost, MediaType } from \"@lib/interface\";\n\nimport { FaPlay } from \"react-icons/fa\";\nimport Link"
},
{
"path": "components/Instagram/InstagramPostLoading.tsx",
"chars": 461,
"preview": "import React from \"react\";\n\nexport default function InstagramPostLoading({ count }: { count: number }) {\n return (\n "
},
{
"path": "components/Instagram/InstagramSection.tsx",
"chars": 1853,
"preview": "import { HomeHeading } from \"pages\";\nimport { InstagramData } from \"@lib/interface\";\nimport InstagramPost from \"./Instag"
},
{
"path": "components/MDXComponents/Code.tsx",
"chars": 428,
"preview": "type Props = {\n children?: string | React.ReactNode;\n};\n\nexport default function Code(props: Props) {\n return (\n <>"
},
{
"path": "components/MDXComponents/CodeSandbox.tsx",
"chars": 723,
"preview": "export default function CodeSandbox({\n id,\n hideNavigation = true,\n}: {\n id: string;\n hideNavigation: boolean;\n}) {\n"
},
{
"path": "components/MDXComponents/CodeTitle.tsx",
"chars": 1884,
"preview": "import { BsFileEarmarkCodeFill } from \"react-icons/bs\";\nimport {\n SiCss3,\n SiPython,\n SiGnubash,\n SiHtml5,\n SiReact"
},
{
"path": "components/MDXComponents/Codepen.tsx",
"chars": 351,
"preview": "export default function Codepen({ id }: { id: string }) {\n return (\n <div className=\"my-3 print:hidden\">\n <ifra"
},
{
"path": "components/MDXComponents/Danger.tsx",
"chars": 1217,
"preview": "type Props = { title?: string; text: string };\n\nexport default function Danger({ title, text }: Props) {\n return (\n "
},
{
"path": "components/MDXComponents/EmbedBlog.tsx",
"chars": 826,
"preview": "import Image from \"next/image\";\nimport Link from \"next/link\";\n\nexport default function EmbedBlog({\n img,\n text,\n url,"
},
{
"path": "components/MDXComponents/Figcaption.tsx",
"chars": 355,
"preview": "type Props = {\n src: string;\n caption?: string;\n alt?: string;\n};\n\nexport default function figcaption({ src, caption,"
},
{
"path": "components/MDXComponents/LinkedInEmbed.tsx",
"chars": 502,
"preview": "export default function LinkedInEmbed({ id }: { id: string }) {\n return (\n <div\n style={{\n position: \"re"
},
{
"path": "components/MDXComponents/NextAndPreviousButton.tsx",
"chars": 1571,
"preview": "import Link from \"next/link\";\nimport { IoArrowForwardSharp } from \"react-icons/io5\";\n\ntype Props = {\n prevHref: string;"
},
{
"path": "components/MDXComponents/Pre.tsx",
"chars": 1723,
"preview": "import { ReactNode, useRef, useState } from \"react\";\nimport { MdCheck, MdContentCopy } from \"react-icons/md\";\n\nconst Pre"
},
{
"path": "components/MDXComponents/Step.tsx",
"chars": 633,
"preview": "import React from \"react\";\n\nexport default function Step({\n id,\n children,\n}: {\n id: string;\n children?: JSX.Element"
},
{
"path": "components/MDXComponents/Tip.tsx",
"chars": 472,
"preview": "export default function Tip({\n id,\n children,\n}: {\n id: string;\n children?: React.ReactNode;\n}) {\n return (\n <di"
},
{
"path": "components/MDXComponents/UrlMetaInfo.tsx",
"chars": 2398,
"preview": "import React, { useEffect, useState } from \"react\";\n\nimport Image from \"next/image\";\nimport Link from \"next/link\";\n\ninte"
},
{
"path": "components/MDXComponents/Warning.tsx",
"chars": 1324,
"preview": "type Props = {\n text?: string;\n title?: string;\n children?: React.ReactNode;\n};\n\nexport default function Warning({ te"
},
{
"path": "components/MDXComponents/YouTube.tsx",
"chars": 467,
"preview": "export default function YouTube({ id }: { id: string }) {\n return (\n <div className=\"max-w-full overflow-hidden rela"
},
{
"path": "components/MDXComponents/index.tsx",
"chars": 790,
"preview": "import Code from \"./Code\";\nimport CodeSandbox from \"./CodeSandbox\";\nimport CodeTitle from \"./CodeTitle\";\nimport Codepen "
},
{
"path": "components/MetaData.tsx",
"chars": 4163,
"preview": "import { useEffect, useState } from \"react\";\n\nimport { NextSeo } from \"next-seo\";\nimport useWindowLocation from \"@hooks/"
},
{
"path": "components/MovieCard.tsx",
"chars": 2641,
"preview": "import React, { useState } from \"react\";\nimport { AiFillStar } from \"react-icons/ai\";\nimport { ITMDBData } from \"@lib/in"
},
{
"path": "components/Newsletter.tsx",
"chars": 3348,
"preview": "import { useState } from \"react\";\nimport { ToastContainer, toast } from \"react-toastify\";\nimport { CgSpinnerTwo } from \""
},
{
"path": "components/OgImage.tsx",
"chars": 745,
"preview": "import Image from \"next/image\";\nfunction OgImage({ src, alt }: { src: string; alt: string }) {\n return (\n <div class"
},
{
"path": "components/PageHeader.tsx",
"chars": 2609,
"preview": "import React from \"react\";\nimport { motion } from \"framer-motion\";\n\ninterface PageHeaderProps {\n /** Large background w"
},
{
"path": "components/PageNotFound.tsx",
"chars": 2725,
"preview": "import Link from \"next/link\";\nimport MetaData from \"@components/MetaData\";\nimport { motion } from \"framer-motion\";\n\nexpo"
},
{
"path": "components/PageTop.tsx",
"chars": 929,
"preview": "import {\n fromLeftVariant,\n opacityVariant,\n} from \"../content/FramerMotionVariants\";\nimport AnimatedHeading from \"./F"
},
{
"path": "components/Project.tsx",
"chars": 4178,
"preview": "import { BsGithub } from \"react-icons/bs\";\nimport { MdOutlineLink } from \"react-icons/md\";\nimport Link from \"next/link\";"
},
{
"path": "components/QRCodeContainer.tsx",
"chars": 4302,
"preview": "import QRCode from \"react-qr-code\";\nimport useWindowLocation from \"@hooks/useWindowLocation\";\nimport { MdClose } from \"r"
},
{
"path": "components/SVG/Ditto.tsx",
"chars": 2480,
"preview": "import { useDarkMode } from \"@context/darkModeContext\";\n\nexport default function Ditto() {\n const { isDarkMode } = useD"
},
{
"path": "components/SVG/Flameshot.tsx",
"chars": 1616,
"preview": "import { useDarkMode } from \"@context/darkModeContext\";\n\nexport default function Flameshot({ className }: { className?: "
},
{
"path": "components/SVG/Flux.tsx",
"chars": 1325,
"preview": "import { useDarkMode } from \"@context/darkModeContext\";\n\nexport default function Flux() {\n const { isDarkMode } = useDa"
},
{
"path": "components/SVG/Logo.tsx",
"chars": 865,
"preview": "import { motion } from \"framer-motion\";\n\nexport default function Logo({ className }: { className: string }) {\n return ("
},
{
"path": "components/SVG/MicrosoftToDo.tsx",
"chars": 1979,
"preview": "export default function MicrosoftToDo() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"41\"\n"
},
{
"path": "components/SVG/RainDrop.tsx",
"chars": 2179,
"preview": "export default function RainDrop() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n "
},
{
"path": "components/SVG/ShareX.tsx",
"chars": 6054,
"preview": "import { useDarkMode } from \"@context/darkModeContext\";\n\nexport default function ShareX() {\n const { isDarkMode } = use"
},
{
"path": "components/SVG/UPI.tsx",
"chars": 5089,
"preview": "import React from \"react\";\n\nexport default function UPI({ className }: { className: string }) {\n return (\n <svg\n "
},
{
"path": "components/SVG/Zip7.tsx",
"chars": 926,
"preview": "export default function Zip7() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"32\"\n hei"
},
{
"path": "components/SVG/index.tsx",
"chars": 346,
"preview": "import Ditto from \"./Ditto\";\nimport Flux from \"./Flux\";\nimport MicrosoftToDo from \"./MicrosoftToDo\";\nimport RainDrop fro"
},
{
"path": "components/ScrollProgressBar.tsx",
"chars": 883,
"preview": "import { useCallback, useEffect, useState } from \"react\";\n\nexport default function ScrollProgressBar() {\n const [scroll"
},
{
"path": "components/ScrollToTopButton.tsx",
"chars": 1194,
"preview": "import { IoIosArrowUp } from \"react-icons/io\";\nimport { useEffect, useState } from \"react\";\nimport { motion, AnimatePres"
},
{
"path": "components/ShareOnSocialMedia.tsx",
"chars": 3185,
"preview": "import \"react-toastify/dist/ReactToastify.css\";\n\nimport {\n FacebookShareButton,\n LinkedinShareButton,\n TwitterShareBu"
},
{
"path": "components/SnippetCard.tsx",
"chars": 1667,
"preview": "import { motion } from \"framer-motion\";\nimport { ISnippet } from \"@lib/interface/sanity\";\nimport Image from \"next/image\""
},
{
"path": "components/SnowfallCanvas.tsx",
"chars": 6162,
"preview": "// @ts-nocheck\n\nimport React, { useEffect, useRef } from \"react\";\n\nimport { useDarkMode } from \"@context/darkModeContext"
},
{
"path": "components/StaticPage.tsx",
"chars": 1404,
"preview": "import { IStaticPage } from \"@lib/interface/sanity\";\nimport MDXComponents from \"@components/MDXComponents\";\nimport { MDX"
},
{
"path": "components/Stats/Artist.tsx",
"chars": 1413,
"preview": "import Image from \"next/image\";\nimport Link from \"next/link\";\n\ntype ArtistProps = {\n name: string;\n url: string;\n cov"
},
{
"path": "components/Stats/MonkeyTypeStats.tsx",
"chars": 21152,
"preview": "import {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n ResponsiveContainer,\n Tooltip,\n TooltipProps,\n XA"
},
{
"path": "components/Stats/StatsCard.tsx",
"chars": 915,
"preview": "import { motion } from \"framer-motion\";\n\nconst itemVariants = {\n hidden: { opacity: 0, y: 10 },\n visible: {\n opacit"
},
{
"path": "components/Stats/Track.tsx",
"chars": 1539,
"preview": "import Image from \"next/image\";\nimport Link from \"next/link\";\n\ntype TrackProps = {\n url: string;\n title: string;\n art"
},
{
"path": "components/Support.tsx",
"chars": 6387,
"preview": "import UPI from \"@components/SVG/UPI\";\nimport {\n FadeContainer,\n fromTopVariant,\n popUp,\n} from \"@content/FramerMotio"
},
{
"path": "components/TableOfContents.tsx",
"chars": 6786,
"preview": "import { AnimatePresence, motion } from \"framer-motion\";\nimport { useState } from \"react\";\n\nimport Link from \"next/link\""
},
{
"path": "components/TopNavbar.tsx",
"chars": 8412,
"preview": "/* Importing Modules */\nimport React, { useEffect, useState, useRef, useCallback } from \"react\";\nimport Link from \"next/"
},
{
"path": "content/FramerMotionVariants.ts",
"chars": 2425,
"preview": "import { Variants } from \"framer-motion\";\n\nexport const popUp: Variants = {\n hidden: { scale: 0, opacity: 0 },\n visibl"
},
{
"path": "content/meta.ts",
"chars": 3634,
"preview": "import { PageMeta } from \"@lib/types\";\n\nconst pageMeta: PageMeta = {\n home: {\n title: \"\",\n description:\n \"He"
},
{
"path": "content/siteConfig.ts",
"chars": 4757,
"preview": "import { homeProfileImage } from \"@utils/utils\";\n\nexport type SocialIconKey =\n | \"twitter\"\n | \"linkedin\"\n | \"github\"\n"
},
{
"path": "content/skillsData.ts",
"chars": 1900,
"preview": "import { SkillType } from \"@lib/types\";\nimport {\n SiHtml5,\n SiCss3,\n SiJavascript,\n SiNextdotjs,\n SiTailwindcss,\n "
},
{
"path": "content/socialMedia.ts",
"chars": 835,
"preview": "import { SocialPlatform } from \"@lib/types\";\nimport { AiOutlineInstagram, AiOutlineTwitter } from \"react-icons/ai\";\nimpo"
},
{
"path": "content/support.ts",
"chars": 398,
"preview": "import { SiBuymeacoffee } from \"react-icons/si\";\nimport { BsPaypal } from \"react-icons/bs\";\nimport { SupportMe } from \"@"
},
{
"path": "content/user.ts",
"chars": 712,
"preview": "import siteConfig from \"./siteConfig\";\n\ntype AuthorInfo = {\n name: string;\n image: string;\n org: string | null;\n org"
},
{
"path": "content/utilitiesData.ts",
"chars": 9311,
"preview": "import {\n SiMacos,\n SiHomebrew,\n SiIterm2,\n SiWarp,\n SiArc,\n SiGooglechrome,\n SiVisualstudiocode,\n SiXcode,\n Si"
},
{
"path": "context/darkModeContext.tsx",
"chars": 1198,
"preview": "import React, { useState, useContext, useEffect, createContext } from \"react\";\n\nexport interface DarkModeContextType {\n "
},
{
"path": "docs/sanity-deploy.md",
"chars": 746,
"preview": "# Deploying Sanity Studio\n\nStudio is live at **https://j471n-blog.sanity.studio/**\n\n---\n\n## First-time setup\n\nInstall th"
},
{
"path": "hooks/useBookmarkBlogs.ts",
"chars": 1634,
"preview": "import { useEffect, useState } from \"react\";\n\nimport { BlogPost } from \"@lib/interface/sanity\";\nimport { FrontMatter } f"
},
{
"path": "hooks/useDebounce.ts",
"chars": 349,
"preview": "import { useState, useEffect } from \"react\";\n\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [deboun"
},
{
"path": "hooks/useFetchWithSWR.ts",
"chars": 417,
"preview": "import useSWR from \"swr\";\n\n// fetcher to fetch the data\n// const fetcher = (...args) => fetch(...args).then((res) => res"
},
{
"path": "hooks/useScrollPercentage.ts",
"chars": 730,
"preview": "import { useEffect, useState, useCallback } from \"react\";\nexport default function useScrollPercentage() {\n // fifteen\n "
},
{
"path": "hooks/useShare.ts",
"chars": 360,
"preview": "import { useEffect, useState } from \"react\";\nfunction useShare() {\n // state for share supports\n const [isShareSupport"
},
{
"path": "hooks/useWindowLocation.ts",
"chars": 353,
"preview": "import { useEffect, useState } from \"react\";\nimport { useRouter } from \"next/router\";\n\ntype URL = string;\n\nexport defaul"
},
{
"path": "hooks/useWindowSize.ts",
"chars": 871,
"preview": "import { useState, useEffect } from \"react\";\nexport default function useWindowSize() {\n // Initialize state with undefi"
},
{
"path": "layout/BlogLayout.tsx",
"chars": 5018,
"preview": "import { BlogPost } from \"@lib/interface/sanity\";\nimport { FiPrinter } from \"react-icons/fi\";\nimport Image from \"next/im"
},
{
"path": "layout/Layout.tsx",
"chars": 881,
"preview": "import React, { useState } from \"react\";\n\nimport Footer from \"../components/Footer\";\nimport QRCodeContainer from \"@compo"
},
{
"path": "layout/SnippetLayout.tsx",
"chars": 3141,
"preview": "import { ISnippet } from \"@lib/interface/sanity\";\nimport Image from \"next/image\";\nimport { getFormattedDate } from \"@uti"
},
{
"path": "lib/MDXContent.ts",
"chars": 3539,
"preview": "import { FrontMatter } from \"./types\";\nimport matter from \"gray-matter\";\nimport path from \"path\";\nimport { readFileSync "
},
{
"path": "lib/devto.ts",
"chars": 2068,
"preview": "const PER_PAGE: number = 1000;\nconst DEV_API = process.env.NEXT_PUBLIC_BLOGS_API;\n\n/**\n * Makes a request to the DEV API"
},
{
"path": "lib/fetcher.ts",
"chars": 180,
"preview": "/**\n * Makes a request to the specified URL and returns the response as JSON.\n */\nexport default async function fetcher("
},
{
"path": "lib/generateRSS.ts",
"chars": 1039,
"preview": "import RSS from \"rss\";\nimport { getAllPostsMeta } from \"./sanityContent\";\nimport { writeFileSync } from \"fs\";\n\nexport de"
},
{
"path": "lib/github.ts",
"chars": 4971,
"preview": "import {\n IContributionCalendar,\n IContributionCountByDay,\n IContributionDay,\n IGitHubProfileResponse,\n IGitHubRepo"
},
{
"path": "lib/hardcover.ts",
"chars": 4262,
"preview": "import { HardcoverBook, BookStatusId, HardcoverProfile } from \"./types\";\n\nconst HARDCOVER_GRAPHQL_URL = \"https://api.har"
},
{
"path": "lib/instaposts.ts",
"chars": 1986,
"preview": "import { getUserDataValue, setUserDataValue } from \"./supabase\";\n\nimport { InstagramData } from \"./interface\";\nimport { "
},
{
"path": "lib/interface/sanity.ts",
"chars": 1991,
"preview": "import { ReadTimeResults } from \"reading-time\";\nimport { SanityDocument } from \"@sanity/types\";\nimport { TableOfContents"
},
{
"path": "lib/interface.ts",
"chars": 8522,
"preview": "export interface IExternalUrls {\n spotify: string;\n}\nexport interface IFollowers {\n href?: null;\n total: number;\n}\nex"
},
{
"path": "lib/sanityClient.ts",
"chars": 334,
"preview": "import { createClient } from \"@sanity/client\";\n\nconst client = createClient({\n projectId: process.env.SANITY_PROJECT_ID"
},
{
"path": "lib/sanityContent.ts",
"chars": 5870,
"preview": "import { BlogPost, IEpigraph, ISnippet } from \"./interface/sanity\";\n\nimport groq from \"groq\";\nimport matter from \"gray-m"
},
{
"path": "lib/sitemap.ts",
"chars": 1159,
"preview": "import { getAllSlugs } from \"./sanityContent\";\nimport { globby } from \"globby\";\nimport { writeFileSync } from \"fs\";\n\nexp"
},
{
"path": "lib/spotify.ts",
"chars": 3365,
"preview": "import { IArtistsAPIResponse, ITracksAPIResponse } from \"./interface\";\nimport { SpotifyAccessToken } from \"./types\";\n\nco"
},
{
"path": "lib/supabase.ts",
"chars": 3757,
"preview": "import { createClient } from \"@supabase/supabase-js\";\n\n// A Supabase client object for making requests to a Supabase ser"
},
{
"path": "lib/tmdb.ts",
"chars": 1652,
"preview": "import { ITMDBData } from \"./interface\";\n\nconst options: RequestInit = {\n method: \"GET\",\n headers: {\n accept: \"appl"
},
{
"path": "lib/toc.ts",
"chars": 487,
"preview": "/**\n * Converts a string to a slug by lowercasing it, trimming leading and trailing whitespace,\n * replacing any non-wor"
},
{
"path": "lib/types.ts",
"chars": 3782,
"preview": "import { IconType } from \"react-icons/lib\";\nimport { MDXRemoteSerializeResult } from \"next-mdx-remote\";\nimport React fro"
},
{
"path": "lib/windowsAnimation.ts",
"chars": 882,
"preview": "/* Adds a hover animation to an element by setting its background and border image properties. */\nexport function showHo"
},
{
"path": "next-env.d.ts",
"chars": 201,
"preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edite"
},
{
"path": "next.config.js",
"chars": 834,
"preview": "/**\n * @type {import('next').NextConfig}\n */\n\nconst withPWA = require(\"next-pwa\")({\n dest: \"public\",\n register: true,\n"
},
{
"path": "package.json",
"chars": 1930,
"preview": "{\n \"name\": \"portfolio-next\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"license\": \"MIT\",\n \"scripts\": {\n \"dev\": \"ne"
},
{
"path": "pages/404.tsx",
"chars": 83,
"preview": "import PageNotFound from \"@components/PageNotFound\";\n\nexport default PageNotFound;\n"
},
{
"path": "pages/_app.tsx",
"chars": 1338,
"preview": "import \"@styles/globals.css\";\nimport \"react-toastify/dist/ReactToastify.css\";\nimport Layout from \"@layout/Layout\";\nimpor"
},
{
"path": "pages/_document.tsx",
"chars": 1559,
"preview": "import { Html, Head, Main, NextScript } from \"next/document\";\n\nexport default function Document() {\n return (\n <Html"
},
{
"path": "pages/about.tsx",
"chars": 10100,
"preview": "import { ILinkedinResponse, ITMDBData } from \"@lib/interface\";\nimport { IStaticPage } from \"@lib/interface/sanity\";\nimpo"
},
{
"path": "pages/api/books.ts",
"chars": 758,
"preview": "import { NextApiRequest, NextApiResponse } from \"next\";\nimport { getMyBooks } from \"../../lib/hardcover\";\nimport { Hardc"
},
{
"path": "pages/api/ga.ts",
"chars": 1128,
"preview": "import { NextApiRequest, NextApiResponse } from \"next\";\nimport { BetaAnalyticsDataClient } from \"@google-analytics/data\""
},
{
"path": "pages/api/now-playing.ts",
"chars": 1025,
"preview": "import { NextApiRequest, NextApiResponse } from \"next\";\nimport { currentlyPlayingSong } from \"../../lib/spotify\";\n\nexpor"
},
{
"path": "pages/api/posts/insta.ts",
"chars": 638,
"preview": "import { NextApiRequest, NextApiResponse } from \"next\";\n\nimport { getInstagramPosts } from \"@lib/instaposts\";\n\nexport de"
},
{
"path": "pages/api/revalidate.ts",
"chars": 933,
"preview": "import type { NextApiRequest, NextApiResponse } from \"next\";\n\ninterface ExtendedNextApiRequest extends NextApiRequest {\n"
},
{
"path": "pages/api/stats/artists.ts",
"chars": 604,
"preview": "import { NextApiRequest, NextApiResponse } from \"next\";\nimport { topArtists } from \"../../../lib/spotify\";\n\nexport defau"
},
{
"path": "pages/api/stats/devto.ts",
"chars": 592,
"preview": "import { NextRequest, NextResponse } from \"next/server\";\n\nimport { getUserDataValue } from \"@lib/supabase\";\n\nexport cons"
},
{
"path": "pages/api/stats/github-contribution.ts",
"chars": 397,
"preview": "import { NextApiRequest, NextApiResponse } from \"next\";\nimport { getGithubContribution } from \"../../../lib/github\";\n\nex"
},
{
"path": "pages/api/stats/github.ts",
"chars": 598,
"preview": "import { NextApiRequest, NextApiResponse } from \"next\";\nimport { fetchGithub, getGithubStarsAndForks } from \"../../../li"
},
{
"path": "pages/api/stats/monkeytype.ts",
"chars": 2450,
"preview": "import { NextApiRequest, NextApiResponse } from \"next\";\n\nconst BASE_URL = \"https://api.monkeytype.com\";\n\nasync function "
},
{
"path": "pages/api/stats/tracks.ts",
"chars": 598,
"preview": "import { NextApiRequest, NextApiResponse } from \"next\";\nimport { topTracks } from \"../../../lib/spotify\";\n\nexport defaul"
},
{
"path": "pages/api/validate/email.ts",
"chars": 1552,
"preview": "import { IEmailValidation } from \"@lib/interface\";\nimport { NextApiRequest, NextApiResponse } from \"next\";\n\n// Function "
},
{
"path": "pages/api/views/[slug].ts",
"chars": 2220,
"preview": "import { addView, getViewBySlug } from \"@lib/supabase\";\nimport { NextApiRequest, NextApiResponse } from \"next\";\n\n/* Exte"
},
{
"path": "pages/api/views/index.ts",
"chars": 1012,
"preview": "import { NextRequest, NextResponse } from \"next/server\";\n\nimport { getAllViews } from \"@lib/supabase\";\n\nexport const con"
},
{
"path": "pages/blogs/[slug].tsx",
"chars": 2241,
"preview": "import { getAllSlugs, getPostFromSlug } from \"@lib/sanityContent\";\n\nimport BlogLayout from \"@layout/BlogLayout\";\nimport "
},
{
"path": "pages/blogs/bookmark.tsx",
"chars": 1550,
"preview": "import { motion } from \"framer-motion\";\nimport Blog from \"@components/Blog\";\nimport Metadata from \"@components/MetaData\""
},
{
"path": "pages/blogs/index.tsx",
"chars": 4569,
"preview": "import { AnimatePresence, motion } from \"framer-motion\";\nimport React, { useEffect, useRef, useState } from \"react\";\n\nim"
},
{
"path": "pages/books.tsx",
"chars": 12342,
"preview": "import React, { useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { CgSearch } fr"
},
{
"path": "pages/certificates.tsx",
"chars": 3812,
"preview": "import MetaData from \"@components/MetaData\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport PageHe"
},
{
"path": "pages/epigraphs.tsx",
"chars": 6122,
"preview": "import { useState, useMemo } from \"react\";\nimport { useDebounce } from \"@hooks/useDebounce\";\nimport { IEpigraph, Epigrap"
},
{
"path": "pages/index.tsx",
"chars": 2873,
"preview": "// Page Components START----------\n\nimport { headingFromLeft } from \"@content/FramerMotionVariants\";\n\nimport AnimatedHea"
},
{
"path": "pages/privacy.tsx",
"chars": 624,
"preview": "import { IStaticPage } from \"@lib/interface/sanity\";\nimport StaticPage from \"@components/StaticPage\";\nimport { getStatic"
},
{
"path": "pages/projects.tsx",
"chars": 2000,
"preview": "import React from \"react\";\nimport Project from \"@components/Project\";\nimport Metadata from \"@components/MetaData\";\nimpor"
},
{
"path": "pages/snippets/[slug].tsx",
"chars": 2047,
"preview": "import { getAllSlugs, getSnippetFromSlug } from \"@lib/sanityContent\";\n\nimport { GetStaticPropsContext } from \"next\";\nimp"
},
{
"path": "pages/snippets/index.tsx",
"chars": 1765,
"preview": "import { motion } from \"framer-motion\";\nimport { ISnippet } from \"@lib/interface/sanity\";\nimport Metadata from \"@compone"
},
{
"path": "pages/stats.tsx",
"chars": 10814,
"preview": "// import { SpotifyArtist, SpotifyTrack } from \"@lib/types\";\n\n// import Artist from \"@components/Stats/Artist\";\nimport G"
},
{
"path": "pages/tet.json",
"chars": 20214,
"preview": "{\n \"public_identifier\": \"j471n\",\n \"profile_pic_url\": \"https://i.imgur.com/RF2bEls.jpg\",\n \"background_cover_image_url\""
},
{
"path": "pages/utilities.tsx",
"chars": 4771,
"preview": "import React from \"react\";\nimport Link from \"next/link\";\nimport { motion } from \"framer-motion\";\nimport { FiExternalLink"
},
{
"path": "postcss.config.js",
"chars": 83,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n"
},
{
"path": "public/manifest.json",
"chars": 2758,
"preview": "{\n \"theme_color\": \"#000\",\n \"background_color\": \"#fff\",\n \"display\": \"standalone\",\n \"scope\": \"/\",\n \"start_url\": \"/\",\n"
},
{
"path": "public/robots.txt",
"chars": 59,
"preview": "User-agent: *\nSitemap: https://jatin.vercel.app/sitemap.xml"
},
{
"path": "sanity/.eslintrc",
"chars": 48,
"preview": "{\n \"extends\": \"@sanity/eslint-config-studio\"\n}\n"
},
{
"path": "sanity/.gitignore",
"chars": 420,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# Dependencies\n/node_modules\n/.pn"
},
{
"path": "sanity/README.md",
"chars": 758,
"preview": "# Sanity Blogging Content Studio\n\nCongratulations, you have now installed the Sanity Content Studio, an open source real"
},
{
"path": "sanity/gist.md.ndjson",
"chars": 19956,
"preview": "{\"_type\":\"post\", \"title\":\"ruby rails\",\"_createdAt\":\"2023-05-26T21:06:53Z\",\"publishedAt\":\"2023-05-26T21:06:53Z\",\"body\":[{"
},
{
"path": "sanity/package.json",
"chars": 933,
"preview": "{\n \"name\": \"j471n-blog\",\n \"private\": true,\n \"version\": \"1.0.0\",\n \"main\": \"package.json\",\n \"license\": \"UNLICENSED\",\n"
},
{
"path": "sanity/sanity.cli.ts",
"chars": 149,
"preview": "import {defineCliConfig} from '@sanity/cli'\n\nexport default defineCliConfig({\n api: {\n projectId: '5ec749u0',\n da"
},
{
"path": "sanity/sanity.config.ts",
"chars": 477,
"preview": "// import {defineConfig} from 'sanity/lib/exports' // ONLY USE WHILE BUILDING THE APP\n\nimport {defineConfig} from 'sanit"
},
{
"path": "sanity/schemas/author.ts",
"chars": 879,
"preview": "import {defineField, defineType} from 'sanity'\n\nexport default defineType({\n name: 'author',\n title: 'Author',\n type:"
},
{
"path": "sanity/schemas/blockContent.ts",
"chars": 2040,
"preview": "import {defineType, defineArrayMember} from 'sanity'\n\n/**\n * This is the schema definition for the rich text fields used"
},
{
"path": "sanity/schemas/category.ts",
"chars": 349,
"preview": "import {defineField, defineType} from 'sanity'\n\nexport default defineType({\n name: 'category',\n title: 'Category',\n t"
},
{
"path": "sanity/schemas/epigraph.ts",
"chars": 2593,
"preview": "import {defineField, defineType, defineArrayMember} from 'sanity'\n\nconst SOURCE_TYPES = [\n {title: 'Book', value: 'book"
},
{
"path": "sanity/schemas/index.ts",
"chars": 458,
"preview": "import author from './author'\nimport blockContent from './blockContent'\nimport category from './category'\nimport languag"
},
{
"path": "sanity/schemas/language.ts",
"chars": 627,
"preview": "import {defineField, defineType} from 'sanity'\n\nexport default defineType({\n name: 'language',\n title: 'Language',\n t"
},
{
"path": "sanity/schemas/organization.ts",
"chars": 986,
"preview": "import {defineField, defineType} from 'sanity'\n\nexport default defineType({\n name: 'organization',\n title: 'Organizati"
},
{
"path": "sanity/schemas/post.ts",
"chars": 1608,
"preview": "import {defineField, defineType} from 'sanity'\n\nexport default defineType({\n name: 'post',\n title: 'Post',\n type: 'do"
},
{
"path": "sanity/schemas/snippet.ts",
"chars": 1071,
"preview": "import {defineField, defineType} from 'sanity'\n\nexport default defineType({\n name: 'snippet',\n title: 'Snippet',\n typ"
},
{
"path": "sanity/schemas/static_page.ts",
"chars": 1100,
"preview": "import {defineField, defineType} from 'sanity'\n\nexport default defineType({\n name: 'static_page',\n title: 'Static Page"
},
{
"path": "sanity/static/.gitkeep",
"chars": 81,
"preview": "Files placed here will be served by the Sanity server under the `/static`-prefix\n"
},
{
"path": "sanity/tailwind.config.js",
"chars": 182,
"preview": "module.exports = {\n content: [\n'./pages/**/*.{js,ts,jsx,tsx}',\n'./components/**/*.{js,ts,jsx,tsx}',\n'./app/**/*.{js,ts,"
},
{
"path": "sanity/tsconfig.json",
"chars": 495,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2017\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n "
},
{
"path": "sanity.cli.ts",
"chars": 153,
"preview": "import { defineCliConfig } from \"@sanity/cli\";\n\nexport default defineCliConfig({\n api: {\n projectId: \"5ec749u0\",\n "
},
{
"path": "styles/globals.css",
"chars": 9975,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@font-face {\n font-family: \"Barlow\";\n font-style: normal;\n"
},
{
"path": "tailwind.config.js",
"chars": 1236,
"preview": "module.exports = {\n content: [\n \"./pages/**/*.{js,ts,jsx,tsx}\",\n \"./components/**/*.{js,ts,jsx,tsx}\",\n \"./layo"
},
{
"path": "tsconfig.json",
"chars": 2477,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES5\", /* Set the JavaScript language version for emitted JavaScript and include "
},
{
"path": "utils/date.ts",
"chars": 339,
"preview": "/* Formats a date as a string in the format 'Month day, year'. */\n\nexport const months = [\n \"Jan\",\n \"Feb\",\n \"Mar\",\n "
},
{
"path": "utils/functions.ts",
"chars": 1511,
"preview": "/**\n * Locks the scroll of the document by adding a 'lock-scroll' class to the html element.\n * The 'lock-scroll' class "
},
{
"path": "utils/utils.ts",
"chars": 1051,
"preview": "export const DEFAULT_IMAGE_URL: string = \"https://imgur.com/5dYYce8.png\";\nexport const AvatarImage: string = \"https://im"
},
{
"path": "vercel.json",
"chars": 1989,
"preview": "{\n \"$schema\": \"https://openapi.vercel.sh/vercel.json\",\n \"cleanUrls\": true,\n \"headers\": [\n {\n \"source\": \"/font"
}
]
About this extraction
This page contains the full source code of the j471n/j471n.in GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 177 files (481.1 KB), approximately 136.2k tokens, and a symbol index with 315 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.