Full Code of akionii/Miruro for AI

main 5986e25ef6d8 cached
70 files
286.1 KB
73.6k tokens
96 symbols
1 requests
Download .txt
Showing preview only (305K chars total). Download the full file or copy to clipboard to get everything.
Repository: akionii/Miruro
Branch: main
Commit: 5986e25ef6d8
Files: 70
Total size: 286.1 KB

Directory structure:
gitextract_c4uzzfr4/

├── .eslintrc.cjs
├── .github/
│   ├── FUNDING.yml
│   ├── SECURITY.md
│   └── dependabot.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── api/
│   └── exchange-token.ts
├── functions/
│   └── exchange-token.js
├── index.html
├── package.json
├── public/
│   └── manifest.json
├── renovate.json
├── robots.txt
├── server/
│   ├── README.md
│   └── server.ts
├── src/
│   ├── App.tsx
│   ├── client/
│   │   ├── ApolloClient.tsx
│   │   ├── authService.ts
│   │   ├── useAuth.tsx
│   │   └── userInfoTypes.ts
│   ├── components/
│   │   ├── Cards/
│   │   │   ├── CardGrid.tsx
│   │   │   └── CardItem.tsx
│   │   ├── Home/
│   │   │   ├── EpisodeCard.tsx
│   │   │   ├── HomeCarousel.tsx
│   │   │   └── HomeSideBar.tsx
│   │   ├── Navigation/
│   │   │   ├── DropSearch.tsx
│   │   │   ├── Footer.tsx
│   │   │   ├── Navbar.tsx
│   │   │   └── SearchFilters.tsx
│   │   ├── Profile/
│   │   │   ├── Settings.tsx
│   │   │   ├── SettingsProvider.tsx
│   │   │   └── WatchingAnilist.tsx
│   │   ├── ShortcutsPopup.tsx
│   │   ├── Skeletons/
│   │   │   └── Skeletons.tsx
│   │   ├── ThemeContext.tsx
│   │   ├── Watch/
│   │   │   ├── AnimeDataList.tsx
│   │   │   ├── EpisodeList.tsx
│   │   │   ├── Seasons.tsx
│   │   │   ├── Video/
│   │   │   │   ├── EmbedPlayer.tsx
│   │   │   │   ├── MediaSource.tsx
│   │   │   │   ├── Player.tsx
│   │   │   │   └── PlayerStyles.css
│   │   │   └── WatchAnimeData.tsx
│   │   └── shared/
│   │       └── StatusIndicator.tsx
│   ├── hooks/
│   │   ├── animeInterface.ts
│   │   ├── useApi.ts
│   │   ├── useCountdown.ts
│   │   ├── useFilters.ts
│   │   ├── useScroll.ts
│   │   └── useTIme.ts
│   ├── index.ts
│   ├── main.tsx
│   ├── pages/
│   │   ├── 404.tsx
│   │   ├── About.tsx
│   │   ├── Callback.tsx
│   │   ├── Home.tsx
│   │   ├── PolicyTerms.tsx
│   │   ├── Profile.tsx
│   │   ├── Search.tsx
│   │   └── Watch.tsx
│   ├── styles/
│   │   ├── animations.css
│   │   ├── globals.css
│   │   └── themes.css
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vercel.json
└── vite.config.ts

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

================================================
FILE: .eslintrc.cjs
================================================
module.exports = {
  root: true,
  env: { browser: true, es2020: true },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
  ],
  ignorePatterns: ['dist', '.eslintrc.cjs'],
  parser: '@typescript-eslint/parser',
  plugins: ['react-refresh'],
  rules: {
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
  },
};


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

# github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
# polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .github/SECURITY.md
================================================
# Security Policy

## Supported Versions

| Version | Supported |
| ------- | --------- |
| 0.2.0   | ❔        |
| 0.1.0   | ✅        |
| < 0.1.0 | ❌        |

## Reporting a Vulnerability

If you discover a security vulnerability, please report it by opening an [issue](https://github.com/Miruro-no-kuon/Miruro-no-Kuon/issues). To help us better understand and address the issue, please follow the template provided when creating a new issue.

### Reporting Process

1. **Open a new issue**: Clearly describe the vulnerability, providing as much detail as possible.
2. **Assessment**: Our team will assess the reported vulnerability and respond when available.
3. **Fix and Release**: If the vulnerability is accepted, we will work on fixing it and release a patch within a reasonable timeframe.

### Expectations

- We will strive to keep you informed about the progress of your reported vulnerability.
- If the vulnerability is accepted, it will be prioritized based on severity.
- If the vulnerability is declined, we will provide a reason for the decision.
- We encourage responsible disclosure, and we appreciate your efforts in keeping our project secure.

## Versioning Scheme

We follow [Semantic Versioning](https://semver.org/) for our releases. Security updates will be applied to the latest minor version of the current major version.

- **Major Version**: Significant changes, possibly breaking backward compatibility.
- **Minor Version**: New features, enhancements, and backward-compatible bug fixes.
- **Patch Version**: Backward-compatible bug fixes only.

## Contact

For any questions or additional information regarding security, please contact miruro@proton.me


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
  - package-ecosystem: 'npm' # See documentation for possible values
    directory: '/' # Location of package manifests
    schedule:
      interval: 'weekly'


================================================
FILE: .gitignore
================================================
# Dependency directories
node_modules/
jspm_packages/
.pnpm-store/

# Vite and Build outputs
dist/
dist-ssr/
build/
out/
.temp/

# Bun
.bun/

# TypeScript cache
*.tsbuildinfo

# Compiled binary addons (node-gyp)
build/Release/

# Editor directories and files
.idea/
.vscode/
*.sublime-workspace
*.sublime-project

# Operating System generated files
.DS_Store
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Package manager lock files
package-lock.json
yarn.lock
pnpm-lock.yaml
bun.lockb

# dotenv environment variables files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Caches and logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.cache/
.eslintcache
.stylelintcache

# Temporary files
*.tmp
*~
*.bak
*.sw?
*.swo
*.swn
*.swp
*.orig


================================================
FILE: .prettierrc
================================================
{
  "semi": true,
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "endOfLine": "lf",
  "plugins": ["prettier-plugin-tailwindcss"],
  "arrowParens": "always",
  "bracketSpacing": true,
  "jsxSingleQuote": true,
  "bracketSameLine": false,
  "htmlWhitespaceSensitivity": "css",
  "vueIndentScriptAndStyle": false,
  "embeddedLanguageFormatting": "auto",
  "quoteProps": "as-needed",
  "overrides": [
    {
      "files": "*.{ts,tsx}",
      "options": {
        "parser": "typescript"
      }
    },
    {
      "files": "*.html",
      "options": {
        "printWidth": 120
      }
    }
  ]
}


================================================
FILE: LICENSE
================================================
              CUSTOM ATTRIBUTION-NONCOMMERCIAL (CUSTOM BY-NC) LICENSE

                    © 2024 Miruro no Kuon. All Rights Reserved.

This software, licensed under the Custom BY-NC License, grants users the freedom to
share, copy, distribute, and transmit the code, as well as to adapt, modify, transform,
and build upon it. However, this freedom comes with specific terms:

By using this software, you are required to provide appropriate credit to the original
author(s) by including a visible and clear attribution in any distribution or derivative
work. Attribution is a fundamental condition for utilizing or redistributing the code.

Furthermore, this license explicitly prohibits the commercial use of the code or any
derivative work based on it. Commercial purposes include, but are not limited to, activities
that involve the sale, licensing, or exploitation of the software for financial gain.

If you intend to use the code for commercial use or any purpose not explicitly covered by
this license, please seek explicit permission from the author(s) by contacting them directly.
The author(s) reserves the right to grant or deny permission based on individual circumstances.

It is essential to note that this license does not grant any rights beyond what is explicitly
stated here. Any use of the code not explicitly allowed by this license is strictly prohibited.

For inquiries, additional permissions, or clarification on specific terms, please contact the author(s).


================================================
FILE: README.md
================================================
<h1 align="center">
MIRURO
</h1>

<p align="center">
  <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/typescript-%23007acc.svg?style=for-the-badge&logo=typescript&logoColor=%23ffffff"/></a>
  <a href="https://reactjs.org/"><img src="https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB"/></a>
  <a href="https://vitejs.dev/"><img src="https://img.shields.io/badge/vite-%239269fe.svg?style=for-the-badge&logo=vite&logoColor=yellow&border"/></a>
</p>

<p align="center">
  <a href="https://styled-components.com/"><img src="https://img.shields.io/badge/styled--components-742b66.svg?style=for-the-badge&logo=styled-components&logoColor=#e682d5"/></a>
</p>

<p align="center">
  <a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-339933.svg?style=for-the-badge&logo=node.js&logoColor=white"/></a>
  <a href="https://bunjs.dev/"><img src="https://img.shields.io/badge/Bun.js-febbd0.svg?style=for-the-badge&logo=bun&logoColor=f9f1e1"/></a>
  <a href="https://vercel.com/"><img src="https://img.shields.io/badge/vercel-%23000000.svg?style=for-the-badge&logo=vercel&logoColor=white"/></a>
  <a href="https://www.cloudflare.com/"><img src="https://img.shields.io/badge/cloudflare-white.svg?style=for-the-badge&logo=cloudflare&logoColor=orange"/></a>
</p>

<p align="center">
  <a href="https://www.miruro.com" target="_blank">
    <img src="https://raw.githubusercontent.com/Miruro-no-kuon/Miruro/main/src/assets/miruro-transparent-white.png" alt="Logo" width="200"/>
  </a>
</p>

<p align="center">
  <a href="https://github.com/Miruro-no-Kuon/Miruro/fork">
    <img src="https://img.shields.io/github/forks/Miruro-no-Kuon/Miruro?style=social" alt="fork"/>
  </a>
  <a href="https://github.com/Miruro-no-Kuon/Miruro/stargazers">
    <img src="https://img.shields.io/github/stars/Miruro-no-Kuon/Miruro?style=social" alt="stars"/>
  </a>
</p>

## What is Miruro?

Welcome to **Miruro**, your premier destination for all things anime! Explore a comprehensive collection of high-definition anime with a seamless and user-friendly interface powered by **[Consumet](https://github.com/consumet)**.

Built using **React** and **Vite**, Miruro offers a cutting-edge, minimalist design that ensures both fast loading times and smooth navigation. Whether you're looking for the latest anime series or classic favorites, Miruro has you covered with an ad-free streaming experience that supports both English subtitles and dubbed versions. Additionally, you can download individual episodes without the hassle of creating an account, making your viewing experience as convenient as possible.

<details>
<summary>Features [View More]</summary>

### General

- Sub/Dub Anime support
- User-friendly & Mobile responsive
- Anilist Sync
- Light/Dark theme
- Continue Watching Section

### Watch Page

- **Player**
  - Autoplay next episode
  - Skip op/ed button

</details>

## Installation and Local Development

### 1. Clone this repository using

```bash
git clone https://github.com/Miruro-no-kuon/Miruro.git
```

```bash
cd Miruro
```

### 2. Installation

### Basic Pre-Requisites

> [!TIP]
> This platform is built on [Node.js](https://nodejs.org/) and utilizes [Bun](https://bun.sh/) to ensure the quickest response times achievable. While `npm` can also be used, the commands for npm would mirror those of Bun, simply substituting the specific commands accordingly.

> Bun is now available on **Windows**, **Linux**, and **macOS**. Below are the installation commands for each operating system.

### Install Bun

- Linux & macOS

```bash
curl -fsSL https://bun.sh/install | bash
```

- Windows

```powershell
powershell -c "irm bun.sh/install.ps1 | iex"
```

### Verify installations

- Check that both Node.js and Bun are correctly installed by running.

```bash
node -v
bun -v
```

### Install Dependencies

- You can use Bun to install dependencies quickly. If you prefer, `npm` can also be used with equivalent commands.

```bash
bun install
```

### Copy `.env.example` into `.env.local` in the root folder

- `.env.local` & `.env` are both viable options, you can also set
  `.env.test.local`,
  `.env.development.local` or
  `.env.production.local`

```bash
cp .env.example .env.local
```

### 3. Run on development &/or production (npm also works)

- Run on development mode

```bash
bun run dev
```

- Run on production mode

```bash
bun start
```

## Self-Hosting Notice

> [!CAUTION]
> Self-hosting this application is **strictly limited to personal use only**. Commercial utilization is **prohibited**, and the inclusion of advertisements on your self-hosted website may lead to serious consequences, including **potential site takedown measures**. Ensure compliance to avoid any legal or operational issues.

## License

This project is governed by a Custom BY-NC License. What does this entail? Simply put, you are permitted to utilize, distribute, and modify the code for non-commercial purposes. However, it is imperative that due credit is accorded to our platform. Any commercial utilization of this code is strictly prohibited. For comprehensive details, please refer to the [LICENSE](LICENSE) file. Should you have inquiries or require special permissions, do not hesitate to contact us.

<!-- ## Found a Bug?

Uh-oh, looks like you stumbled upon a bug? No worries, we're here to squash it! Just head over to our [**issues**](https://github.com/Miruro-no-kuon/Miruro-no-Kuon/issues) section on GitHub and let us know what's up.

## Support & Contributions

### Want to Help Out?

- ✴️ [**Star this project**](https://github.com/Miruro-no-kuon/Miruro)

- Feel free to contribute to this project! Whether you're an experienced developer or have been in the field for a while, your help is valuable. -->

## Star History

[![Stargazers over time](https://starchart.cc/Miruro-no-kuon/Miruro.svg?variant=adaptive)](https://starchart.cc/Miruro-no-kuon/Miruro)


================================================
FILE: api/exchange-token.ts
================================================
import axios from 'axios';
import type { VercelRequest, VercelResponse } from '@vercel/node';

export default async function exchangeAccessToken(req: VercelRequest, res: VercelResponse) {
  if (req.method !== 'POST') {
    res.status(405).send('Method Not Allowed');
    return;
  }

  const { code } = req.body;
  if (!code) {
    return res.status(400).send('Authorization code is required');
  }

  const payload = {
    client_id: process.env.VITE_CLIENT_ID,
    client_secret: process.env.VITE_CLIENT_SECRET,
    code,
    grant_type: 'authorization_code',
    redirect_uri: process.env.VITE_REDIRECT_URI,
  };

  const url = 'https://anilist.co/api/v2/oauth/token';

  try {
    const response = await axios.post(url, payload, {
      headers: {
        'Content-Type': 'application/json',
        'Accept-Encoding': 'identity',
      },
    });

    if (response.data.access_token) {
      res.json({ accessToken: response.data.access_token });
    } else {
      throw new Error('Access token not found in the response');
    }
  } catch (error: unknown) {
    // First, check if it's an instance of Error
    if (error instanceof Error) {
      // Now you can safely read the message property
      const message = error.message;
      // If it's an axios error, it may have a response object
      const details = axios.isAxiosError(error) && error.response ? error.response.data : message;
      res.status(500).json({
        error: 'Failed to exchange token',
        details,
      });
    } else {
      // If it's not an Error object, handle it as a generic error
      res.status(500).json({
        error: 'Failed to exchange token',
        details: 'An unknown error occurred',
      });
    }
  }
}


================================================
FILE: functions/exchange-token.js
================================================
export async function onRequest(context) {
  const url = new URL(context.request.url);
  const path = url.pathname;

  if (path === '/exchange-token') {
    return handleTokenExchange(context);
  } else {
    return new Response('Not found', { status: 404 });
  }
}

async function handleTokenExchange(context) {
  const request = context.request;
  if (request.method !== 'POST') {
    return new Response('Method Not Allowed', { status: 405 });
  }

  try {
    const data = await request.json();
    const code = data.code;
    if (!code) {
      return new Response('Authorization code is required', { status: 400 });
    }

    const payload = {
      client_id: context.env.VITE_CLIENT_ID,
      client_secret: context.env.VITE_CLIENT_SECRET,
      code,
      grant_type: 'authorization_code',
      redirect_uri: context.env.VITE_REDIRECT_URI,
    };

    const apiResponse = await fetch('https://anilist.co/api/v2/oauth/token', {
      method: 'POST',
      body: JSON.stringify(payload),
      headers: {
        'Content-Type': 'application/json',
        'Accept-Encoding': 'identity',
      },
    });

    const responseBody = await apiResponse.text();
    if (!apiResponse.ok) {
      console.error('API response error:', responseBody);
      throw new Error(`API responded with status: ${apiResponse.status}`);
    }

    const responseData = JSON.parse(responseBody);
    if (responseData.access_token) {
      return new Response(
        JSON.stringify({ accessToken: responseData.access_token }),
        {
          headers: { 'Content-Type': 'application.json' },
        },
      );
    } else {
      console.error(
        'Access token not found in the API response:',
        responseBody,
      );
      throw new Error('Access token not found in the response');
    }
  } catch (error) {
    console.error(`Error when handling token exchange: ${error}`);
    return new Response(
      JSON.stringify({
        error: 'Failed to exchange token',
        details: error.message,
      }),
      {
        status: 500,
        headers: { 'Content-Type': 'application.json' },
      },
    );
  }
}


================================================
FILE: index.html
================================================
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- Disable zooming -->
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
    <meta
      name="description"
      content="Miruro no Kuon - Anime Streaming Website with minimal UI 🍜. Enjoy HD fast streaming of your favorite anime, manga reading, and explore anime-related forums on miruro.tv, miruro.online, miruro.com Discover a world of anime entertainment at Miruro no Kuon!"
    />
    <!-- Link to web app manifest -->
    <link rel="manifest" href="/manifest.json" />
    <!-- Main Favicon -->
    <link rel="icon" type="image/png" href="/android-chrome-512x512.png" />
    <!-- Apple touch Favicon -->
    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
    <!-- Change appearance of status bar style for Apple devices -->
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
    <!-- Import Google Material Icons -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
    <!-- Title of the website -->
    <title>Miruro | Watch Anime Online, Free Anime Streaming</title>
    <!-- Internal styles -->
    <link rel="stylesheet" href="/src/styles/globals.css" />
    <!-- Animations -->
    <link rel="stylesheet" href="/src/styles/animations.css" />
    <!-- Dark/Light themes -->
    <link rel="stylesheet" href="/src/styles/themes.css" />
  </head>
  <body data-simplebar>
    <!-- Root element for the React app -->
    <div id="root"></div>
    <!-- Main JavaScript file -->
    <script type="module" src="/src/main.tsx"></script>
    <!-- JavaScript to toggle dark mode -->
    <script>
      // Check user's theme preference or default to system preference
      const themePreference =
        localStorage.getItem('themePreference') ||
        (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
      // Get the root HTML element
      const rootElement = document.documentElement;
      // Apply dark mode class if preferred theme is dark
      if (themePreference === 'dark') {
        rootElement.classList.add('dark-mode');
      } else {
        rootElement.classList.remove('dark-mode');
      }
    </script>
  </body>
</html>


================================================
FILE: package.json
================================================
{
  "name": "miruro_no_kuon",
  "private": true,
  "version": "0.5.2",
  "type": "module",
  "scripts": {
    "dev": "vite --host",
    "build": "vite build",
    "preview": "vite preview",
    "start": "vite build && bun run ./server/server.ts",
    "lint": "eslint . --ext js,jsx,ts,tsx --report-unused-disable-directives --max-warnings 0",
    "format": "prettier --write ."
  },
  "dependencies": {
    "@apollo/client": "^3.10.1",
    "@fortawesome/fontawesome-svg-core": "^6.5.1",
    "@fortawesome/free-solid-svg-icons": "^6.5.1",
    "@fortawesome/react-fontawesome": "^0.2.0",
    "@types/cors": "^2.8.17",
    "@types/express": "^4.17.21",
    "@types/lodash": "^4.17.0",
    "@types/uuid": "^9.0.8",
    "@vercel/analytics": "^1.2.2",
    "@vidstack/react": "next",
    "axios": "^1.6.8",
    "body-parser": "^1.20.2",
    "eslint": "8.x",
    "express": "^4.19.2",
    "graphql": "^16.8.1",
    "lodash": "^4.17.21",
    "lru-cache": "latest",
    "prettier": "^3.2.5",
    "prettier-plugin-tailwindcss": "^0.5.14",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-ga4": "^2.1.0",
    "react-icons": "^5.0.1",
    "react-router-dom": "latest",
    "react-select": "^5.8.0",
    "styled-components": "^6.1.0",
    "swiper": "^11.0.7",
    "typescript": "^5.0.0",
    "uuid": "^9.0.1",
    "wrangler": "^3.52.0"
  },
  "devDependencies": {
    "@types/bun": "latest",
    "@types/react": "^18.2.55",
    "@types/react-dom": "^18.2.19",
    "@types/react-slick": "^0.23.13",
    "@typescript-eslint/eslint-plugin": "^7.4.0",
    "@typescript-eslint/parser": "^7.4.0",
    "@vercel/node": "^3.0.27",
    "@vitejs/plugin-react": "^4.2.1",
    "eslint-plugin-react": "^7.32.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.5",
    "vite": "5.x"
  },
  "module": "index.ts",
  "peerDependencies": {
    "typescript": "^5.0.0"
  }
}


================================================
FILE: public/manifest.json
================================================
{
  "name": "Miruro",
  "short_name": "Miruro",
  "description": "Watch HD Anime for Free",
  "lang": "en",
  "background_color": "#080808",
  "display": "standalone",
  "orientation": "standalone",
  "scope": "/",
  "start_url": "/",
  "screenshots": [
    {
      "src": "/preview.png",
      "sizes": "960x540",
      "type": "image/png",
      "form_factor": "wide",
      "label": "Wonder Widgets"
    },
    {
      "src": "/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png",
      "form_factor": "narrow",
      "label": "Icon Widget"
    }
  ],
  "icons": [
    {
      "src": "/favicon-16x16.png",
      "sizes": "16x16",
      "type": "image/png"
    },
    {
      "src": "/favicon-32x32.png",
      "sizes": "32x32",
      "type": "image/png"
    },
    {
      "src": "/favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
    {
      "src": "/apple-touch-icon.png",
      "sizes": "180x180",
      "type": "image/png"
    },
    {
      "src": "/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ]
}


================================================
FILE: renovate.json
================================================
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"]
}


================================================
FILE: robots.txt
================================================
User-agent: *
Disallow: /


================================================
FILE: server/README.md
================================================
# Server README

This README provides an overview of the `server.ts` file, which is an Express server designed to serve static files, handle error logging, and provide instructions for running it using the Bun JavaScript runtime.

## `server.ts` Overview ℹ️

The `server.ts` file includes the following features:

- Express server setup
- Static file serving to serve files from the `dist` directory 📂
- Error logging for server-side errors 📝

## Installation and Running 🛠️

To run the server, follow these steps:

1. Clone this repository to your local machine 📦

2. Install project dependencies:

   ```bash
   bun install
   ```

3. Start the server:

   ```bash
   bun run server.ts
   ```

- The server will start running on <http://localhost:${PORT}> by default. You can modify the `PORT` .env variable to change the port in `server.ts` as needed.


================================================
FILE: server/server.ts
================================================
import express from 'express';
import axios from 'axios';
import path from 'path';
import os from 'os';
import bodyParser from 'body-parser';

const app = express();

// Environment Configuration
const PORT = process.env.VITE_PORT || 5173;
const {
  VITE_CLIENT_ID: CLIENT_ID,
  VITE_CLIENT_SECRET: CLIENT_SECRET,
  VITE_REDIRECT_URI: REDIRECT_URI,
} = process.env;

// Directory paths for static assets
const DIST_DIR = path.join(__dirname, '../dist');
const INDEX_FILE = path.join(DIST_DIR, 'index.html');

// Middleware for static assets and JSON parsing
app.use(express.static(DIST_DIR));
app.use(express.json());
app.use(bodyParser.json());

// API Endpoint for exchanging authorization token
const apiEndpoint = '/api/exchange-token';
app.post(apiEndpoint, async (req, res) => {
  const { code } = req.body;
  if (!code) {
    console.error('Authorization code is missing');
    return res.status(400).send('Authorization code is required');
  }

  const payload = {
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
    code,
    grant_type: 'authorization_code',
    redirect_uri: REDIRECT_URI,
  };
  const url = 'https://anilist.co/api/v2/oauth/token';

  // Logging the request details
  console.log('Sending request to AniList API');
  console.log('URL:', url);
  console.log('Payload:', payload);

  try {
    const response = await axios.post(url, payload, {
      headers: {
        'Content-Type': 'application/json',
        'Accept-Encoding': 'identity',
      },
    });

    // Logging the response details
    console.log('Received response from AniList API');
    console.log('Response Status:', response.status);
    console.log('Response Data:', response.data);

    if (response.data.access_token) {
      res.json({ accessToken: response.data.access_token });
    } else {
      throw new Error('Access token not found in the response');
    }
  } catch (error) {
    console.error('Error during token exchange:', error.message);
    if (error.response) {
      console.error('Error Status:', error.response.status);
      console.error('Error Details:', error.response.data);
    }
    res.status(500).json({
      error: 'Failed to exchange token',
      details: error.response?.data || error.message,
    });
  }
});

// Serve the main index.html for any non-API requests
app.get('*', (req, res) => {
  res.sendFile(INDEX_FILE, (err) => {
    if (err) {
      console.error('Error serving index.html:', err);
      res.status(500).send('An error occurred while serving the application');
    }
  });
});

// Utility to get the first non-internal IPv4 address
function getLocalIpAddress() {
  const networkInterfaces = os.networkInterfaces();
  for (const networkInterface of Object.values(networkInterfaces)) {
    const found = networkInterface?.find(
      (net) => net.family === 'IPv4' && !net.internal,
    );
    if (found) return found.address;
  }
  return 'localhost';
}

// Starting the server
app.listen(PORT, () => {
  const ipAddress = getLocalIpAddress();
  console.log(
    `Server is running at:\n- Localhost: http://localhost:${PORT}\n- Local IP: http://${ipAddress}:${PORT}`,
  );
});


================================================
FILE: src/App.tsx
================================================
import {
  BrowserRouter as Router,
  Routes,
  Route,
  useLocation,
} from 'react-router-dom';
import { useEffect } from 'react';
import {
  Profile,
  Navbar,
  ThemeProvider,
  Footer,
  Home,
  Watch,
  Search,
  Page404,
  About,
  PolicyTerms,
  ShortcutsPopup,
  ScrollToTop,
  usePreserveScrollOnReload,
  Callback,
  ApolloClientProvider,
  Settings,
  SettingsProvider,
} from './index';
import { register } from 'swiper/element/bundle';
import { Analytics } from '@vercel/analytics/react';
import { AuthProvider } from './client/useAuth';
import ReactGA from 'react-ga4';

register();

function App() {
  usePreserveScrollOnReload();
  const measurementId = import.meta.env.VITE_GA_MEASUREMENT_ID;

  useEffect(() => {
    if (measurementId) {
      ReactGA.initialize(measurementId);
    }
  }, [measurementId]);

  return (
    <ApolloClientProvider>
      <Router>
        <AuthProvider>
          <ThemeProvider>
            <SettingsProvider>
              <Navbar />
              <ShortcutsPopup />
              <ScrollToTop />
              <TrackPageViews />
              <div style={{ minHeight: '35rem' }}>
                <Routes>
                  <Route path='/' element={<Home />} />
                  <Route path='/home' element={<Home />} />
                  <Route path='/search' element={<Search />} />
                  <Route path='/watch/:animeId' element={<Watch />} />
                  <Route
                    path='/watch/:animeId/:animeTitle/:episodeNumber'
                    element={<Watch />}
                  />
                  <Route path='/profile' element={<Profile />} />
                  <Route path='/profile/settings' element={<Settings />} />
                  <Route path='/about' element={<About />} />
                  <Route path='/pptos' element={<PolicyTerms />} />
                  <Route path='/callback' element={<Callback />} />
                  <Route path='*' element={<Page404 />} />
                </Routes>
              </div>
              <Footer />
            </SettingsProvider>
          </ThemeProvider>
        </AuthProvider>
      </Router>
      <Analytics />
    </ApolloClientProvider>
  );
}

function TrackPageViews() {
  const { pathname } = useLocation();

  useEffect(() => {
    ReactGA.send({ hitType: 'pageview', page: pathname });
  }, [pathname]);

  return null;
}

export default App;


================================================
FILE: src/client/ApolloClient.tsx
================================================
// apolloClient.ts
import {
  ApolloClient,
  InMemoryCache,
  createHttpLink,
  ApolloProvider,
  makeVar,
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
import axios from 'axios';
import { buildAuthUrl, fetchUserData, UserData } from '../index';
import { ReactNode, useEffect } from 'react';

// Reactive variables for user authentication state
const isLoggedInVar = makeVar<boolean>(false);
const userDataVar = makeVar<UserData | null>(null);

const httpLink = createHttpLink({
  uri: 'https://graphql.anilist.co', // Update to your GraphQL server URL
});

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('accessToken');
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    },
  };
});

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    graphQLErrors.forEach(({ message, locations, path }) =>
      console.error(
        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
      ),
    );
  }

  if (networkError) console.error(`[Network error]: ${networkError}`);
});

const client = new ApolloClient({
  link: errorLink.concat(authLink.concat(httpLink)),
  cache: new InMemoryCache(),
});

// Functions for handling authentication
function login() {
  axios
    .get('/get-csrf-token')
    .then((response) => {
      const csrfToken = response.data.csrfToken;
      const authUrl = buildAuthUrl(csrfToken);
      window.location.href = authUrl;
    })
    .catch((error) => {
      console.error('Error fetching CSRF token or building auth URL:', error);
    });
}

function logout() {
  localStorage.removeItem('accessToken');
  isLoggedInVar(false);
  userDataVar(null);
  window.location.href = '/profile'; // Adjust as necessary
  window.dispatchEvent(new CustomEvent('authUpdate'));
}

function handleAuthUpdate() {
  const token = localStorage.getItem('accessToken');
  if (token) {
    fetchUserData(token)
      .then((data) => {
        userDataVar(data);
        isLoggedInVar(true);
      })
      .catch((err) => {
        console.error('Failed to fetch user data:', err);
        logout(); // Ensures clean state on failure
      });
  } else {
    isLoggedInVar(false);
    userDataVar(null);
  }
}

export const ApolloClientProvider = ({ children }: { children: ReactNode }) => {
  useEffect(() => {
    window.addEventListener('authUpdate', handleAuthUpdate);
    handleAuthUpdate();
    return () => {
      window.removeEventListener('authUpdate', handleAuthUpdate);
    };
  }, []);

  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

export {
  client as defaultApolloClient,
  login,
  logout,
  isLoggedInVar,
  userDataVar,
};


================================================
FILE: src/client/authService.ts
================================================
// src/services/authService.ts
import axios from 'axios';
import { v4 as uuidv4 } from 'uuid'; // Ensure uuid is installed via npm or yarn
import { UserData, MediaListStatus } from '../index'; // Assuming this is the correct path
import { useQuery, gql } from '@apollo/client';

// Constants for AniList OAuth, ideally should be loaded from environment variables
const clientId = import.meta.env.VITE_CLIENT_ID || 'default_client_id';
const clientSecret =
  import.meta.env.VITE_CLIENT_SECRET || 'default_client_secret';
const redirectUri = import.meta.env.VITE_REDIRECT_URI || 'default_redirect_uri';

/**
 * Generates a new CSRF token for each session
 * @returns {string} A UUID v4 CSRF token
 */
export const generateCsrfToken = (): string => {
  return uuidv4();
};

/**
 * Builds the authorization URL with CSRF protection
 * @param {string} csrfToken CSRF token for state parameter
 * @returns {string} URL to redirect user to AniList OAuth login page
 */

// authService.ts
export const buildAuthUrl = (csrfToken: string): string => {
  const scope = encodeURIComponent('');
  const state = encodeURIComponent(csrfToken);
  const encodedRedirectUri = encodeURIComponent(redirectUri);

  return `https://anilist.co/api/v2/oauth/authorize?client_id=${clientId}&scope=${scope}&response_type=code&redirect_uri=${encodedRedirectUri}&state=${state}`;
};

/**
 * Requests an access token from AniList using the authorization code
 * @param {string} code The authorization code received from AniList after user consent
 * @returns {Promise<string>} A promise that resolves to the access token
 */
export const getAccessToken = async (code: string): Promise<string> => {
  const url = 'https://anilist.co/api/v2/oauth/token';
  const payload = {
    client_id: clientId,
    client_secret: clientSecret,
    code,
    grant_type: 'authorization_code',
    redirect_uri: redirectUri,
  };

  try {
    const response = await axios.post(url, payload);
    if (response.data.access_token) {
      return response.data.access_token;
    } else {
      throw new Error('Access token not found in the response');
    }
  } catch (error) {
    console.error('Error obtaining access token:', error);
    throw new Error('Failed to obtain access token');
  }
};

// src/services/authService.js
export const fetchUserData = async (accessToken: string): Promise<UserData> => {
  try {
    const response = await axios.post(
      'https://graphql.anilist.co',
      {
        query: `
          query {
              Viewer {
                  id
                  name
                  avatar {
                      large
                  }
                  statistics {
                      anime {
                          count
                          episodesWatched
                          meanScore
                          minutesWatched
                      }
                  }
              }
          }
      `,
      },
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
          Accept: 'application/json',
        },
      },
    );
    return response.data.data.Viewer; // Ensure the structure matches UserData interface
  } catch (error) {
    console.error('Error fetching user data:', error);
    throw new Error('Failed to fetch user data');
  }
};

const GET_USER_ANIME_LIST = gql`
  query GetUserAnimeList($username: String!, $status: MediaListStatus!) {
    MediaListCollection(
      userName: $username
      type: ANIME
      status: $status
      sort: UPDATED_TIME_DESC
    ) {
      lists {
        entries {
          media {
            id
            format
            title {
              romaji
              english
            }
            coverImage {
              large
              color
            }
            status
            episodes
            startDate {
              year
              month
              day
            }
            averageScore
            genres
          }
        }
      }
    }
  }
`;

export const useUserAnimeList = (username: string, status: MediaListStatus) => {
  const { data, loading, error } = useQuery(GET_USER_ANIME_LIST, {
    variables: { username, status },
    skip: !username || !status, // Ensuring not to proceed without necessary variables
  });

  return {
    animeList: data?.MediaListCollection,
    loading,
    error,
  };
};


================================================
FILE: src/client/useAuth.tsx
================================================
import {
  createContext,
  useContext,
  useState,
  useEffect,
  ReactNode,
} from 'react';
import axios from 'axios';
import { UserData } from './userInfoTypes'; // Adjust the path as necessary
import { fetchUserData, buildAuthUrl } from './authService'; // Adjust the path as necessary

type AuthContextType = {
  isLoggedIn: boolean;
  userData: UserData | null;
  username: string | null; // This property must be handled
  login: () => void;
  logout: () => void;
};

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [userData, setUserData] = useState<UserData | null>(null);
  const [authLoading, setAuthLoading] = useState(true); // Add a loading state for auth status

  // Calculate username from userData
  const username = userData ? userData.name : null; // Assuming 'username' is a property of UserData

  useEffect(() => {
    const token = localStorage.getItem('accessToken');
    if (token) {
      fetchUserData(token)
        .then((data) => {
          setUserData(data);
          setIsLoggedIn(true);
          setAuthLoading(false); // Set loading to false once user data is fetched
        })
        .catch((err) => {
          console.error('Failed to fetch user data:', err);
          logout(); // Ensures clean state on failure
          setAuthLoading(false); // Ensure loading state is handled even in error
        });
    } else {
      setAuthLoading(false); // If no token, ensure loading is set to false
    }
  }, []);

  const login = async () => {
    try {
      const response = await axios.get('/get-csrf-token');
      const csrfToken = response.data.csrfToken;
      const authUrl = buildAuthUrl(csrfToken);
      window.location.href = authUrl;
    } catch (error) {
      console.error('Error fetching CSRF token or building auth URL:', error);
    }
  };

  const logout = () => {
    localStorage.removeItem('accessToken');
    setIsLoggedIn(false);
    setUserData(null);
    setAuthLoading(true); // Reset auth loading state on logout
    window.location.href = '/profile';
    window.dispatchEvent(new CustomEvent('authUpdate'));
  };

  // Prevent rendering of children if authentication status is unknown
  if (authLoading) {
    return null; // Or you could return a loading spinner or a similar component
  }

  return (
    <AuthContext.Provider
      value={{ isLoggedIn, userData, username, login, logout }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};


================================================
FILE: src/client/userInfoTypes.ts
================================================
// Enums or types for sorting, assuming sorting options are known
type UserStatisticsSort =
  | 'COUNT_ASC'
  | 'COUNT_DESC'
  | 'SCORE_ASC'
  | 'SCORE_DESC';
export interface UserData {
  name: string;
  avatar: {
    large: string;
  };
  statistics: UserStatistics;
}

export enum MediaListStatus {
  CURRENT = 'CURRENT',
  PLANNING = 'PLANNING',
  COMPLETED = 'COMPLETED',
  REPEATING = 'REPEATING',
  PAUSED = 'PAUSED',
  DROPPED = 'DROPPED',
}

export interface UserStatistics {
  anime: AnimeMangaStatistics;
  manga: AnimeMangaStatistics;
}

export interface AnimeMangaStatistics {
  count: number;
  meanScore: number;
  standardDeviation: number;
  minutesWatched: number; // For anime
  episodesWatched: number; // For anime
  chaptersRead: number; // For manga
  volumesRead: number; // For manga
  formats: UserFormatStatistic[];
  statuses: UserStatusStatistic[];
  scores: UserScoreStatistic[];
  lengths: UserLengthStatistic[];
  releaseYears: UserReleaseYearStatistic[];
  startYears: UserStartYearStatistic[];
  genres: UserGenreStatistic[];
  tags: UserTagStatistic[];
  countries: UserCountryStatistic[];
  voiceActors: UserVoiceActorStatistic[];
  staff: UserStaffStatistic[];
  studios: UserStudioStatistic[];
}

export interface StatisticLimitSort {
  limit: number;
  sort: UserStatisticsSort[];
}

export interface UserFormatStatistic {
  format: string;
  count: number;
}

export interface UserStatusStatistic {
  status: string;
  count: number;
}

export interface UserScoreStatistic {
  score: number;
  count: number;
}

export interface UserLengthStatistic {
  length: string;
  count: number;
}

export interface UserReleaseYearStatistic {
  year: number;
  count: number;
}

export interface UserStartYearStatistic {
  year: number;
  count: number;
}

export interface UserGenreStatistic {
  genre: string;
  count: number;
}

export interface UserTagStatistic {
  tag: string;
  count: number;
}

export interface UserCountryStatistic {
  country: string;
  count: number;
}

export interface UserVoiceActorStatistic {
  voiceActorId: number;
  name: string;
  count: number;
  language: string;
}

export interface UserStaffStatistic {
  staffId: number;
  name: string;
  role: string;
  count: number;
}

export interface UserStudioStatistic {
  studioId: number;
  name: string;
  count: number;
}


================================================
FILE: src/components/Cards/CardGrid.tsx
================================================
import React, { useEffect, useCallback } from 'react';
import styled from 'styled-components';
import { CardItem, Anime } from '../../index';

interface CardGridProps {
  animeData: Anime[];
  hasNextPage: boolean;
  onLoadMore: () => void;
}

export const CardGrid: React.FC<CardGridProps> = ({
  animeData,
  hasNextPage,
  onLoadMore,
}) => {
  const handleLoadMore = useCallback(() => {
    if (hasNextPage) {
      onLoadMore();
    }
  }, [hasNextPage, onLoadMore]);

  useEffect(() => {
    const handleScroll = () => {
      const windowHeight = window.innerHeight;
      const documentHeight = document.documentElement.offsetHeight;
      const scrollTop =
        document.documentElement.scrollTop || document.body.scrollTop;

      let threshold = 0;

      if (window.innerWidth <= 450) {
        threshold = 1;
      }

      if (windowHeight + scrollTop >= documentHeight - threshold) {
        handleLoadMore();
      }
    };

    window.addEventListener('scroll', handleScroll);
    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, [handleLoadMore, hasNextPage]);

  return (
    <StyledCardGrid>
      {animeData.map((anime) => (
        <CardItem key={anime.id} anime={anime} />
      ))}
    </StyledCardGrid>
  );
};

export const StyledCardGrid = styled.div`
  margin: 0 auto;
  display: grid;
  position: relative;
  grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
  grid-template-rows: auto;
  gap: 2rem;
  transition: 0s;

  @media (max-width: 1000px) {
    gap: 1.5rem;
  }

  @media (max-width: 800px) {
    grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
    gap: 1rem;
  }

  @media (max-width: 450px) {
    grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
    gap: 0.8rem;
  }
`;


================================================
FILE: src/components/Cards/CardItem.tsx
================================================
import React, { useEffect, useState, useMemo } from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { SkeletonCard, StatusIndicator, type Anime } from '../../index'; // Adjust the import path to correctly point to your index.ts location
import { FaPlay } from 'react-icons/fa'; // For the play icon
import { TbCards } from 'react-icons/tb';
import { FaStar, FaCalendarAlt } from 'react-icons/fa';

const StyledCardWrapper = styled(Link)`
  color: var(--global-text);
  animation: slideUp 0.4s ease;
  text-decoration: none;
  &:hover,
  &:active,
  &:focus {
    z-index: 2;
  }
`;

const StyledCardItem = styled.div`
  width: 100%;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  transform: scale(1);
  transition: 0.2s ease-in-out;
`;

const ImageDisplayWrapper = styled.div`
  transition: 0.2s ease-in-out;
  @media (min-width: 501px) {
    &:hover,
    &:active,
    &:focus {
      transform: translateY(-10px);
    }
  }
`;

const AnimeImage = styled.div`
  position: relative;
  text-align: left;
  overflow: hidden;
  border-radius: var(--global-border-radius);
  padding-top: calc(100% * 184 / 133);
  background: var(--global-card-bg);
  box-shadow: 2px 2px 10px var(--global-card-shadow);
  transition: background-color 0.2s ease-in-out;
  animation: slideUp 0.5s ease-in-out;
`;

const PlayIcon = styled(FaPlay)`
  position: absolute;
  top: 50%;
  left: 50%;
  color: #fff;
  transform: translate(-50%, -50%);
  font-size: 2rem;
  opacity: 0;
  transition: opacity 0.3s ease;
  z-index: 1;
`;

const ImageWrapper = styled.div`
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;

  img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border-radius: var(--global-border-radius);
    transition: 0.3s ease-in-out;
    transition: filter 0.3s ease-in-out; // Ensure the filter transition is smooth
  }

  &:hover img {
    filter: brightness(0.5); // Decrease brightness to 60% on hover
  }

  &:hover ${PlayIcon} {
    opacity: 1;
  }
`;

const TitleContainer = styled.div<{ $isHovered: boolean }>`
  display: flex;
  align-items: center;
  padding: 0.5rem;
  margin-top: 0.35rem;
  gap: 0.4rem;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  transition: background 0.2s ease;

  &:hover,
  &:active,
  &:focus {
    background: var(--global-card-title-bg);
  }
`;

const Title = styled.h5<{ $isHovered: boolean; color?: string }>`
  margin: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: ${(props) => (props.$isHovered ? props.color : 'var(--title-color)')};
  transition: 0.2s ease-in-out;

  @media (max-width: 500px) {
    font-size: 0.7rem;
  }
`;

const ImgDetail = React.memo(styled.p<{ $isHovered: boolean; color?: string }>`
  animation: slideRight 0.2s ease-in-out;
  position: absolute;
  bottom: 0;
  margin: 0.25rem;
  padding: 0.2rem;
  font-size: 0.8rem;
  font-weight: bold;
  color: ${(props) => props.color};
  opacity: 0.9;
  background-color: var(--global-button-shadow);
  border-radius: var(--global-border-radius);
  backdrop-filter: blur(10px);
  transition: 0.2s ease-in-out;
`);

const CardDetails = styled.div`
  animation: slideRight 0.4s ease-in-out;
  width: 100%;
  font-family: Arial;
  font-weight: bold;
  font-size: 0.75rem;
  color: rgba(102, 102, 102, 0.65);
  margin: 0;
  display: flex;
  align-items: center;
  padding: 0.25rem 0rem;
  gap: 0.5rem;
  white-space: nowrap;
  overflow: hidden; // Ensures that overflow text is hidden
  text-overflow: ellipsis; // Adds an ellipsis to indicate that text has been cut off
  svg {
    margin-bottom: 0.12rem;
    margin-right: -0.4rem;
  }
`;

export const CardItem: React.FC<{ anime: Anime }> = ({ anime }) => {
  const [loading, setLoading] = useState(true);
  const [isHovered, setIsHovered] = useState(false);

  useEffect(() => {
    const timer = setTimeout(() => {
      setLoading(false);
    }, 0);

    return () => clearTimeout(timer);
  }, [anime.id]);

  const handleMouseEnter = () => {
    setIsHovered(true);
  };

  const handleMouseLeave = () => {
    setIsHovered(false);
  };

  const imageSrc = anime.image || '';
  const animeColor = anime.color || '#999999';
  const displayTitle = useMemo(
    () => anime.title.english || anime.title.romaji || 'No Title',
    [anime.title.english, anime.title.romaji],
  );

  const truncateTitle = useMemo(
    () => (title: string, maxLength: number) =>
      title.length > maxLength ? `${title.slice(0, maxLength)}...` : title,
    [],
  );

  const handleImageLoad = () => {
    setLoading(false); // Set loading to false when image is loaded
  };

  const displayDetail = useMemo(() => {
    // Any complex logic can go here
    return (
      <ImgDetail $isHovered={isHovered} color={anime.color}>
        {anime.type}
      </ImgDetail>
    );
  }, [isHovered, anime.color, anime.type]);

  return (
    <>
      {loading ? (
        <SkeletonCard />
      ) : (
        <StyledCardWrapper
          to={`/watch/${anime.id}`}
          onMouseEnter={handleMouseEnter}
          onMouseLeave={handleMouseLeave}
          color={animeColor}
          title={anime.title.english || anime.title.romaji}
        >
          <StyledCardItem>
            <ImageDisplayWrapper>
              <AnimeImage>
                <ImageWrapper>
                  <img
                    src={imageSrc}
                    onLoad={handleImageLoad}
                    loading='eager'
                    alt={
                      anime.title.english || anime.title.romaji + ' Cover Image'
                    }
                  />
                  <PlayIcon
                    title={
                      'Play ' + (anime.title.english || anime.title.romaji)
                    }
                  />
                </ImageWrapper>
                {isHovered && displayDetail}
              </AnimeImage>
            </ImageDisplayWrapper>
            <TitleContainer $isHovered={isHovered}>
              <StatusIndicator status={anime.status} />
              <Title
                $isHovered={isHovered}
                color={anime.color}
                title={'Title: ' + (anime.title.english || anime.title.romaji)}
              >
                {truncateTitle(displayTitle, 35)}
              </Title>
            </TitleContainer>
            <div>
              <CardDetails title='Romaji Title'>
                {truncateTitle(anime.title.romaji || '', 24)}
              </CardDetails>
              <CardDetails title='Card Details'>
                {anime.releaseDate && (
                  <>
                    <FaCalendarAlt />
                    {anime.releaseDate}
                  </>
                )}
                {(anime.totalEpisodes || anime.episodes) && (
                  <>
                    <TbCards />
                    {anime.totalEpisodes || anime.episodes}
                  </>
                )}
                {anime.rating && (
                  <>
                    <FaStar />
                    {anime.rating}
                  </>
                )}
              </CardDetails>
            </div>
          </StyledCardItem>
        </StyledCardWrapper>
      )}
    </>
  );
};


================================================
FILE: src/components/Home/EpisodeCard.tsx
================================================
import React, { useState, useEffect, useMemo } from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { FaPlay } from 'react-icons/fa';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/swiper-bundle.css';
import { Episode } from '../../index';
import { IoIosCloseCircleOutline } from 'react-icons/io';

const LOCAL_STORAGE_KEYS = {
  WATCHED_EPISODES: 'watched-episodes',
  LAST_ANIME_VISITED: 'last-anime-visited',
};

interface LastEpisodes {
  [key: string]: Episode;
}

interface LastVisitedData {
  [key: string]: {
    timestamp?: number;
    titleEnglish?: string;
    titleRomaji?: string;
  };
}

const StyledSwiperContainer = styled(Swiper)`
  position: relative;
  max-width: 100%;
  height: auto;
  border-radius: var(--global-border-radius);
  cursor: grab;
`;

const StyledSwiperSlide = styled(SwiperSlide)``;

const PlayIcon = styled.div`
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #ffffff;
  font-size: 2.5rem;
  opacity: 0;
  z-index: 1;
  transition: opacity 0.2s ease-in-out;
  display: flex;
  align-items: center;
  justify-content: center;
`;

const AnimeEpisodeCard = styled(Link)`
  position: relative;
  display: flex;
  flex-direction: column;
  margin: 1rem 0;
  padding: 0;
  border-radius: var(--global-border-radius);
  overflow: hidden;
  transition: 0.2s ease-in-out;
  transition-delay: 0.25s;

  &:hover,
  &:active,
  &:focus {
    box-shadow: 2px 2px 10px var(--global-card-hover-shadow);
    ${PlayIcon} {
      opacity: 1;
    }

    img {
      filter: brightness(0.5); // Optional: Slightly darken the image itself
    }
  }

  @media (min-width: 768px) {
    &:hover,
    &:active,
    &:focus {
      // transform: translateY(-10px);
    }
  }

  img {
    animation: slideDown 0.5s ease-in-out;
    height: auto;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    transition: filter 0.2s ease-in-out; // Smooth transition for the filter
  }
  .episode-info {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    padding: 0.5rem;
    background: linear-gradient(
      360deg,
      rgba(8, 8, 8, 1) -15%,
      transparent 100%
    );
    color: white;
    .episode-title {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      font-size: 0.95rem;
      font-weight: bold;
      margin: 0.25rem 0;
    }
    .episode-number {
      font-size: 0.75rem;
      color: rgba(255, 255, 255, 0.65);
      margin: 0;
    }
  }
`;

const Section = styled.section`
  padding: 0rem;
  border-radius: var(--global-border-radius);
`;

const ProgressBar = styled.div`
  position: absolute;
  bottom: 0;
  left: 0;
  height: 0.25rem;
  border-radius: var(--global-border-radius);
  background-color: var(--primary-accent);
  transition: width 0.3s ease-in-out;
`;

const ContinueWatchingTitle = styled.h2`
  color: var(--global-text);
  font-size: 1.25rem;
  margin-bottom: 0.25rem;
`;

const CloseButton = styled.button`
  position: absolute;
  right: 0;
  background: transparent;
  border: none;
  color: #ffffff;
  cursor: pointer;
  display: none;
  animation: slideDown 0.25s ease-in-out;
  transition: 0.2s ease-in-out;
  padding-right: 0.2rem;
  padding-top: 0.2rem;

  svg {
    transition: 0.2s ease-in-out;
    transform: scale(0.95);
    font-size: 1.75rem;
    &:hover,
    &:active,
    &:focus {
      transform: scale(1);
    }
  }
  ${AnimeEpisodeCard}:hover & {
    display: block; // Show only on hover
  }
`;

const FaCircle = styled(IoIosCloseCircleOutline)`
  font-size: 2.25rem;
`;

const calculateSlidesPerView = (windowWidth: number): number => {
  if (windowWidth >= 1200) return 5;
  if (windowWidth >= 1000) return 4;
  if (windowWidth >= 700) return 3;
  if (windowWidth >= 500) return 2;
  return 2;
};

export const EpisodeCard: React.FC = () => {
  const [watchedEpisodesData, setWatchedEpisodesData] = useState(
    localStorage.getItem('watched-episodes'),
  );
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  const lastVisitedData = useMemo<LastVisitedData>(() => {
    const data = localStorage.getItem(LOCAL_STORAGE_KEYS.LAST_ANIME_VISITED);
    return data ? JSON.parse(data) : {};
  }, []);

  useEffect(() => {
    const handleResize = () => {
      setWindowWidth(window.innerWidth);
    };
    const debouncedResize = setTimeout(handleResize, 200);
    window.addEventListener('resize', handleResize);
    return () => {
      clearTimeout(debouncedResize);
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  const episodesToRender = useMemo(() => {
    if (!watchedEpisodesData) return [];
    try {
      const allEpisodes: Record<string, Episode[]> =
        JSON.parse(watchedEpisodesData);

      const lastEpisodes = Object.entries(allEpisodes).reduce<LastEpisodes>(
        (acc, [animeId, episodes]) => {
          const lastEpisode = episodes[episodes.length - 1]; // Assuming the episodes are in order
          if (lastEpisode) {
            acc[animeId] = lastEpisode;
          }
          return acc;
        },
        {},
      );

      const orderedAnimeIds = Object.keys(lastEpisodes).sort((a, b) => {
        const lastVisitedA = lastVisitedData[a]?.timestamp || 0;
        const lastVisitedB = lastVisitedData[b]?.timestamp || 0;
        return lastVisitedB - lastVisitedA;
      });

      return orderedAnimeIds.map((animeId) => {
        const episode = lastEpisodes[animeId];
        const playbackInfo = JSON.parse(
          localStorage.getItem('all_episode_times') || '{}',
        ) as { [key: string]: { playbackPercentage: number } };

        const playbackPercentage =
          playbackInfo[episode.id]?.playbackPercentage || 0;

        // Determine anime title, preferring English, falling back to Romaji, then to "Episode Title"
        const animeTitle =
          lastVisitedData[animeId]?.titleEnglish ||
          lastVisitedData[animeId]?.titleRomaji ||
          '';

        // Conditional title display
        const displayTitle = `${animeTitle}${episode.title ? ` - ${episode.title}` : ''}`;

        const handleRemoveAllEpisodes = (animeId: string) => {
          const updatedEpisodes = JSON.parse(watchedEpisodesData || '{}');
          delete updatedEpisodes[animeId];

          const newWatchedEpisodesData = JSON.stringify(updatedEpisodes);
          localStorage.setItem('watched-episodes', newWatchedEpisodesData);
          setWatchedEpisodesData(newWatchedEpisodesData); // Trigger re-render
        };

        return (
          <StyledSwiperSlide key={episode.id}>
            <AnimeEpisodeCard
              to={`/watch/${animeId}`}
              style={{ textDecoration: 'none' }}
              title={`Continue Watching ${displayTitle}`}
            >
              <img src={episode.image} alt={`Cover for ${animeTitle}`} />
              <PlayIcon aria-label='Play Episode'>
                <FaPlay />
              </PlayIcon>
              <div className='episode-info'>
                <p className='episode-title'>{displayTitle}</p>
                <p className='episode-number'>{`Episode ${episode.number}`}</p>
              </div>
              <ProgressBar
                style={{ width: `${Math.max(playbackPercentage, 5)}%` }}
              />
              <CloseButton
                onClick={(e) => {
                  e.preventDefault(); // Prevents the default action of the event
                  e.stopPropagation(); // Prevents the event from bubbling up to any parent elements
                  handleRemoveAllEpisodes(animeId);
                }}
              >
                <FaCircle aria-label='Close' />
              </CloseButton>
            </AnimeEpisodeCard>
          </StyledSwiperSlide>
        );
      });
    } catch (error) {
      console.error('Failed to parse watched episodes data:', error);
      return [];
    }
  }, [watchedEpisodesData, lastVisitedData]);

  const swiperSettings = useMemo(
    () => ({
      spaceBetween: 20,
      slidesPerView: calculateSlidesPerView(windowWidth),
      loop: true,
      freeMode: true,
      grabCursor: true,
      keyboard: true,
      autoplay: {
        delay: 6000,
        disableOnInteraction: false,
      },
    }),
    [windowWidth],
  );

  return (
    <Section aria-labelledby='continueWatchingTitle'>
      {episodesToRender.length > 0 && (
        <ContinueWatchingTitle id='continueWatchingTitle'>
          CONTINUE WATCHING
        </ContinueWatchingTitle>
      )}
      <StyledSwiperContainer {...swiperSettings} aria-label='Episodes carousel'>
        {episodesToRender}
      </StyledSwiperContainer>
    </Section>
  );
};


================================================
FILE: src/components/Home/HomeCarousel.tsx
================================================
import { FC } from 'react';
import styled from 'styled-components';
import { FaPlay } from 'react-icons/fa';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/swiper-bundle.css';
import { useNavigate } from 'react-router-dom';
import { SkeletonSlide, Anime } from '../../index';
import { TbCards } from 'react-icons/tb';
import { FaStar } from 'react-icons/fa';
import { FaClock } from 'react-icons/fa6';

const StyledSwiperContainer = styled(Swiper)`
  position: relative;
  max-width: 100%;
  height: 24rem;
  border-radius: var(--global-border-radius);
  cursor: grab;

  @media (max-width: 1000px) {
    height: 20rem;
  }
  @media (max-width: 500px) {
    height: 18rem;
  }
`;

const StyledSwiperSlide = styled(SwiperSlide)`
  position: relative;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  animation: fadeIn 0.4s ease-in-out forwards;
`;

const DarkOverlay = styled.div`
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  border-radius: var(--global-border-radius);
  z-index: 1;
  background: linear-gradient(45deg, rgba(8, 8, 8, 1) 0%, transparent 60%);
`;

const SlideImageWrapper = styled.div`
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: var(--global-border-radius);
`;

const SlideImage = styled.img`
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--global-border-radius);
  position: absolute;
`;

const ContentWrapper = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 100%;
`;

const SlideContent = styled.div`
  position: absolute;
  left: 2rem;
  bottom: 1.5rem;
  z-index: 5;
  max-width: 60%;

  animation: slideUp 0.4s ease-in-out;

  @media (max-width: 1000px) {
    left: 1rem;
    bottom: 1.5rem;
  }
`;

const SlideTitle = styled.h2`
  color: var(--white, #fff);
  font-size: clamp(1.2rem, 3vw, 2.5rem);
  margin: auto;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;

  @media (min-width: 500px) {
    white-space: nowrap;
    max-width: 100%;
  }
`;

const SlideInfo = styled.div`
  display: flex;
  gap: 0.75rem;
  color: #ffffff;
  margin: auto;
  margin-top: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;

  @media (max-width: 1000px) {
    font-size: 0.8rem;
    gap: 0.5rem;
  }
  @media (max-width: 500px) {
    font-size: 0.7rem;
    gap: 0.45rem;
  }
`;

const SlideInfoItem = styled.p`
  display: flex;
  gap: 0.25rem;
`;

const SlideDescription = styled.p<{
  $maxLines: boolean;
}>`
  color: var(--white, #ccc);
  background: transparent;
  font-size: clamp(0.9rem, 1.5vw, 0.9rem);
  line-height: 1.2;
  max-width: 60%;
  max-height: 5rem;
  overflow: hidden;
  -webkit-line-clamp: 3;
  margin: 0;

  @media (max-width: 1000px) {
    line-height: 1.2;
    max-width: 70%;
    font-size: clamp(0.8rem, 1.2vw, 0.9rem);
    max-height: 3rem;
  }

  @media (max-width: 500px) {
    max-width: 100%;
    font-size: clamp(0.7rem, 1vw, 0.8rem);
    max-height: 2.5rem;
  }

  /* Add overflow-y: auto if the content exceeds max height */
  overflow-y: ${({ $maxLines }) => ($maxLines ? 'auto' : 'hidden')};
`;

const PlayButtonWrapper = styled.div`
  position: absolute;
  right: 2rem;
  bottom: 1.5rem;
  z-index: 5;
  display: flex;
  align-items: center; /* Center vertically */
  justify-content: center; /* Center horizontally */

  @media (max-width: 1000px) {
    right: 1.5rem;
    bottom: 1.5rem;
  }
`;

const PlayButton = styled.button`
  display: flex;
  gap: 0.5rem;
  background-color: var(--global-button-bg);
  color: var(--global-text);
  border: none;
  border-radius: 0.4rem;
  font-size: 1rem; /* Increased font size */
  font-weight: bold;
  cursor: pointer;
  transition: 0.2s ease;
  padding: 1.2rem 2rem; /* Increased padding */
  display: flex;
  align-items: center;

  &:hover,
  &:active,
  &:focus {
    background-color: var(--primary-accent-bg);
    transform: scale(1.05); /* Slightly larger scale on hover */
  }

  @media (max-width: 1000px) {
    padding: 1rem 2rem; /* Adjusted for medium-sized devices */
  }

  @media (max-width: 500px) {
    border-radius: 50%;
    padding: 1.4rem; /* Adjusted for small devices */
    padding-right: 1.5rem;
    font-size: 1.25rem; /* Adjusted font size for small devices */
    span {
      display: none;
    }
  }
`;

const PlayIcon = styled(FaPlay)``;

const PaginationStyle = styled.div`
  .swiper-pagination-bullet {
    background: var(--global-primary-bg, #007bff);
    opacity: 0.7;
    margin: 0 3px;
  }

  .swiper-pagination-bullet-active {
    background: var(--global-text);
    opacity: 1;
  }
`;

// Adjust the Carousel component to use correctly typed props and state
interface HomeCarouselProps {
  data: Anime[];
  loading: boolean;
  error?: string | null;
}

export const HomeCarousel: FC<HomeCarouselProps> = ({
  data = [],
  loading,
  error,
}) => {
  const navigate = useNavigate();

  const handlePlayButtonClick = (id: string) => {
    navigate(`/watch/${id}`);
  };

  const truncateTitle = (title: string, maxLength: number = 40): string => {
    return title.length > maxLength
      ? `${title.substring(0, maxLength)}...`
      : title;
  };

  const validData = data.filter(
    (item) =>
      item.title &&
      item.title.english &&
      item.description &&
      item.cover !== item.image,
  );

  // const formatGenres = (genres: string[]): string => genres.join(', ');

  return (
    <>
      {loading || error ? (
        <SkeletonSlide />
      ) : (
        <PaginationStyle>
          <StyledSwiperContainer
            spaceBetween={30}
            slidesPerView={1}
            loop={true}
            autoplay={{
              delay: 5000,
              disableOnInteraction: false,
            }}
            navigation={{
              nextEl: '.swiper-button-next',
              prevEl: '.swiper-button-prev',
            }}
            pagination={{
              el: '.swiper-pagination',
              clickable: true,
              dynamicBullets: true,
              type: 'bullets',
            }}
            freeMode={false}
            virtual={true}
            grabCursor={true}
            keyboard={true}
            centeredSlides={true}
          >
            {validData.map(
              ({
                id,
                cover,
                title,
                description,
                // status,
                rating,
                // genres,
                totalEpisodes,
                duration,
                type,
              }) => (
                <StyledSwiperSlide
                  key={id}
                  title={title.english || title.romaji}
                >
                  <SlideImageWrapper>
                    <SlideImage
                      src={cover}
                      alt={title.english || title.romaji + ' Banner Image'}
                      loading='eager'
                    />
                    <ContentWrapper>
                      <SlideContent>
                        <SlideTitle>{truncateTitle(title.english)}</SlideTitle>
                        <SlideInfo>
                          {type && <SlideInfoItem>{type}</SlideInfoItem>}
                          {totalEpisodes && (
                            <SlideInfoItem>
                              <TbCards />
                              {totalEpisodes}
                            </SlideInfoItem>
                          )}
                          {rating && (
                            <SlideInfoItem>
                              <FaStar />
                              {rating}
                            </SlideInfoItem>
                          )}
                          {duration && (
                            <SlideInfoItem>
                              <FaClock />
                              {duration}mins
                            </SlideInfoItem>
                          )}
                        </SlideInfo>
                        <SlideDescription
                          dangerouslySetInnerHTML={{ __html: description }}
                          $maxLines={description.length > 200}
                        />
                      </SlideContent>
                      <PlayButtonWrapper>
                        <PlayButton
                          onClick={() => handlePlayButtonClick(id)}
                          title={
                            'Watch ' + (title.english || title.romaji) + ' Now'
                          }
                        >
                          <PlayIcon />
                          <span>WATCH NOW</span>
                        </PlayButton>
                      </PlayButtonWrapper>
                    </ContentWrapper>
                    <DarkOverlay />
                  </SlideImageWrapper>
                </StyledSwiperSlide>
              ),
            )}
            <div className='swiper-pagination'></div>
          </StyledSwiperContainer>
        </PaginationStyle>
      )}
    </>
  );
};


================================================
FILE: src/components/Home/HomeSideBar.tsx
================================================
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom'; // Assuming you're using React Router for navigation
import { TbCards } from 'react-icons/tb';
import { FaStar, FaCalendarAlt } from 'react-icons/fa';
import { Anime, StatusIndicator } from '../../index';

const SidebarStyled = styled.div`
  transition: 0.2s ease-in-out;
  margin: 0;
  padding: 0;
  max-width: 24rem;
  @media (max-width: 1000px) {
    max-width: unset;
  }
`;

const TitleWithDot = styled.div`
  display: flex;
  align-items: center;
  padding: 0.5rem;
  margin-top: 0.35rem;
  gap: 0.4rem;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  transition: background 0.2s ease;
`;

const AnimeCard = styled.div`
  display: flex;
  background-color: var(--global-div);
  border-radius: var(--global-border-radius);
  align-items: center;
  overflow: hidden;
  gap: 0.5rem;
  cursor: pointer;
  margin-bottom: 0.5rem;
  animation: slideUp 0.5s ease-in-out;
  animation-fill-mode: backwards;
  transition:
    background-color 0s ease-in-out,
    margin-left 0.2s ease-in-out 0.1s;
    box-shadow 0.2s ease-in-out;

  &:hover,
  &:active,
  &:focus {
    background-color: var(--global-div-tr);
    margin-left: 0.35rem;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
  }

  @media (max-width: 500px) {
    &:hover,
    &:active,
    &:focus {
      margin-left: unset;
    }
  }
`;

const AnimeImageStyled = styled.img`
  width: 4.25rem;
  height: 6rem;
  object-fit: cover;
  border-radius: var(--global-border-radius);
`;

const InfoStyled = styled.div``;

const Title = styled.p`
  top: 0;
  margin-bottom: 0.5rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.9rem;
  margin: 0;
`;

const Details = styled.p`
  font-size: 0.75rem;
  margin: 0;
  color: rgba(102, 102, 102, 0.75);
  svg {
    margin-left: 0.4rem;
  }
`;

export const HomeSideBar: React.FC<{ animeData: Anime[] }> = ({
  animeData,
}) => {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      setWindowWidth(window.innerWidth);
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  const displayedAnime = windowWidth <= 500 ? animeData.slice(0, 5) : animeData;

  return (
    <SidebarStyled>
      {displayedAnime.map((anime: Anime, index) => (
        <Link
          to={`/watch/${anime.id}`}
          key={anime.id}
          style={{ textDecoration: 'none', color: 'inherit' }}
          title={`${anime.title.userPreferred}`}
          aria-label={`Watch ${anime.title.userPreferred}`}
        >
          <AnimeCard
            key={anime.id}
            style={{ animationDelay: `${index * 0.1}s` }}
          >
            <AnimeImageStyled
              src={anime.image}
              alt={anime.title.userPreferred}
            />
            <InfoStyled>
              <TitleWithDot>
                <StatusIndicator status={anime.status} />
                <Title>{anime.title.english || anime.title.romaji}</Title>
              </TitleWithDot>
              <Details>
                {anime.type && <>{anime.type}</>}
                {anime.releaseDate && (
                  <>
                    <FaCalendarAlt /> {anime.releaseDate}
                  </>
                )}
                {anime.currentEpisode !== null &&
                  anime.currentEpisode !== undefined &&
                  anime.totalEpisodes !== null &&
                  anime.totalEpisodes !== undefined &&
                  anime.totalEpisodes !== 0 &&
                  anime.totalEpisodes !== 0 && (
                    <>
                      <TbCards /> {anime.currentEpisode}
                      {' / '}
                      {anime.totalEpisodes}
                    </>
                  )}

                {anime.rating && (
                  <>
                    <FaStar /> {anime.rating}
                  </>
                )}
              </Details>
            </InfoStyled>
          </AnimeCard>
        </Link>
      ))}
    </SidebarStyled>
  );
};


================================================
FILE: src/components/Navigation/DropSearch.tsx
================================================
import React, { useEffect, useRef } from 'react';
import styled from 'styled-components';
import { useNavigate } from 'react-router-dom';
import { Anime } from '../../index';
import { FaArrowRight, FaStar } from 'react-icons/fa';
import { TbCards } from 'react-icons/tb';
import { BsArrowUpSquare, BsArrowDownSquare } from 'react-icons/bs';
import { PiKeyReturn } from 'react-icons/pi';

const Container = styled.div<{ $isVisible: boolean; width: number }>`
  display: ${({ $isVisible }) => ($isVisible ? 'block' : 'none')};
  position: absolute;
  z-index: -1;
  top: 1rem;
  width: ${({ width }) => `${width}px`};
  margin-left: -0.6rem;
  overflow-y: auto;
  background-color: var(--global-div);
  border-top: none;
  border-radius: var(--global-border-radius);
  padding-top: 2.5rem;
  animation: dropDown 0.5s ease-in-out;

  @media (max-width: 500px) {
    top: 4rem;
    width: 96.4%;
  }

  scrollbar-width: none;
  -ms-overflow-style: none;
  &::-webkit-scrollbar {
    display: none;
  }

  visibility: ${({ $isVisible }) => ($isVisible ? 'visible' : 'hidden')};
  max-height: ${({ $isVisible }) => ($isVisible ? '500px' : '0')};
`;

const Details = styled.p<{ $isSelected: boolean }>`
  margin: 0.25rem 0;
  animation: slideDropDown 0.5s ease-in-out;
  color: ${({ $isSelected }) =>
    $isSelected ? 'var(--primary-text)' : 'rgba(102, 102, 102, 0.75)'};
  font-size: 0.65rem;
  font-weight: bold;
  padding: 0 0.5rem;
  display: flex;
`;

const Item = styled.div<{ $isSelected: boolean }>`
  display: flex;
  animation: slideDropDown 0.5s ease-in-out;
  padding: 0.5rem;
  margin: 0;
  cursor: pointer;
  background-color: ${({ $isSelected }) =>
    $isSelected ? 'var(--primary-accent-bg)' : 'transparent'};
  transition: 0.05s ease-in-out;

  &:hover,
  &:active,
  &:focus {
    background-color: var(--primary-accent-bg);
    ${Details} {
      color: var(--global-text);
    }
  }
`;
const ViewAllItem = styled(Item)<{ $isSelected: boolean }>`
  font-size: 0.9rem;
  font-weight: bold;
  display: flex;
  justify-content: space-between; // This spreads out the children to the extremes
  align-items: center;
  color: ${({ $isSelected }) => ($isSelected ? '' : '#666')};
  &:hover,
  &:active,
  &:focus {
    color: var(--global-text);
  }
  svg {
    margin-bottom: -0.1rem;
  }
`;

const Shorcuts = styled.div`
  font-weight: normal;
  @media (max-width: 600px) {
    display: none;
  }
`;

const Image = styled.img`
  animation: slideDropDown 0.5s ease-in-out;
  width: 2.5rem;
  height: 3.5rem;
  border-radius: var(--global-border-radius);
  object-fit: cover;

  @media (max-width: 500px) {
    width: 2.5rem;
    height: 2.5rem;
  }
`;

const Title = styled.p`
  margin: 0 0.5rem;
  padding: 0.1rem;
  animation: slideDropDown 0.5s ease-in-out;
  text-align: left;
  overflow: hidden;
  font-size: 0.9rem;
  font-weight: bold;
  text-overflow: ellipsis;
  white-space: nowrap;

  @media (max-width: 500px) {
    font-size: 0.8rem;
  }
`;

interface Props {
  searchResults: Anime[];
  onClose: () => void;
  isVisible: boolean;
  selectedIndex: number | null;
  setSelectedIndex: React.Dispatch<React.SetStateAction<number | null>>;
  searchQuery: string;
  containerWidth: number;
}

export const DropDownSearch: React.FC<Props> = ({
  searchResults,
  onClose,
  isVisible,
  selectedIndex,
  setSelectedIndex,
  searchQuery,
  containerWidth,
}) => {
  const navigate = useNavigate();
  const ref = useRef<HTMLDivElement>(null);

  const handleClickOutside = (event: MouseEvent) => {
    if (ref.current && !ref.current.contains(event.target as Node)) {
      onClose();
    }
  };

  useEffect(() => {
    if (isVisible) {
      document.addEventListener('mousedown', handleClickOutside);
    } else {
      document.removeEventListener('mousedown', handleClickOutside);
    }
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [isVisible, onClose]);

  useEffect(() => {
    if (!isVisible) {
      setSelectedIndex(null);
    }
  }, [isVisible]);

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (!isVisible) return;

      const total = searchResults.length;
      let index = selectedIndex !== null ? selectedIndex : -1;

      if (e.key === 'ArrowDown') {
        e.preventDefault();
        index = (index + 1) % (total + 1);
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        index = (index - 1 + total + 1) % (total + 1);
      } else if (e.key === 'Enter' && selectedIndex !== null) {
        e.preventDefault();
        if (selectedIndex < total) {
          onClose();
          navigate(`/watch/${searchResults[selectedIndex].id}`);
        } else {
          navigate(`/search?query=${encodeURIComponent(searchQuery)}`);
          onClose();
        }
      }

      setSelectedIndex(index);
    };

    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, [isVisible, searchResults, selectedIndex]);

  return (
    <Container
      width={containerWidth}
      $isVisible={isVisible && searchResults.length > 0}
      ref={ref}
      role='list'
    >
      {searchResults.map((result, index) => (
        <Item
          key={result.id}
          title={result.title.english || result.title.romaji}
          $isSelected={index === selectedIndex}
          onClick={() => {
            onClose();
            navigate(`/watch/${result.id}`);
          }}
          role='listitem'
        >
          <Image
            src={result.image || ''}
            alt={result.title?.english || result.title?.romaji || 'n/a'}
          />
          <div>
            <Title>
              {result.title?.english || result.title?.romaji || 'n/a'}
            </Title>
            <Details $isSelected={index === selectedIndex}>
              <span>&nbsp;{result.type}</span>
              <span>&nbsp;&nbsp;</span>
              <TbCards color='#' />
              <span>&nbsp;</span>
              <span>{result.totalEpisodes || 'N/A'}&nbsp;</span>
              <FaStar color='#' />
              <span>&nbsp;</span>
              <span>{result.rating ? result.rating / 10 : 'N/A'}&nbsp;</span>
              <span>&nbsp;&nbsp;</span>
            </Details>
          </div>
        </Item>
      ))}
      <div>
        <ViewAllItem
          $isSelected={selectedIndex === searchResults.length}
          onClick={() => {
            navigate(`/search?query=${encodeURIComponent(searchQuery)}`);
            onClose();
          }}
          role='listitem'
          tabIndex={0}
        >
          <Shorcuts>
            <BsArrowUpSquare /> <BsArrowDownSquare /> to navigate{' '}
            <PiKeyReturn /> to select | Esc to exit &nbsp;
          </Shorcuts>
          <div>
            <>View All</> &nbsp; <FaArrowRight />
          </div>
        </ViewAllItem>
      </div>
    </Container>
  );
};


================================================
FILE: src/components/Navigation/Footer.tsx
================================================
import styled from 'styled-components';
import { FaReddit, FaDiscord, FaTwitter, FaGithub } from 'react-icons/fa';
import { Link } from 'react-router-dom';
import { year } from '../../hooks/useTIme';

const PageWrapper = styled.div`
  margin-top: 2rem;
  @media (max-width: 1000px) {
    padding: 0 0.5rem;
  }
`;

const FooterBaseContainer = styled.footer<{ $isSub: boolean }>`
  color: var(--global-text);
  padding: ${({ $isSub }) => ($isSub ? '0' : '0.5rem 0')};
  display: flex;
  justify-content: space-between;
  border-top: ${({ $isSub }) => ($isSub ? '0.125rem solid' : 'none')}
    var(--global-secondary-bg);
  flex-direction: column;

  @media (max-width: 1000px) {
    padding: ${({ $isSub }) => ($isSub ? '0 0 1rem 0' : '0.5rem 0')};
  }

  @media (min-width: 601px) {
    flex-direction: row;
  }

  @media (max-width: 600px) {
    padding: ${({ $isSub }) => ($isSub ? '0' : '0.5rem 0')};
  }
`;

const StyledLinkList = styled.div`
  display: flex;
  flex-direction: column;
  margin: 0.5rem 0;
  margin-top: auto;
`;

const FooterLink = styled(Link)`
  align-items: center;
  padding: 0.5rem 0;
  color: grey;
  font-size: 0.9rem;
  text-decoration: none;
  transition: color 0.1s ease-in-out;
  bottom: 0;
  align-self: auto;

  @media (min-width: 601px) {
    align-self: end;
  }

  &:hover,
  &:active,
  &:focus {
    color: var(--global-button-text);
  }
`;

const SocialIconsWrapper = styled.div`
  padding-top: 1rem;
  display: flex;
  gap: 1rem;
`;

const FooterLogoImage = styled.img`
  content: var(--logo-transparent);
  max-width: 4rem;
  height: 4.375rem;
`;

const Text = styled.div<{ $isSub: boolean }>`
  color: grey;
  font-size: ${({ $isSub }) => ($isSub ? '0.75rem' : '0.65rem')};
  margin: ${({ $isSub }) => ($isSub ? '1rem 0 0 0' : '1rem 0')};
  max-width: 25rem;

  strong {
    color: var(--global-text);
  }
`;

const ShareButton = styled.a`
  display: inline-block;
  color: grey;
  transition: 0.2s ease-in-out;

  svg {
    font-size: 1.2rem;
  }

  &:hover,
  &:active,
  &:focus {
    transform: scale(1.15);
    color: var(--global-button-text);
    text-decoration: underline;
  }

  @media (max-width: 600px) {
    margin-bottom: 1rem;
  }
`;

export function Footer() {
  return (
    <PageWrapper>
      <footer>
        <FooterBaseContainer aria-label='Main Footer' $isSub={false}>
          <Text as='p' $isSub={false}>
            <FooterLogoImage alt='Footer Logo' /> <br />
            This website does not retain any files on its server. Rather, it
            solely provides links to media content hosted by third-party
            services.
          </Text>
          <StyledLinkList aria-label='Footer Links'>
            <FooterLink to='/about' title='About Us'>
              About
            </FooterLink>
            <FooterLink
              to='https://www.miruro.com'
              target='_blank'
              title='Domains'
            >
              Domains
            </FooterLink>
            <FooterLink to='/pptos' title='Privacy Policy and Terms of Service'>
              Privacy & ToS
            </FooterLink>
          </StyledLinkList>
        </FooterBaseContainer>
        <FooterBaseContainer aria-label='Sub Footer' $isSub={true}>
          <Text as='p' $isSub={true}>
            &copy; {year}{' '}
            <a
              href='https://www.miruro.com'
              rel='noopener noreferrer'
              style={{ color: 'grey' }}
            >
              miruro.com
            </a>{' '}
            | Website Made by <strong>Miruro no Kuon</strong>
          </Text>
          <nav aria-label='Social Links'>
            <SocialIconsWrapper>
              {[
                {
                  href: 'https://www.reddit.com/r/miruro',
                  Icon: FaReddit,
                  label: 'Reddit',
                },
                {
                  href: 'https://discord.gg/dubRrtfpFn',
                  Icon: FaDiscord,
                  label: 'Discord',
                },
                {
                  href: 'https://twitter.com/miruro_official',
                  Icon: FaTwitter,
                  label: 'Twitter',
                },
              ].map(({ href, Icon, label }) => (
                <ShareButton
                  key={href}
                  href={href}
                  target='_blank'
                  rel='noopener noreferrer'
                  aria-label={`Miruro on ${label}`}
                >
                  <Icon aria-hidden='true' />
                </ShareButton>
              ))}
            </SocialIconsWrapper>
          </nav>
        </FooterBaseContainer>
      </footer>
    </PageWrapper>
  );
}


================================================
FILE: src/components/Navigation/Navbar.tsx
================================================
import React, { useRef, useEffect, useState, useCallback } from 'react';
import styled from 'styled-components';
import {
  useNavigate,
  useSearchParams,
  Link,
  useLocation,
} from 'react-router-dom';
import { DropDownSearch, useAuth } from '../../index';
import { fetchAdvancedSearch, type Anime } from '../..';
import { FiSun, FiMoon, FiX /* FiMenu */ } from 'react-icons/fi';
import { GoCommandPalette } from 'react-icons/go';
import { IoIosSearch } from 'react-icons/io';
import { CgProfile } from 'react-icons/cg';

const StyledNavbar = styled.div<{ $isExtended?: boolean }>`
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  text-align: center;
  margin: 0;
  padding: 1rem;
  background-color: var(--global-primary-bg-tr);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: 100;
  animation: fadeIn('var(--global-primary-bg-tr)') 0.5s ease-in-out;
  transition: 0.1s ease-in-out;

  @media (max-width: 500px) {
    padding: 1rem 0.5rem;
  }
`;

const NavbarWrapper = styled.div`
  max-width: 105rem;
  margin: auto;
`;

const TopContainer = styled.div`
  display: flex;
  gap: 0.5rem;
  align-items: center;
  justify-content: space-between;
`;

const LogoImg = styled(Link)`
  width: 7rem;
  font-size: 1.2rem;
  font-weight: bold;
  text-decoration: none;
  color: var(--global-text);
  content: var(--logo-text-transparent);
  cursor: pointer;
  transition:
    color 0.2s ease-in-out,
    transform 0.2s ease-in-out;

  &:hover,
  &:active,
  &:focus {
    color: black;
    transform: scale(1.05);
  }

  &:active {
    transform: scale(0.95);
  }

  @media (max-width: 500px) {
    max-width: 6rem;
  }
`;

const InputContainer = styled.div<{ $isVisible: boolean }>`
  display: flex;
  flex: 1;
  max-width: 35rem;
  height: 1.2rem;
  align-items: center;
  padding: 0.6rem;
  border-radius: var(--global-border-radius);
  background-color: var(--global-div);
  animation: fadeIn 0.1s ease-in-out;
  animation: slideDropDown 0.5s ease;

  @media (max-width: 1000px) {
    max-width: 30rem;
  }

  @media (max-width: 500px) {
    max-width: 100%;
    margin-top: 1rem;
    display: ${({ $isVisible }) => ($isVisible ? 'flex' : 'none')};
  }
`;

const RightContent = styled.div`
  gap: 0.5rem;
  display: flex;
  align-items: center;
  height: 2rem;
`;

const Icon = styled.div<{ $isFocused: boolean }>`
  margin: 0;
  padding: 0 0.25rem;
  color: var(--global-text);
  opacity: ${({ $isFocused }) => ($isFocused ? 1 : 0.5)};
  font-size: 1.2rem;
  transition: opacity 0.2s;
  max-height: 100%;
  display: flex;
  align-items: center;
`;

const SearchInput = styled.input`
  background: transparent;
  border: none;
  color: var(--global-text);
  display: inline-block;
  font-size: 0.85rem;
  outline: 0;
  padding: 0;
  max-height: 100%;
  display: flex;
  align-items: center;
  padding-top: 0;
  width: 100%;
  transition:
    border-color 0.2s ease-in-out,
    box-shadow 0.2s ease-in-out;
`;

const ClearButton = styled.button<{ $query: string }>`
  background: transparent;
  border: none;
  color: var(--global-text);
  font-size: 1.2rem;
  cursor: pointer;
  opacity: ${({ $query }) => ($query ? 0.5 : 0)};
  visibility: ${({ $query }) => ($query ? 'visible' : 'hidden')};
  transition:
    color 0.2s,
    opacity 0.2s;
  max-height: 100%;
  display: flex;
  align-items: center;

  &:hover,
  &:active,
  &:focus {
    color: var(--global-text);
    opacity: 1;
  }
`;

const StyledButton = styled.button<{ isInputToggle?: boolean }>`
  background: transparent;
  background-color: var(--global-div);
  color: var(--global-text);
  font-size: 1.2rem;
  cursor: pointer;
  padding: 1.2rem 0.6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--global-border-radius);
  width: 100%;
  height: 100%;
  transition:
    color 0.2s ease-in-out,
    transform 0.1s ease-in-out;
  border: none;

  &:active {
    transform: scale(0.9);
  }

  @media (max-width: 500px) {
    display: flex;
    margin: ${({ isInputToggle }) => (isInputToggle ? '0' : '0')};
  }
`;

const SlashToggleBtn = styled.div<{ $isFocused: boolean }>`
  font-size: 1.2rem;
  cursor: pointer;
  opacity: ${({ $isFocused }) => ($isFocused ? 1 : 0.5)};

  &:hover,
  &:active,
  &:focus {
    opacity: 1;
  }

  @media (max-width: 1000px) {
    display: none;
  }
`;

const detectUserTheme = () => {
  if (
    window.matchMedia &&
    window.matchMedia('(prefers-color-scheme: dark)').matches
  ) {
    return true;
  }
  return false;
};

const saveThemePreference = (isDarkMode: boolean) => {
  localStorage.setItem('themePreference', isDarkMode ? 'dark' : 'light');
};

const getInitialThemePreference = () => {
  const storedThemePreference = localStorage.getItem('themePreference');

  if (storedThemePreference) {
    return storedThemePreference === 'dark';
  }

  return detectUserTheme();
};

export const Navbar = () => {
  const { isLoggedIn, userData } = useAuth();
  const [isPaddingExtended, setIsPaddingExtended] = useState(false);
  const inputContainerRef = useRef<HTMLDivElement>(null);
  const navigate = useNavigate();
  const location = useLocation();
  const [inputContainerWidth, setInputContainerWidth] = useState(0);
  const [searchParams, setSearchParams] = useSearchParams();
  const inputRef = useRef<HTMLInputElement>(null);
  const navbarRef = useRef(null);
  const dropdownRef = useRef<HTMLDivElement>(null); // Ref for the dropdown container
  const [searchResults, setSearchResults] = useState<Anime[]>([]);
  const debounceTimeout = useRef<Timer | null>(null);
  const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
  const [search, setSearch] = useState({
    isSearchFocused: false,
    searchQuery: searchParams.get('query') || '',
    isDropdownOpen: false,
  });
  const [isInputVisible, setIsInputVisible] = useState(false); // Default to false
  const [isMobileView, setIsMobileView] = useState(window.innerWidth < 500);
  const fetchSearchResults = async (query: string) => {
    if (!query.trim()) return;

    try {
      const fetchedData = await fetchAdvancedSearch(query, 1, 5); // Fetch first 5 results for the dropdown
      const formattedResults = fetchedData.results.map((anime: Anime) => ({
        id: anime.id, // Make sure to include the ID field
        title: anime.title,
        image: anime.image,
        type: anime.type,
        totalEpisodes: anime.totalEpisodes,
        rating: anime.rating,
      }));
      setSearchResults(formattedResults);
    } catch (error) {
      console.error('Failed to fetch search results:', error);
      setSearchResults([]);
    }
  };

  const handleCloseDropdown = () => {
    setSearch((prevState) => ({
      ...prevState,
      isDropdownOpen: false,
    }));
  };

  const handleClickOutside = (event: MouseEvent) => {
    if (
      dropdownRef.current &&
      !dropdownRef.current.contains(event.target as Node)
    ) {
      handleCloseDropdown();
    }
  };

  useEffect(() => {
    document.addEventListener('mousedown', handleClickOutside);
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  });

  const [isDarkMode, setIsDarkMode] = useState(getInitialThemePreference());

  useEffect(() => {
    document.documentElement.classList.toggle('dark-mode', isDarkMode);
  }, [isDarkMode]);

  const toggleTheme = useCallback(() => {
    const newIsDarkMode = !isDarkMode;
    setIsDarkMode(newIsDarkMode);
    saveThemePreference(newIsDarkMode);
  }, [isDarkMode, setIsDarkMode]);

  const handleKeyDown = useCallback(
    (e: KeyboardEvent) => {
      if (e.key === '/' && inputRef.current) {
        e.preventDefault();
        inputRef.current.focus();
        setSearch((prevState) => ({
          ...prevState,
          isSearchFocused: true,
        }));
      } else if (e.key === 'Escape' && inputRef.current) {
        inputRef.current.blur();
        setSearch((prevState) => ({
          ...prevState,
          isSearchFocused: false,
        }));
        handleCloseDropdown(); // Close dropdown on Escape key
      } else if (e.shiftKey && e.key.toLowerCase() === 'd') {
        if (document.activeElement !== inputRef.current) {
          e.preventDefault();
          toggleTheme();
        }
      }
    },
    [toggleTheme],
  );

  useEffect(() => {
    const listener = handleKeyDown as EventListener;
    document.addEventListener('keydown', listener);
    return () => {
      document.removeEventListener('keydown', listener);
    };
  }, [handleKeyDown]);

  useEffect(() => {
    setSearch({ ...search, searchQuery: searchParams.get('query') || '' });
  }, [searchParams]);

  const navigateWithQuery = useCallback(
    (value: string) => {
      if (location.pathname == '/search') {
        const params = new URLSearchParams();

        params.set('query', value);
        setSearchParams(params, { replace: true });
      } else {
        navigate(value ? `/search?query=${value}` : '/search');
      }
    },
    [navigate, location.pathname, setSearchParams],
  );

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const newValue = e.target.value;
    setSearch({ ...search, searchQuery: newValue });

    if (debounceTimeout.current) clearTimeout(debounceTimeout.current);

    debounceTimeout.current = setTimeout(() => {
      fetchSearchResults(newValue);
      setSearch((prevState) => ({
        ...prevState,
        isDropdownOpen: true,
      }));
    }, 300);
  };

  const handleKeyDownOnInput = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') {
      e.preventDefault(); // Prevent default form submission behavior
      if (selectedIndex !== null && searchResults[selectedIndex]) {
        // Navigate to the selected search result if it exists
        const animeId = searchResults[selectedIndex].id;
        navigate(`/watch/${animeId}`);
        handleCloseDropdown();
      } else {
        // Fallback to navigating with the search query if the selected index is not in searchResults
        navigateWithQuery(search.searchQuery);
      }
      if (debounceTimeout.current) {
        clearTimeout(debounceTimeout.current);
      }
      setSearch((prevState) => ({
        ...prevState,
        isDropdownOpen: false,
      }));
      if (inputRef.current) {
        inputRef.current.blur();
      }
    }
  };

  useEffect(() => {
    // Function to update the width
    const updateWidth = () => {
      if (inputContainerRef.current) {
        setInputContainerWidth(inputContainerRef.current.offsetWidth);
      }
    };

    // Update width on mount
    updateWidth();

    // Add event listener for window resize
    window.addEventListener('resize', updateWidth);

    // Cleanup function to remove the event listener
    return () => window.removeEventListener('resize', updateWidth);
  }, []);

  useEffect(() => {
    // This effect runs when the location.pathname changes or enter is pressed (Hide the InputContainer)
    if (isMobileView) {
      setIsInputVisible(false);
    }
  }, [location.pathname, isMobileView]);

  const handleClearSearch = () => {
    setSearch((prevState) => ({
      ...prevState,
      searchQuery: '',
    }));
    setSearchResults([]);
    setSearch((prevState) => ({
      ...prevState,
      isDropdownOpen: false, // Close dropdown when search is cleared
    }));
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  useEffect(() => {
    function handleResize() {
      setIsMobileView(window.innerWidth < 500);
    }

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  //navigate to profile
  const navigateToProfile = () => {
    // Check if the current location's pathname is not '/profile' before navigating
    if (location.pathname !== '/profile') {
      navigate('/profile');
    }
  };

  return (
    <>
      <StyledNavbar $isExtended={isPaddingExtended} ref={navbarRef}>
        <NavbarWrapper>
          <TopContainer>
            <LogoImg
              title='MIRURO.tv'
              to='/home'
              onClick={() => window.scrollTo(0, 0)}
            >
              見るろ の 久遠
            </LogoImg>

            {/* Render InputContainer within the navbar for screens larger than 500px */}
            {!isMobileView && (
              <InputContainer
                ref={inputContainerRef}
                $isVisible={isInputVisible}
              >
                <Icon $isFocused={search.isSearchFocused}>
                  <IoIosSearch />
                </Icon>
                <SearchInput
                  type='text'
                  placeholder='Search Anime'
                  value={search.searchQuery}
                  onChange={handleInputChange}
                  onKeyDown={handleKeyDownOnInput}
                  onFocus={() => {
                    setSearch((prevState) => ({
                      ...prevState,
                      isDropdownOpen: true,
                      isSearchFocused: true,
                    }));
                  }}
                  ref={inputRef}
                  aria-label='Search Anime'
                />
                <DropDownSearch
                  searchResults={searchResults}
                  onClose={handleCloseDropdown}
                  isVisible={search.isDropdownOpen}
                  selectedIndex={selectedIndex}
                  setSelectedIndex={setSelectedIndex}
                  searchQuery={search.searchQuery}
                  containerWidth={inputContainerWidth}
                />

                <ClearButton
                  $query={search.searchQuery}
                  onClick={handleClearSearch}
                  aria-label='Clear Search'
                >
                  <FiX />
                </ClearButton>
                <Icon $isFocused={search.isSearchFocused}>
                  <GoCommandPalette />
                </Icon>
              </InputContainer>
            )}
            <RightContent>
              {isMobileView && (
                <StyledButton
                  onClick={() => {
                    setIsInputVisible((prev) => !prev);
                    setIsPaddingExtended((prev) => !prev); // Toggle padding extension when toggling input visibility
                  }}
                  aria-label='Toggle Search Input'
                >
                  <IoIosSearch />
                </StyledButton>
              )}
              <StyledButton onClick={toggleTheme} aria-label='Toggle Dark Mode'>
                {isDarkMode ? <FiSun /> : <FiMoon />}
              </StyledButton>
              <StyledButton onClick={navigateToProfile}>
                {isLoggedIn && userData ? (
                  <img
                    src={userData.avatar.large}
                    alt={`${userData.name}'s avatar`}
                    style={{
                      width: '25px',
                      height: '25px',
                      borderRadius: '50%',
                    }}
                  />
                ) : (
                  <CgProfile />
                )}
              </StyledButton>
            </RightContent>
          </TopContainer>

          {isMobileView && isInputVisible && (
            <InputContainer $isVisible={isInputVisible}>
              <Icon $isFocused={search.isSearchFocused}>
                <IoIosSearch />
              </Icon>
              <SearchInput
                type='text'
                placeholder='Search Anime'
                value={search.searchQuery}
                onChange={handleInputChange}
                onKeyDown={handleKeyDownOnInput}
                onFocus={() => {
                  setSearch((prevState) => ({
                    ...prevState,
                    isDropdownOpen: true,
                    isSearchFocused: true,
                  }));
                }}
                ref={inputRef}
              />
              <DropDownSearch
                searchResults={searchResults}
                onClose={handleCloseDropdown}
                isVisible={search.isDropdownOpen}
                selectedIndex={selectedIndex}
                setSelectedIndex={setSelectedIndex}
                searchQuery={search.searchQuery}
                containerWidth={inputContainerWidth}
              />

              <ClearButton
                $query={search.searchQuery}
                onClick={handleClearSearch}
              >
                <FiX />
              </ClearButton>
              <SlashToggleBtn $isFocused={search.isSearchFocused}>
                <GoCommandPalette />
              </SlashToggleBtn>
            </InputContainer>
          )}
        </NavbarWrapper>
      </StyledNavbar>
      {/* Conditionally render InputContainer below the navbar for mobile view when visibility is toggled */}
    </>
  );
};


================================================
FILE: src/components/Navigation/SearchFilters.tsx
================================================
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import Select, { components } from 'react-select';
import makeAnimated from 'react-select/animated';
import {
  FaSearch,
  FaSortAmountDown,
  FaSortAmountDownAlt,
  FaCheckCircle,
  FaTrashAlt,
} from 'react-icons/fa';
import { FiX } from 'react-icons/fi';
import {
  Option,
  FilterProps,
  genreOptions,
  anyOption,
  yearOptions,
  seasonOptions,
  formatOptions,
  statusOptions,
  sortOptions,
} from '../../index';

interface StateProps {
  data: {
    label: string;
  };
  isSelected: boolean;
  isFocused: boolean;
}

const selectStyles: any = {
  placeholder: (provided: object) => ({
    ...provided,
    color: 'var(--global-text-muted)',
  }),
  singleValue: (provided: object, state: StateProps) => ({
    ...provided,
    color:
      state.data.label === 'Popularity' || state.data.label === 'Any'
        ? 'var(--global-text-muted)'
        : 'var(--primary-accent)',
  }),
  control: (provided: object) => ({
    ...provided,
    width: '11.5rem',
    backgroundColor: 'var(--global-secondary-bg)',
    borderColor: 'transparent',
    color: 'var(--global-text)',
    boxShadow: 'none',
    '&:hover': {
      borderColor: 'var(--primary-accent)',
    },
    '@media (max-width: 500px)': {
      width: '10rem',
    },
  }),
  menu: (provided: object) => ({
    ...provided,
    zIndex: 5,
    padding: '0.25rem',
    backgroundColor: 'var(--global-secondary-bg)',
    borderColor: 'var(--global-border)',
    color: 'var(--global-text)',
  }),
  option: (provided: object, state: StateProps) => ({
    ...provided,
    backgroundColor:
      state.isSelected || state.isFocused
        ? 'var(--global-tertiary-bg)'
        : 'var(--global-secondary-bg)',
    color:
      state.isSelected || state.isFocused
        ? 'var(--primary-accent)'
        : 'var(--global-text)',
    borderRadius: 'var(--global-border-radius)',
    '&:hover': {
      backgroundColor: 'var(--global-tertiary-bg)',
      color: 'var(--primary-accent)',
    },
    marginBottom: '0.25rem',
  }),
  multiValue: (provided: object) => ({
    ...provided,
    backgroundColor: 'var(--global-genre-button-bg)',
  }),
  multiValueLabel: (provided: object) => ({
    ...provided,
    color: 'var(--global-text)',
  }),
  multiValueRemove: (provided: object) => ({
    ...provided,
    '&:hover': {
      backgroundColor: 'var(--primary-accent)',
      color: 'var(--global-secondary-bg)',
    },
  }),
};

const InputContainer = styled.div`
  display: flex;
  max-width: 10.4rem;
  flex: 1;
  align-items: center;
  padding: 0 0.3rem;
  border-radius: var(--global-border-radius);
  background-color: var(--global-div);
  @media (max-width: 500px) {
    max-width: 100%;
  }
`;

const Icon = styled.div`
  font-size: 0.8rem;
  margin: 0;
  padding: 0 0.25rem;
  color: 'var(--global-text-muted)',
  transition: opacity 0.2s;
  max-height: 100%;
  display: flex;
  align-items: center;
`;

const SearchInput = styled.input`
  background: transparent;
  border: none;
  color: var(--global-text);
  display: inline-block;
  font-size: 0.8rem;
  outline: 0;
  padding: 0;
  max-height: 100%;
  display: flex;
  align-items: center;
  width: 100%;
  height: 2.375rem;

  transition:
    border-color 0.2s ease-in-out,
    box-shadow 0.2s ease-in-out;
`;

const FiltersWrapper = styled.div`
  display: flex;
  flex-direction: column;
  gap: 1rem;
`;

const FiltersContainer = styled.div`
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  grid-template-rows: auto;
  margin: 0 auto;
  position: relative;
  gap: 1rem;
  justify-content: left;
  align-items: center;
  font-size: 0.8rem;
  font-weight: bold;
  flex-wrap: wrap;

  @media (max-width: 500px) {
    display: flex;
    justify-content: center;
  }
`;

const FilterSection = styled.div`
  display: flex;
  flex-direction: column;
  align-items: start;

  gap: 0.5rem;
`;

const FilterLabel = styled.label`
  font-weight: bold;
  font-size: 0.9rem;
`;

const ButtonBase = styled.button`
  flex: 1;
  align-items: center;
  justify-content: center;
  padding: 0.6rem;
  max-width: 4.5rem;
  min-width: 4.5rem;
  border: none;
  font-weight: bold;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  background-color: var(--global-div);
  color: var(--global-text);
  transition:
    background-color 0.2s ease,
    transform 0.2s ease-in-out;
  text-align: center;
  &:active,
  &:focus {
    transform: scale(1.025);
  }
  &:active {
    transform: scale(0.975);
  }
  svg {
    margin-bottom: -0.1rem;
  }
`;

const Button = styled(ButtonBase)`
  &.active {
    background-color: var(--primary-accent);
  }
`;

const ClearFilters = styled(ButtonBase)`
  &.active {
    background-color: none;
  }
  &:hover,
  &:active,
  &:focus {
    background-color: red;
    opacity: 1;
  }
`;

const ButtonContainer = styled.div`
  display: flex;
  gap: 1rem;
  justify-content: flex-end;

  @media (max-width: 500px) {
    justify-content: center;
  }
`;

const ClearButton = styled.button<{ $query: string }>`
  background: transparent;
  border: none;
  color: var(--global-text);
  font-size: 1.2rem;
  cursor: pointer;
  opacity: ${({ $query }) => ($query ? 0.5 : 0)};
  visibility: ${({ $query }) => ($query ? 'visible' : 'hidden')};
  transition:
    color 0.2s,
    opacity 0.2s;
  max-height: 100%;
  display: flex;
  align-items: center;

  &:hover,
  &:active,
  &:focus {
    color: var(--global-text);
    opacity: 1;
  }
`;

const animatedComponents = makeAnimated();

const FilterSelect: React.FC<FilterProps> = ({
  label,
  options,
  onChange,
  value,
  isMulti = false,
}) => {
  // Local state to handle input value and debounce
  const [inputValue, setInputValue] = useState(value);

  useEffect(() => {
    // Update local state when external value prop changes
    setInputValue(value);
  }, [value]);

  useEffect(() => {
    if (label === 'Search') {
      // Set up a delay for executing the onChange handler only for the Search input
      const handler = setTimeout(() => {
        onChange && onChange(inputValue);
      }, 300); // 300ms delay for debounce

      // Cleanup function to clear the timeout
      return () => {
        clearTimeout(handler);
      };
    }
  }, [inputValue, onChange, label]);

  //Add Check Circle to clicked option
  const CustomOption = (props: any) => {
    return (
      <components.Option {...props}>
        <div
          style={{
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
          }}
        >
          <span>{props.data.label}</span>
          {props.isSelected && <FaCheckCircle style={{ marginLeft: '10px' }} />}
        </div>
      </components.Option>
    );
  };
  return (
    <FilterSection>
      <FilterLabel>
        {label === 'Search'}
        {label}
      </FilterLabel>
      {label === 'Search' ? (
        <InputContainer>
          <Icon>
            <FaSearch
              style={{
                marginRight: '0.25rem',
                color: 'var(--global-text-muted)',
              }}
            />
          </Icon>
          <SearchInput
            type='text'
            value={inputValue} // Use the local state value here
            onChange={(e) => setInputValue(e.target.value)} // Update local state instead of calling onChange directly
            placeholder=''
          />
          <ClearButton
            $query={inputValue}
            onClick={() => {
              setInputValue(''); // Reset the local state
              onChange?.(''); // Propagate the change upwards
            }}
            aria-label='Clear Search'
          >
            <FiX />
          </ClearButton>
        </InputContainer>
      ) : (
        <Select
          components={{
            ...animatedComponents,
            Option: CustomOption,
            IndicatorSeparator: () => null,
          }}
          isMulti={isMulti}
          options={options}
          onChange={onChange}
          value={value}
          placeholder='Any'
          styles={selectStyles}
          isSearchable={false}
        />
      )}
    </FilterSection>
  );
};

export const SearchFilters: React.FC<{
  query: string;
  setQuery: React.Dispatch<React.SetStateAction<string>>;
  selectedGenres: Option[];
  setSelectedGenres: React.Dispatch<React.SetStateAction<Option[]>>;
  selectedYear: Option;
  setSelectedYear: React.Dispatch<React.SetStateAction<Option>>;
  selectedSeason: Option;
  setSelectedSeason: React.Dispatch<React.SetStateAction<Option>>;
  selectedFormat: Option;
  setSelectedFormat: React.Dispatch<React.SetStateAction<Option>>;
  selectedStatus: Option;
  setSelectedStatus: React.Dispatch<React.SetStateAction<Option>>;
  selectedSort: Option;
  setSelectedSort: React.Dispatch<React.SetStateAction<Option>>;
  sortDirection: 'DESC' | 'ASC';
  setSortDirection: React.Dispatch<React.SetStateAction<'DESC' | 'ASC'>>;
  updateSearchParams: () => void; // Added prop for updating search params
}> = ({
  query,
  setQuery,
  selectedGenres,
  setSelectedGenres,
  selectedYear,
  setSelectedYear,
  selectedSeason,
  setSelectedSeason,
  selectedFormat,
  setSelectedFormat,
  selectedStatus,
  setSelectedStatus,
  selectedSort,
  setSelectedSort,
  sortDirection,
  setSortDirection,
  updateSearchParams,
}) => {
  // State to track if any filter is changed from its default value
  const [filtersChanged, setFiltersChanged] = useState(false);

  const handleResetFilters = () => {
    setSelectedGenres([]);
    setSelectedYear(anyOption);
    setSelectedSeason(anyOption);
    setSelectedFormat(anyOption);
    setSelectedStatus(anyOption);
    setSelectedSort({ value: 'POPULARITY_DESC', label: 'Popularity' });
    setSortDirection('DESC');
    setQuery('');
    updateSearchParams(); // Also reset URL parameters
  };

  useEffect(() => {
    const hasFiltersChanged =
      query !== '' || // Check if query is not default
      selectedGenres.length > 0 || // Check if any genres are selected
      selectedYear.value !== anyOption.value || // Check if year is not "Any"
      selectedSeason.value !== anyOption.value || // Same for season, type, status...
      selectedFormat.value !== anyOption.value ||
      selectedStatus.value !== anyOption.value ||
      selectedSort.value !== 'POPULARITY_DESC' || // Check if sort criteria is not "Popularity"
      sortDirection !== 'DESC'; // Check if sort direction is not descending

    setFiltersChanged(hasFiltersChanged);
  }, [
    query,
    selectedGenres,
    selectedYear,
    selectedSeason,
    selectedFormat,
    selectedStatus,
    selectedSort,
    sortDirection,
  ]);

  const handleChange =
    (
      setter:
        | React.Dispatch<React.SetStateAction<Option[]>>
        | React.Dispatch<React.SetStateAction<Option>>
        | React.Dispatch<React.SetStateAction<string>>,
    ) =>
    (
      newValue: React.SetStateAction<Option[]> &
        React.SetStateAction<Option> &
        React.SetStateAction<string>,
    ) => {
      setter(newValue);
      updateSearchParams();
    };

  return (
    <FiltersWrapper>
      <div>
        <FiltersContainer>
          <FilterSelect
            label='Search'
            value={query}
            onChange={handleChange(setQuery)}
          />
          <FilterSelect
            label='Genres'
            options={genreOptions}
            isMulti
            onChange={handleChange(setSelectedGenres)}
            value={selectedGenres}
          />
          <FilterSelect
            label='Year'
            options={yearOptions}
            onChange={handleChange(setSelectedYear)}
            value={selectedYear}
          />
          <FilterSelect
            label='Season'
            options={seasonOptions}
            onChange={handleChange(setSelectedSeason)}
            value={selectedSeason}
          />
          <FilterSelect
            label='Type'
            options={formatOptions}
            onChange={handleChange(setSelectedFormat)}
            value={selectedFormat}
          />
          <FilterSelect
            label='Status'
            options={statusOptions}
            onChange={handleChange(setSelectedStatus)}
            value={selectedStatus}
          />
          <FilterSelect
            label='Sort By'
            options={sortOptions}
            onChange={handleChange(setSelectedSort)}
            value={selectedSort}
          />
        </FiltersContainer>
      </div>
      <ButtonContainer>
        <Button
          onClick={() => {
            setSortDirection(sortDirection === 'DESC' ? 'ASC' : 'DESC');
            updateSearchParams(); // Ensure sort direction changes also update URL
          }}
        >
          {sortDirection === 'DESC' ? (
            <FaSortAmountDown />
          ) : (
            <FaSortAmountDownAlt />
          )}
        </Button>
        {filtersChanged && (
          <ClearFilters onClick={handleResetFilters}>
            <FaTrashAlt />
          </ClearFilters>
        )}
      </ButtonContainer>
    </FiltersWrapper>
  );
};


================================================
FILE: src/components/Profile/Settings.tsx
================================================
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { useNavigate } from 'react-router-dom';
import { IoArrowBack } from 'react-icons/io5';
import { useSettings } from '../../index';
interface Preferences {
  defaultLanguage: string;
  titleLanguage: string;
  characterNameLanguage: string;
  ratingSource: string;
  openKeyboardShortcuts: string;
  autoskipIntroOutro: string;
  autoPlay: string;
  autoNext: string;
  defaultServers: string;
  restoreDefaultPreferences: string;
  clearContinueWatching: string;
  openButton: string;
}

const Goback = styled.div`
  border-radius: var(--global-border-radius);
  display: flex;
  cursor: pointer;
  justify-content: center;
  align-items: center;
  background-color: var(--global-div);
  color: var(--global-text);
  width: 3rem;
  margin-right: 0.75rem;
  &:active {
    transform: scale(0.975);
  }
`;

const SettingsDiv = styled.div`
  gap: 1rem;
  max-width: 45rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: auto; /* This centers the div horizontally */
`;

const PreferencesTable = styled.table`
  background-color: var(--global-div-tr);
  border-radius: var(--global-border-radius);
  border-collapse: collapse;
  width: 100%;
`;

const TableRow = styled.tr``;

const TableCell = styled.td`
  padding: 1rem;
`;

const Title = styled.h2`
  display: flex;
  color: var(--global-text);
  font-size: 1.5rem;
  margin: 0rem;
  margin-top: 1rem;
`;

const SectionTitle = styled.h3`
  color: var(--global-text);
  font-size: 1.25rem;
  margin: 1rem;
`;

const Divider = styled.hr`
  border: none;
  height: 1px;
  background-color: var(--global-secondary-bg);
  margin-top: 1rem;
  margin-bottom: 1rem;
`;

const StyledButton = styled.button<{ isSelected: boolean }>`
  background: var(--global-div);
  color: var(--global-text);
  padding: 0.4rem;
  cursor: pointer;
  border: none;
  border-radius: var(--global-border-radius);
  transition: background-color 0.2s ease-in-out;
`;

const StyledSelect = styled.select`
  background: var(--global-div);
  color: var(--global-text);
  padding: 0.25rem;
  cursor: pointer;
  border: none;
  border-radius: var(--global-border-radius);
  transition: background-color 0.2s ease-in-out;
`;

export const Settings: React.FC = () => {
  const navigate = useNavigate();
  const { settings, setSettings } = useSettings();

  const [preferences, setPreferences] = useState<Preferences>({
    defaultLanguage: settings.defaultLanguage,
    titleLanguage: 'Romaji',
    characterNameLanguage: 'Romaji',
    ratingSource: 'Anilist',
    openKeyboardShortcuts: 'Open',
    autoskipIntroOutro: settings.autoSkip ? 'Enabled' : 'Disabled',
    autoPlay: settings.autoPlay ? 'Enabled' : 'Disabled',
    autoNext: settings.autoNext ? 'Enabled' : 'Disabled',
    defaultServers: 'Default',
    restoreDefaultPreferences: 'Restore',
    clearContinueWatching: 'Clear',
    openButton: 'Open',
  });

  useEffect(() => {
    setPreferences((prev) => ({
      ...prev,
      defaultLanguage: settings.defaultLanguage,
      autoskipIntroOutro: settings.autoSkip ? 'Enabled' : 'Disabled',
      autoPlay: settings.autoPlay ? 'Enabled' : 'Disabled',
      autoNext: settings.autoNext ? 'Enabled' : 'Disabled',
    }));
  }, [settings]);

  const getOptionsForPreference = (key: string): string[] => {
    switch (key) {
      case 'defaultLanguage':
        return ['Sub', 'Dub'];
      case 'titleLanguage':
        return [
          'English (Attack on Titan)',
          'Romaji (Shingeki no Kyojin)',
          'Native (進撃の巨人)',
        ];
      case 'characterNameLanguage':
        return ['Romaji (Zoldyck Killua)', 'Native (キルア=ゾルディック)'];
      case 'ratingSource':
        return ['Anilist', 'IMDb', 'MyAnimeList'];
      case 'autoskipIntroOutro':
        return ['Enabled', 'Disabled'];
      case 'autoPlay':
        return ['Enabled', 'Disabled'];
      case 'defaultServers':
        return ['Default', 'Vidstreaming', 'Gogo'];
      case 'autoNext':
        return ['Enabled', 'Disabled'];
      default:
        return [];
    }
  };

  const handlePreferenceChange = (
    preferenceName: keyof Preferences,
    value: string,
  ) => {
    setPreferences((prev) => ({
      ...prev,
      [preferenceName]: value,
    }));

    switch (preferenceName) {
      case 'autoskipIntroOutro':
        setSettings({ autoSkip: value === 'Enabled' });
        break;
      case 'autoPlay':
        setSettings({ autoPlay: value === 'Enabled' });
        break;
      case 'autoNext':
        setSettings({ autoNext: value === 'Enabled' });
        break;
      case 'defaultLanguage':
        setSettings({ defaultLanguage: value });
        break;
      case 'defaultServers':
        setSettings({ defaultServers: value });
        break;
    }
  };

  const formatPreferenceName = (key: string) => {
    return key
      .replace(/([A-Z])/g, ' $1')
      .trim()
      .toLowerCase()
      .replace(/^\w/, (c) => c.toUpperCase());
  };

  const handleGoback = () => {
    navigate('/profile');
  };

  // Profile Page Document Title
  useEffect(() => {
    document.title = `Settings | Profile`;
  });

  return (
    <SettingsDiv>
      <Title>
        <Goback onClick={handleGoback}>
          <IoArrowBack />
        </Goback>
        Settings
      </Title>
      <PreferencesTable>
        <SectionTitle>General</SectionTitle>
        <tbody>
          {[
            'titleLanguage',
            'characterNameLanguage',
            'ratingSource',
            'openKeyboardShortcuts',
          ].map((key) => (
            <TableRow key={key}>
              <TableCell>{formatPreferenceName(key)}</TableCell>
              <TableCell>
                {key === 'openKeyboardShortcuts' ? (
                  <StyledButton isSelected={true} disabled={true}>
                    {preferences[key as keyof Preferences]}
                  </StyledButton>
                ) : (
                  <StyledSelect
                    value={preferences[key as keyof Preferences]}
                    onChange={(e) =>
                      handlePreferenceChange(
                        key as keyof Preferences,
                        e.target.value,
                      )
                    }
                  >
                    {getOptionsForPreference(key).map((option) => (
                      <option key={option} value={option}>
                        {option}
                      </option>
                    ))}
                  </StyledSelect>
                )}
              </TableCell>
            </TableRow>
          ))}
        </tbody>
        <Divider />
        <SectionTitle>Media</SectionTitle>
        <tbody>
          {[
            'defaultLanguage',
            'defaultServers',
            'autoskipIntroOutro',
            'autoPlay',
            'autoNext',
          ].map((key) => (
            <TableRow key={key}>
              <TableCell>{formatPreferenceName(key)}</TableCell>
              <TableCell>
                <StyledSelect
                  value={preferences[key as keyof Preferences]}
                  onChange={(e) =>
                    handlePreferenceChange(
                      key as keyof Preferences,
                      e.target.value,
                    )
                  }
                >
                  {getOptionsForPreference(key).map((option) => (
                    <option key={option} value={option}>
                      {option}
                    </option>
                  ))}
                </StyledSelect>
              </TableCell>
            </TableRow>
          ))}
        </tbody>
        <Divider />
        <SectionTitle>Other</SectionTitle>
        <tbody>
          {[
            { key: 'restoreDefaultPreferences', text: 'Restore' },
            { key: 'clearContinueWatching', text: 'Clear' },
          ].map(({ key, text }) => (
            <TableRow key={key}>
              <TableCell>{formatPreferenceName(key)}</TableCell>
              <TableCell>
                <StyledButton
                  isSelected={true}
                  onClick={() =>
                    handlePreferenceChange(key as keyof Preferences, text)
                  }
                >
                  {text}
                </StyledButton>
              </TableCell>
            </TableRow>
          ))}
        </tbody>
      </PreferencesTable>
    </SettingsDiv>
  );
};


================================================
FILE: src/components/Profile/SettingsProvider.tsx
================================================
import React, {
  createContext,
  useContext,
  useState,
  useEffect,
  ReactNode,
} from 'react';

// Define the type for the context state
interface SettingsContextType {
  settings: {
    autoSkip: boolean;
    autoPlay: boolean;
    autoNext: boolean;
    defaultLanguage: string;
    defaultServers: string;
  };
  setSettings: (settings: Partial<SettingsContextType['settings']>) => void;
}

// Create the context with a default value
const SettingsContext = createContext<SettingsContextType | undefined>(
  undefined,
);

export function useSettings() {
  const context = useContext(SettingsContext);
  if (context === undefined) {
    throw new Error('useSettings must be used within a SettingsProvider');
  }
  return context;
}

interface SettingsProviderProps {
  children: ReactNode;
}

export const SettingsProvider: React.FC<SettingsProviderProps> = ({
  children,
}) => {
  const [settings, setSettingsState] = useState({
    autoSkip: localStorage.getItem('autoSkip') === 'true',
    autoPlay: localStorage.getItem('autoPlay') === 'true',
    autoNext: localStorage.getItem('autoNext') === 'true',
    defaultLanguage: localStorage.getItem('defaultLanguage') || 'sub',
    defaultServers: localStorage.getItem('defaultServers') || 'default',
  });

  useEffect(() => {
    // This useEffect will ensure that any changes to the settings state are reflected in local storage
    // console.log('Settings updated:', settings);
    localStorage.setItem('autoSkip', settings.autoSkip ? 'true' : 'false');
    localStorage.setItem('autoPlay', settings.autoPlay ? 'true' : 'false');
    localStorage.setItem('autoNext', settings.autoNext ? 'true' : 'false');
    localStorage.setItem('defaultLanguage', settings.defaultLanguage);
    localStorage.setItem('defaultServers', settings.defaultServers);
  }, [settings]);

  const setSettings = (
    newSettings: Partial<SettingsContextType['settings']>,
  ) => {
    setSettingsState((prev) => {
      const updatedSettings = { ...prev, ...newSettings };
      return updatedSettings;
    });
  };

  return (
    <SettingsContext.Provider value={{ settings, setSettings }}>
      {children}
    </SettingsContext.Provider>
  );
};


================================================
FILE: src/components/Profile/WatchingAnilist.tsx
================================================
import { useState, useEffect } from 'react';
import styled from 'styled-components';
import { useAuth, useUserAnimeList, MediaListStatus } from '../../index';
import { CardGrid } from '../../index';

const Container = styled.div`
  margin-top: 1rem;
  margin-bottom: 1rem;
`;

const NoEntriesMessage = styled.div`
  margin: 1.5rem;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 1.5rem;
  font-weight: bold;
`;

const NotLoggedIn = styled.div`
  margin: 5rem;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 1.5rem;
  font-weight: bold;
`;

const StatusDropdown = styled.select`
  margin-left: 1rem;
  margin-bottom: 1.5rem;
  padding: 0.75rem;
  border-radius: var(--global-border-radius);
  background-color: var(--global-secondary-bg);
  color: var(--global-text);
  border: none;
`;

const statusLabels = {
  CURRENT: 'Watching',
  PLANNING: 'Plan to Watch',
  COMPLETED: 'Completed',
  REPEATING: 'Re-watching',
  PAUSED: 'Paused',
  DROPPED: 'Dropped',
};

const apiStatusToUserFriendly = {
  FINISHED: 'Completed',
  RELEASING: 'Ongoing',
  NOT_YET_RELEASED: 'Not yet aired',
  CANCELLED: 'Cancelled',
  HIATUS: 'Paused',
};

export const WatchingAnilist = () => {
  const { isLoggedIn, userData } = useAuth();
  const [selectedStatus, setSelectedStatus] = useState<string>(
    localStorage.getItem('selectedStatus') || 'CURRENT',
  );

  useEffect(() => {
    if (isLoggedIn && userData) {
      console.log('User is logged in, username:', userData.name);
    } else {
      console.log('User is not logged in or userData is not available');
    }
  }, [isLoggedIn, userData]);

  const { animeList, loading, error } = useUserAnimeList(
    userData?.name,
    selectedStatus as MediaListStatus,
  );

  if (!isLoggedIn)
    return <NotLoggedIn>Please Log in to view your AniList.</NotLoggedIn>;
  if (loading) return <NoEntriesMessage>Loading...</NoEntriesMessage>;
  if (error)
    return (
      <NoEntriesMessage>
        Error loading anime list: {error.message}
      </NoEntriesMessage>
    );

  const animeData = animeList.lists.flatMap((list) =>
    list.entries.map((entry) => ({
      id: entry.media.id,
      image: entry.media.coverImage.large,
      title: {
        romaji: entry.media.title.romaji,
        english: entry.media.title.english || entry.media.title.romaji,
      },
      status: apiStatusToUserFriendly[entry.media.status] || 'Unknown',
      rating: entry.media.averageScore,
      releaseDate: entry.media.startDate.year,
      totalEpisodes: entry.media.episodes,
      color: entry.media.coverImage.color,
      type: entry.media.format,
    })),
  );

  const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const newStatus = e.target.value;
    setSelectedStatus(newStatus);
    localStorage.setItem('selectedStatus', newStatus);
  };

  return (
    <Container>
      <h3>
        AniList
        <StatusDropdown value={selectedStatus} onChange={handleStatusChange}>
          {Object.values(MediaListStatus).map((status) => (
            <option key={status} value={status}>
              {statusLabels[status] || status}
            </option>
          ))}
        </StatusDropdown>
      </h3>
      {animeData.length > 0 ? (
        <CardGrid
          animeData={animeData}
          hasNextPage={false}
          onLoadMore={() => {}}
        />
      ) : (
        <NoEntriesMessage>No Results</NoEntriesMessage>
      )}
    </Container>
  );
};


================================================
FILE: src/components/ShortcutsPopup.tsx
================================================
import styled from 'styled-components';
import { useState, useEffect } from 'react';
import { FaTimes } from 'react-icons/fa';

const Overlay = styled.table`
  font-size: 0.85rem;
  animation: fadeIn 0.3s ease-in-out;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(10px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
`;

const TableCell = styled.td`
  padding: 0.5rem;
  border-bottom: 1px solid rgba(128, 128, 128, 0.3);
`;

const Column1 = styled(TableCell)`
  padding-right: 15rem;
  opacity: 0.7;
`;

const Column2 = styled(TableCell)`
  padding-right: 5rem;
`;

const CloseButton = styled.button`
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  padding: 0.5rem;
  padding-left: 0.6rem;
  background-color: var(--global-primary-bg-tr);
  color: var(--global-text);
  border: none;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  outline: none;
  transition: 0.1s ease-in-out;
  display: flex;
  justify-content: center;
  align-items: center;

  &:hover {
    transform: scale(1.06);
    svg {
      padding-bottom: 0.1rem;
    }
  }

  &:active,
  &:focus {
    transform: scale(0.94);
  }
`;

const PopUp = styled.thead`
  display: flex;
  flex-direction: column;
  gap: 1rem;
  animation: slideUp 0.3s ease-in-out;
  position: relative;
  border-radius: var(--global-border-radius);
  padding: 1rem;
  line-height: 1.8rem;
  background: var(--global-primary-bg);
  z-index: 1100;
  overflow: auto;
  max-height: 90vh;
  max-width: 90vw;
`;

const KeyboardShortcutsPopup = ({ onClose }: { onClose: () => void }) => {
  return (
    <Overlay onClick={onClose}>
      <PopUp className='popup-content' onClick={(e) => e.stopPropagation()}>
        <div
          style={{
            background: 'var(--global-div)',
            borderRadius: 'var(--global-border-radius)',
            padding: '0.5rem',
          }}
        >
          <tr>
            <td>
              <CloseButton onClick={onClose}>
                <FaTimes size={'1rem'} />
              </CloseButton>
            </td>
          </tr>
          <tr>
            <td>
              <strong>Keyboard Shortcuts</strong>(shift+/)
            </td>
          </tr>
        </div>
        <div
          style={{
            background: 'var(--global-div)',
            borderRadius: 'var(--global-border-radius)',
            padding: '0.5rem',
          }}
        >
          <tr>
            <Column1>Play/Pause Toggle</Column1>
            <Column2>K / Space</Column2>
          </tr>
          <tr>
            <Column1>Seek Backward 10 Seconds</Column1>
            <Column2>J</Column2>
          </tr>
          <tr>
            <Column1>Seek Forward 10 Seconds</Column1>
            <Column2>L</Column2>
          </tr>
          <tr>
            <Column1>Toggle Fullscreen</Column1>
            <Column2>F</Column2>
          </tr>
          <tr>
            <Column1>Toggle Mute</Column1>
            <Column2>M</Column2>
          </tr>
          <tr>
            <Column1>Previous Episode</Column1>
            <Column2>(SHIFT+P)</Column2>
          </tr>
          <tr>
            <Column1>Next Episode</Column1>
            <Column2>(SHIFT+N)</Column2>
          </tr>
          <tr>
            <Column1>Increase Volume</Column1>
            <Column2>Arrow Up</Column2>
          </tr>
          <tr>
            <Column1>Decrease Volume</Column1>
            <Column2>Arrow Down</Column2>
          </tr>
          <tr>
            <Column1>Seek Forward 5 Seconds</Column1>
            <Column2>Arrow Right</Column2>
          </tr>
          <tr>
            <Column1>Seek Backward 5 Seconds</Column1>
            <Column2>Arrow Left</Column2>
          </tr>
          <tr>
            <Column1>Increase Playback Speed</Column1>
            <Column2>&gt; (SHIFT+,)</Column2>
          </tr>
          <tr>
            <Column1>Decrease Playback Speed</Column1>
            <Column2>&lt; (SHIFT+.)</Column2>
          </tr>
          <tr>
            <Column1>Jump to Percentage (0-90%)</Column1>
            <Column2>0-9</Column2>
          </tr>
        </div>
      </PopUp>
    </Overlay>
  );
};

export const ShortcutsPopup = () => {
  const [showPopup, setShowPopup] = useState(false);

  useEffect(() => {
    const togglePopupWithShortcut = (e: KeyboardEvent) => {
      if (
        e.target &&
        ['INPUT', 'TEXTAREA', 'SELECT'].includes((e.target as Element).tagName)
      ) {
        return;
      }

      if (e.shiftKey && e.key === '?') {
        e.preventDefault();
        setShowPopup(!showPopup);
      } else if (e.key === 'Escape') {
        setShowPopup(false);
      }
    };

    window.addEventListener('keydown', togglePopupWithShortcut);

    return () => {
      window.removeEventListener('keydown', togglePopupWithShortcut);
    };
  }, [showPopup]);

  const togglePopup = () => setShowPopup(!showPopup);

  return (
    <div className='App'>
      {showPopup && <KeyboardShortcutsPopup onClose={togglePopup} />}
    </div>
  );
};


================================================
FILE: src/components/Skeletons/Skeletons.tsx
================================================
import React from 'react';
import styled, { keyframes, css } from 'styled-components';

const pulseAnimation = keyframes`
  0%, 100% { background-color: var(--global-primary-skeleton); }
  50% { background-color: var(--global-secondary-skeleton); }
`;

const popInAnimation = keyframes`
  0%, 100% { opacity: 0; transform: scale(0.95); }
  50% { opacity: 1; transform: scale(1); }
  75% { opacity: 0.5; transform: scale(1); }
`;

const playerPopInAnimation = keyframes`
  0% { opacity: 0; transform: scale(0.9); }
  100% { opacity: 1; transform: scale(1); }
`;

const SkeletonPulse = keyframes`
  0%, 100% { background-color: var(--global-primary-skeleton); }
  25%, 75% { background-color: var(--global-secondary-skeleton); }
  50% { background-color: var(--global-primary-skeleton); }
`;

const animationMixin = css`
  animation:
    ${pulseAnimation} 1s infinite,
    ${popInAnimation} 1s infinite;
`;

const BaseSkeleton = styled.div`
  background: var(--global-primary-skeleton);
  border-radius: var(--global-border-radius);
`;

const SkeletonCards = styled(BaseSkeleton)`
  width: 100%;
  height: 0;
  padding-top: calc(100% * 184 / 133);
  margin-bottom: 5.1rem;
  ${animationMixin};
`;

const SkeletonTitle = styled(BaseSkeleton)`
  height: 1.4rem;
  margin: 0.5rem 0 0.3rem;
  ${animationMixin};
`;

const SkeletonDetails = styled(SkeletonTitle)`
  height: 1.3rem;
  width: 80%;
`;

export const SkeletonCard = React.memo(() => (
  <SkeletonCards>
    <SkeletonTitle />
    <SkeletonDetails />
    <SkeletonDetails />
  </SkeletonCards>
));

const SkeletonSlides = styled(BaseSkeleton)<{ loading?: boolean }>`
  width: 100%;
  height: 24rem;
  ${({ loading }) => !loading && animationMixin}
  @media (max-width: 1000px) {
    height: 20rem;
  }
  @media (max-width: 500px) {
    height: 18rem;
  }
`;

export const SkeletonSlide: React.FC<{ loading?: boolean }> = React.memo(
  ({ loading }) => (
    <SkeletonSlides loading={loading}>
      <SkeletonImage />
    </SkeletonSlides>
  ),
);

const SkeletonContainer = styled.div`
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
`;

const PlayerSkeleton = styled(BaseSkeleton)`
  position: relative;
  padding-top: 56.25%;
  width: 100%;
  height: 0;
  animation:
    ${SkeletonPulse} 2.5s ease-in-out infinite,
    ${playerPopInAnimation} 0.5s ease-in-out;
`;

const PlayerButtons = styled(BaseSkeleton)`
  position: relative;
  height: 23px;
  width: 100%;
  animation:
    ${SkeletonPulse} 2.5s ease-in-out infinite,
    ${playerPopInAnimation} 0.5s ease-in-out;
`;

export const SkeletonPlayer = React.memo(() => (
  <SkeletonContainer>
    <PlayerSkeleton />
    <PlayerButtons />
  </SkeletonContainer>
));

const SkeletonImage = styled(BaseSkeleton)`
  width: 100%;
  height: 100%;
`;


================================================
FILE: src/components/ThemeContext.tsx
================================================
import React, {
  createContext,
  useContext,
  useState,
  useEffect,
  ReactNode,
} from 'react';

type ThemeContextType = {
  isDarkMode: boolean;
  toggleTheme: () => void;
};

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export const useTheme = () => useContext(ThemeContext)!;

type ThemeProviderProps = {
  children: ReactNode;
};

export const ThemeProvider = ({ children }: ThemeProviderProps) => {
  const [isDarkMode, setIsDarkMode] = useState<boolean>(() =>
    getInitialThemePreference(),
  );

  useEffect(() => {
    document.documentElement.classList.toggle('dark-mode', isDarkMode);
    localStorage.setItem('themePreference', isDarkMode ? 'dark' : 'light');
  }, [isDarkMode]);

  const toggleTheme = () => {
    setIsDarkMode(!isDarkMode);
  };

  return (
    <ThemeContext.Provider value={{ isDarkMode, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

const getInitialThemePreference = (): boolean => {
  const storedThemePreference = localStorage.getItem('themePreference');
  if (storedThemePreference) {
    return storedThemePreference === 'dark';
  }
  return (
    window.matchMedia &&
    window.matchMedia('(prefers-color-scheme: dark)').matches
  );
};


================================================
FILE: src/components/Watch/AnimeDataList.tsx
================================================
import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { TbCards } from 'react-icons/tb';
import { FaStar } from 'react-icons/fa';
import { Anime, StatusIndicator } from '../../index';

const Sidebar = styled.div`
  display: flex;
  flex-direction: column;
  gap: 1rem;
  transition: 0.2s ease-in-out;
  .Section-Title {
    margin: 0;
    padding: 0 0 0.5rem 0;
    color: var(--global-text);
    font-size: 1.25rem;
    font-weight: bold;
  }
`;

const SidebarContainer = styled.div`
  padding: 0.75rem;
  background-color: var(--global-div-tr);
  border-radius: var(--global-border-radius);
`;

const Card = styled.div`
  display: flex;
  background-color: var(--global-div);
  border-radius: var(--global-border-radius);
  align-items: center;
  overflow: hidden;
  gap: 0.5rem;
  cursor: pointer;
  margin-bottom: 0.5rem;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  animation: slideUp 0.5s ease-in-out;
  animation-fill-mode: backwards;
  transition:
    background-color 0s ease-in-out,
    margin-left 0.2s ease-in-out 0.1s;
  &:hover,
  &:active,
  &:focus {
    background-color: var(--global-div-tr);
    margin-left: 0.35rem;
    @media (max-width: 500px) {
      margin-left: unset;
    }
`;

const AnimeImage = styled.img`
  width: 4.25rem;
  height: 6rem;
  object-fit: cover;
  border-radius: var(--global-border-radius);
`;

const Info = styled.div``;

const TitleWithDot = styled.div`
  display: flex;
  align-items: center;
  padding: 0.5rem;
  margin-top: 0.35rem;
  gap: 0.4rem;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  transition: background 0.2s ease;
`;

const Title = styled.p`
  top: 0;
  margin-bottom: 0.5rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.9rem;
  margin: 0;
`;

const Details = styled.p`
  font-size: 0.75rem;
  margin: 0;
  color: rgba(102, 102, 102, 0.75);
  svg {
    margin-left: 0.4rem;
  }
`;

export const AnimeDataList: React.FC<{ animeData: Anime }> = ({
  animeData,
}) => {
  const filteredRecommendations = animeData.recommendations.filter((rec) =>
    ['OVA', 'SPECIAL', 'TV', 'MOVIE', 'ONA', 'NOVEL'].includes(rec.type || ''),
  );

  const filteredRelations = animeData.relations.filter((rel) =>
    ['OVA', 'SPECIAL', 'TV', 'MOVIE', 'ONA', 'NOVEL', 'MANGA'].includes(
      rel.type || '',
    ),
  );

  return (
    <Sidebar>
      {filteredRelations.length > 0 && (
        <SidebarContainer>
          <>
            <p className='Section-Title'>RELATED</p>
            {filteredRelations
              .slice(0, window.innerWidth > 500 ? 5 : 3)
              .map((relation, index) => (
                <Link
                  to={`/watch/${relation.id}`}
                  key={relation.id}
                  style={{ textDecoration: 'none', color: 'inherit' }}
                  title={`${relation.title.userPreferred}`}
                  aria-label={`Watch ${relation.title.userPreferred}`}
                >
                  <Card style={{ animationDelay: `${index * 0.1}s` }}>
                    <AnimeImage
                      src={relation.image}
                      alt={relation.title.userPreferred}
                      loading='lazy'
                    />
                    <Info>
                      <TitleWithDot>
                        <StatusIndicator status={relation.status} />
                        <Title>
                          {relation.title.english ??
                            relation.title.romaji ??
                            relation.title.userPreferred}
                        </Title>
                      </TitleWithDot>
                      <Details
                        aria-label={`Details about ${relation.title.userPreferred}`}
                      >
                        {/* Conditionally render each piece of detail only if it's not null or empty */}
                        {relation.type && `${relation.type} `}
                        {relation.episodes && (
                          <>
                            <TbCards aria-hidden='true' />{' '}
                            {`${relation.episodes} `}
                          </>
                        )}
                        {relation.rating && (
                          <>
                            <FaStar aria-hidden='true' />{' '}
                            {`${relation.rating} `}
                          </>
                        )}
                      </Details>
                    </Info>
                  </Card>
                </Link>
              ))}
          </>
        </SidebarContainer>
      )}
      {filteredRecommendations.length > 0 && (
        <SidebarContainer>
          <>
            <p className='Section-Title'>RECOMMENDED</p>
            {filteredRecommendations
              .slice(0, window.innerWidth > 500 ? 5 : 3)
              .map((recommendation, index) => (
                <Link
                  to={`/watch/${recommendation.id}`}
                  key={recommendation.id}
                  style={{ textDecoration: 'none', color: 'inherit' }}
                  title={`Watch ${recommendation.title.userPreferred}`}
                >
                  <Card style={{ animationDelay: `${index * 0.1}s` }}>
                    <AnimeImage
                      src={recommendation.image}
                      alt={recommendation.title.userPreferred}
                      loading='lazy'
                    />
                    <Info>
                      <TitleWithDot>
                        <StatusIndicator status={recommendation.status} />
                        <Title>
                          {recommendation.title.english ??
                            recommendation.title.romaji ??
                            recommendation.title.userPreferred}
                        </Title>
                      </TitleWithDot>
                      <Details
                        aria-label={`Details about ${recommendation.title.userPreferred}`}
                      >
                        {/* Similar conditional rendering for recommendation details */}
                        {recommendation.type && `${recommendation.type} `}
                        {recommendation.episodes && (
                          <>
                            <TbCards aria-hidden='true' />{' '}
                            {`${recommendation.episodes} `}
                          </>
                        )}
                        {recommendation.rating && (
                          <>
                            <FaStar aria-hidden='true' />{' '}
                            {`${recommendation.rating} `}
                          </>
                        )}
                      </Details>
                    </Info>
                  </Card>
                </Link>
              ))}
          </>
        </SidebarContainer>
      )}
    </Sidebar>
  );
};


================================================
FILE: src/components/Watch/EpisodeList.tsx
================================================
import React, {
  useState,
  useMemo,
  useCallback,
  useEffect,
  useRef,
} from 'react';
import styled from 'styled-components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
  faPlay,
  faThList,
  faTh,
  faSearch,
  faImage,
} from '@fortawesome/free-solid-svg-icons';
import { Episode } from '../../index';

interface Props {
  animeId: string | undefined;
  episodes: Episode[];
  selectedEpisodeId: string;
  onEpisodeSelect: (id: string) => void;
  maxListHeight: string;
}

// Styled components for the episode list
const ListContainer = styled.div<{ $maxHeight: string }>`
  background-color: var(--global-secondary-bg);
  color: var(--global-text);
  border-radius: var(--global-border-radius);
  overflow: hidden;
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  max-height: ${({ $maxHeight }) => $maxHeight};
  @media (max-width: 1000px) {
    max-height: 18rem;
  }
  @media (max-width: 500px) {
    max-height: ${({ $maxHeight }) => $maxHeight};
  }
`;

const EpisodeGrid = styled.div<{ $isRowLayout: boolean }>`
  display: grid;
  grid-template-columns: ${({ $isRowLayout }) =>
    $isRowLayout ? '1fr' : 'repeat(auto-fill, minmax(4rem, 1fr))'};
  gap: 0.29rem;
  padding: 0.4rem;
  overflow-y: auto;
  flex-grow: 1;
`;

const EpisodeImage = styled.img`
  max-width: 250px;
  max-height: 150px;
  height: auto;
  margin-top: 0.5rem;
  border-radius: var(--global-border-radius);
  @media (max-width: 500px) {
    max-width: 125px;
    max-height: 80px;
  }
`;

const ListItem = styled.button<{
  $isSelected: boolean;
  $isRowLayout: boolean;
  $isWatched: boolean;
}>`
  transition:
    padding 0.3s ease-in-out,
    transform 0.3s ease-in-out;
  animation: popIn 0.3s ease-in-out;
  background-color: ${({ $isSelected, $isWatched }) =>
    $isSelected
      ? $isWatched
        ? 'var(--primary-accent)' // Selected and watched
        : 'var(--primary-accent-bg)' // Selected but not watched
      : $isWatched
        ? 'var(--primary-accent-bg); filter: brightness(0.8);' // Not selected but watched
        : 'var(--global-tertiary-bg)'};

  border: none;
  border-radius: var(--global-border-radius);
  color: ${({ $isSelected, $isWatched }) =>
    $isSelected
      ? $isWatched
        ? 'var(--global-text)' // Selected and watched
        : 'var(--global-text)' // Selected but not watched
      : $isWatched
        ? 'var(--primary-accent); filter: brightness(0.8);' // Not selected but watched
        : 'grey'}; // Not selected and not watched

  padding: ${({ $isRowLayout }) =>
    $isRowLayout ? '0.6rem 0.5rem' : '0.4rem 0'};
  text-align: ${({ $isRowLayout }) => ($isRowLayout ? 'left' : 'center')};
  cursor: pointer;
  justify-content: ${({ $isRowLayout }) =>
    $isRowLayout ? 'space-between' : 'center'};
  align-items: center;

  &:hover,
  &:active,
  &:focus {
    ${({ $isSelected, $isWatched }) =>
      $isSelected
        ? $isWatched
          ? 'filter: brightness(1.1)' // Selected and watched
          : 'filter: brightness(1.1)' // Selected but not watched
        : $isWatched
          ? 'filter: brightness(1.1)' // Not selected but watched
          : 'background-color: var(--global-button-hover-bg); filter: brightness(1.05); color: #FFFFFF'};
    padding-left: ${({ $isRowLayout }) => ($isRowLayout ? '1rem' : '')};
  }
`;

const ControlsContainer = styled.div`
  display: flex;
  align-items: center;
  background-color: var(--global-secondary-bg);
  border-bottom: 1px solid var(--global-shadow);
  padding: 0.25rem 0;
`;

const SelectInterval = styled.select`
  padding: 0.5rem;
  background-color: var(--global-secondary-bg);
  color: var(--global-text);
  border: none;
  border-radius: var(--global-border-radius);
`;

const LayoutToggle = styled.button`
  background-color: var(--global-secondary-bg);
  border: 1px solid var(--global-shadow);
  padding: 0.5rem;
  margin-right: 0.5rem;
  cursor: pointer;
  color: var(--global-text);
  border-radius: var(--global-border-radius);
  transition:
    background-color 0.15s,
    color 0.15s;

  &:hover,
  &:active,
  &:focus {
    background-color: var(--global-button-hover-bg);
  }
`;

const SearchContainer = styled.div`
  display: flex;
  align-items: center;
  background-color: var(--global-secondary-bg);
  border: 1px solid var(--global-shadow);
  padding: 0.5rem;
  gap: 0.25rem;
  margin: 0 0.5rem;
  border-radius: var(--global-border-radius);
  transition:
    background-color 0.15s,
    color 0.15s;

  &:hover,
  &:active,
  &:focus {
    background-color: var(--global-button-hover-bg);
  }
`;

const SearchInput = styled.input`
  border: none;
  background-color: transparent;
  color: var(--global-text);
  outline: none;
  width: 100%;

  &::placeholder {
    color: var(--global-placeholder);
  }
`;

const Icon = styled.div`
  color: var(--global-text);
  opacity: 0.5;
  font-size: 0.8rem;
  transition: opacity 0.2s;

  @media (max-width: 768px) {
    display: none; /* Hide on mobile */
  }
`;

const EpisodeNumber = styled.span``;
const EpisodeTitle = styled.span`
  padding: 0.5rem;
`;

// The updated EpisodeList component
export const EpisodeList: React.FC<Props> = ({
  animeId,
  episodes,
  selectedEpisodeId,
  onEpisodeSelect,
  maxListHeight,
}) => {
  // State for interval, layout, user layout preference, search term, and watched episodes
  const episodeGridRef = useRef<HTMLDivElement>(null);
  const episodeRefs = useRef<{ [key: string]: HTMLButtonElement | null }>({});
  const [interval, setInterval] = useState<[number, number]>([0, 99]);
  const [isRowLayout, setIsRowLayout] = useState(true);
  const [userLayoutPreference, setUserLayoutPreference] = useState<
    boolean | null
  >(null);
  const [searchTerm, setSearchTerm] = useState('');
  const [watchedEpisodes, setWatchedEpisodes] = useState<Episode[]>([]);
  const defaultLayoutMode = episodes.every((episode) => episode.title)
    ? 'list'
    : 'grid';
  const [displayMode, setDisplayMode] = useState<'list' | 'grid' | 'imageList'>(
    () => {
      const savedMode = animeId
        ? localStorage.getItem(`listLayout-[${animeId}]`)
        : null;
      return (savedMode as 'list' | 'grid' | 'imageList') || defaultLayoutMode;
    },
  );

  const [selectionInitiatedByUser, setSelectionInitiatedByUser] =
    useState(false);
  // Update local storage when watched episodes change
  useEffect(() => {
    if (animeId && watchedEpisodes.length > 0) {
      localStorage.setItem(
        `watched-episodes-${animeId}`,
        JSON.stringify(watchedEpisodes),
      );
    }
  }, [animeId, watchedEpisodes]);
  // Load watched episodes from local storage when animeId changes
  useEffect(() => {
    if (animeId) {
      localStorage.setItem(`listLayout-[${animeId}]`, displayMode);
      const watched = localStorage.getItem('watched-episodes');
      if (watched) {
        const watchedEpisodesObject = JSON.parse(watched);
        const watchedEpisodesForAnime = watchedEpisodesObject[animeId];
        if (watchedEpisodesForAnime) {
          setWatchedEpisodes(watchedEpisodesForAnime);
        }
      }
    }
  }, [animeId]);

  // Function to handle episode selection
  // Function to mark an episode as watched
  const markEpisodeAsWatched = useCallback(
    (id: string) => {
      if (animeId) {
        setWatchedEpisodes((prevWatchedEpisodes) => {
          const updatedWatchedEpisodes = [...prevWatchedEpisodes];
          const selectedEpisodeIndex = updatedWatchedEpisodes.findIndex(
            (episode) => episode.id === id,
          );
          if (selectedEpisodeIndex === -1) {
            const selectedEpisode = episodes.find(
              (episode) => episode.id === id,
            );
            if (selectedEpisode) {
              updatedWatchedEpisodes.push(selectedEpisode);
              // Update the watched episodes object in local storage
              localStorage.setItem(
                'watched-episodes',
                JSON.stringify({
                  ...JSON.parse(
                    localStorage.getItem('watched-episodes') || '{}',
                  ),
                  [animeId]: updatedWatchedEpisodes,
                }),
              );
              return updatedWatchedEpisodes;
            }
          }
          return prevWatchedEpisodes;
        });
      }
    },
    [episodes, animeId],
  );
  const handleEpisodeSelect = useCallback(
    (id: string) => {
      setSelectionInitiatedByUser(true);
      markEpisodeAsWatched(id); // Mark the episode as watched
      onEpisodeSelect(id);
    },
    [onEpisodeSelect, markEpisodeAsWatched],
  );

  // Update watched episodes when a new episode is selected or visited
  useEffect(() => {
    if (selectedEpisodeId && !selectionInitiatedByUser) {
      markEpisodeAsWatched(selectedEpisodeId);
    }
  }, [selectedEpisodeId, selectionInitiatedByUser, markEpisodeAsWatched]);

  // Generate interval options
  const intervalOptions = useMemo(() => {
    return episodes.reduce<{ start: number; end: number }[]>(
      (options, _, index) => {
        if (index % 100 === 0) {
          const start = index;
          const end = Math.min(index + 99, episodes.length - 1);
          options.push({ start, end });
        }
        return options;
      },
      [],
    );
  }, [episodes]);

  // Handle interval change
  const handleIntervalChange = useCallback(
    (e: React.ChangeEvent<HTMLSelectElement>) => {
      const [start, end] = e.target.value.split('-').map(Number);
      setInterval([start, end]);
    },
    [],
  );

  // Toggle layout preference
  const toggleLayoutPreference = useCallback(() => {
    setDisplayMode((prevMode) => {
      const nextMode =
        prevMode === 'list'
          ? 'grid'
          : prevMode === 'grid'
            ? 'imageList'
            : 'list';
      if (animeId) {
        localStorage.setItem(`listLayout-[${animeId}]`, nextMode);
      }
      return nextMode;
    });
  }, [animeId]);

  // Filter episodes based on search input
  const filteredEpisodes = useMemo(() => {
    const searchQuery = searchTerm.toLowerCase();
    return episodes.filter(
      (episode) =>
        episode.title?.toLowerCase().includes(searchQuery) ||
        episode.number.toString().includes(searchQuery),
    );
  }, [episodes, searchTerm]);

  // Apply the interval to the filtered episodes
  const displayedEpisodes = useMemo(() => {
    if (!searchTerm) {
      // If there's no search term, apply interval to all episodes
      return episodes.slice(interval[0], interval[1] + 1);
    }
    // If there is a search term, display filtered episodes without applying interval
    return filteredEpisodes;
  }, [episodes, filteredEpisodes, interval, searchTerm]);

  // Determine layout based on episodes and user preference
  useEffect(() => {
    const allTitlesNull = episodes.every((episode) => episode.title === null);
    const defaultLayout = episodes.length <= 26 && !allTitlesNull;

    setIsRowLayout(
      userLayoutPreference !== null ? userLayoutPreference : defaultLayout,
    );

    // Find the selected episode
    if (!selectionInitiatedByUser) {
      const selectedEpisode = episodes.find(
        (episode) => episode.id === selectedEpisodeId,
      );
      if (selectedEpisode) {
        // Find the interval containing the selected episode
        for (let i = 0; i < intervalOptions.length; i++) {
          const { start, end } = intervalOptions[i];
          if (
            selectedEpisode.number >= start + 1 &&
            selectedEpisode.number <= end + 1
          ) {
            setInterval([start, end]);
            break;
          }
        }
      }
    }
  }, [
    episodes,
    userLayoutPreference,
    selectedEpisodeId,
    intervalOptions,
    selectionInitiatedByUser,
  ]);

  useEffect(() => {
    const timer = setTimeout(() => {
      if (
        selectedEpisodeId &&
        episodeRefs.current[selectedEpisodeId] &&
        episodeGridRef.current &&
        !selectionInitiatedByUser
      ) {
        const episodeElement = episodeRefs.current[selectedEpisodeId];
        const container = episodeGridRef.current;

        // Ensure episodeElement is not null before proceeding
        if (episodeElement && container) {
          // Calculate episode's top position relative to the container
          const episodeTop =
            episodeElement.getBoundingClientRect().top -
            container.getBoundingClientRect().top;

          // Calculate the desired scroll position to center the episode in the container
          const episodeHeight = episodeElement.offsetHeight;
          const containerHeight = container.offsetHeight;
          const desiredScrollPosition =
            episodeTop + episodeHeight / 2 - containerHeight / 2;

          container.scrollTo({
            top: desiredScrollPosition,
            behavior: 'smooth',
          });

          setSelectionInitiatedByUser(false);
        }
      }
    }, 100); // A delay ensures the layout has stabilized, especially after dynamic content loading.

    return () => clearTimeout(timer);
  }, [selectedEpisodeId, episodes, displayMode, selectionInitiatedByUser]);

  // Render the EpisodeList component
  return (
    <ListContainer $maxHeight={maxListHeight}>
      <ControlsContainer>
        <SelectInterval
          onChange={handleIntervalChange}
          value={`${interval[0]}-${interval[1]}`}
        >
          {intervalOptions.map(({ start, end }, index) => (
            <option key={index} value={`${start}-${end}`}>
              Episodes {start + 1} - {end + 1}
            </option>
          ))}
        </SelectInterval>

        <SearchContainer>
          <Icon>
            <FontAwesomeIcon icon={faSearch} />
          </Icon>
          <SearchInput
            type='text'
            placeholder='Search episodes...'
            value={searchTerm}
            onChange={(e) => setSearchTerm(e.target.value)}
          />
        </SearchContainer>
        <LayoutToggle onClick={toggleLayoutPreference}>
          {displayMode === 'list' && <FontAwesomeIcon icon={faThList} />}
          {displayMode === 'grid' && <FontAwesomeIcon icon={faTh} />}
          {displayMode === 'imageList' && <FontAwesomeIcon icon={faImage} />}
        </LayoutToggle>
      </ControlsContainer>
      <EpisodeGrid
        key={`episode-grid-${displayMode}`}
        $isRowLayout={displayMode === 'list' || displayMode === 'imageList'}
        ref={episodeGridRef}
      >
        {displayedEpisodes.map((episode) => {
          const $isSelected = episode.id === selectedEpisodeId;
          const $isWatched = watchedEpisodes.some((e) => e.id === episode.id);

          return (
            <ListItem
              key={episode.id}
              $isSelected={$isSelected}
              $isRowLayout={
                displayMode === 'list' || displayMode === 'imageList'
              }
              $isWatched={$isWatched}
              onClick={() => handleEpisodeSelect(episode.id)}
              aria-selected={$isSelected}
              ref={(el) => (episodeRefs.current[episode.id] = el)} // Reference to each episode's button
            >
              {displayMode === 'imageList' ? (
                <>
                  <div>
                    <EpisodeNumber>{episode.number}. </EpisodeNumber>
                    <EpisodeTitle>{episode.title}</EpisodeTitle>
                  </div>
                  <EpisodeImage
                    src={episode.image}
                    alt={`Episode ${episode.number} - ${episode.title}`}
                  />
                </>
              ) : displayMode === 'grid' ? (
                <>
                  <div
                    style={{
                      display: 'flex',
                      flexDirection: 'column',
                      justifyContent: 'center',
                      alignItems: 'center',
                      height: '100%',
                    }}
                  >
                    {$isSelected ? (
                      <FontAwesomeIcon icon={faPlay} />
                    ) : (
                      <EpisodeNumber>{episode.number}</EpisodeNumber>
                    )}
                  </div>
                </>
              ) : (
                // Render for 'list' layout
                <>
                  <EpisodeNumber>{episode.number}. </EpisodeNumber>
                  <EpisodeTitle>{episode.title}</EpisodeTitle>
                  {$isSelected && <FontAwesomeIcon icon={faPlay} />}
                </>
              )}
            </ListItem>
          );
        })}
      </EpisodeGrid>
    </ListContainer>
  );
};


================================================
FILE: src/components/Watch/Seasons.tsx
================================================
import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { Relation } from '../../index';

const SeasonCardContainer = styled.div`
  display: flex;
  flex-wrap: wrap;
  justify-content: left;
  gap: 1rem;
  margin-top: 1rem;
  margin-bottom: 1rem;
  @media (max-width: 500px) {
    justify-content: center;
  }
`;

const SeasonCard = styled(Link)`
  background-size: cover;
  background-position: center;
  padding: 0.9rem;
  height: 6rem;
  width: 20rem;
  @media (max-width: 500px) {
    height: 3rem;
    width: 8rem;
    padding: 1.3rem;
  }
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  border-radius: 0.3rem;
  box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
  overflow: hidden;
  cursor: pointer;
  text-decoration: none;

  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
    border-radius: var(--global-border-radius);
    z-index: 1;
  }
  transition: transform 0.2s ease-in-out;

  &:hover,
  &:active &:focus {
    transform: translateY(-5px);
    @media (max-width: 500px) {
      transform: none;
    }
  }
`;

const Content = styled.div`
  position: relative;
  z-index: 2;
`;

const SeasonName = styled.div`
  font-size: 0.9rem;
  @media (max-width: 500px) {
    display: none;
    width: 8rem;
    font-size: 0.8rem;
  }
  color: white;
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
`;

const RelationType = styled.div`
  font-size: 1.3rem;
  @media (max-width: 500px) {
    font-size: 1.1rem;
    width: 8rem;
    margin-bottom: 0.25rem;
  }
  font-weight: bold;
  color: white;
  border-radius: var(--global-border-radius);
  text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
  margin-bottom: 0.75rem;
`;

export const Seasons: React.FC<{ relations: Relation[] }> = ({ relations }) => {
  const sortedRelations = relations.sort((a, b) => {
    if (a.relationType === 'PREQUEL' && b.relationType !== 'PREQUEL') {
      return -1;
    }
    if (a.relationType !== 'PREQUEL' && b.relationType === 'PREQUEL') {
      return 1;
    }
    return 0;
  });

  return (
    <SeasonCardContainer>
      {sortedRelations.map((relation) => (
        <SeasonCard
          key={relation.id}
          to={`/watch/${relation.id}`}
          title={`Watch ${relation.title.english || relation.title.romaji || relation.title.userPreferred}`}
          aria-label={`Watch ${relation.title.english || relation.title.romaji || relation.title.userPreferred}`}
          style={{ backgroundImage: `url(${relation.image})` }}
        >
          <img
            src={relation.image}
            alt={`${relation.title.english || relation.title.romaji || relation.title.userPreferred} Cover`}
            style={{ display: 'none' }}
          />
          <Content>
            <RelationType>{relation.relationType}</RelationType>
            <SeasonName>
              {relation.title.english ||
                relation.title.romaji ||
                relation.title.userPreferred}
            </SeasonName>
          </Content>
        </SeasonCard>
      ))}
    </SeasonCardContainer>
  );
};


================================================
FILE: src/components/Watch/Video/EmbedPlayer.tsx
================================================
import React from 'react';
import styled from 'styled-components';

const Container = styled.div``;

const Iframe = styled.iframe`
  border-radius: var(--global-border-radius);
  border: none;
  min-height: 16.24rem;
`;

export const EmbedPlayer: React.FC<{ src: string }> = ({ src }) => {
  return (
    <Container>
      <Iframe src={src} allowFullScreen />
    </Container>
  );
};


================================================
FILE: src/components/Watch/Video/MediaSource.tsx
================================================
import React, { useState } from 'react';
import styled from 'styled-components';
import {
  FaMicrophone,
  FaClosedCaptioning,
  FaBell,
  FaDownload,
  FaShare,
} from 'react-icons/fa';

// Props interface
interface MediaSourceProps {
  sourceType: string;
  setSourceType: (sourceType: string) => void;
  language: string;
  setLanguage: (language: string) => void;
  downloadLink: string;
  episodeId?: string;
  airingTime?: string;
  nextEpisodenumber?: string;
}

// Adjust the Container for responsive layout
const UpdatedContainer = styled.div`
  justify-content: center;
  margin-top: 1rem;
  gap: 1rem;
  display: flex;
  @media (max-width: 1000px) {
    flex-direction: column;
  }
`;

const Table = styled.table`
  font-size: 0.9rem;
  border-collapse: collapse;
  font-weight: bold;
  margin-left: auto;
  margin-right: auto;
`;

const TableRow = styled.tr``;

const TableCell = styled.td`
  padding: 0.35rem;
  @media (max-width: 500px) {
    text-align: center;
    font-size: 0.8rem;
  }
  svg {
    margin-bottom: -0.1rem;
    @media (max-width: 500px) {
      margin-bottom: 0rem;
    }
  }
`;

const ButtonWrapper = styled.div`
  width: 90px; // Or a specific pixel width, if preferred
  display: flex;
  justify-content: center;
  gap: 0.5rem;
`;

const ButtonBase = styled.button`
  flex: 1; // Make the button expand to fill the wrapper
  padding: 0.5rem;
  border: none;
  font-weight: bold;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  background-color: var(--global-div);
  color: var(--global-text);
  transition:
    background-color 0.2s ease,
    transform 0.2s ease-in-out;
  text-align: center;

  &:hover {
    background-color: var(--primary-accent);
    transform: scale(1.025);
  }
  &:active {
    transform: scale(0.975);
  }
`;

const Button = styled(ButtonBase)`
  &.active {
    background-color: var(--primary-accent);
  }
`;

const DownloadLink = styled.a`
  display: inline-flex; // Use inline-flex to easily center the icon
  align-items: center; // Align the icon vertically center
  margin-left: 0.5rem;
  padding: 0.5rem;
  gap: 0.25rem;
  font-size: 0.9rem;
  font-weight: bold;
  border: none;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  background-color: var(--global-div);
  color: var(--global-text);
  text-align: center;
  text-decoration: none;
  transition:
    background-color 0.3s ease,
    transform 0.2s ease-in-out;

  svg {
    font-size: 0.85rem; // Adjust icon size
  }

  &:hover {
    background-color: var(--primary-accent);
    transform: scale(1.025);
  }
  &:active {
    transform: scale(0.975);
  }
`;

const ShareButton = styled(ButtonBase)`
  display: inline-flex; // Align items in a row
  align-items: center; // Center items vertically
  margin-left: 0.5rem;
  padding: 0.5rem;
  gap: 0.25rem;
  font-size: 0.9rem;
  border: none;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  background-color: var(--global-div);
  color: var(--global-text);
  text-decoration: none;
  svg {
    font-size: 0.85rem; // Adjust icon size
  }
`;

const ResponsiveTableContainer = styled.div`
  background-color: var(--global-div-tr);
  padding: 0.75rem;
  border-radius: var(--global-border-radius);
  @media (max-width: 500px) {
    display: block;
  }
`;

const EpisodeInfoColumn = styled.div`
  flex-grow: 1;
  display: block;
  background-color: var(--global-div-tr);
  border-radius: var(--global-border-radius);
  padding: 0.75rem;
  @media (max-width: 1000px) {
    display: block;
    margin-right: 0rem;
  }
  p {
    font-size: 0.9rem;
    margin: 0;
  }
  h4 {
    margin: 0rem;
    font-size: 1.15rem;
    margin-bottom: 1rem;
  }
  @media (max-width: 500px) {
    p {
      font-size: 0.8rem;
      margin: 0rem;
    }
    h4 {
      font-size: 1rem;
      margin-bottom: 0rem;
    }
  }
`;

export const MediaSource: React.FC<MediaSourceProps> = ({
  sourceType,
  setSourceType,
  language,
  setLanguage,
  downloadLink,
  episodeId,
  airingTime,
  nextEpisodenumber,
}) => {
  const [isCopied, setIsCopied] = useState(false);

  const handleShareClick = () => {
    navigator.clipboard.writeText(window.location.href);
    setIsCopied(true);
    setTimeout(() => {
      setIsCopied(false);
    }, 2000);
  };

  return (
    <UpdatedContainer>
      <EpisodeInfoColumn>
        {episodeId ? (
          <>
            You're watching <strong>Episode {episodeId}</strong>
            <DownloadLink
              href={downloadLink}
              target='_blank'
              rel='noopener noreferrer'
            >
              <FaDownload />
            </DownloadLink>
            <ShareButton onClick={handleShareClick}>
              <FaShare />
            </ShareButton>
            {isCopied && <p>Copied to clipboard!</p>}
            <br />
            <br />
            <p>If current servers don't work, please try other servers.</p>
          </>
        ) : (
          'Loading episode information...'
        )}
        {airingTime && (
          <>
            <p>
              Episode <strong>{nextEpisodenumber}</strong> will air in{' '}
              <FaBell />
              <strong> {airingTime}</strong>.
            </p>
          </>
        )}
      </EpisodeInfoColumn>
      <ResponsiveTableContainer>
        <Table>
          <tbody>
            <TableRow>
              <TableCell>
                <FaClosedCaptioning /> Sub
              </TableCell>
              <TableCell>
                <ButtonWrapper>
                  <Button
                    className={
                      sourceType === 'default' && language === 'sub'
                        ? 'active'
                        : ''
                    }
                    onClick={() => {
                      setSourceType('default');
                      setLanguage('sub');
                    }}
                  >
                    Default
                  </Button>
                </ButtonWrapper>
              </TableCell>
              <TableCell>
                <ButtonWrapper>
                  <Button
                    className={
                      sourceType === 'vidstreaming' && language === 'sub'
                        ? 'active'
                        : ''
                    }
                    onClick={() => {
                      setSourceType('vidstreaming');
                      setLanguage('sub');
                    }}
                  >
                    Vidstream
                  </Button>
                </ButtonWrapper>
              </TableCell>
              <TableCell>
                <ButtonWrapper>
                  <Button
                    className={
                      sourceType === 'gogo' && language === 'sub'
                        ? 'active'
                        : ''
                    }
                    onClick={() => {
                      setSourceType('gogo');
                      setLanguage('sub');
                    }}
                  >
                    Gogo
                  </Button>
                </ButtonWrapper>
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <FaMicrophone /> Dub
              </TableCell>
              <TableCell>
                <ButtonWrapper>
                  <Button
                    className={
                      sourceType === 'default' && language === 'dub'
                        ? 'active'
                        : ''
                    }
                    onClick={() => {
                      setSourceType('default');
                      setLanguage('dub');
                    }}
                  >
                    Default
                  </Button>
                </ButtonWrapper>
              </TableCell>
              <TableCell>
                <ButtonWrapper>
                  <Button
                    className={
                      sourceType === 'vidstreaming' && language === 'dub'
                        ? 'active'
                        : ''
                    }
                    onClick={() => {
                      setSourceType('vidstreaming');
                      setLanguage('dub');
                    }}
                  >
                    Vidstream
                  </Button>
                </ButtonWrapper>
              </TableCell>
              <TableCell>
                <ButtonWrapper>
                  <Button
                    className={
                      sourceType === 'gogo' && language === 'dub'
                        ? 'active'
                        : ''
                    }
                    onClick={() => {
                      setSourceType('gogo');
                      setLanguage('dub');
                    }}
                  >
                    Gogo
                  </Button>
                </ButtonWrapper>
              </TableCell>
            </TableRow>
          </tbody>
        </Table>
      </ResponsiveTableContainer>
    </UpdatedContainer>
  );
};


================================================
FILE: src/components/Watch/Video/Player.tsx
================================================
import { useEffect, useRef, useState } from 'react';
import './PlayerStyles.css';
import {
  isHLSProvider,
  MediaPlayer,
  MediaProvider,
  Poster,
  Track,
  type MediaProviderAdapter,
  type MediaProviderChangeEvent,
  type MediaPlayerInstance,
} from '@vidstack/react';
import styled from 'styled-components';
import {
  fetchSkipTimes,
  fetchAnimeStreamingLinks,
  useSettings,
} from '../../../index';
import {
  DefaultAudioLayout,
  defaultLayoutIcons,
  DefaultVideoLayout,
} from '@vidstack/react/player/layouts/default';
import { TbPlayerTrackPrev, TbPlayerTrackNext } from 'react-icons/tb';
import { FaCheck } from 'react-icons/fa6';
import { RiCheckboxBlankFill } from 'react-icons/ri';

const Button = styled.button<{ $autoskip?: boolean }>`
  padding: 0.25rem;
  font-size: 0.8rem;
  border: none;
  margin-right: 0.25rem;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  background-color: var(--global-div);
  color: var(--global-text);
  svg {
    margin-bottom: -0.1rem;
    color: grey;
  }
  @media (max-width: 500px) {
    font-size: 0.7rem;
  }

  &.active {
    background-color: var(--primary-accent);
  }
  ${({ $autoskip }) =>
    $autoskip &&
    `
    color: #d69e00; 
    svg {
      color: #d69e00; 
    }
  `}
`;

type PlayerProps = {
  episodeId: string;
  banner?: string;
  malId?: string;
  updateDownloadLink: (link: string) => void;
  onEpisodeEnd: () => Promise<void>;
  onPrevEpisode: () => void;
  onNextEpisode: () => void;
  animeTitle?: string;
};

type StreamingSource = {
  url: string;
  quality: string;
};

type SkipTime = {
  interval: {
    startTime: number;
    endTime: number;
  };
  skipType: string;
};

type FetchSkipTimesResponse = {
  results: SkipTime[];
};

export function Player({
  episodeId,
  banner,
  malId,
  updateDownloadLink,
  onEpisodeEnd,
  onPrevEpisode,
  onNextEpisode,
  animeTitle,
}: PlayerProps) {
  const player = useRef<MediaPlayerInstance>(null);
  const [src, setSrc] = useState<string>('');
  const [vttUrl, setVttUrl] = useState<string>('');
  const [currentTime, setCurrentTime] = useState<number>(0);
  const [skipTimes, setSkipTimes] = useState<SkipTime[]>([]);
  const [totalDuration, setTotalDuration] = useState<number>(0);
  const [vttGenerated, setVttGenerated] = useState<boolean>(false);
  const episodeNumber = getEpisodeNumber(episodeId);
  const animeVideoTitle = animeTitle;

  const { settings, setSettings } = useSettings();
  const { autoPlay, autoNext, autoSkip } = settings;

  useEffect(() => {
    setCurrentTime(parseFloat(localStorage.getItem('currentTime') || '0'));

    fetchAndSetAnimeSource();
    fetchAndProcessSkipTimes();
    return () => {
      if (vttUrl) URL.revokeObjectURL(vttUrl);
    };
  }, [episodeId, malId, updateDownloadLink]);

  useEffect(() => {
    if (autoPlay && player.current) {
      player.current
        .play()
        .catch((e) =>
          console.log('Playback failed to start automatically:', e),
        );
    }
  }, [autoPlay, src]);

  useEffect(() => {
    if (player.current && currentTime) {
      player.current.currentTime = currentTime;
    }
  }, [currentTime]);

  function onProviderChange(
    provider: MediaProviderAdapter | null,
    _nativeEvent: MediaProviderChangeEvent,
  ) {
    if (isHLSProvider(provider)) {
      provider.config = {};
    }
  }

  function onLoadedMetadata() {
    if (player.current) {
      setTotalDuration(player.current.duration);
    }
  }

  function onTimeUpdate() {
    if (player.current) {
      const currentTime = player.current.currentTime;
      const duration = player.current.duration || 1;
      const playbackPercentage = (currentTime / duration) * 100;
      const playbackInfo = {
        currentTime,
        playbackPercentage,
      };
      const allPlaybackInfo = JSON.parse(
        localStorage.getItem('all_episode_times') || '{}',
      );
      allPlaybackInfo[episodeId] = playbackInfo;
      localStorage.setItem(
        'all_episode_times',
        JSON.stringify(allPlaybackInfo),
      );

      if (autoSkip && skipTimes.length) {
        const skipInterval = skipTimes.find(
          ({ interval }) =>
            currentTime >= interval.startTime && currentTime < interval.endTime,
        );
        if (skipInterval) {
          player.current.currentTime = skipInterval.interval.endTime;
        }
      }
    }
  }

  function generateWebVTTFromSkipTimes(
    skipTimes: FetchSkipTimesResponse,
    totalDuration: number,
  ): string {
    let vttString = 'WEBVTT\n\n';
    let previousEndTime = 0;

    const sortedSkipTimes = skipTimes.results.sort(
      (a, b) => a.interval.startTime - b.interval.startTime,
    );

    sortedSkipTimes.forEach((skipTime, index) => {
      const { startTime, endTime } = skipTime.interval;
      const skipType =
        skipTime.skipType.toUpperCase() === 'OP' ? 'Opening' : 'Outro';

      // Insert default title chapter before this skip time if there's a gap
      if (previousEndTime < startTime) {
        vttString += `${formatTime(previousEndTime)} --> ${formatTime(startTime)}\n`;
        vttString += `${animeVideoTitle} - Episode ${episodeNumber}\n\n`;
      }

      // Insert this skip time
      vttString += `${formatTime(startTime)} --> ${formatTime(endTime)}\n`;
      vttString += `${skipType}\n\n`;
      previousEndTime = endTime;

      // Insert default title chapter after the last skip time
      if (index === sortedSkipTimes.length - 1 && endTime < totalDuration) {
        vttString += `${formatTime(endTime)} --> ${formatTime(totalDuration)}\n`;
        vttString += `${animeVideoTitle} - Episode ${episodeNumber}\n\n`;
      }
    });

    return vttString;
  }

  async function fetchAndProcessSkipTimes() {
    if (malId && episodeId) {
      const episodeNumber = getEpisodeNumber(episodeId);
      try {
        const response: FetchSkipTimesResponse = await fetchSkipTimes({
          malId: malId.toString(),
          episodeNumber,
        });
        const filteredSkipTimes = response.results.filter(
          ({ skipType }) => skipType === 'op' || skipType === 'ed',
        );
        if (!vttGenerated) {
          const vttContent = generateWebVTTFromSkipTimes(
            { results: filteredSkipTimes },
            totalDuration,
          );
          const blob = new Blob([vttContent], { type: 'text/vtt' });
          const vttBlobUrl = URL.createObjectURL(blob);
          setVttUrl(vttBlobUrl);
          setSkipTimes(filteredSkipTimes);
          setVttGenerated(true);
        }
      } catch (error) {
        console.error('Failed to fetch skip times', error);
      }
    }
  }

  async function fetchAndSetAnimeSource() {
    try {
      const response = await fetchAnimeStreamingLinks(episodeId);
      const backupSource = response.sources.find(
        (source: StreamingSource) => source.quality === 'default',
      );
      if (backupSource) {
        setSrc(backupSource.url);
        updateDownloadLink(response.download);
      } else {
        console.error('Backup source not found');
      }
    } catch (error) {
      console.error('Failed to fetch anime streaming links', error);
    }
  }

  function formatTime(seconds: number): string {
    const minutes = Math.floor(seconds / 60);
    const remainingSeconds = Math.floor(seconds % 60);
    return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
  }

  function getEpisodeNumber(id: string): string {
    const parts = id.split('-');
    return parts[parts.length - 1];
  }

  const toggleAutoPlay = () =>
    setSettings({ ...settings, autoPlay: !autoPlay });
  const toggleAutoNext = () =>
    setSettings({ ...settings, autoNext: !autoNext });
  const toggleAutoSkip = () =>
    setSettings({ ...settings, autoSkip: !autoSkip });

  const handlePlaybackEnded = async () => {
    if (!autoNext) return;

    try {
      player.current?.pause();

      await new Promise((resolve) => setTimeout(resolve, 200)); // Delay for transition
      await onEpisodeEnd();
    } catch (error) {
      console.error('Error moving to the next episode:', error);
    }
  };

  return (
    <div style={{ animation: 'popIn 0.25s ease-in-out' }}>
      <MediaPlayer
        className='player'
        title={`${animeVideoTitle} - Episode ${episodeNumber}`}
        src={src}
        autoplay={autoPlay}
        crossorigin
        playsinline
        onLoadedMetadata={onLoadedMetadata}
        onProviderChange={onProviderChange}
        onTimeUpdate={onTimeUpdate}
        ref={player}
        aspectRatio='16/9'
        load='eager'
        posterLoad='eager'
        streamType='on-demand'
        storage='storage-key'
        keyTarget='player'
        onEnded={handlePlaybackEnded}
      >
        <MediaProvider>
          <Poster className='vds-poster' src={banner} alt='' />
          {vttUrl && (
            <Track kind='chapters' src={vttUrl} default label='Skip Times' />
          )}
        </MediaProvider>
        <DefaultAudioLayout icons={defaultLayoutIcons} />
        <DefaultVideoLayout icons={defaultLayoutIcons} />
      </MediaPlayer>
      <div
        className='player-menu'
        style={{
          backgroundColor: 'var(--global-div-tr)',
          borderRadius: 'var(--global-border-radius)',
        }}
      >
        <Button onClick={toggleAutoPlay}>
          {autoPlay ? <FaCheck /> : <RiCheckboxBlankFill />} Autoplay
        </Button>
        <Button $autoskip onClick={toggleAutoSkip}>
          {autoSkip ? <FaCheck /> : <RiCheckboxBlankFill />} Auto Skip
        </Button>
        <Button onClick={onPrevEpisode}>
          <TbPlayerTrackPrev /> Prev
        </Button>
        <Button onClick={onNextEpisode}>
          <TbPlayerTrackNext /> Next
        </Button>
        <Button onClick={toggleAutoNext}>
          {autoNext ? <FaCheck /> : <RiCheckboxBlankFill />} Auto Next
        </Button>
      </div>
    </div>
  );
}


================================================
FILE: src/components/Watch/Video/PlayerStyles.css
================================================
@import '@vidstack/react/player/styles/default/theme.css';
@import '@vidstack/react/player/styles/default/layouts/audio.css';
@import '@vidstack/react/player/styles/default/layouts/video.css';

.player {
  --brand-color: #f5f5f5;
  --focus-color: #4e9cf6;

  --audio-brand: var(--brand-color);
  --audio-focus-ring-color: var(--focus-color);
  --audio-border-radius: var(--global-border-radius);

  --video-brand: var(--brand-color);
  --video-focus-ring-color: var(--focus-color);
  --video-border-radius: var(--global-border-radius);
  --video-border: none;

  /* 👉 https://vidstack.io/docs/player/components/layouts/default#css-variables for more. */
}

.player[data-view-type='audio'] .vds-poster {
  display: none;
  border-radius: 5rem;
}

.player[data-view-type='video'] {
  aspect-ratio: 16 /9;
}

.src-buttons {
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  margin-top: 40px;
  margin-inline: auto;
  max-width: 300px;
}

.vds-poster {
  object-fit: cover;
}


================================================
FILE: src/components/Watch/WatchAnimeData.tsx
================================================
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { Seasons, Anime } from '../../index';
import { SiMyanimelist, SiAnilist } from 'react-icons/si';

const AnimeDataContainer = styled.div`
  margin-bottom: 1.5rem;

  @media (max-width: 1000px) {
    margin-bottom: 0rem;
  }
`;

const AnimeDataContainerTop = styled.div`
  border-radius: var(--global-border-radius);
  background-color: var(--global-div-tr);
  margin: 1rem 0;
  padding: 0.75rem;
  color: var(--global-text);
  align-items: center;
  flex-direction: row;
  align-items: flex-start;
  display: flex;
`;
const AnimeDataContainerMiddle = styled.div`
  border-radius: var(--global-border-radius);
  padding-top: 0.6rem;
  color: var(--global-text);
  align-items: center;
  flex-direction: row;
  align-items: flex-start;
  display: flex;
  @media (max-width: 500px) {
    padding-top: 0.4rem;
  }
`;

const AnimeDataContainerBottom = styled.div`
  margin-top: 0.6rem;
  @media (max-width: 750px) {
    margin-top: 0rem;
  }
`;

const ParentContainer = styled.div`
  display: grid;
  grid-template-columns: 1fr; // Default to single column for narrow screens
  @media (min-width: 750px) {
    grid-template-columns: 1.2fr 1fr; // Switch to two columns on wider screens
  }
  @media (min-width: 1500px) {
    grid-template-columns: 1.25fr 1fr; // Switch to two columns on wider screens
  }
`;

const AnimeDataText = styled.div`
  text-align: left;
  font-size: 0.8rem;
  .anime-title {
    line-height: 1.6rem;
    font-size: 1.5rem;
    font-weight: bold;
    color: var(--global-text);
    margin-bottom: 0.5rem;
    @media (max-width: 500px) {
      font-size: 1.25rem;
      margin-bottom: 0.2rem;
    }
  }
  .anime-title-romaji {
    font-style: italic;
    margin-top: 0rem;
    line-height: 0.6rem;
    margin-bottom: 0.5rem;
    @media (max-width: 500px) {
      line-height: 1rem;
      margin-bottom: 0.25rem;
    }
  }
  p {
    color: #828181;
    margin-top: 0rem;
    margin-bottom: 0.2rem;
    line-height: 1.3rem;
    @media (max-width: 500px) {
      line-height: 1rem;
    }
  }
  .Description {
    line-height: 1rem;
    max-width: 50rem;
    font-size: 0.9rem;
  }
  strong {
    color: var(--global-text);
  }
  .Seasons-Sections-Titles {
    color: var(--global-text);
    margin-top: 1rem;
    font-size: 1.25rem;
    font-weight: bold;
  }
`;

const AnimeInfoImage = styled.img`
  border-radius: var(--global-border-radius);
  max-height: 15rem;
  width: 10.5rem;
  margin-right: 1rem;
  margin-bottom: 0.5rem;
  @media (max-width: 500px) {
    max-height: 12rem;
    width: 8.5rem;
  }
`;

const Button = styled.button`
  padding: 0.5rem 0.6rem;
  background-color: var(--primary-accent);
  color: white;
  border: none;
  border-radius: var(--global-border-radius);
  cursor: pointer;
  transition: background-color 0.3s ease;
  outline: none;

  &:hover,
  &:active,
  &:focus {
    background-color: var(--primary-accent-bg);
  }

  @media (max-width: 1000px) {
    display: block;
    margin: 0 auto;
    margin-bottom: 0.5rem;
  }
`;

const ShowTrailerButton = styled(Button)`
  margin-right: 1rem;
  padding: 0rem;
  width: 10.5rem; //same as anime picture width.
  background-color: var(--global-div);
  transition:
    background-color 0.3s ease,
    transform 0.2s ease-in-out;
  color: var(--global-text);
  font-size: 0.85rem;
  margin-bottom: 0.5rem;
  &:hover,
  &:active,
  &:focus {
    background-color: var(--primary-accent);
    z-index: 2;
  }
  @media (max-width: 500px) {
    font-size: 0.8rem;
    width: 8.5rem;
  }
`;
const MalAniContainer = styled.div`
  display: flex; /* or grid */
  gap: 0.5rem;
  margin-right: 1rem;
`;

const MalAnilistSvg = styled.div`
  height: 2.5rem;
  width: 5rem;
  border-radius: var(--global-border-radius);
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: var(--global-div);
  color: var(--global-text);
  transition: 0.1s ease-in-out;

  &:hover,
  &:active,
  &:focus {
    transform: scale(1.05);
  }

  &:active {
    transform: scale(0.975);
  }

  @media (max-width: 500px) {
    width: 4rem;
    height: 2rem;
  }
`;

const ShowMoreButton = styled.button`
  background-color: var(--global-div);
  color: #828181;
  display: flex;
  border: none;
  padding: 0.5rem;
  border-radius: var(--global-border-radius);
  margin: 0.5rem 0;
  text-align: left;
  &:hover,
  &:active,
  &:focus {
    background-color: var(--global-div);
  }
  transition:
    color 0.3s ease,
    transform 0.2s ease-in-out;
  @media (max-width: 500px) {
    margin: 0rem;
    margin-top: 1rem;
  }
`;

const IframeTrailer = styled.iframe`
  aspect-ratio: 16/9;
  margin-bottom: 2rem;
  position: relative;
  border: none;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;

  @media (max-width: 1000px) {
    width: 100%;
    height: 100%;
  }
`;

const TrailerOverlay = styled.div`
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  animation: fadeIn 0.3s ease-in-out;
  animation: slideUp 0.3s ease-in-out;
  aspect-ratio: 16 / 9; // Maintain a 16:9 aspect ratio
`;

const TrailerOverlayContent = styled.div`
  width: 60%; // Adjusted width for better visibility
  aspect-ratio: 16 / 9; // Maintain a 16:9 aspect ratio
  background: white;
  border-radius: var(--global-border-radius);
  overflow: hidden;
  background-color: var(--global-div);
  @media (max-width: 500px) {
    width: 95%;
  }
`;

export const WatchAnimeData: React.FC<{ animeData: Anime }> = ({
  animeData,
}) => {
  const [isDescriptionExpanded, setDescriptionExpanded] = useState(false);
  const [showTrailer, setShowTrailer] = useState(false);

  const getAnimeIdFromUrl = () => {
    const pathParts = window.location.pathname.split('/');
    return pathParts[2];
  };

  const toggleDescription = () => {
    setDescriptionExpanded(!isDescriptionExpanded);
  };

  useEffect(() => {
    setDescriptionExpanded(false);
  }, [getAnimeIdFromUrl()]);

  const removeHTMLTags = (description: string): string => {
    return description.replace(/<[^>]+>/g, '').replace(/\([^)]*\)/g, '');
  };

  const toggleTrailer = () => {
    setShowTrailer(!showTrailer);
  };

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Escape' && showTrailer) {
        setShowTrailer(false);
      }
    };

    document.addEventListener('keydown', handleKeyDown);

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [showTrailer]);

  function capitalizeFirstLetter(str: string) {
    if (!str) return str;
    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
  }

  const isScreenUnder500px = () => window.innerWidth < 500;

  return (
    <>
      {animeData && (
        <AnimeDataContainer>
          <AnimeDataContainerTop>
            <div
              style={{
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
              }}
            >
              <AnimeInfoImage src={animeData.image} alt='Anime Title Image' />
              {animeData.trailer && animeData.status !== 'Not yet aired' && (
                <ShowTrailerButton onClick={toggleTrailer}>
                  <p>
                    <strong>TRAILER</strong>
                  </p>
                </ShowTrailerButton>
              )}
              {showTrailer && (
                <TrailerOverlay onClick={toggleTrailer}>
                  <TrailerOverlayContent onClick={(e) => e.stopPropagation()}>
                    <IframeTrailer
                      src={`https://www.youtube.com/embed/${animeData.trailer.id}`}
                      allowFullScreen
                    />
                  </TrailerOverlayContent>
                </TrailerOverlay>
              )}
              <MalAniContainer>
                {animeData.id && (
                  <a
                    href={`https://anilist.co/${!animeData.type ? 'anime' : animeData.type.toLowerCase() === 'manga' || animeData.type.toLowerCase() === 'novel' ? 'manga' : 'anime'}/${animeData.id}`}
                    target='_blank'
                    rel='noopener noreferrer'
                  >
                    <MalAnilistSvg>
                      <SiAnilist size={'1.5rem'} />
                    </MalAnilistSvg>
                  </a>
                )}
                {animeData.malId && (
                  <a
                    href={`https://myanimelist.net/${!animeData.type ? 'anime' : animeData.type.toLowerCase() === 'manga' || animeData.type.toLowerCase() === 'novel' ? 'manga' : 'anime'}/${animeData.malId}`}
                    target='_blank'
                    rel='noopener noreferrer'
                  >
                    <MalAnilistSvg>
                      <SiMyanimelist size={'2.75rem'} />
                    </MalAnilistSvg>
                  </a>
                )}
              </MalAniContainer>
            </div>
            <AnimeDataText>
              <>
                <p className='anime-title'>
                  {animeData.title.english
                    ? animeData.title.english
                    : animeData.title.romaji}
                </p>
                <p
                  className='anime-title-romaji'
                  style={{ color: animeData.color }}
                >
                  {animeData.title.romaji
                    ? animeData.title.romaji
                    : animeData.title.native}
                </p>
              </>
              {!isScreenUnder500px() && animeData.d
Download .txt
gitextract_c4uzzfr4/

├── .eslintrc.cjs
├── .github/
│   ├── FUNDING.yml
│   ├── SECURITY.md
│   └── dependabot.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── api/
│   └── exchange-token.ts
├── functions/
│   └── exchange-token.js
├── index.html
├── package.json
├── public/
│   └── manifest.json
├── renovate.json
├── robots.txt
├── server/
│   ├── README.md
│   └── server.ts
├── src/
│   ├── App.tsx
│   ├── client/
│   │   ├── ApolloClient.tsx
│   │   ├── authService.ts
│   │   ├── useAuth.tsx
│   │   └── userInfoTypes.ts
│   ├── components/
│   │   ├── Cards/
│   │   │   ├── CardGrid.tsx
│   │   │   └── CardItem.tsx
│   │   ├── Home/
│   │   │   ├── EpisodeCard.tsx
│   │   │   ├── HomeCarousel.tsx
│   │   │   └── HomeSideBar.tsx
│   │   ├── Navigation/
│   │   │   ├── DropSearch.tsx
│   │   │   ├── Footer.tsx
│   │   │   ├── Navbar.tsx
│   │   │   └── SearchFilters.tsx
│   │   ├── Profile/
│   │   │   ├── Settings.tsx
│   │   │   ├── SettingsProvider.tsx
│   │   │   └── WatchingAnilist.tsx
│   │   ├── ShortcutsPopup.tsx
│   │   ├── Skeletons/
│   │   │   └── Skeletons.tsx
│   │   ├── ThemeContext.tsx
│   │   ├── Watch/
│   │   │   ├── AnimeDataList.tsx
│   │   │   ├── EpisodeList.tsx
│   │   │   ├── Seasons.tsx
│   │   │   ├── Video/
│   │   │   │   ├── EmbedPlayer.tsx
│   │   │   │   ├── MediaSource.tsx
│   │   │   │   ├── Player.tsx
│   │   │   │   └── PlayerStyles.css
│   │   │   └── WatchAnimeData.tsx
│   │   └── shared/
│   │       └── StatusIndicator.tsx
│   ├── hooks/
│   │   ├── animeInterface.ts
│   │   ├── useApi.ts
│   │   ├── useCountdown.ts
│   │   ├── useFilters.ts
│   │   ├── useScroll.ts
│   │   └── useTIme.ts
│   ├── index.ts
│   ├── main.tsx
│   ├── pages/
│   │   ├── 404.tsx
│   │   ├── About.tsx
│   │   ├── Callback.tsx
│   │   ├── Home.tsx
│   │   ├── PolicyTerms.tsx
│   │   ├── Profile.tsx
│   │   ├── Search.tsx
│   │   └── Watch.tsx
│   ├── styles/
│   │   ├── animations.css
│   │   ├── globals.css
│   │   └── themes.css
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vercel.json
└── vite.config.ts
Download .txt
SYMBOL INDEX (96 symbols across 29 files)

FILE: api/exchange-token.ts
  function exchangeAccessToken (line 4) | async function exchangeAccessToken(req: VercelRequest, res: VercelRespon...

FILE: functions/exchange-token.js
  function onRequest (line 1) | async function onRequest(context) {
  function handleTokenExchange (line 12) | async function handleTokenExchange(context) {

FILE: server/server.ts
  constant PORT (line 10) | const PORT = process.env.VITE_PORT || 5173;
  constant DIST_DIR (line 18) | const DIST_DIR = path.join(__dirname, '../dist');
  constant INDEX_FILE (line 19) | const INDEX_FILE = path.join(DIST_DIR, 'index.html');
  function getLocalIpAddress (line 91) | function getLocalIpAddress() {

FILE: src/App.tsx
  function App (line 34) | function App() {
  function TrackPageViews (line 82) | function TrackPageViews() {

FILE: src/client/ApolloClient.tsx
  function login (line 51) | function login() {
  function logout (line 64) | function logout() {
  function handleAuthUpdate (line 72) | function handleAuthUpdate() {

FILE: src/client/authService.ts
  constant GET_USER_ANIME_LIST (line 105) | const GET_USER_ANIME_LIST = gql`

FILE: src/client/useAuth.tsx
  type AuthContextType (line 12) | type AuthContextType = {

FILE: src/client/userInfoTypes.ts
  type UserStatisticsSort (line 2) | type UserStatisticsSort =
  type UserData (line 7) | interface UserData {
  type MediaListStatus (line 15) | enum MediaListStatus {
  type UserStatistics (line 24) | interface UserStatistics {
  type AnimeMangaStatistics (line 29) | interface AnimeMangaStatistics {
  type StatisticLimitSort (line 51) | interface StatisticLimitSort {
  type UserFormatStatistic (line 56) | interface UserFormatStatistic {
  type UserStatusStatistic (line 61) | interface UserStatusStatistic {
  type UserScoreStatistic (line 66) | interface UserScoreStatistic {
  type UserLengthStatistic (line 71) | interface UserLengthStatistic {
  type UserReleaseYearStatistic (line 76) | interface UserReleaseYearStatistic {
  type UserStartYearStatistic (line 81) | interface UserStartYearStatistic {
  type UserGenreStatistic (line 86) | interface UserGenreStatistic {
  type UserTagStatistic (line 91) | interface UserTagStatistic {
  type UserCountryStatistic (line 96) | interface UserCountryStatistic {
  type UserVoiceActorStatistic (line 101) | interface UserVoiceActorStatistic {
  type UserStaffStatistic (line 108) | interface UserStaffStatistic {
  type UserStudioStatistic (line 115) | interface UserStudioStatistic {

FILE: src/components/Cards/CardGrid.tsx
  type CardGridProps (line 5) | interface CardGridProps {

FILE: src/components/Home/EpisodeCard.tsx
  constant LOCAL_STORAGE_KEYS (line 10) | const LOCAL_STORAGE_KEYS = {
  type LastEpisodes (line 15) | interface LastEpisodes {
  type LastVisitedData (line 19) | interface LastVisitedData {

FILE: src/components/Home/HomeCarousel.tsx
  type HomeCarouselProps (line 221) | interface HomeCarouselProps {

FILE: src/components/Navigation/DropSearch.tsx
  type Props (line 122) | interface Props {

FILE: src/components/Navigation/Footer.tsx
  function Footer (line 108) | function Footer() {

FILE: src/components/Navigation/Navbar.tsx
  function handleResize (line 436) | function handleResize() {

FILE: src/components/Navigation/SearchFilters.tsx
  type StateProps (line 25) | interface StateProps {

FILE: src/components/Profile/Settings.tsx
  type Preferences (line 6) | interface Preferences {

FILE: src/components/Profile/SettingsProvider.tsx
  type SettingsContextType (line 10) | interface SettingsContextType {
  function useSettings (line 26) | function useSettings() {
  type SettingsProviderProps (line 34) | interface SettingsProviderProps {

FILE: src/components/ThemeContext.tsx
  type ThemeContextType (line 9) | type ThemeContextType = {
  type ThemeProviderProps (line 18) | type ThemeProviderProps = {

FILE: src/components/Watch/EpisodeList.tsx
  type Props (line 19) | interface Props {

FILE: src/components/Watch/Video/MediaSource.tsx
  type MediaSourceProps (line 12) | interface MediaSourceProps {

FILE: src/components/Watch/Video/Player.tsx
  type PlayerProps (line 58) | type PlayerProps = {
  type StreamingSource (line 69) | type StreamingSource = {
  type SkipTime (line 74) | type SkipTime = {
  type FetchSkipTimesResponse (line 82) | type FetchSkipTimesResponse = {
  function Player (line 86) | function Player({

FILE: src/components/Watch/WatchAnimeData.tsx
  function capitalizeFirstLetter (line 305) | function capitalizeFirstLetter(str: string) {

FILE: src/hooks/animeInterface.ts
  type Title (line 1) | interface Title {
  type Trailer (line 8) | interface Trailer {
  type VoiceActor (line 15) | interface VoiceActor {
  type Recommendation (line 23) | interface Recommendation {
  type Character (line 37) | interface Character {
  type Relation (line 46) | interface Relation {
  type Mapping (line 61) | interface Mapping {
  type Artwork (line 68) | interface Artwork {
  type Episode (line 74) | interface Episode {
  type Anime (line 84) | interface Anime {
  type Paging (line 129) | interface Paging {

FILE: src/hooks/useApi.ts
  function ensureUrlEndsWithSlash (line 5) | function ensureUrlEndsWithSlash(url: string): string {
  constant BASE_URL (line 10) | const BASE_URL = ensureUrlEndsWithSlash(
  constant SKIP_TIMES (line 13) | const SKIP_TIMES = ensureUrlEndsWithSlash(
  constant PROXY_URL (line 16) | let PROXY_URL = import.meta.env.VITE_PROXY_URL;
  constant API_KEY (line 22) | const API_KEY = import.meta.env.VITE_API_KEY as string;
  function handleError (line 35) | function handleError(error: any, context: string) {
  function generateCacheKey (line 73) | function generateCacheKey(...args: string[]) {
  type CacheItem (line 77) | interface CacheItem {
  function createOptimizedSessionStorageCache (line 84) | function createOptimizedSessionStorageCache(
  constant CACHE_SIZE (line 134) | const CACHE_SIZE = 20;
  constant CACHE_MAX_AGE (line 135) | const CACHE_MAX_AGE = 24 * 60 * 60 * 1000;
  function createCache (line 139) | function createCache(cacheKey: string) {
  type FetchOptions (line 147) | interface FetchOptions {
  function fetchFromProxy (line 169) | async function fetchFromProxy(url: string, cache: any, cacheKey: string) {
  function fetchAdvancedSearch (line 208) | async function fetchAdvancedSearch(
  function fetchAnimeData (line 238) | async function fetchAnimeData(
  function fetchAnimeInfo (line 250) | async function fetchAnimeInfo(
  function fetchList (line 262) | async function fetchList(
  function fetchAnimeEpisodes (line 345) | async function fetchAnimeEpisodes(
  function fetchAnimeEmbeddedEpisodes (line 363) | async function fetchAnimeEmbeddedEpisodes(episodeId: string) {
  function fetchAnimeStreamingLinks (line 371) | async function fetchAnimeStreamingLinks(episodeId: string) {
  type FetchSkipTimesParams (line 379) | interface FetchSkipTimesParams {
  function fetchSkipTimes (line 386) | async function fetchSkipTimes({
  function fetchRecentEpisodes (line 409) | async function fetchRecentEpisodes(

FILE: src/hooks/useFilters.ts
  type Option (line 3) | interface Option {
  type FilterProps (line 8) | interface FilterProps {

FILE: src/hooks/useScroll.ts
  function ScrollToTop (line 4) | function ScrollToTop() {

FILE: src/pages/About.tsx
  function About (line 119) | function About() {

FILE: src/pages/PolicyTerms.tsx
  function PolicyTerms (line 128) | function PolicyTerms() {

FILE: src/pages/Watch.tsx
  constant LOCAL_STORAGE_KEYS (line 143) | const LOCAL_STORAGE_KEYS = {
Condensed preview — 70 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (308K chars).
[
  {
    "path": ".eslintrc.cjs",
    "chars": 437,
    "preview": "module.exports = {\n  root: true,\n  env: { browser: true, es2020: true },\n  extends: [\n    'eslint:recommended',\n    'plu"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 823,
    "preview": "# These are supported funding model platforms\n\n# github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., "
  },
  {
    "path": ".github/SECURITY.md",
    "chars": 1683,
    "preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported |\n| ------- | --------- |\n| 0.2.0   | ❔        |\n| 0.1.0"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 525,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".gitignore",
    "chars": 815,
    "preview": "# Dependency directories\nnode_modules/\njspm_packages/\n.pnpm-store/\n\n# Vite and Build outputs\ndist/\ndist-ssr/\nbuild/\nout/"
  },
  {
    "path": ".prettierrc",
    "chars": 654,
    "preview": "{\n  \"semi\": true,\n  \"trailingComma\": \"all\",\n  \"singleQuote\": true,\n  \"printWidth\": 80,\n  \"tabWidth\": 2,\n  \"useTabs\": fal"
  },
  {
    "path": "LICENSE",
    "chars": 1483,
    "preview": "              CUSTOM ATTRIBUTION-NONCOMMERCIAL (CUSTOM BY-NC) LICENSE\n\n                    © 2024 Miruro no Kuon. All Ri"
  },
  {
    "path": "README.md",
    "chars": 5966,
    "preview": "<h1 align=\"center\">\nMIRURO\n</h1>\n\n<p align=\"center\">\n  <a href=\"https://www.typescriptlang.org/\"><img src=\"https://img.s"
  },
  {
    "path": "api/exchange-token.ts",
    "chars": 1720,
    "preview": "import axios from 'axios';\nimport type { VercelRequest, VercelResponse } from '@vercel/node';\n\nexport default async func"
  },
  {
    "path": "functions/exchange-token.js",
    "chars": 2125,
    "preview": "export async function onRequest(context) {\n  const url = new URL(context.request.url);\n  const path = url.pathname;\n\n  i"
  },
  {
    "path": "index.html",
    "chars": 2340,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <!-- Disable zooming -->\n    <meta name=\"view"
  },
  {
    "path": "package.json",
    "chars": 1890,
    "preview": "{\n  \"name\": \"miruro_no_kuon\",\n  \"private\": true,\n  \"version\": \"0.5.2\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vi"
  },
  {
    "path": "public/manifest.json",
    "chars": 1779,
    "preview": "{\n  \"name\": \"Miruro\",\n  \"short_name\": \"Miruro\",\n  \"description\": \"Watch HD Anime for Free\",\n  \"lang\": \"en\",\n  \"backgroun"
  },
  {
    "path": "renovate.json",
    "chars": 106,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\"config:recommended\"]\n}\n"
  },
  {
    "path": "robots.txt",
    "chars": 26,
    "preview": "User-agent: *\nDisallow: /\n"
  },
  {
    "path": "server/README.md",
    "chars": 855,
    "preview": "# Server README\n\nThis README provides an overview of the `server.ts` file, which is an Express server designed to serve "
  },
  {
    "path": "server/server.ts",
    "chars": 3143,
    "preview": "import express from 'express';\nimport axios from 'axios';\nimport path from 'path';\nimport os from 'os';\nimport bodyParse"
  },
  {
    "path": "src/App.tsx",
    "chars": 2393,
    "preview": "import {\n  BrowserRouter as Router,\n  Routes,\n  Route,\n  useLocation,\n} from 'react-router-dom';\nimport { useEffect } fr"
  },
  {
    "path": "src/client/ApolloClient.tsx",
    "chars": 2825,
    "preview": "// apolloClient.ts\nimport {\n  ApolloClient,\n  InMemoryCache,\n  createHttpLink,\n  ApolloProvider,\n  makeVar,\n} from '@apo"
  },
  {
    "path": "src/client/authService.ts",
    "chars": 4408,
    "preview": "// src/services/authService.ts\nimport axios from 'axios';\nimport { v4 as uuidv4 } from 'uuid'; // Ensure uuid is install"
  },
  {
    "path": "src/client/useAuth.tsx",
    "chars": 2788,
    "preview": "import {\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  ReactNode,\n} from 'react';\nimport axios from 'axios';"
  },
  {
    "path": "src/client/userInfoTypes.ts",
    "chars": 2338,
    "preview": "// Enums or types for sorting, assuming sorting options are known\ntype UserStatisticsSort =\n  | 'COUNT_ASC'\n  | 'COUNT_D"
  },
  {
    "path": "src/components/Cards/CardGrid.tsx",
    "chars": 1788,
    "preview": "import React, { useEffect, useCallback } from 'react';\nimport styled from 'styled-components';\nimport { CardItem, Anime "
  },
  {
    "path": "src/components/Cards/CardItem.tsx",
    "chars": 7301,
    "preview": "import React, { useEffect, useState, useMemo } from 'react';\nimport styled from 'styled-components';\nimport { Link } fro"
  },
  {
    "path": "src/components/Home/EpisodeCard.tsx",
    "chars": 8697,
    "preview": "import React, { useState, useEffect, useMemo } from 'react';\nimport styled from 'styled-components';\nimport { Link } fro"
  },
  {
    "path": "src/components/Home/HomeCarousel.tsx",
    "chars": 9028,
    "preview": "import { FC } from 'react';\nimport styled from 'styled-components';\nimport { FaPlay } from 'react-icons/fa';\nimport { Sw"
  },
  {
    "path": "src/components/Home/HomeSideBar.tsx",
    "chars": 4234,
    "preview": "import { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { Link } from 'react-router-"
  },
  {
    "path": "src/components/Navigation/DropSearch.tsx",
    "chars": 6931,
    "preview": "import React, { useEffect, useRef } from 'react';\nimport styled from 'styled-components';\nimport { useNavigate } from 'r"
  },
  {
    "path": "src/components/Navigation/Footer.tsx",
    "chars": 4669,
    "preview": "import styled from 'styled-components';\nimport { FaReddit, FaDiscord, FaTwitter, FaGithub } from 'react-icons/fa';\nimpor"
  },
  {
    "path": "src/components/Navigation/Navbar.tsx",
    "chars": 16883,
    "preview": "import React, { useRef, useEffect, useState, useCallback } from 'react';\nimport styled from 'styled-components';\nimport "
  },
  {
    "path": "src/components/Navigation/SearchFilters.tsx",
    "chars": 13122,
    "preview": "import React, { useState, useEffect } from 'react';\nimport styled from 'styled-components';\nimport Select, { components "
  },
  {
    "path": "src/components/Profile/Settings.tsx",
    "chars": 8472,
    "preview": "import React, { useState, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { useNavigate } from "
  },
  {
    "path": "src/components/Profile/SettingsProvider.tsx",
    "chars": 2191,
    "preview": "import React, {\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  ReactNode,\n} from 'react';\n\n// Define the type"
  },
  {
    "path": "src/components/Profile/WatchingAnilist.tsx",
    "chars": 3492,
    "preview": "import { useState, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { useAuth, useUserAnimeList,"
  },
  {
    "path": "src/components/ShortcutsPopup.tsx",
    "chars": 5112,
    "preview": "import styled from 'styled-components';\nimport { useState, useEffect } from 'react';\nimport { FaTimes } from 'react-icon"
  },
  {
    "path": "src/components/Skeletons/Skeletons.tsx",
    "chars": 2760,
    "preview": "import React from 'react';\nimport styled, { keyframes, css } from 'styled-components';\n\nconst pulseAnimation = keyframes"
  },
  {
    "path": "src/components/ThemeContext.tsx",
    "chars": 1240,
    "preview": "import React, {\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  ReactNode,\n} from 'react';\n\ntype ThemeContextT"
  },
  {
    "path": "src/components/Watch/AnimeDataList.tsx",
    "chars": 6983,
    "preview": "import React from 'react';\nimport styled from 'styled-components';\nimport { Link } from 'react-router-dom';\nimport { TbC"
  },
  {
    "path": "src/components/Watch/EpisodeList.tsx",
    "chars": 16618,
    "preview": "import React, {\n  useState,\n  useMemo,\n  useCallback,\n  useEffect,\n  useRef,\n} from 'react';\nimport styled from 'styled-"
  },
  {
    "path": "src/components/Watch/Seasons.tsx",
    "chars": 3213,
    "preview": "import React from 'react';\nimport styled from 'styled-components';\nimport { Link } from 'react-router-dom';\nimport { Rel"
  },
  {
    "path": "src/components/Watch/Video/EmbedPlayer.tsx",
    "chars": 385,
    "preview": "import React from 'react';\nimport styled from 'styled-components';\n\nconst Container = styled.div``;\n\nconst Iframe = styl"
  },
  {
    "path": "src/components/Watch/Video/MediaSource.tsx",
    "chars": 9040,
    "preview": "import React, { useState } from 'react';\nimport styled from 'styled-components';\nimport {\n  FaMicrophone,\n  FaClosedCapt"
  },
  {
    "path": "src/components/Watch/Video/Player.tsx",
    "chars": 9923,
    "preview": "import { useEffect, useRef, useState } from 'react';\nimport './PlayerStyles.css';\nimport {\n  isHLSProvider,\n  MediaPlaye"
  },
  {
    "path": "src/components/Watch/Video/PlayerStyles.css",
    "chars": 997,
    "preview": "@import '@vidstack/react/player/styles/default/theme.css';\n@import '@vidstack/react/player/styles/default/layouts/audio."
  },
  {
    "path": "src/components/Watch/WatchAnimeData.tsx",
    "chars": 16734,
    "preview": "import React, { useState, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { Seasons, Anime } fr"
  },
  {
    "path": "src/components/shared/StatusIndicator.tsx",
    "chars": 1296,
    "preview": "import styled from 'styled-components';\nimport React, { useMemo } from 'react';\n\nconst IndicatorDot = styled.div`\n  widt"
  },
  {
    "path": "src/hooks/animeInterface.ts",
    "chars": 2322,
    "preview": "export interface Title {\n  romaji: string;\n  english: string;\n  native: string;\n  userPreferred: string;\n}\n\nexport inter"
  },
  {
    "path": "src/hooks/useApi.ts",
    "chars": 13934,
    "preview": "import axios from 'axios';\nimport { year, getCurrentSeason, getNextSeason } from '../index';\n\n// Utility function to ens"
  },
  {
    "path": "src/hooks/useCountdown.ts",
    "chars": 1058,
    "preview": "// /hooks/useCountdown.ts\nimport { useState, useEffect } from 'react';\n\nexport const useCountdown = (targetDate: number "
  },
  {
    "path": "src/hooks/useFilters.ts",
    "chars": 2815,
    "preview": "import { year as currentYear } from '../index';\n\nexport interface Option {\n  value: string;\n  label: string;\n}\n\nexport i"
  },
  {
    "path": "src/hooks/useScroll.ts",
    "chars": 1807,
    "preview": "import { useEffect, useRef } from 'react';\nimport { useLocation } from 'react-router-dom';\n\nexport function ScrollToTop("
  },
  {
    "path": "src/hooks/useTIme.ts",
    "chars": 785,
    "preview": "export const date = new Date();\n\nexport const time = new Date().getTime();\n\nexport const year = new Date().getFullYear()"
  },
  {
    "path": "src/index.ts",
    "chars": 2739,
    "preview": "// * ==== Components ====\n// TODO Shared components\nexport { StatusIndicator } from './components/shared/StatusIndicator"
  },
  {
    "path": "src/main.tsx",
    "chars": 334,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\n\nconst rootElement = docume"
  },
  {
    "path": "src/pages/404.tsx",
    "chars": 1196,
    "preview": "import React, { useEffect } from 'react';\nimport styled from 'styled-components';\nimport Image404URL from '/src/assets/4"
  },
  {
    "path": "src/pages/About.tsx",
    "chars": 4024,
    "preview": "import styled from 'styled-components';\nimport { useEffect } from 'react';\nimport { FaCheckCircle } from 'react-icons/fa"
  },
  {
    "path": "src/pages/Callback.tsx",
    "chars": 2028,
    "preview": "import { useEffect, useState } from 'react';\nimport { useLocation, useNavigate } from 'react-router-dom';\nimport axios f"
  },
  {
    "path": "src/pages/Home.tsx",
    "chars": 8902,
    "preview": "import { useState, useEffect } from 'react';\nimport styled from 'styled-components';\nimport {\n  HomeCarousel,\n  CardGrid"
  },
  {
    "path": "src/pages/PolicyTerms.tsx",
    "chars": 5100,
    "preview": "import styled from 'styled-components';\nimport { useEffect } from 'react';\nconst colors = {\n  textColor: 'var(--global-t"
  },
  {
    "path": "src/pages/Profile.tsx",
    "chars": 4521,
    "preview": "import React, { useEffect } from 'react';\nimport styled from 'styled-components';\nimport { IoLogOutOutline } from 'react"
  },
  {
    "path": "src/pages/Search.tsx",
    "chars": 7635,
    "preview": "import { useState, useEffect, useRef, useCallback } from 'react';\nimport styled from 'styled-components';\nimport { useSe"
  },
  {
    "path": "src/pages/Watch.tsx",
    "chars": 24686,
    "preview": "import React, { useEffect, useState, useCallback, useRef } from 'react';\nimport { useParams, useNavigate } from 'react-r"
  },
  {
    "path": "src/styles/animations.css",
    "chars": 928,
    "preview": "@keyframes slideUp {\n  0% {\n    opacity: 0;\n    transform: translateY(10px);\n  }\n  100% {\n    opacity: 1;\n    transform:"
  },
  {
    "path": "src/styles/globals.css",
    "chars": 1505,
    "preview": "@import url('animations.css');\n@import url('themes.css');\n\n/* Basic Styles for the App */\nbody {\n  font-family:\n    ui-s"
  },
  {
    "path": "src/styles/themes.css",
    "chars": 2177,
    "preview": "/* Light Mode  */\n:root {\n  --global-primary-bg: #f5f5f5;\n  --global-primary-bg-tr: rgba(245, 245, 245, 0.8);\n  --global"
  },
  {
    "path": "src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "tsconfig.json",
    "chars": 663,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\n      \"ES2020\",\n     "
  },
  {
    "path": "tsconfig.node.json",
    "chars": 233,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\""
  },
  {
    "path": "vercel.json",
    "chars": 62,
    "preview": "{\n  \"rewrites\": [{ \"source\": \"/(.*)\", \"destination\": \"/\" }]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "chars": 1262,
    "preview": "import { defineConfig, loadEnv, UserConfigExport, ConfigEnv } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n//"
  }
]

About this extraction

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

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

Copied to clipboard!