[
  {
    "path": ".eslintrc.cjs",
    "content": "module.exports = {\n  root: true,\n  env: { browser: true, es2020: true },\n  extends: [\n    'eslint:recommended',\n    'plugin:@typescript-eslint/recommended',\n    'plugin:react-hooks/recommended',\n  ],\n  ignorePatterns: ['dist', '.eslintrc.cjs'],\n  parser: '@typescript-eslint/parser',\n  plugins: ['react-refresh'],\n  rules: {\n    'react-refresh/only-export-components': [\n      'warn',\n      { allowConstantExport: true },\n    ],\n  },\n};\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\n# github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\n# polar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported |\n| ------- | --------- |\n| 0.2.0   | ❔        |\n| 0.1.0   | ✅        |\n| < 0.1.0 | ❌        |\n\n## Reporting a Vulnerability\n\nIf 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.\n\n### Reporting Process\n\n1. **Open a new issue**: Clearly describe the vulnerability, providing as much detail as possible.\n2. **Assessment**: Our team will assess the reported vulnerability and respond when available.\n3. **Fix and Release**: If the vulnerability is accepted, we will work on fixing it and release a patch within a reasonable timeframe.\n\n### Expectations\n\n- We will strive to keep you informed about the progress of your reported vulnerability.\n- If the vulnerability is accepted, it will be prioritized based on severity.\n- If the vulnerability is declined, we will provide a reason for the decision.\n- We encourage responsible disclosure, and we appreciate your efforts in keeping our project secure.\n\n## Versioning Scheme\n\nWe follow [Semantic Versioning](https://semver.org/) for our releases. Security updates will be applied to the latest minor version of the current major version.\n\n- **Major Version**: Significant changes, possibly breaking backward compatibility.\n- **Minor Version**: New features, enhancements, and backward-compatible bug fixes.\n- **Patch Version**: Backward-compatible bug fixes only.\n\n## Contact\n\nFor any questions or additional information regarding security, please contact miruro@proton.me\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: 'npm' # See documentation for possible values\n    directory: '/' # Location of package manifests\n    schedule:\n      interval: 'weekly'\n"
  },
  {
    "path": ".gitignore",
    "content": "# Dependency directories\nnode_modules/\njspm_packages/\n.pnpm-store/\n\n# Vite and Build outputs\ndist/\ndist-ssr/\nbuild/\nout/\n.temp/\n\n# Bun\n.bun/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Compiled binary addons (node-gyp)\nbuild/Release/\n\n# Editor directories and files\n.idea/\n.vscode/\n*.sublime-workspace\n*.sublime-project\n\n# Operating System generated files\n.DS_Store\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\n\n# Package manager lock files\npackage-lock.json\nyarn.lock\npnpm-lock.yaml\nbun.lockb\n\n# dotenv environment variables files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Caches and logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n.cache/\n.eslintcache\n.stylelintcache\n\n# Temporary files\n*.tmp\n*~\n*.bak\n*.sw?\n*.swo\n*.swn\n*.swp\n*.orig\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"semi\": true,\n  \"trailingComma\": \"all\",\n  \"singleQuote\": true,\n  \"printWidth\": 80,\n  \"tabWidth\": 2,\n  \"useTabs\": false,\n  \"endOfLine\": \"lf\",\n  \"plugins\": [\"prettier-plugin-tailwindcss\"],\n  \"arrowParens\": \"always\",\n  \"bracketSpacing\": true,\n  \"jsxSingleQuote\": true,\n  \"bracketSameLine\": false,\n  \"htmlWhitespaceSensitivity\": \"css\",\n  \"vueIndentScriptAndStyle\": false,\n  \"embeddedLanguageFormatting\": \"auto\",\n  \"quoteProps\": \"as-needed\",\n  \"overrides\": [\n    {\n      \"files\": \"*.{ts,tsx}\",\n      \"options\": {\n        \"parser\": \"typescript\"\n      }\n    },\n    {\n      \"files\": \"*.html\",\n      \"options\": {\n        \"printWidth\": 120\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "              CUSTOM ATTRIBUTION-NONCOMMERCIAL (CUSTOM BY-NC) LICENSE\n\n                    © 2024 Miruro no Kuon. All Rights Reserved.\n\nThis software, licensed under the Custom BY-NC License, grants users the freedom to\nshare, copy, distribute, and transmit the code, as well as to adapt, modify, transform,\nand build upon it. However, this freedom comes with specific terms:\n\nBy using this software, you are required to provide appropriate credit to the original\nauthor(s) by including a visible and clear attribution in any distribution or derivative\nwork. Attribution is a fundamental condition for utilizing or redistributing the code.\n\nFurthermore, this license explicitly prohibits the commercial use of the code or any\nderivative work based on it. Commercial purposes include, but are not limited to, activities\nthat involve the sale, licensing, or exploitation of the software for financial gain.\n\nIf you intend to use the code for commercial use or any purpose not explicitly covered by\nthis license, please seek explicit permission from the author(s) by contacting them directly.\nThe author(s) reserves the right to grant or deny permission based on individual circumstances.\n\nIt is essential to note that this license does not grant any rights beyond what is explicitly\nstated here. Any use of the code not explicitly allowed by this license is strictly prohibited.\n\nFor inquiries, additional permissions, or clarification on specific terms, please contact the author(s).\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">\nMIRURO\n</h1>\n\n<p align=\"center\">\n  <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>\n  <a href=\"https://reactjs.org/\"><img src=\"https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB\"/></a>\n  <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>\n</p>\n\n<p align=\"center\">\n  <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>\n</p>\n\n<p align=\"center\">\n  <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>\n  <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>\n  <a href=\"https://vercel.com/\"><img src=\"https://img.shields.io/badge/vercel-%23000000.svg?style=for-the-badge&logo=vercel&logoColor=white\"/></a>\n  <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>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.miruro.com\" target=\"_blank\">\n    <img src=\"https://raw.githubusercontent.com/Miruro-no-kuon/Miruro/main/src/assets/miruro-transparent-white.png\" alt=\"Logo\" width=\"200\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/Miruro-no-Kuon/Miruro/fork\">\n    <img src=\"https://img.shields.io/github/forks/Miruro-no-Kuon/Miruro?style=social\" alt=\"fork\"/>\n  </a>\n  <a href=\"https://github.com/Miruro-no-Kuon/Miruro/stargazers\">\n    <img src=\"https://img.shields.io/github/stars/Miruro-no-Kuon/Miruro?style=social\" alt=\"stars\"/>\n  </a>\n</p>\n\n## What is Miruro?\n\nWelcome 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)**.\n\nBuilt 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.\n\n<details>\n<summary>Features [View More]</summary>\n\n### General\n\n- Sub/Dub Anime support\n- User-friendly & Mobile responsive\n- Anilist Sync\n- Light/Dark theme\n- Continue Watching Section\n\n### Watch Page\n\n- **Player**\n  - Autoplay next episode\n  - Skip op/ed button\n\n</details>\n\n## Installation and Local Development\n\n### 1. Clone this repository using\n\n```bash\ngit clone https://github.com/Miruro-no-kuon/Miruro.git\n```\n\n```bash\ncd Miruro\n```\n\n### 2. Installation\n\n### Basic Pre-Requisites\n\n> [!TIP]\n> 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.\n\n> Bun is now available on **Windows**, **Linux**, and **macOS**. Below are the installation commands for each operating system.\n\n### Install Bun\n\n- Linux & macOS\n\n```bash\ncurl -fsSL https://bun.sh/install | bash\n```\n\n- Windows\n\n```powershell\npowershell -c \"irm bun.sh/install.ps1 | iex\"\n```\n\n### Verify installations\n\n- Check that both Node.js and Bun are correctly installed by running.\n\n```bash\nnode -v\nbun -v\n```\n\n### Install Dependencies\n\n- You can use Bun to install dependencies quickly. If you prefer, `npm` can also be used with equivalent commands.\n\n```bash\nbun install\n```\n\n### Copy `.env.example` into `.env.local` in the root folder\n\n- `.env.local` & `.env` are both viable options, you can also set\n  `.env.test.local`,\n  `.env.development.local` or\n  `.env.production.local`\n\n```bash\ncp .env.example .env.local\n```\n\n### 3. Run on development &/or production (npm also works)\n\n- Run on development mode\n\n```bash\nbun run dev\n```\n\n- Run on production mode\n\n```bash\nbun start\n```\n\n## Self-Hosting Notice\n\n> [!CAUTION]\n> 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.\n\n## License\n\nThis 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.\n\n<!-- ## Found a Bug?\n\nUh-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.\n\n## Support & Contributions\n\n### Want to Help Out?\n\n- ✴️ [**Star this project**](https://github.com/Miruro-no-kuon/Miruro)\n\n- 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. -->\n\n## Star History\n\n[![Stargazers over time](https://starchart.cc/Miruro-no-kuon/Miruro.svg?variant=adaptive)](https://starchart.cc/Miruro-no-kuon/Miruro)\n"
  },
  {
    "path": "api/exchange-token.ts",
    "content": "import axios from 'axios';\nimport type { VercelRequest, VercelResponse } from '@vercel/node';\n\nexport default async function exchangeAccessToken(req: VercelRequest, res: VercelResponse) {\n  if (req.method !== 'POST') {\n    res.status(405).send('Method Not Allowed');\n    return;\n  }\n\n  const { code } = req.body;\n  if (!code) {\n    return res.status(400).send('Authorization code is required');\n  }\n\n  const payload = {\n    client_id: process.env.VITE_CLIENT_ID,\n    client_secret: process.env.VITE_CLIENT_SECRET,\n    code,\n    grant_type: 'authorization_code',\n    redirect_uri: process.env.VITE_REDIRECT_URI,\n  };\n\n  const url = 'https://anilist.co/api/v2/oauth/token';\n\n  try {\n    const response = await axios.post(url, payload, {\n      headers: {\n        'Content-Type': 'application/json',\n        'Accept-Encoding': 'identity',\n      },\n    });\n\n    if (response.data.access_token) {\n      res.json({ accessToken: response.data.access_token });\n    } else {\n      throw new Error('Access token not found in the response');\n    }\n  } catch (error: unknown) {\n    // First, check if it's an instance of Error\n    if (error instanceof Error) {\n      // Now you can safely read the message property\n      const message = error.message;\n      // If it's an axios error, it may have a response object\n      const details = axios.isAxiosError(error) && error.response ? error.response.data : message;\n      res.status(500).json({\n        error: 'Failed to exchange token',\n        details,\n      });\n    } else {\n      // If it's not an Error object, handle it as a generic error\n      res.status(500).json({\n        error: 'Failed to exchange token',\n        details: 'An unknown error occurred',\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "functions/exchange-token.js",
    "content": "export async function onRequest(context) {\n  const url = new URL(context.request.url);\n  const path = url.pathname;\n\n  if (path === '/exchange-token') {\n    return handleTokenExchange(context);\n  } else {\n    return new Response('Not found', { status: 404 });\n  }\n}\n\nasync function handleTokenExchange(context) {\n  const request = context.request;\n  if (request.method !== 'POST') {\n    return new Response('Method Not Allowed', { status: 405 });\n  }\n\n  try {\n    const data = await request.json();\n    const code = data.code;\n    if (!code) {\n      return new Response('Authorization code is required', { status: 400 });\n    }\n\n    const payload = {\n      client_id: context.env.VITE_CLIENT_ID,\n      client_secret: context.env.VITE_CLIENT_SECRET,\n      code,\n      grant_type: 'authorization_code',\n      redirect_uri: context.env.VITE_REDIRECT_URI,\n    };\n\n    const apiResponse = await fetch('https://anilist.co/api/v2/oauth/token', {\n      method: 'POST',\n      body: JSON.stringify(payload),\n      headers: {\n        'Content-Type': 'application/json',\n        'Accept-Encoding': 'identity',\n      },\n    });\n\n    const responseBody = await apiResponse.text();\n    if (!apiResponse.ok) {\n      console.error('API response error:', responseBody);\n      throw new Error(`API responded with status: ${apiResponse.status}`);\n    }\n\n    const responseData = JSON.parse(responseBody);\n    if (responseData.access_token) {\n      return new Response(\n        JSON.stringify({ accessToken: responseData.access_token }),\n        {\n          headers: { 'Content-Type': 'application.json' },\n        },\n      );\n    } else {\n      console.error(\n        'Access token not found in the API response:',\n        responseBody,\n      );\n      throw new Error('Access token not found in the response');\n    }\n  } catch (error) {\n    console.error(`Error when handling token exchange: ${error}`);\n    return new Response(\n      JSON.stringify({\n        error: 'Failed to exchange token',\n        details: error.message,\n      }),\n      {\n        status: 500,\n        headers: { 'Content-Type': 'application.json' },\n      },\n    );\n  }\n}\n"
  },
  {
    "path": "index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <!-- Disable zooming -->\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" />\n    <meta\n      name=\"description\"\n      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!\"\n    />\n    <!-- Link to web app manifest -->\n    <link rel=\"manifest\" href=\"/manifest.json\" />\n    <!-- Main Favicon -->\n    <link rel=\"icon\" type=\"image/png\" href=\"/android-chrome-512x512.png\" />\n    <!-- Apple touch Favicon -->\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png\" />\n    <!-- Change appearance of status bar style for Apple devices -->\n    <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\n    <!-- Import Google Material Icons -->\n    <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" />\n    <!-- Title of the website -->\n    <title>Miruro | Watch Anime Online, Free Anime Streaming</title>\n    <!-- Internal styles -->\n    <link rel=\"stylesheet\" href=\"/src/styles/globals.css\" />\n    <!-- Animations -->\n    <link rel=\"stylesheet\" href=\"/src/styles/animations.css\" />\n    <!-- Dark/Light themes -->\n    <link rel=\"stylesheet\" href=\"/src/styles/themes.css\" />\n  </head>\n  <body data-simplebar>\n    <!-- Root element for the React app -->\n    <div id=\"root\"></div>\n    <!-- Main JavaScript file -->\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n    <!-- JavaScript to toggle dark mode -->\n    <script>\n      // Check user's theme preference or default to system preference\n      const themePreference =\n        localStorage.getItem('themePreference') ||\n        (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');\n      // Get the root HTML element\n      const rootElement = document.documentElement;\n      // Apply dark mode class if preferred theme is dark\n      if (themePreference === 'dark') {\n        rootElement.classList.add('dark-mode');\n      } else {\n        rootElement.classList.remove('dark-mode');\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"miruro_no_kuon\",\n  \"private\": true,\n  \"version\": \"0.5.2\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite --host\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"start\": \"vite build && bun run ./server/server.ts\",\n    \"lint\": \"eslint . --ext js,jsx,ts,tsx --report-unused-disable-directives --max-warnings 0\",\n    \"format\": \"prettier --write .\"\n  },\n  \"dependencies\": {\n    \"@apollo/client\": \"^3.10.1\",\n    \"@fortawesome/fontawesome-svg-core\": \"^6.5.1\",\n    \"@fortawesome/free-solid-svg-icons\": \"^6.5.1\",\n    \"@fortawesome/react-fontawesome\": \"^0.2.0\",\n    \"@types/cors\": \"^2.8.17\",\n    \"@types/express\": \"^4.17.21\",\n    \"@types/lodash\": \"^4.17.0\",\n    \"@types/uuid\": \"^9.0.8\",\n    \"@vercel/analytics\": \"^1.2.2\",\n    \"@vidstack/react\": \"next\",\n    \"axios\": \"^1.6.8\",\n    \"body-parser\": \"^1.20.2\",\n    \"eslint\": \"8.x\",\n    \"express\": \"^4.19.2\",\n    \"graphql\": \"^16.8.1\",\n    \"lodash\": \"^4.17.21\",\n    \"lru-cache\": \"latest\",\n    \"prettier\": \"^3.2.5\",\n    \"prettier-plugin-tailwindcss\": \"^0.5.14\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-ga4\": \"^2.1.0\",\n    \"react-icons\": \"^5.0.1\",\n    \"react-router-dom\": \"latest\",\n    \"react-select\": \"^5.8.0\",\n    \"styled-components\": \"^6.1.0\",\n    \"swiper\": \"^11.0.7\",\n    \"typescript\": \"^5.0.0\",\n    \"uuid\": \"^9.0.1\",\n    \"wrangler\": \"^3.52.0\"\n  },\n  \"devDependencies\": {\n    \"@types/bun\": \"latest\",\n    \"@types/react\": \"^18.2.55\",\n    \"@types/react-dom\": \"^18.2.19\",\n    \"@types/react-slick\": \"^0.23.13\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.4.0\",\n    \"@typescript-eslint/parser\": \"^7.4.0\",\n    \"@vercel/node\": \"^3.0.27\",\n    \"@vitejs/plugin-react\": \"^4.2.1\",\n    \"eslint-plugin-react\": \"^7.32.2\",\n    \"eslint-plugin-react-hooks\": \"^4.6.0\",\n    \"eslint-plugin-react-refresh\": \"^0.4.5\",\n    \"vite\": \"5.x\"\n  },\n  \"module\": \"index.ts\",\n  \"peerDependencies\": {\n    \"typescript\": \"^5.0.0\"\n  }\n}\n"
  },
  {
    "path": "public/manifest.json",
    "content": "{\n  \"name\": \"Miruro\",\n  \"short_name\": \"Miruro\",\n  \"description\": \"Watch HD Anime for Free\",\n  \"lang\": \"en\",\n  \"background_color\": \"#080808\",\n  \"display\": \"standalone\",\n  \"orientation\": \"standalone\",\n  \"scope\": \"/\",\n  \"start_url\": \"/\",\n  \"screenshots\": [\n    {\n      \"src\": \"/preview.png\",\n      \"sizes\": \"960x540\",\n      \"type\": \"image/png\",\n      \"form_factor\": \"wide\",\n      \"label\": \"Wonder Widgets\"\n    },\n    {\n      \"src\": \"/icon-256x256.png\",\n      \"sizes\": \"256x256\",\n      \"type\": \"image/png\",\n      \"form_factor\": \"narrow\",\n      \"label\": \"Icon Widget\"\n    }\n  ],\n  \"icons\": [\n    {\n      \"src\": \"/favicon-16x16.png\",\n      \"sizes\": \"16x16\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/favicon-32x32.png\",\n      \"sizes\": \"32x32\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    },\n    {\n      \"src\": \"/apple-touch-icon.png\",\n      \"sizes\": \"180x180\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/android-chrome-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/icon-256x256.png\",\n      \"sizes\": \"256x256\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/icon-384x384.png\",\n      \"sizes\": \"384x384\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/icon-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable any\"\n    },\n    {\n      \"src\": \"/android-chrome-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable\"\n    }\n  ]\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\"config:recommended\"]\n}\n"
  },
  {
    "path": "robots.txt",
    "content": "User-agent: *\nDisallow: /\n"
  },
  {
    "path": "server/README.md",
    "content": "# Server README\n\nThis 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.\n\n## `server.ts` Overview ℹ️\n\nThe `server.ts` file includes the following features:\n\n- Express server setup\n- Static file serving to serve files from the `dist` directory 📂\n- Error logging for server-side errors 📝\n\n## Installation and Running 🛠️\n\nTo run the server, follow these steps:\n\n1. Clone this repository to your local machine 📦\n\n2. Install project dependencies:\n\n   ```bash\n   bun install\n   ```\n\n3. Start the server:\n\n   ```bash\n   bun run server.ts\n   ```\n\n- 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.\n"
  },
  {
    "path": "server/server.ts",
    "content": "import express from 'express';\nimport axios from 'axios';\nimport path from 'path';\nimport os from 'os';\nimport bodyParser from 'body-parser';\n\nconst app = express();\n\n// Environment Configuration\nconst PORT = process.env.VITE_PORT || 5173;\nconst {\n  VITE_CLIENT_ID: CLIENT_ID,\n  VITE_CLIENT_SECRET: CLIENT_SECRET,\n  VITE_REDIRECT_URI: REDIRECT_URI,\n} = process.env;\n\n// Directory paths for static assets\nconst DIST_DIR = path.join(__dirname, '../dist');\nconst INDEX_FILE = path.join(DIST_DIR, 'index.html');\n\n// Middleware for static assets and JSON parsing\napp.use(express.static(DIST_DIR));\napp.use(express.json());\napp.use(bodyParser.json());\n\n// API Endpoint for exchanging authorization token\nconst apiEndpoint = '/api/exchange-token';\napp.post(apiEndpoint, async (req, res) => {\n  const { code } = req.body;\n  if (!code) {\n    console.error('Authorization code is missing');\n    return res.status(400).send('Authorization code is required');\n  }\n\n  const payload = {\n    client_id: CLIENT_ID,\n    client_secret: CLIENT_SECRET,\n    code,\n    grant_type: 'authorization_code',\n    redirect_uri: REDIRECT_URI,\n  };\n  const url = 'https://anilist.co/api/v2/oauth/token';\n\n  // Logging the request details\n  console.log('Sending request to AniList API');\n  console.log('URL:', url);\n  console.log('Payload:', payload);\n\n  try {\n    const response = await axios.post(url, payload, {\n      headers: {\n        'Content-Type': 'application/json',\n        'Accept-Encoding': 'identity',\n      },\n    });\n\n    // Logging the response details\n    console.log('Received response from AniList API');\n    console.log('Response Status:', response.status);\n    console.log('Response Data:', response.data);\n\n    if (response.data.access_token) {\n      res.json({ accessToken: response.data.access_token });\n    } else {\n      throw new Error('Access token not found in the response');\n    }\n  } catch (error) {\n    console.error('Error during token exchange:', error.message);\n    if (error.response) {\n      console.error('Error Status:', error.response.status);\n      console.error('Error Details:', error.response.data);\n    }\n    res.status(500).json({\n      error: 'Failed to exchange token',\n      details: error.response?.data || error.message,\n    });\n  }\n});\n\n// Serve the main index.html for any non-API requests\napp.get('*', (req, res) => {\n  res.sendFile(INDEX_FILE, (err) => {\n    if (err) {\n      console.error('Error serving index.html:', err);\n      res.status(500).send('An error occurred while serving the application');\n    }\n  });\n});\n\n// Utility to get the first non-internal IPv4 address\nfunction getLocalIpAddress() {\n  const networkInterfaces = os.networkInterfaces();\n  for (const networkInterface of Object.values(networkInterfaces)) {\n    const found = networkInterface?.find(\n      (net) => net.family === 'IPv4' && !net.internal,\n    );\n    if (found) return found.address;\n  }\n  return 'localhost';\n}\n\n// Starting the server\napp.listen(PORT, () => {\n  const ipAddress = getLocalIpAddress();\n  console.log(\n    `Server is running at:\\n- Localhost: http://localhost:${PORT}\\n- Local IP: http://${ipAddress}:${PORT}`,\n  );\n});\n"
  },
  {
    "path": "src/App.tsx",
    "content": "import {\n  BrowserRouter as Router,\n  Routes,\n  Route,\n  useLocation,\n} from 'react-router-dom';\nimport { useEffect } from 'react';\nimport {\n  Profile,\n  Navbar,\n  ThemeProvider,\n  Footer,\n  Home,\n  Watch,\n  Search,\n  Page404,\n  About,\n  PolicyTerms,\n  ShortcutsPopup,\n  ScrollToTop,\n  usePreserveScrollOnReload,\n  Callback,\n  ApolloClientProvider,\n  Settings,\n  SettingsProvider,\n} from './index';\nimport { register } from 'swiper/element/bundle';\nimport { Analytics } from '@vercel/analytics/react';\nimport { AuthProvider } from './client/useAuth';\nimport ReactGA from 'react-ga4';\n\nregister();\n\nfunction App() {\n  usePreserveScrollOnReload();\n  const measurementId = import.meta.env.VITE_GA_MEASUREMENT_ID;\n\n  useEffect(() => {\n    if (measurementId) {\n      ReactGA.initialize(measurementId);\n    }\n  }, [measurementId]);\n\n  return (\n    <ApolloClientProvider>\n      <Router>\n        <AuthProvider>\n          <ThemeProvider>\n            <SettingsProvider>\n              <Navbar />\n              <ShortcutsPopup />\n              <ScrollToTop />\n              <TrackPageViews />\n              <div style={{ minHeight: '35rem' }}>\n                <Routes>\n                  <Route path='/' element={<Home />} />\n                  <Route path='/home' element={<Home />} />\n                  <Route path='/search' element={<Search />} />\n                  <Route path='/watch/:animeId' element={<Watch />} />\n                  <Route\n                    path='/watch/:animeId/:animeTitle/:episodeNumber'\n                    element={<Watch />}\n                  />\n                  <Route path='/profile' element={<Profile />} />\n                  <Route path='/profile/settings' element={<Settings />} />\n                  <Route path='/about' element={<About />} />\n                  <Route path='/pptos' element={<PolicyTerms />} />\n                  <Route path='/callback' element={<Callback />} />\n                  <Route path='*' element={<Page404 />} />\n                </Routes>\n              </div>\n              <Footer />\n            </SettingsProvider>\n          </ThemeProvider>\n        </AuthProvider>\n      </Router>\n      <Analytics />\n    </ApolloClientProvider>\n  );\n}\n\nfunction TrackPageViews() {\n  const { pathname } = useLocation();\n\n  useEffect(() => {\n    ReactGA.send({ hitType: 'pageview', page: pathname });\n  }, [pathname]);\n\n  return null;\n}\n\nexport default App;\n"
  },
  {
    "path": "src/client/ApolloClient.tsx",
    "content": "// apolloClient.ts\nimport {\n  ApolloClient,\n  InMemoryCache,\n  createHttpLink,\n  ApolloProvider,\n  makeVar,\n} from '@apollo/client';\nimport { setContext } from '@apollo/client/link/context';\nimport { onError } from '@apollo/client/link/error';\nimport axios from 'axios';\nimport { buildAuthUrl, fetchUserData, UserData } from '../index';\nimport { ReactNode, useEffect } from 'react';\n\n// Reactive variables for user authentication state\nconst isLoggedInVar = makeVar<boolean>(false);\nconst userDataVar = makeVar<UserData | null>(null);\n\nconst httpLink = createHttpLink({\n  uri: 'https://graphql.anilist.co', // Update to your GraphQL server URL\n});\n\nconst authLink = setContext((_, { headers }) => {\n  const token = localStorage.getItem('accessToken');\n  return {\n    headers: {\n      ...headers,\n      authorization: token ? `Bearer ${token}` : '',\n    },\n  };\n});\n\nconst errorLink = onError(({ graphQLErrors, networkError }) => {\n  if (graphQLErrors) {\n    graphQLErrors.forEach(({ message, locations, path }) =>\n      console.error(\n        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,\n      ),\n    );\n  }\n\n  if (networkError) console.error(`[Network error]: ${networkError}`);\n});\n\nconst client = new ApolloClient({\n  link: errorLink.concat(authLink.concat(httpLink)),\n  cache: new InMemoryCache(),\n});\n\n// Functions for handling authentication\nfunction login() {\n  axios\n    .get('/get-csrf-token')\n    .then((response) => {\n      const csrfToken = response.data.csrfToken;\n      const authUrl = buildAuthUrl(csrfToken);\n      window.location.href = authUrl;\n    })\n    .catch((error) => {\n      console.error('Error fetching CSRF token or building auth URL:', error);\n    });\n}\n\nfunction logout() {\n  localStorage.removeItem('accessToken');\n  isLoggedInVar(false);\n  userDataVar(null);\n  window.location.href = '/profile'; // Adjust as necessary\n  window.dispatchEvent(new CustomEvent('authUpdate'));\n}\n\nfunction handleAuthUpdate() {\n  const token = localStorage.getItem('accessToken');\n  if (token) {\n    fetchUserData(token)\n      .then((data) => {\n        userDataVar(data);\n        isLoggedInVar(true);\n      })\n      .catch((err) => {\n        console.error('Failed to fetch user data:', err);\n        logout(); // Ensures clean state on failure\n      });\n  } else {\n    isLoggedInVar(false);\n    userDataVar(null);\n  }\n}\n\nexport const ApolloClientProvider = ({ children }: { children: ReactNode }) => {\n  useEffect(() => {\n    window.addEventListener('authUpdate', handleAuthUpdate);\n    handleAuthUpdate();\n    return () => {\n      window.removeEventListener('authUpdate', handleAuthUpdate);\n    };\n  }, []);\n\n  return <ApolloProvider client={client}>{children}</ApolloProvider>;\n};\n\nexport {\n  client as defaultApolloClient,\n  login,\n  logout,\n  isLoggedInVar,\n  userDataVar,\n};\n"
  },
  {
    "path": "src/client/authService.ts",
    "content": "// src/services/authService.ts\nimport axios from 'axios';\nimport { v4 as uuidv4 } from 'uuid'; // Ensure uuid is installed via npm or yarn\nimport { UserData, MediaListStatus } from '../index'; // Assuming this is the correct path\nimport { useQuery, gql } from '@apollo/client';\n\n// Constants for AniList OAuth, ideally should be loaded from environment variables\nconst clientId = import.meta.env.VITE_CLIENT_ID || 'default_client_id';\nconst clientSecret =\n  import.meta.env.VITE_CLIENT_SECRET || 'default_client_secret';\nconst redirectUri = import.meta.env.VITE_REDIRECT_URI || 'default_redirect_uri';\n\n/**\n * Generates a new CSRF token for each session\n * @returns {string} A UUID v4 CSRF token\n */\nexport const generateCsrfToken = (): string => {\n  return uuidv4();\n};\n\n/**\n * Builds the authorization URL with CSRF protection\n * @param {string} csrfToken CSRF token for state parameter\n * @returns {string} URL to redirect user to AniList OAuth login page\n */\n\n// authService.ts\nexport const buildAuthUrl = (csrfToken: string): string => {\n  const scope = encodeURIComponent('');\n  const state = encodeURIComponent(csrfToken);\n  const encodedRedirectUri = encodeURIComponent(redirectUri);\n\n  return `https://anilist.co/api/v2/oauth/authorize?client_id=${clientId}&scope=${scope}&response_type=code&redirect_uri=${encodedRedirectUri}&state=${state}`;\n};\n\n/**\n * Requests an access token from AniList using the authorization code\n * @param {string} code The authorization code received from AniList after user consent\n * @returns {Promise<string>} A promise that resolves to the access token\n */\nexport const getAccessToken = async (code: string): Promise<string> => {\n  const url = 'https://anilist.co/api/v2/oauth/token';\n  const payload = {\n    client_id: clientId,\n    client_secret: clientSecret,\n    code,\n    grant_type: 'authorization_code',\n    redirect_uri: redirectUri,\n  };\n\n  try {\n    const response = await axios.post(url, payload);\n    if (response.data.access_token) {\n      return response.data.access_token;\n    } else {\n      throw new Error('Access token not found in the response');\n    }\n  } catch (error) {\n    console.error('Error obtaining access token:', error);\n    throw new Error('Failed to obtain access token');\n  }\n};\n\n// src/services/authService.js\nexport const fetchUserData = async (accessToken: string): Promise<UserData> => {\n  try {\n    const response = await axios.post(\n      'https://graphql.anilist.co',\n      {\n        query: `\n          query {\n              Viewer {\n                  id\n                  name\n                  avatar {\n                      large\n                  }\n                  statistics {\n                      anime {\n                          count\n                          episodesWatched\n                          meanScore\n                          minutesWatched\n                      }\n                  }\n              }\n          }\n      `,\n      },\n      {\n        headers: {\n          Authorization: `Bearer ${accessToken}`,\n          'Content-Type': 'application/json',\n          Accept: 'application/json',\n        },\n      },\n    );\n    return response.data.data.Viewer; // Ensure the structure matches UserData interface\n  } catch (error) {\n    console.error('Error fetching user data:', error);\n    throw new Error('Failed to fetch user data');\n  }\n};\n\nconst GET_USER_ANIME_LIST = gql`\n  query GetUserAnimeList($username: String!, $status: MediaListStatus!) {\n    MediaListCollection(\n      userName: $username\n      type: ANIME\n      status: $status\n      sort: UPDATED_TIME_DESC\n    ) {\n      lists {\n        entries {\n          media {\n            id\n            format\n            title {\n              romaji\n              english\n            }\n            coverImage {\n              large\n              color\n            }\n            status\n            episodes\n            startDate {\n              year\n              month\n              day\n            }\n            averageScore\n            genres\n          }\n        }\n      }\n    }\n  }\n`;\n\nexport const useUserAnimeList = (username: string, status: MediaListStatus) => {\n  const { data, loading, error } = useQuery(GET_USER_ANIME_LIST, {\n    variables: { username, status },\n    skip: !username || !status, // Ensuring not to proceed without necessary variables\n  });\n\n  return {\n    animeList: data?.MediaListCollection,\n    loading,\n    error,\n  };\n};\n"
  },
  {
    "path": "src/client/useAuth.tsx",
    "content": "import {\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  ReactNode,\n} from 'react';\nimport axios from 'axios';\nimport { UserData } from './userInfoTypes'; // Adjust the path as necessary\nimport { fetchUserData, buildAuthUrl } from './authService'; // Adjust the path as necessary\n\ntype AuthContextType = {\n  isLoggedIn: boolean;\n  userData: UserData | null;\n  username: string | null; // This property must be handled\n  login: () => void;\n  logout: () => void;\n};\n\nconst AuthContext = createContext<AuthContextType | undefined>(undefined);\n\nexport const AuthProvider = ({ children }: { children: ReactNode }) => {\n  const [isLoggedIn, setIsLoggedIn] = useState(false);\n  const [userData, setUserData] = useState<UserData | null>(null);\n  const [authLoading, setAuthLoading] = useState(true); // Add a loading state for auth status\n\n  // Calculate username from userData\n  const username = userData ? userData.name : null; // Assuming 'username' is a property of UserData\n\n  useEffect(() => {\n    const token = localStorage.getItem('accessToken');\n    if (token) {\n      fetchUserData(token)\n        .then((data) => {\n          setUserData(data);\n          setIsLoggedIn(true);\n          setAuthLoading(false); // Set loading to false once user data is fetched\n        })\n        .catch((err) => {\n          console.error('Failed to fetch user data:', err);\n          logout(); // Ensures clean state on failure\n          setAuthLoading(false); // Ensure loading state is handled even in error\n        });\n    } else {\n      setAuthLoading(false); // If no token, ensure loading is set to false\n    }\n  }, []);\n\n  const login = async () => {\n    try {\n      const response = await axios.get('/get-csrf-token');\n      const csrfToken = response.data.csrfToken;\n      const authUrl = buildAuthUrl(csrfToken);\n      window.location.href = authUrl;\n    } catch (error) {\n      console.error('Error fetching CSRF token or building auth URL:', error);\n    }\n  };\n\n  const logout = () => {\n    localStorage.removeItem('accessToken');\n    setIsLoggedIn(false);\n    setUserData(null);\n    setAuthLoading(true); // Reset auth loading state on logout\n    window.location.href = '/profile';\n    window.dispatchEvent(new CustomEvent('authUpdate'));\n  };\n\n  // Prevent rendering of children if authentication status is unknown\n  if (authLoading) {\n    return null; // Or you could return a loading spinner or a similar component\n  }\n\n  return (\n    <AuthContext.Provider\n      value={{ isLoggedIn, userData, username, login, logout }}\n    >\n      {children}\n    </AuthContext.Provider>\n  );\n};\n\nexport const useAuth = () => {\n  const context = useContext(AuthContext);\n  if (context === undefined) {\n    throw new Error('useAuth must be used within an AuthProvider');\n  }\n  return context;\n};\n"
  },
  {
    "path": "src/client/userInfoTypes.ts",
    "content": "// Enums or types for sorting, assuming sorting options are known\ntype UserStatisticsSort =\n  | 'COUNT_ASC'\n  | 'COUNT_DESC'\n  | 'SCORE_ASC'\n  | 'SCORE_DESC';\nexport interface UserData {\n  name: string;\n  avatar: {\n    large: string;\n  };\n  statistics: UserStatistics;\n}\n\nexport enum MediaListStatus {\n  CURRENT = 'CURRENT',\n  PLANNING = 'PLANNING',\n  COMPLETED = 'COMPLETED',\n  REPEATING = 'REPEATING',\n  PAUSED = 'PAUSED',\n  DROPPED = 'DROPPED',\n}\n\nexport interface UserStatistics {\n  anime: AnimeMangaStatistics;\n  manga: AnimeMangaStatistics;\n}\n\nexport interface AnimeMangaStatistics {\n  count: number;\n  meanScore: number;\n  standardDeviation: number;\n  minutesWatched: number; // For anime\n  episodesWatched: number; // For anime\n  chaptersRead: number; // For manga\n  volumesRead: number; // For manga\n  formats: UserFormatStatistic[];\n  statuses: UserStatusStatistic[];\n  scores: UserScoreStatistic[];\n  lengths: UserLengthStatistic[];\n  releaseYears: UserReleaseYearStatistic[];\n  startYears: UserStartYearStatistic[];\n  genres: UserGenreStatistic[];\n  tags: UserTagStatistic[];\n  countries: UserCountryStatistic[];\n  voiceActors: UserVoiceActorStatistic[];\n  staff: UserStaffStatistic[];\n  studios: UserStudioStatistic[];\n}\n\nexport interface StatisticLimitSort {\n  limit: number;\n  sort: UserStatisticsSort[];\n}\n\nexport interface UserFormatStatistic {\n  format: string;\n  count: number;\n}\n\nexport interface UserStatusStatistic {\n  status: string;\n  count: number;\n}\n\nexport interface UserScoreStatistic {\n  score: number;\n  count: number;\n}\n\nexport interface UserLengthStatistic {\n  length: string;\n  count: number;\n}\n\nexport interface UserReleaseYearStatistic {\n  year: number;\n  count: number;\n}\n\nexport interface UserStartYearStatistic {\n  year: number;\n  count: number;\n}\n\nexport interface UserGenreStatistic {\n  genre: string;\n  count: number;\n}\n\nexport interface UserTagStatistic {\n  tag: string;\n  count: number;\n}\n\nexport interface UserCountryStatistic {\n  country: string;\n  count: number;\n}\n\nexport interface UserVoiceActorStatistic {\n  voiceActorId: number;\n  name: string;\n  count: number;\n  language: string;\n}\n\nexport interface UserStaffStatistic {\n  staffId: number;\n  name: string;\n  role: string;\n  count: number;\n}\n\nexport interface UserStudioStatistic {\n  studioId: number;\n  name: string;\n  count: number;\n}\n"
  },
  {
    "path": "src/components/Cards/CardGrid.tsx",
    "content": "import React, { useEffect, useCallback } from 'react';\nimport styled from 'styled-components';\nimport { CardItem, Anime } from '../../index';\n\ninterface CardGridProps {\n  animeData: Anime[];\n  hasNextPage: boolean;\n  onLoadMore: () => void;\n}\n\nexport const CardGrid: React.FC<CardGridProps> = ({\n  animeData,\n  hasNextPage,\n  onLoadMore,\n}) => {\n  const handleLoadMore = useCallback(() => {\n    if (hasNextPage) {\n      onLoadMore();\n    }\n  }, [hasNextPage, onLoadMore]);\n\n  useEffect(() => {\n    const handleScroll = () => {\n      const windowHeight = window.innerHeight;\n      const documentHeight = document.documentElement.offsetHeight;\n      const scrollTop =\n        document.documentElement.scrollTop || document.body.scrollTop;\n\n      let threshold = 0;\n\n      if (window.innerWidth <= 450) {\n        threshold = 1;\n      }\n\n      if (windowHeight + scrollTop >= documentHeight - threshold) {\n        handleLoadMore();\n      }\n    };\n\n    window.addEventListener('scroll', handleScroll);\n    return () => {\n      window.removeEventListener('scroll', handleScroll);\n    };\n  }, [handleLoadMore, hasNextPage]);\n\n  return (\n    <StyledCardGrid>\n      {animeData.map((anime) => (\n        <CardItem key={anime.id} anime={anime} />\n      ))}\n    </StyledCardGrid>\n  );\n};\n\nexport const StyledCardGrid = styled.div`\n  margin: 0 auto;\n  display: grid;\n  position: relative;\n  grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));\n  grid-template-rows: auto;\n  gap: 2rem;\n  transition: 0s;\n\n  @media (max-width: 1000px) {\n    gap: 1.5rem;\n  }\n\n  @media (max-width: 800px) {\n    grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));\n    gap: 1rem;\n  }\n\n  @media (max-width: 450px) {\n    grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));\n    gap: 0.8rem;\n  }\n`;\n"
  },
  {
    "path": "src/components/Cards/CardItem.tsx",
    "content": "import React, { useEffect, useState, useMemo } from 'react';\nimport styled from 'styled-components';\nimport { Link } from 'react-router-dom';\nimport { SkeletonCard, StatusIndicator, type Anime } from '../../index'; // Adjust the import path to correctly point to your index.ts location\nimport { FaPlay } from 'react-icons/fa'; // For the play icon\nimport { TbCards } from 'react-icons/tb';\nimport { FaStar, FaCalendarAlt } from 'react-icons/fa';\n\nconst StyledCardWrapper = styled(Link)`\n  color: var(--global-text);\n  animation: slideUp 0.4s ease;\n  text-decoration: none;\n  &:hover,\n  &:active,\n  &:focus {\n    z-index: 2;\n  }\n`;\n\nconst StyledCardItem = styled.div`\n  width: 100%;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  transform: scale(1);\n  transition: 0.2s ease-in-out;\n`;\n\nconst ImageDisplayWrapper = styled.div`\n  transition: 0.2s ease-in-out;\n  @media (min-width: 501px) {\n    &:hover,\n    &:active,\n    &:focus {\n      transform: translateY(-10px);\n    }\n  }\n`;\n\nconst AnimeImage = styled.div`\n  position: relative;\n  text-align: left;\n  overflow: hidden;\n  border-radius: var(--global-border-radius);\n  padding-top: calc(100% * 184 / 133);\n  background: var(--global-card-bg);\n  box-shadow: 2px 2px 10px var(--global-card-shadow);\n  transition: background-color 0.2s ease-in-out;\n  animation: slideUp 0.5s ease-in-out;\n`;\n\nconst PlayIcon = styled(FaPlay)`\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  color: #fff;\n  transform: translate(-50%, -50%);\n  font-size: 2rem;\n  opacity: 0;\n  transition: opacity 0.3s ease;\n  z-index: 1;\n`;\n\nconst ImageWrapper = styled.div`\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n\n  img {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    border-radius: var(--global-border-radius);\n    transition: 0.3s ease-in-out;\n    transition: filter 0.3s ease-in-out; // Ensure the filter transition is smooth\n  }\n\n  &:hover img {\n    filter: brightness(0.5); // Decrease brightness to 60% on hover\n  }\n\n  &:hover ${PlayIcon} {\n    opacity: 1;\n  }\n`;\n\nconst TitleContainer = styled.div<{ $isHovered: boolean }>`\n  display: flex;\n  align-items: center;\n  padding: 0.5rem;\n  margin-top: 0.35rem;\n  gap: 0.4rem;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  transition: background 0.2s ease;\n\n  &:hover,\n  &:active,\n  &:focus {\n    background: var(--global-card-title-bg);\n  }\n`;\n\nconst Title = styled.h5<{ $isHovered: boolean; color?: string }>`\n  margin: 0;\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n  color: ${(props) => (props.$isHovered ? props.color : 'var(--title-color)')};\n  transition: 0.2s ease-in-out;\n\n  @media (max-width: 500px) {\n    font-size: 0.7rem;\n  }\n`;\n\nconst ImgDetail = React.memo(styled.p<{ $isHovered: boolean; color?: string }>`\n  animation: slideRight 0.2s ease-in-out;\n  position: absolute;\n  bottom: 0;\n  margin: 0.25rem;\n  padding: 0.2rem;\n  font-size: 0.8rem;\n  font-weight: bold;\n  color: ${(props) => props.color};\n  opacity: 0.9;\n  background-color: var(--global-button-shadow);\n  border-radius: var(--global-border-radius);\n  backdrop-filter: blur(10px);\n  transition: 0.2s ease-in-out;\n`);\n\nconst CardDetails = styled.div`\n  animation: slideRight 0.4s ease-in-out;\n  width: 100%;\n  font-family: Arial;\n  font-weight: bold;\n  font-size: 0.75rem;\n  color: rgba(102, 102, 102, 0.65);\n  margin: 0;\n  display: flex;\n  align-items: center;\n  padding: 0.25rem 0rem;\n  gap: 0.5rem;\n  white-space: nowrap;\n  overflow: hidden; // Ensures that overflow text is hidden\n  text-overflow: ellipsis; // Adds an ellipsis to indicate that text has been cut off\n  svg {\n    margin-bottom: 0.12rem;\n    margin-right: -0.4rem;\n  }\n`;\n\nexport const CardItem: React.FC<{ anime: Anime }> = ({ anime }) => {\n  const [loading, setLoading] = useState(true);\n  const [isHovered, setIsHovered] = useState(false);\n\n  useEffect(() => {\n    const timer = setTimeout(() => {\n      setLoading(false);\n    }, 0);\n\n    return () => clearTimeout(timer);\n  }, [anime.id]);\n\n  const handleMouseEnter = () => {\n    setIsHovered(true);\n  };\n\n  const handleMouseLeave = () => {\n    setIsHovered(false);\n  };\n\n  const imageSrc = anime.image || '';\n  const animeColor = anime.color || '#999999';\n  const displayTitle = useMemo(\n    () => anime.title.english || anime.title.romaji || 'No Title',\n    [anime.title.english, anime.title.romaji],\n  );\n\n  const truncateTitle = useMemo(\n    () => (title: string, maxLength: number) =>\n      title.length > maxLength ? `${title.slice(0, maxLength)}...` : title,\n    [],\n  );\n\n  const handleImageLoad = () => {\n    setLoading(false); // Set loading to false when image is loaded\n  };\n\n  const displayDetail = useMemo(() => {\n    // Any complex logic can go here\n    return (\n      <ImgDetail $isHovered={isHovered} color={anime.color}>\n        {anime.type}\n      </ImgDetail>\n    );\n  }, [isHovered, anime.color, anime.type]);\n\n  return (\n    <>\n      {loading ? (\n        <SkeletonCard />\n      ) : (\n        <StyledCardWrapper\n          to={`/watch/${anime.id}`}\n          onMouseEnter={handleMouseEnter}\n          onMouseLeave={handleMouseLeave}\n          color={animeColor}\n          title={anime.title.english || anime.title.romaji}\n        >\n          <StyledCardItem>\n            <ImageDisplayWrapper>\n              <AnimeImage>\n                <ImageWrapper>\n                  <img\n                    src={imageSrc}\n                    onLoad={handleImageLoad}\n                    loading='eager'\n                    alt={\n                      anime.title.english || anime.title.romaji + ' Cover Image'\n                    }\n                  />\n                  <PlayIcon\n                    title={\n                      'Play ' + (anime.title.english || anime.title.romaji)\n                    }\n                  />\n                </ImageWrapper>\n                {isHovered && displayDetail}\n              </AnimeImage>\n            </ImageDisplayWrapper>\n            <TitleContainer $isHovered={isHovered}>\n              <StatusIndicator status={anime.status} />\n              <Title\n                $isHovered={isHovered}\n                color={anime.color}\n                title={'Title: ' + (anime.title.english || anime.title.romaji)}\n              >\n                {truncateTitle(displayTitle, 35)}\n              </Title>\n            </TitleContainer>\n            <div>\n              <CardDetails title='Romaji Title'>\n                {truncateTitle(anime.title.romaji || '', 24)}\n              </CardDetails>\n              <CardDetails title='Card Details'>\n                {anime.releaseDate && (\n                  <>\n                    <FaCalendarAlt />\n                    {anime.releaseDate}\n                  </>\n                )}\n                {(anime.totalEpisodes || anime.episodes) && (\n                  <>\n                    <TbCards />\n                    {anime.totalEpisodes || anime.episodes}\n                  </>\n                )}\n                {anime.rating && (\n                  <>\n                    <FaStar />\n                    {anime.rating}\n                  </>\n                )}\n              </CardDetails>\n            </div>\n          </StyledCardItem>\n        </StyledCardWrapper>\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "src/components/Home/EpisodeCard.tsx",
    "content": "import React, { useState, useEffect, useMemo } from 'react';\nimport styled from 'styled-components';\nimport { Link } from 'react-router-dom';\nimport { FaPlay } from 'react-icons/fa';\nimport { Swiper, SwiperSlide } from 'swiper/react';\nimport 'swiper/swiper-bundle.css';\nimport { Episode } from '../../index';\nimport { IoIosCloseCircleOutline } from 'react-icons/io';\n\nconst LOCAL_STORAGE_KEYS = {\n  WATCHED_EPISODES: 'watched-episodes',\n  LAST_ANIME_VISITED: 'last-anime-visited',\n};\n\ninterface LastEpisodes {\n  [key: string]: Episode;\n}\n\ninterface LastVisitedData {\n  [key: string]: {\n    timestamp?: number;\n    titleEnglish?: string;\n    titleRomaji?: string;\n  };\n}\n\nconst StyledSwiperContainer = styled(Swiper)`\n  position: relative;\n  max-width: 100%;\n  height: auto;\n  border-radius: var(--global-border-radius);\n  cursor: grab;\n`;\n\nconst StyledSwiperSlide = styled(SwiperSlide)``;\n\nconst PlayIcon = styled.div`\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  color: #ffffff;\n  font-size: 2.5rem;\n  opacity: 0;\n  z-index: 1;\n  transition: opacity 0.2s ease-in-out;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n`;\n\nconst AnimeEpisodeCard = styled(Link)`\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  margin: 1rem 0;\n  padding: 0;\n  border-radius: var(--global-border-radius);\n  overflow: hidden;\n  transition: 0.2s ease-in-out;\n  transition-delay: 0.25s;\n\n  &:hover,\n  &:active,\n  &:focus {\n    box-shadow: 2px 2px 10px var(--global-card-hover-shadow);\n    ${PlayIcon} {\n      opacity: 1;\n    }\n\n    img {\n      filter: brightness(0.5); // Optional: Slightly darken the image itself\n    }\n  }\n\n  @media (min-width: 768px) {\n    &:hover,\n    &:active,\n    &:focus {\n      // transform: translateY(-10px);\n    }\n  }\n\n  img {\n    animation: slideDown 0.5s ease-in-out;\n    height: auto;\n    aspect-ratio: 16 / 9;\n    object-fit: cover;\n    transition: filter 0.2s ease-in-out; // Smooth transition for the filter\n  }\n  .episode-info {\n    position: absolute;\n    bottom: 0;\n    left: 0;\n    width: 100%;\n    padding: 0.5rem;\n    background: linear-gradient(\n      360deg,\n      rgba(8, 8, 8, 1) -15%,\n      transparent 100%\n    );\n    color: white;\n    .episode-title {\n      white-space: nowrap;\n      overflow: hidden;\n      text-overflow: ellipsis;\n      font-size: 0.95rem;\n      font-weight: bold;\n      margin: 0.25rem 0;\n    }\n    .episode-number {\n      font-size: 0.75rem;\n      color: rgba(255, 255, 255, 0.65);\n      margin: 0;\n    }\n  }\n`;\n\nconst Section = styled.section`\n  padding: 0rem;\n  border-radius: var(--global-border-radius);\n`;\n\nconst ProgressBar = styled.div`\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  height: 0.25rem;\n  border-radius: var(--global-border-radius);\n  background-color: var(--primary-accent);\n  transition: width 0.3s ease-in-out;\n`;\n\nconst ContinueWatchingTitle = styled.h2`\n  color: var(--global-text);\n  font-size: 1.25rem;\n  margin-bottom: 0.25rem;\n`;\n\nconst CloseButton = styled.button`\n  position: absolute;\n  right: 0;\n  background: transparent;\n  border: none;\n  color: #ffffff;\n  cursor: pointer;\n  display: none;\n  animation: slideDown 0.25s ease-in-out;\n  transition: 0.2s ease-in-out;\n  padding-right: 0.2rem;\n  padding-top: 0.2rem;\n\n  svg {\n    transition: 0.2s ease-in-out;\n    transform: scale(0.95);\n    font-size: 1.75rem;\n    &:hover,\n    &:active,\n    &:focus {\n      transform: scale(1);\n    }\n  }\n  ${AnimeEpisodeCard}:hover & {\n    display: block; // Show only on hover\n  }\n`;\n\nconst FaCircle = styled(IoIosCloseCircleOutline)`\n  font-size: 2.25rem;\n`;\n\nconst calculateSlidesPerView = (windowWidth: number): number => {\n  if (windowWidth >= 1200) return 5;\n  if (windowWidth >= 1000) return 4;\n  if (windowWidth >= 700) return 3;\n  if (windowWidth >= 500) return 2;\n  return 2;\n};\n\nexport const EpisodeCard: React.FC = () => {\n  const [watchedEpisodesData, setWatchedEpisodesData] = useState(\n    localStorage.getItem('watched-episodes'),\n  );\n  const [windowWidth, setWindowWidth] = useState(window.innerWidth);\n\n  const lastVisitedData = useMemo<LastVisitedData>(() => {\n    const data = localStorage.getItem(LOCAL_STORAGE_KEYS.LAST_ANIME_VISITED);\n    return data ? JSON.parse(data) : {};\n  }, []);\n\n  useEffect(() => {\n    const handleResize = () => {\n      setWindowWidth(window.innerWidth);\n    };\n    const debouncedResize = setTimeout(handleResize, 200);\n    window.addEventListener('resize', handleResize);\n    return () => {\n      clearTimeout(debouncedResize);\n      window.removeEventListener('resize', handleResize);\n    };\n  }, []);\n\n  const episodesToRender = useMemo(() => {\n    if (!watchedEpisodesData) return [];\n    try {\n      const allEpisodes: Record<string, Episode[]> =\n        JSON.parse(watchedEpisodesData);\n\n      const lastEpisodes = Object.entries(allEpisodes).reduce<LastEpisodes>(\n        (acc, [animeId, episodes]) => {\n          const lastEpisode = episodes[episodes.length - 1]; // Assuming the episodes are in order\n          if (lastEpisode) {\n            acc[animeId] = lastEpisode;\n          }\n          return acc;\n        },\n        {},\n      );\n\n      const orderedAnimeIds = Object.keys(lastEpisodes).sort((a, b) => {\n        const lastVisitedA = lastVisitedData[a]?.timestamp || 0;\n        const lastVisitedB = lastVisitedData[b]?.timestamp || 0;\n        return lastVisitedB - lastVisitedA;\n      });\n\n      return orderedAnimeIds.map((animeId) => {\n        const episode = lastEpisodes[animeId];\n        const playbackInfo = JSON.parse(\n          localStorage.getItem('all_episode_times') || '{}',\n        ) as { [key: string]: { playbackPercentage: number } };\n\n        const playbackPercentage =\n          playbackInfo[episode.id]?.playbackPercentage || 0;\n\n        // Determine anime title, preferring English, falling back to Romaji, then to \"Episode Title\"\n        const animeTitle =\n          lastVisitedData[animeId]?.titleEnglish ||\n          lastVisitedData[animeId]?.titleRomaji ||\n          '';\n\n        // Conditional title display\n        const displayTitle = `${animeTitle}${episode.title ? ` - ${episode.title}` : ''}`;\n\n        const handleRemoveAllEpisodes = (animeId: string) => {\n          const updatedEpisodes = JSON.parse(watchedEpisodesData || '{}');\n          delete updatedEpisodes[animeId];\n\n          const newWatchedEpisodesData = JSON.stringify(updatedEpisodes);\n          localStorage.setItem('watched-episodes', newWatchedEpisodesData);\n          setWatchedEpisodesData(newWatchedEpisodesData); // Trigger re-render\n        };\n\n        return (\n          <StyledSwiperSlide key={episode.id}>\n            <AnimeEpisodeCard\n              to={`/watch/${animeId}`}\n              style={{ textDecoration: 'none' }}\n              title={`Continue Watching ${displayTitle}`}\n            >\n              <img src={episode.image} alt={`Cover for ${animeTitle}`} />\n              <PlayIcon aria-label='Play Episode'>\n                <FaPlay />\n              </PlayIcon>\n              <div className='episode-info'>\n                <p className='episode-title'>{displayTitle}</p>\n                <p className='episode-number'>{`Episode ${episode.number}`}</p>\n              </div>\n              <ProgressBar\n                style={{ width: `${Math.max(playbackPercentage, 5)}%` }}\n              />\n              <CloseButton\n                onClick={(e) => {\n                  e.preventDefault(); // Prevents the default action of the event\n                  e.stopPropagation(); // Prevents the event from bubbling up to any parent elements\n                  handleRemoveAllEpisodes(animeId);\n                }}\n              >\n                <FaCircle aria-label='Close' />\n              </CloseButton>\n            </AnimeEpisodeCard>\n          </StyledSwiperSlide>\n        );\n      });\n    } catch (error) {\n      console.error('Failed to parse watched episodes data:', error);\n      return [];\n    }\n  }, [watchedEpisodesData, lastVisitedData]);\n\n  const swiperSettings = useMemo(\n    () => ({\n      spaceBetween: 20,\n      slidesPerView: calculateSlidesPerView(windowWidth),\n      loop: true,\n      freeMode: true,\n      grabCursor: true,\n      keyboard: true,\n      autoplay: {\n        delay: 6000,\n        disableOnInteraction: false,\n      },\n    }),\n    [windowWidth],\n  );\n\n  return (\n    <Section aria-labelledby='continueWatchingTitle'>\n      {episodesToRender.length > 0 && (\n        <ContinueWatchingTitle id='continueWatchingTitle'>\n          CONTINUE WATCHING\n        </ContinueWatchingTitle>\n      )}\n      <StyledSwiperContainer {...swiperSettings} aria-label='Episodes carousel'>\n        {episodesToRender}\n      </StyledSwiperContainer>\n    </Section>\n  );\n};\n"
  },
  {
    "path": "src/components/Home/HomeCarousel.tsx",
    "content": "import { FC } from 'react';\nimport styled from 'styled-components';\nimport { FaPlay } from 'react-icons/fa';\nimport { Swiper, SwiperSlide } from 'swiper/react';\nimport 'swiper/swiper-bundle.css';\nimport { useNavigate } from 'react-router-dom';\nimport { SkeletonSlide, Anime } from '../../index';\nimport { TbCards } from 'react-icons/tb';\nimport { FaStar } from 'react-icons/fa';\nimport { FaClock } from 'react-icons/fa6';\n\nconst StyledSwiperContainer = styled(Swiper)`\n  position: relative;\n  max-width: 100%;\n  height: 24rem;\n  border-radius: var(--global-border-radius);\n  cursor: grab;\n\n  @media (max-width: 1000px) {\n    height: 20rem;\n  }\n  @media (max-width: 500px) {\n    height: 18rem;\n  }\n`;\n\nconst StyledSwiperSlide = styled(SwiperSlide)`\n  position: relative;\n  display: flex;\n  justify-content: flex-start;\n  align-items: center;\n  animation: fadeIn 0.4s ease-in-out forwards;\n`;\n\nconst DarkOverlay = styled.div`\n  content: '';\n  position: absolute;\n  left: 0;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  border-radius: var(--global-border-radius);\n  z-index: 1;\n  background: linear-gradient(45deg, rgba(8, 8, 8, 1) 0%, transparent 60%);\n`;\n\nconst SlideImageWrapper = styled.div`\n  position: relative;\n  width: 100%;\n  height: 100%;\n  border-radius: var(--global-border-radius);\n`;\n\nconst SlideImage = styled.img`\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n  border-radius: var(--global-border-radius);\n  position: absolute;\n`;\n\nconst ContentWrapper = styled.div`\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n  height: 100%;\n`;\n\nconst SlideContent = styled.div`\n  position: absolute;\n  left: 2rem;\n  bottom: 1.5rem;\n  z-index: 5;\n  max-width: 60%;\n\n  animation: slideUp 0.4s ease-in-out;\n\n  @media (max-width: 1000px) {\n    left: 1rem;\n    bottom: 1.5rem;\n  }\n`;\n\nconst SlideTitle = styled.h2`\n  color: var(--white, #fff);\n  font-size: clamp(1.2rem, 3vw, 2.5rem);\n  margin: auto;\n  max-width: 100%;\n  overflow: hidden;\n  text-overflow: ellipsis;\n\n  @media (min-width: 500px) {\n    white-space: nowrap;\n    max-width: 100%;\n  }\n`;\n\nconst SlideInfo = styled.div`\n  display: flex;\n  gap: 0.75rem;\n  color: #ffffff;\n  margin: auto;\n  margin-top: 0;\n  max-width: 100%;\n  overflow: hidden;\n  text-overflow: ellipsis;\n\n  @media (max-width: 1000px) {\n    font-size: 0.8rem;\n    gap: 0.5rem;\n  }\n  @media (max-width: 500px) {\n    font-size: 0.7rem;\n    gap: 0.45rem;\n  }\n`;\n\nconst SlideInfoItem = styled.p`\n  display: flex;\n  gap: 0.25rem;\n`;\n\nconst SlideDescription = styled.p<{\n  $maxLines: boolean;\n}>`\n  color: var(--white, #ccc);\n  background: transparent;\n  font-size: clamp(0.9rem, 1.5vw, 0.9rem);\n  line-height: 1.2;\n  max-width: 60%;\n  max-height: 5rem;\n  overflow: hidden;\n  -webkit-line-clamp: 3;\n  margin: 0;\n\n  @media (max-width: 1000px) {\n    line-height: 1.2;\n    max-width: 70%;\n    font-size: clamp(0.8rem, 1.2vw, 0.9rem);\n    max-height: 3rem;\n  }\n\n  @media (max-width: 500px) {\n    max-width: 100%;\n    font-size: clamp(0.7rem, 1vw, 0.8rem);\n    max-height: 2.5rem;\n  }\n\n  /* Add overflow-y: auto if the content exceeds max height */\n  overflow-y: ${({ $maxLines }) => ($maxLines ? 'auto' : 'hidden')};\n`;\n\nconst PlayButtonWrapper = styled.div`\n  position: absolute;\n  right: 2rem;\n  bottom: 1.5rem;\n  z-index: 5;\n  display: flex;\n  align-items: center; /* Center vertically */\n  justify-content: center; /* Center horizontally */\n\n  @media (max-width: 1000px) {\n    right: 1.5rem;\n    bottom: 1.5rem;\n  }\n`;\n\nconst PlayButton = styled.button`\n  display: flex;\n  gap: 0.5rem;\n  background-color: var(--global-button-bg);\n  color: var(--global-text);\n  border: none;\n  border-radius: 0.4rem;\n  font-size: 1rem; /* Increased font size */\n  font-weight: bold;\n  cursor: pointer;\n  transition: 0.2s ease;\n  padding: 1.2rem 2rem; /* Increased padding */\n  display: flex;\n  align-items: center;\n\n  &:hover,\n  &:active,\n  &:focus {\n    background-color: var(--primary-accent-bg);\n    transform: scale(1.05); /* Slightly larger scale on hover */\n  }\n\n  @media (max-width: 1000px) {\n    padding: 1rem 2rem; /* Adjusted for medium-sized devices */\n  }\n\n  @media (max-width: 500px) {\n    border-radius: 50%;\n    padding: 1.4rem; /* Adjusted for small devices */\n    padding-right: 1.5rem;\n    font-size: 1.25rem; /* Adjusted font size for small devices */\n    span {\n      display: none;\n    }\n  }\n`;\n\nconst PlayIcon = styled(FaPlay)``;\n\nconst PaginationStyle = styled.div`\n  .swiper-pagination-bullet {\n    background: var(--global-primary-bg, #007bff);\n    opacity: 0.7;\n    margin: 0 3px;\n  }\n\n  .swiper-pagination-bullet-active {\n    background: var(--global-text);\n    opacity: 1;\n  }\n`;\n\n// Adjust the Carousel component to use correctly typed props and state\ninterface HomeCarouselProps {\n  data: Anime[];\n  loading: boolean;\n  error?: string | null;\n}\n\nexport const HomeCarousel: FC<HomeCarouselProps> = ({\n  data = [],\n  loading,\n  error,\n}) => {\n  const navigate = useNavigate();\n\n  const handlePlayButtonClick = (id: string) => {\n    navigate(`/watch/${id}`);\n  };\n\n  const truncateTitle = (title: string, maxLength: number = 40): string => {\n    return title.length > maxLength\n      ? `${title.substring(0, maxLength)}...`\n      : title;\n  };\n\n  const validData = data.filter(\n    (item) =>\n      item.title &&\n      item.title.english &&\n      item.description &&\n      item.cover !== item.image,\n  );\n\n  // const formatGenres = (genres: string[]): string => genres.join(', ');\n\n  return (\n    <>\n      {loading || error ? (\n        <SkeletonSlide />\n      ) : (\n        <PaginationStyle>\n          <StyledSwiperContainer\n            spaceBetween={30}\n            slidesPerView={1}\n            loop={true}\n            autoplay={{\n              delay: 5000,\n              disableOnInteraction: false,\n            }}\n            navigation={{\n              nextEl: '.swiper-button-next',\n              prevEl: '.swiper-button-prev',\n            }}\n            pagination={{\n              el: '.swiper-pagination',\n              clickable: true,\n              dynamicBullets: true,\n              type: 'bullets',\n            }}\n            freeMode={false}\n            virtual={true}\n            grabCursor={true}\n            keyboard={true}\n            centeredSlides={true}\n          >\n            {validData.map(\n              ({\n                id,\n                cover,\n                title,\n                description,\n                // status,\n                rating,\n                // genres,\n                totalEpisodes,\n                duration,\n                type,\n              }) => (\n                <StyledSwiperSlide\n                  key={id}\n                  title={title.english || title.romaji}\n                >\n                  <SlideImageWrapper>\n                    <SlideImage\n                      src={cover}\n                      alt={title.english || title.romaji + ' Banner Image'}\n                      loading='eager'\n                    />\n                    <ContentWrapper>\n                      <SlideContent>\n                        <SlideTitle>{truncateTitle(title.english)}</SlideTitle>\n                        <SlideInfo>\n                          {type && <SlideInfoItem>{type}</SlideInfoItem>}\n                          {totalEpisodes && (\n                            <SlideInfoItem>\n                              <TbCards />\n                              {totalEpisodes}\n                            </SlideInfoItem>\n                          )}\n                          {rating && (\n                            <SlideInfoItem>\n                              <FaStar />\n                              {rating}\n                            </SlideInfoItem>\n                          )}\n                          {duration && (\n                            <SlideInfoItem>\n                              <FaClock />\n                              {duration}mins\n                            </SlideInfoItem>\n                          )}\n                        </SlideInfo>\n                        <SlideDescription\n                          dangerouslySetInnerHTML={{ __html: description }}\n                          $maxLines={description.length > 200}\n                        />\n                      </SlideContent>\n                      <PlayButtonWrapper>\n                        <PlayButton\n                          onClick={() => handlePlayButtonClick(id)}\n                          title={\n                            'Watch ' + (title.english || title.romaji) + ' Now'\n                          }\n                        >\n                          <PlayIcon />\n                          <span>WATCH NOW</span>\n                        </PlayButton>\n                      </PlayButtonWrapper>\n                    </ContentWrapper>\n                    <DarkOverlay />\n                  </SlideImageWrapper>\n                </StyledSwiperSlide>\n              ),\n            )}\n            <div className='swiper-pagination'></div>\n          </StyledSwiperContainer>\n        </PaginationStyle>\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "src/components/Home/HomeSideBar.tsx",
    "content": "import { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { Link } from 'react-router-dom'; // Assuming you're using React Router for navigation\nimport { TbCards } from 'react-icons/tb';\nimport { FaStar, FaCalendarAlt } from 'react-icons/fa';\nimport { Anime, StatusIndicator } from '../../index';\n\nconst SidebarStyled = styled.div`\n  transition: 0.2s ease-in-out;\n  margin: 0;\n  padding: 0;\n  max-width: 24rem;\n  @media (max-width: 1000px) {\n    max-width: unset;\n  }\n`;\n\nconst TitleWithDot = styled.div`\n  display: flex;\n  align-items: center;\n  padding: 0.5rem;\n  margin-top: 0.35rem;\n  gap: 0.4rem;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  transition: background 0.2s ease;\n`;\n\nconst AnimeCard = styled.div`\n  display: flex;\n  background-color: var(--global-div);\n  border-radius: var(--global-border-radius);\n  align-items: center;\n  overflow: hidden;\n  gap: 0.5rem;\n  cursor: pointer;\n  margin-bottom: 0.5rem;\n  animation: slideUp 0.5s ease-in-out;\n  animation-fill-mode: backwards;\n  transition:\n    background-color 0s ease-in-out,\n    margin-left 0.2s ease-in-out 0.1s;\n    box-shadow 0.2s ease-in-out;\n\n  &:hover,\n  &:active,\n  &:focus {\n    background-color: var(--global-div-tr);\n    margin-left: 0.35rem;\n    box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);\n  }\n\n  @media (max-width: 500px) {\n    &:hover,\n    &:active,\n    &:focus {\n      margin-left: unset;\n    }\n  }\n`;\n\nconst AnimeImageStyled = styled.img`\n  width: 4.25rem;\n  height: 6rem;\n  object-fit: cover;\n  border-radius: var(--global-border-radius);\n`;\n\nconst InfoStyled = styled.div``;\n\nconst Title = styled.p`\n  top: 0;\n  margin-bottom: 0.5rem;\n  display: -webkit-box;\n  -webkit-line-clamp: 2;\n  -webkit-box-orient: vertical;\n  overflow: hidden;\n  font-size: 0.9rem;\n  margin: 0;\n`;\n\nconst Details = styled.p`\n  font-size: 0.75rem;\n  margin: 0;\n  color: rgba(102, 102, 102, 0.75);\n  svg {\n    margin-left: 0.4rem;\n  }\n`;\n\nexport const HomeSideBar: React.FC<{ animeData: Anime[] }> = ({\n  animeData,\n}) => {\n  const [windowWidth, setWindowWidth] = useState(window.innerWidth);\n\n  useEffect(() => {\n    const handleResize = () => {\n      setWindowWidth(window.innerWidth);\n    };\n\n    window.addEventListener('resize', handleResize);\n    return () => window.removeEventListener('resize', handleResize);\n  }, []);\n\n  const displayedAnime = windowWidth <= 500 ? animeData.slice(0, 5) : animeData;\n\n  return (\n    <SidebarStyled>\n      {displayedAnime.map((anime: Anime, index) => (\n        <Link\n          to={`/watch/${anime.id}`}\n          key={anime.id}\n          style={{ textDecoration: 'none', color: 'inherit' }}\n          title={`${anime.title.userPreferred}`}\n          aria-label={`Watch ${anime.title.userPreferred}`}\n        >\n          <AnimeCard\n            key={anime.id}\n            style={{ animationDelay: `${index * 0.1}s` }}\n          >\n            <AnimeImageStyled\n              src={anime.image}\n              alt={anime.title.userPreferred}\n            />\n            <InfoStyled>\n              <TitleWithDot>\n                <StatusIndicator status={anime.status} />\n                <Title>{anime.title.english || anime.title.romaji}</Title>\n              </TitleWithDot>\n              <Details>\n                {anime.type && <>{anime.type}</>}\n                {anime.releaseDate && (\n                  <>\n                    <FaCalendarAlt /> {anime.releaseDate}\n                  </>\n                )}\n                {anime.currentEpisode !== null &&\n                  anime.currentEpisode !== undefined &&\n                  anime.totalEpisodes !== null &&\n                  anime.totalEpisodes !== undefined &&\n                  anime.totalEpisodes !== 0 &&\n                  anime.totalEpisodes !== 0 && (\n                    <>\n                      <TbCards /> {anime.currentEpisode}\n                      {' / '}\n                      {anime.totalEpisodes}\n                    </>\n                  )}\n\n                {anime.rating && (\n                  <>\n                    <FaStar /> {anime.rating}\n                  </>\n                )}\n              </Details>\n            </InfoStyled>\n          </AnimeCard>\n        </Link>\n      ))}\n    </SidebarStyled>\n  );\n};\n"
  },
  {
    "path": "src/components/Navigation/DropSearch.tsx",
    "content": "import React, { useEffect, useRef } from 'react';\nimport styled from 'styled-components';\nimport { useNavigate } from 'react-router-dom';\nimport { Anime } from '../../index';\nimport { FaArrowRight, FaStar } from 'react-icons/fa';\nimport { TbCards } from 'react-icons/tb';\nimport { BsArrowUpSquare, BsArrowDownSquare } from 'react-icons/bs';\nimport { PiKeyReturn } from 'react-icons/pi';\n\nconst Container = styled.div<{ $isVisible: boolean; width: number }>`\n  display: ${({ $isVisible }) => ($isVisible ? 'block' : 'none')};\n  position: absolute;\n  z-index: -1;\n  top: 1rem;\n  width: ${({ width }) => `${width}px`};\n  margin-left: -0.6rem;\n  overflow-y: auto;\n  background-color: var(--global-div);\n  border-top: none;\n  border-radius: var(--global-border-radius);\n  padding-top: 2.5rem;\n  animation: dropDown 0.5s ease-in-out;\n\n  @media (max-width: 500px) {\n    top: 4rem;\n    width: 96.4%;\n  }\n\n  scrollbar-width: none;\n  -ms-overflow-style: none;\n  &::-webkit-scrollbar {\n    display: none;\n  }\n\n  visibility: ${({ $isVisible }) => ($isVisible ? 'visible' : 'hidden')};\n  max-height: ${({ $isVisible }) => ($isVisible ? '500px' : '0')};\n`;\n\nconst Details = styled.p<{ $isSelected: boolean }>`\n  margin: 0.25rem 0;\n  animation: slideDropDown 0.5s ease-in-out;\n  color: ${({ $isSelected }) =>\n    $isSelected ? 'var(--primary-text)' : 'rgba(102, 102, 102, 0.75)'};\n  font-size: 0.65rem;\n  font-weight: bold;\n  padding: 0 0.5rem;\n  display: flex;\n`;\n\nconst Item = styled.div<{ $isSelected: boolean }>`\n  display: flex;\n  animation: slideDropDown 0.5s ease-in-out;\n  padding: 0.5rem;\n  margin: 0;\n  cursor: pointer;\n  background-color: ${({ $isSelected }) =>\n    $isSelected ? 'var(--primary-accent-bg)' : 'transparent'};\n  transition: 0.05s ease-in-out;\n\n  &:hover,\n  &:active,\n  &:focus {\n    background-color: var(--primary-accent-bg);\n    ${Details} {\n      color: var(--global-text);\n    }\n  }\n`;\nconst ViewAllItem = styled(Item)<{ $isSelected: boolean }>`\n  font-size: 0.9rem;\n  font-weight: bold;\n  display: flex;\n  justify-content: space-between; // This spreads out the children to the extremes\n  align-items: center;\n  color: ${({ $isSelected }) => ($isSelected ? '' : '#666')};\n  &:hover,\n  &:active,\n  &:focus {\n    color: var(--global-text);\n  }\n  svg {\n    margin-bottom: -0.1rem;\n  }\n`;\n\nconst Shorcuts = styled.div`\n  font-weight: normal;\n  @media (max-width: 600px) {\n    display: none;\n  }\n`;\n\nconst Image = styled.img`\n  animation: slideDropDown 0.5s ease-in-out;\n  width: 2.5rem;\n  height: 3.5rem;\n  border-radius: var(--global-border-radius);\n  object-fit: cover;\n\n  @media (max-width: 500px) {\n    width: 2.5rem;\n    height: 2.5rem;\n  }\n`;\n\nconst Title = styled.p`\n  margin: 0 0.5rem;\n  padding: 0.1rem;\n  animation: slideDropDown 0.5s ease-in-out;\n  text-align: left;\n  overflow: hidden;\n  font-size: 0.9rem;\n  font-weight: bold;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n\n  @media (max-width: 500px) {\n    font-size: 0.8rem;\n  }\n`;\n\ninterface Props {\n  searchResults: Anime[];\n  onClose: () => void;\n  isVisible: boolean;\n  selectedIndex: number | null;\n  setSelectedIndex: React.Dispatch<React.SetStateAction<number | null>>;\n  searchQuery: string;\n  containerWidth: number;\n}\n\nexport const DropDownSearch: React.FC<Props> = ({\n  searchResults,\n  onClose,\n  isVisible,\n  selectedIndex,\n  setSelectedIndex,\n  searchQuery,\n  containerWidth,\n}) => {\n  const navigate = useNavigate();\n  const ref = useRef<HTMLDivElement>(null);\n\n  const handleClickOutside = (event: MouseEvent) => {\n    if (ref.current && !ref.current.contains(event.target as Node)) {\n      onClose();\n    }\n  };\n\n  useEffect(() => {\n    if (isVisible) {\n      document.addEventListener('mousedown', handleClickOutside);\n    } else {\n      document.removeEventListener('mousedown', handleClickOutside);\n    }\n    return () => {\n      document.removeEventListener('mousedown', handleClickOutside);\n    };\n  }, [isVisible, onClose]);\n\n  useEffect(() => {\n    if (!isVisible) {\n      setSelectedIndex(null);\n    }\n  }, [isVisible]);\n\n  useEffect(() => {\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (!isVisible) return;\n\n      const total = searchResults.length;\n      let index = selectedIndex !== null ? selectedIndex : -1;\n\n      if (e.key === 'ArrowDown') {\n        e.preventDefault();\n        index = (index + 1) % (total + 1);\n      } else if (e.key === 'ArrowUp') {\n        e.preventDefault();\n        index = (index - 1 + total + 1) % (total + 1);\n      } else if (e.key === 'Enter' && selectedIndex !== null) {\n        e.preventDefault();\n        if (selectedIndex < total) {\n          onClose();\n          navigate(`/watch/${searchResults[selectedIndex].id}`);\n        } else {\n          navigate(`/search?query=${encodeURIComponent(searchQuery)}`);\n          onClose();\n        }\n      }\n\n      setSelectedIndex(index);\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n    return () => document.removeEventListener('keydown', handleKeyDown);\n  }, [isVisible, searchResults, selectedIndex]);\n\n  return (\n    <Container\n      width={containerWidth}\n      $isVisible={isVisible && searchResults.length > 0}\n      ref={ref}\n      role='list'\n    >\n      {searchResults.map((result, index) => (\n        <Item\n          key={result.id}\n          title={result.title.english || result.title.romaji}\n          $isSelected={index === selectedIndex}\n          onClick={() => {\n            onClose();\n            navigate(`/watch/${result.id}`);\n          }}\n          role='listitem'\n        >\n          <Image\n            src={result.image || ''}\n            alt={result.title?.english || result.title?.romaji || 'n/a'}\n          />\n          <div>\n            <Title>\n              {result.title?.english || result.title?.romaji || 'n/a'}\n            </Title>\n            <Details $isSelected={index === selectedIndex}>\n              <span>&nbsp;{result.type}</span>\n              <span>&nbsp;&nbsp;</span>\n              <TbCards color='#' />\n              <span>&nbsp;</span>\n              <span>{result.totalEpisodes || 'N/A'}&nbsp;</span>\n              <FaStar color='#' />\n              <span>&nbsp;</span>\n              <span>{result.rating ? result.rating / 10 : 'N/A'}&nbsp;</span>\n              <span>&nbsp;&nbsp;</span>\n            </Details>\n          </div>\n        </Item>\n      ))}\n      <div>\n        <ViewAllItem\n          $isSelected={selectedIndex === searchResults.length}\n          onClick={() => {\n            navigate(`/search?query=${encodeURIComponent(searchQuery)}`);\n            onClose();\n          }}\n          role='listitem'\n          tabIndex={0}\n        >\n          <Shorcuts>\n            <BsArrowUpSquare /> <BsArrowDownSquare /> to navigate{' '}\n            <PiKeyReturn /> to select | Esc to exit &nbsp;\n          </Shorcuts>\n          <div>\n            <>View All</> &nbsp; <FaArrowRight />\n          </div>\n        </ViewAllItem>\n      </div>\n    </Container>\n  );\n};\n"
  },
  {
    "path": "src/components/Navigation/Footer.tsx",
    "content": "import styled from 'styled-components';\nimport { FaReddit, FaDiscord, FaTwitter, FaGithub } from 'react-icons/fa';\nimport { Link } from 'react-router-dom';\nimport { year } from '../../hooks/useTIme';\n\nconst PageWrapper = styled.div`\n  margin-top: 2rem;\n  @media (max-width: 1000px) {\n    padding: 0 0.5rem;\n  }\n`;\n\nconst FooterBaseContainer = styled.footer<{ $isSub: boolean }>`\n  color: var(--global-text);\n  padding: ${({ $isSub }) => ($isSub ? '0' : '0.5rem 0')};\n  display: flex;\n  justify-content: space-between;\n  border-top: ${({ $isSub }) => ($isSub ? '0.125rem solid' : 'none')}\n    var(--global-secondary-bg);\n  flex-direction: column;\n\n  @media (max-width: 1000px) {\n    padding: ${({ $isSub }) => ($isSub ? '0 0 1rem 0' : '0.5rem 0')};\n  }\n\n  @media (min-width: 601px) {\n    flex-direction: row;\n  }\n\n  @media (max-width: 600px) {\n    padding: ${({ $isSub }) => ($isSub ? '0' : '0.5rem 0')};\n  }\n`;\n\nconst StyledLinkList = styled.div`\n  display: flex;\n  flex-direction: column;\n  margin: 0.5rem 0;\n  margin-top: auto;\n`;\n\nconst FooterLink = styled(Link)`\n  align-items: center;\n  padding: 0.5rem 0;\n  color: grey;\n  font-size: 0.9rem;\n  text-decoration: none;\n  transition: color 0.1s ease-in-out;\n  bottom: 0;\n  align-self: auto;\n\n  @media (min-width: 601px) {\n    align-self: end;\n  }\n\n  &:hover,\n  &:active,\n  &:focus {\n    color: var(--global-button-text);\n  }\n`;\n\nconst SocialIconsWrapper = styled.div`\n  padding-top: 1rem;\n  display: flex;\n  gap: 1rem;\n`;\n\nconst FooterLogoImage = styled.img`\n  content: var(--logo-transparent);\n  max-width: 4rem;\n  height: 4.375rem;\n`;\n\nconst Text = styled.div<{ $isSub: boolean }>`\n  color: grey;\n  font-size: ${({ $isSub }) => ($isSub ? '0.75rem' : '0.65rem')};\n  margin: ${({ $isSub }) => ($isSub ? '1rem 0 0 0' : '1rem 0')};\n  max-width: 25rem;\n\n  strong {\n    color: var(--global-text);\n  }\n`;\n\nconst ShareButton = styled.a`\n  display: inline-block;\n  color: grey;\n  transition: 0.2s ease-in-out;\n\n  svg {\n    font-size: 1.2rem;\n  }\n\n  &:hover,\n  &:active,\n  &:focus {\n    transform: scale(1.15);\n    color: var(--global-button-text);\n    text-decoration: underline;\n  }\n\n  @media (max-width: 600px) {\n    margin-bottom: 1rem;\n  }\n`;\n\nexport function Footer() {\n  return (\n    <PageWrapper>\n      <footer>\n        <FooterBaseContainer aria-label='Main Footer' $isSub={false}>\n          <Text as='p' $isSub={false}>\n            <FooterLogoImage alt='Footer Logo' /> <br />\n            This website does not retain any files on its server. Rather, it\n            solely provides links to media content hosted by third-party\n            services.\n          </Text>\n          <StyledLinkList aria-label='Footer Links'>\n            <FooterLink to='/about' title='About Us'>\n              About\n            </FooterLink>\n            <FooterLink\n              to='https://www.miruro.com'\n              target='_blank'\n              title='Domains'\n            >\n              Domains\n            </FooterLink>\n            <FooterLink to='/pptos' title='Privacy Policy and Terms of Service'>\n              Privacy & ToS\n            </FooterLink>\n          </StyledLinkList>\n        </FooterBaseContainer>\n        <FooterBaseContainer aria-label='Sub Footer' $isSub={true}>\n          <Text as='p' $isSub={true}>\n            &copy; {year}{' '}\n            <a\n              href='https://www.miruro.com'\n              rel='noopener noreferrer'\n              style={{ color: 'grey' }}\n            >\n              miruro.com\n            </a>{' '}\n            | Website Made by <strong>Miruro no Kuon</strong>\n          </Text>\n          <nav aria-label='Social Links'>\n            <SocialIconsWrapper>\n              {[\n                {\n                  href: 'https://www.reddit.com/r/miruro',\n                  Icon: FaReddit,\n                  label: 'Reddit',\n                },\n                {\n                  href: 'https://discord.gg/dubRrtfpFn',\n                  Icon: FaDiscord,\n                  label: 'Discord',\n                },\n                {\n                  href: 'https://twitter.com/miruro_official',\n                  Icon: FaTwitter,\n                  label: 'Twitter',\n                },\n              ].map(({ href, Icon, label }) => (\n                <ShareButton\n                  key={href}\n                  href={href}\n                  target='_blank'\n                  rel='noopener noreferrer'\n                  aria-label={`Miruro on ${label}`}\n                >\n                  <Icon aria-hidden='true' />\n                </ShareButton>\n              ))}\n            </SocialIconsWrapper>\n          </nav>\n        </FooterBaseContainer>\n      </footer>\n    </PageWrapper>\n  );\n}\n"
  },
  {
    "path": "src/components/Navigation/Navbar.tsx",
    "content": "import React, { useRef, useEffect, useState, useCallback } from 'react';\nimport styled from 'styled-components';\nimport {\n  useNavigate,\n  useSearchParams,\n  Link,\n  useLocation,\n} from 'react-router-dom';\nimport { DropDownSearch, useAuth } from '../../index';\nimport { fetchAdvancedSearch, type Anime } from '../..';\nimport { FiSun, FiMoon, FiX /* FiMenu */ } from 'react-icons/fi';\nimport { GoCommandPalette } from 'react-icons/go';\nimport { IoIosSearch } from 'react-icons/io';\nimport { CgProfile } from 'react-icons/cg';\n\nconst StyledNavbar = styled.div<{ $isExtended?: boolean }>`\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  text-align: center;\n  margin: 0;\n  padding: 1rem;\n  background-color: var(--global-primary-bg-tr);\n  backdrop-filter: blur(10px);\n  -webkit-backdrop-filter: blur(10px);\n  z-index: 100;\n  animation: fadeIn('var(--global-primary-bg-tr)') 0.5s ease-in-out;\n  transition: 0.1s ease-in-out;\n\n  @media (max-width: 500px) {\n    padding: 1rem 0.5rem;\n  }\n`;\n\nconst NavbarWrapper = styled.div`\n  max-width: 105rem;\n  margin: auto;\n`;\n\nconst TopContainer = styled.div`\n  display: flex;\n  gap: 0.5rem;\n  align-items: center;\n  justify-content: space-between;\n`;\n\nconst LogoImg = styled(Link)`\n  width: 7rem;\n  font-size: 1.2rem;\n  font-weight: bold;\n  text-decoration: none;\n  color: var(--global-text);\n  content: var(--logo-text-transparent);\n  cursor: pointer;\n  transition:\n    color 0.2s ease-in-out,\n    transform 0.2s ease-in-out;\n\n  &:hover,\n  &:active,\n  &:focus {\n    color: black;\n    transform: scale(1.05);\n  }\n\n  &:active {\n    transform: scale(0.95);\n  }\n\n  @media (max-width: 500px) {\n    max-width: 6rem;\n  }\n`;\n\nconst InputContainer = styled.div<{ $isVisible: boolean }>`\n  display: flex;\n  flex: 1;\n  max-width: 35rem;\n  height: 1.2rem;\n  align-items: center;\n  padding: 0.6rem;\n  border-radius: var(--global-border-radius);\n  background-color: var(--global-div);\n  animation: fadeIn 0.1s ease-in-out;\n  animation: slideDropDown 0.5s ease;\n\n  @media (max-width: 1000px) {\n    max-width: 30rem;\n  }\n\n  @media (max-width: 500px) {\n    max-width: 100%;\n    margin-top: 1rem;\n    display: ${({ $isVisible }) => ($isVisible ? 'flex' : 'none')};\n  }\n`;\n\nconst RightContent = styled.div`\n  gap: 0.5rem;\n  display: flex;\n  align-items: center;\n  height: 2rem;\n`;\n\nconst Icon = styled.div<{ $isFocused: boolean }>`\n  margin: 0;\n  padding: 0 0.25rem;\n  color: var(--global-text);\n  opacity: ${({ $isFocused }) => ($isFocused ? 1 : 0.5)};\n  font-size: 1.2rem;\n  transition: opacity 0.2s;\n  max-height: 100%;\n  display: flex;\n  align-items: center;\n`;\n\nconst SearchInput = styled.input`\n  background: transparent;\n  border: none;\n  color: var(--global-text);\n  display: inline-block;\n  font-size: 0.85rem;\n  outline: 0;\n  padding: 0;\n  max-height: 100%;\n  display: flex;\n  align-items: center;\n  padding-top: 0;\n  width: 100%;\n  transition:\n    border-color 0.2s ease-in-out,\n    box-shadow 0.2s ease-in-out;\n`;\n\nconst ClearButton = styled.button<{ $query: string }>`\n  background: transparent;\n  border: none;\n  color: var(--global-text);\n  font-size: 1.2rem;\n  cursor: pointer;\n  opacity: ${({ $query }) => ($query ? 0.5 : 0)};\n  visibility: ${({ $query }) => ($query ? 'visible' : 'hidden')};\n  transition:\n    color 0.2s,\n    opacity 0.2s;\n  max-height: 100%;\n  display: flex;\n  align-items: center;\n\n  &:hover,\n  &:active,\n  &:focus {\n    color: var(--global-text);\n    opacity: 1;\n  }\n`;\n\nconst StyledButton = styled.button<{ isInputToggle?: boolean }>`\n  background: transparent;\n  background-color: var(--global-div);\n  color: var(--global-text);\n  font-size: 1.2rem;\n  cursor: pointer;\n  padding: 1.2rem 0.6rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: var(--global-border-radius);\n  width: 100%;\n  height: 100%;\n  transition:\n    color 0.2s ease-in-out,\n    transform 0.1s ease-in-out;\n  border: none;\n\n  &:active {\n    transform: scale(0.9);\n  }\n\n  @media (max-width: 500px) {\n    display: flex;\n    margin: ${({ isInputToggle }) => (isInputToggle ? '0' : '0')};\n  }\n`;\n\nconst SlashToggleBtn = styled.div<{ $isFocused: boolean }>`\n  font-size: 1.2rem;\n  cursor: pointer;\n  opacity: ${({ $isFocused }) => ($isFocused ? 1 : 0.5)};\n\n  &:hover,\n  &:active,\n  &:focus {\n    opacity: 1;\n  }\n\n  @media (max-width: 1000px) {\n    display: none;\n  }\n`;\n\nconst detectUserTheme = () => {\n  if (\n    window.matchMedia &&\n    window.matchMedia('(prefers-color-scheme: dark)').matches\n  ) {\n    return true;\n  }\n  return false;\n};\n\nconst saveThemePreference = (isDarkMode: boolean) => {\n  localStorage.setItem('themePreference', isDarkMode ? 'dark' : 'light');\n};\n\nconst getInitialThemePreference = () => {\n  const storedThemePreference = localStorage.getItem('themePreference');\n\n  if (storedThemePreference) {\n    return storedThemePreference === 'dark';\n  }\n\n  return detectUserTheme();\n};\n\nexport const Navbar = () => {\n  const { isLoggedIn, userData } = useAuth();\n  const [isPaddingExtended, setIsPaddingExtended] = useState(false);\n  const inputContainerRef = useRef<HTMLDivElement>(null);\n  const navigate = useNavigate();\n  const location = useLocation();\n  const [inputContainerWidth, setInputContainerWidth] = useState(0);\n  const [searchParams, setSearchParams] = useSearchParams();\n  const inputRef = useRef<HTMLInputElement>(null);\n  const navbarRef = useRef(null);\n  const dropdownRef = useRef<HTMLDivElement>(null); // Ref for the dropdown container\n  const [searchResults, setSearchResults] = useState<Anime[]>([]);\n  const debounceTimeout = useRef<Timer | null>(null);\n  const [selectedIndex, setSelectedIndex] = useState<number | null>(null);\n  const [search, setSearch] = useState({\n    isSearchFocused: false,\n    searchQuery: searchParams.get('query') || '',\n    isDropdownOpen: false,\n  });\n  const [isInputVisible, setIsInputVisible] = useState(false); // Default to false\n  const [isMobileView, setIsMobileView] = useState(window.innerWidth < 500);\n  const fetchSearchResults = async (query: string) => {\n    if (!query.trim()) return;\n\n    try {\n      const fetchedData = await fetchAdvancedSearch(query, 1, 5); // Fetch first 5 results for the dropdown\n      const formattedResults = fetchedData.results.map((anime: Anime) => ({\n        id: anime.id, // Make sure to include the ID field\n        title: anime.title,\n        image: anime.image,\n        type: anime.type,\n        totalEpisodes: anime.totalEpisodes,\n        rating: anime.rating,\n      }));\n      setSearchResults(formattedResults);\n    } catch (error) {\n      console.error('Failed to fetch search results:', error);\n      setSearchResults([]);\n    }\n  };\n\n  const handleCloseDropdown = () => {\n    setSearch((prevState) => ({\n      ...prevState,\n      isDropdownOpen: false,\n    }));\n  };\n\n  const handleClickOutside = (event: MouseEvent) => {\n    if (\n      dropdownRef.current &&\n      !dropdownRef.current.contains(event.target as Node)\n    ) {\n      handleCloseDropdown();\n    }\n  };\n\n  useEffect(() => {\n    document.addEventListener('mousedown', handleClickOutside);\n    return () => {\n      document.removeEventListener('mousedown', handleClickOutside);\n    };\n  });\n\n  const [isDarkMode, setIsDarkMode] = useState(getInitialThemePreference());\n\n  useEffect(() => {\n    document.documentElement.classList.toggle('dark-mode', isDarkMode);\n  }, [isDarkMode]);\n\n  const toggleTheme = useCallback(() => {\n    const newIsDarkMode = !isDarkMode;\n    setIsDarkMode(newIsDarkMode);\n    saveThemePreference(newIsDarkMode);\n  }, [isDarkMode, setIsDarkMode]);\n\n  const handleKeyDown = useCallback(\n    (e: KeyboardEvent) => {\n      if (e.key === '/' && inputRef.current) {\n        e.preventDefault();\n        inputRef.current.focus();\n        setSearch((prevState) => ({\n          ...prevState,\n          isSearchFocused: true,\n        }));\n      } else if (e.key === 'Escape' && inputRef.current) {\n        inputRef.current.blur();\n        setSearch((prevState) => ({\n          ...prevState,\n          isSearchFocused: false,\n        }));\n        handleCloseDropdown(); // Close dropdown on Escape key\n      } else if (e.shiftKey && e.key.toLowerCase() === 'd') {\n        if (document.activeElement !== inputRef.current) {\n          e.preventDefault();\n          toggleTheme();\n        }\n      }\n    },\n    [toggleTheme],\n  );\n\n  useEffect(() => {\n    const listener = handleKeyDown as EventListener;\n    document.addEventListener('keydown', listener);\n    return () => {\n      document.removeEventListener('keydown', listener);\n    };\n  }, [handleKeyDown]);\n\n  useEffect(() => {\n    setSearch({ ...search, searchQuery: searchParams.get('query') || '' });\n  }, [searchParams]);\n\n  const navigateWithQuery = useCallback(\n    (value: string) => {\n      if (location.pathname == '/search') {\n        const params = new URLSearchParams();\n\n        params.set('query', value);\n        setSearchParams(params, { replace: true });\n      } else {\n        navigate(value ? `/search?query=${value}` : '/search');\n      }\n    },\n    [navigate, location.pathname, setSearchParams],\n  );\n\n  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const newValue = e.target.value;\n    setSearch({ ...search, searchQuery: newValue });\n\n    if (debounceTimeout.current) clearTimeout(debounceTimeout.current);\n\n    debounceTimeout.current = setTimeout(() => {\n      fetchSearchResults(newValue);\n      setSearch((prevState) => ({\n        ...prevState,\n        isDropdownOpen: true,\n      }));\n    }, 300);\n  };\n\n  const handleKeyDownOnInput = (e: React.KeyboardEvent<HTMLInputElement>) => {\n    if (e.key === 'Enter') {\n      e.preventDefault(); // Prevent default form submission behavior\n      if (selectedIndex !== null && searchResults[selectedIndex]) {\n        // Navigate to the selected search result if it exists\n        const animeId = searchResults[selectedIndex].id;\n        navigate(`/watch/${animeId}`);\n        handleCloseDropdown();\n      } else {\n        // Fallback to navigating with the search query if the selected index is not in searchResults\n        navigateWithQuery(search.searchQuery);\n      }\n      if (debounceTimeout.current) {\n        clearTimeout(debounceTimeout.current);\n      }\n      setSearch((prevState) => ({\n        ...prevState,\n        isDropdownOpen: false,\n      }));\n      if (inputRef.current) {\n        inputRef.current.blur();\n      }\n    }\n  };\n\n  useEffect(() => {\n    // Function to update the width\n    const updateWidth = () => {\n      if (inputContainerRef.current) {\n        setInputContainerWidth(inputContainerRef.current.offsetWidth);\n      }\n    };\n\n    // Update width on mount\n    updateWidth();\n\n    // Add event listener for window resize\n    window.addEventListener('resize', updateWidth);\n\n    // Cleanup function to remove the event listener\n    return () => window.removeEventListener('resize', updateWidth);\n  }, []);\n\n  useEffect(() => {\n    // This effect runs when the location.pathname changes or enter is pressed (Hide the InputContainer)\n    if (isMobileView) {\n      setIsInputVisible(false);\n    }\n  }, [location.pathname, isMobileView]);\n\n  const handleClearSearch = () => {\n    setSearch((prevState) => ({\n      ...prevState,\n      searchQuery: '',\n    }));\n    setSearchResults([]);\n    setSearch((prevState) => ({\n      ...prevState,\n      isDropdownOpen: false, // Close dropdown when search is cleared\n    }));\n    if (inputRef.current) {\n      inputRef.current.focus();\n    }\n  };\n\n  useEffect(() => {\n    function handleResize() {\n      setIsMobileView(window.innerWidth < 500);\n    }\n\n    window.addEventListener('resize', handleResize);\n    return () => window.removeEventListener('resize', handleResize);\n  }, []);\n\n  //navigate to profile\n  const navigateToProfile = () => {\n    // Check if the current location's pathname is not '/profile' before navigating\n    if (location.pathname !== '/profile') {\n      navigate('/profile');\n    }\n  };\n\n  return (\n    <>\n      <StyledNavbar $isExtended={isPaddingExtended} ref={navbarRef}>\n        <NavbarWrapper>\n          <TopContainer>\n            <LogoImg\n              title='MIRURO.tv'\n              to='/home'\n              onClick={() => window.scrollTo(0, 0)}\n            >\n              見るろ の 久遠\n            </LogoImg>\n\n            {/* Render InputContainer within the navbar for screens larger than 500px */}\n            {!isMobileView && (\n              <InputContainer\n                ref={inputContainerRef}\n                $isVisible={isInputVisible}\n              >\n                <Icon $isFocused={search.isSearchFocused}>\n                  <IoIosSearch />\n                </Icon>\n                <SearchInput\n                  type='text'\n                  placeholder='Search Anime'\n                  value={search.searchQuery}\n                  onChange={handleInputChange}\n                  onKeyDown={handleKeyDownOnInput}\n                  onFocus={() => {\n                    setSearch((prevState) => ({\n                      ...prevState,\n                      isDropdownOpen: true,\n                      isSearchFocused: true,\n                    }));\n                  }}\n                  ref={inputRef}\n                  aria-label='Search Anime'\n                />\n                <DropDownSearch\n                  searchResults={searchResults}\n                  onClose={handleCloseDropdown}\n                  isVisible={search.isDropdownOpen}\n                  selectedIndex={selectedIndex}\n                  setSelectedIndex={setSelectedIndex}\n                  searchQuery={search.searchQuery}\n                  containerWidth={inputContainerWidth}\n                />\n\n                <ClearButton\n                  $query={search.searchQuery}\n                  onClick={handleClearSearch}\n                  aria-label='Clear Search'\n                >\n                  <FiX />\n                </ClearButton>\n                <Icon $isFocused={search.isSearchFocused}>\n                  <GoCommandPalette />\n                </Icon>\n              </InputContainer>\n            )}\n            <RightContent>\n              {isMobileView && (\n                <StyledButton\n                  onClick={() => {\n                    setIsInputVisible((prev) => !prev);\n                    setIsPaddingExtended((prev) => !prev); // Toggle padding extension when toggling input visibility\n                  }}\n                  aria-label='Toggle Search Input'\n                >\n                  <IoIosSearch />\n                </StyledButton>\n              )}\n              <StyledButton onClick={toggleTheme} aria-label='Toggle Dark Mode'>\n                {isDarkMode ? <FiSun /> : <FiMoon />}\n              </StyledButton>\n              <StyledButton onClick={navigateToProfile}>\n                {isLoggedIn && userData ? (\n                  <img\n                    src={userData.avatar.large}\n                    alt={`${userData.name}'s avatar`}\n                    style={{\n                      width: '25px',\n                      height: '25px',\n                      borderRadius: '50%',\n                    }}\n                  />\n                ) : (\n                  <CgProfile />\n                )}\n              </StyledButton>\n            </RightContent>\n          </TopContainer>\n\n          {isMobileView && isInputVisible && (\n            <InputContainer $isVisible={isInputVisible}>\n              <Icon $isFocused={search.isSearchFocused}>\n                <IoIosSearch />\n              </Icon>\n              <SearchInput\n                type='text'\n                placeholder='Search Anime'\n                value={search.searchQuery}\n                onChange={handleInputChange}\n                onKeyDown={handleKeyDownOnInput}\n                onFocus={() => {\n                  setSearch((prevState) => ({\n                    ...prevState,\n                    isDropdownOpen: true,\n                    isSearchFocused: true,\n                  }));\n                }}\n                ref={inputRef}\n              />\n              <DropDownSearch\n                searchResults={searchResults}\n                onClose={handleCloseDropdown}\n                isVisible={search.isDropdownOpen}\n                selectedIndex={selectedIndex}\n                setSelectedIndex={setSelectedIndex}\n                searchQuery={search.searchQuery}\n                containerWidth={inputContainerWidth}\n              />\n\n              <ClearButton\n                $query={search.searchQuery}\n                onClick={handleClearSearch}\n              >\n                <FiX />\n              </ClearButton>\n              <SlashToggleBtn $isFocused={search.isSearchFocused}>\n                <GoCommandPalette />\n              </SlashToggleBtn>\n            </InputContainer>\n          )}\n        </NavbarWrapper>\n      </StyledNavbar>\n      {/* Conditionally render InputContainer below the navbar for mobile view when visibility is toggled */}\n    </>\n  );\n};\n"
  },
  {
    "path": "src/components/Navigation/SearchFilters.tsx",
    "content": "import React, { useState, useEffect } from 'react';\nimport styled from 'styled-components';\nimport Select, { components } from 'react-select';\nimport makeAnimated from 'react-select/animated';\nimport {\n  FaSearch,\n  FaSortAmountDown,\n  FaSortAmountDownAlt,\n  FaCheckCircle,\n  FaTrashAlt,\n} from 'react-icons/fa';\nimport { FiX } from 'react-icons/fi';\nimport {\n  Option,\n  FilterProps,\n  genreOptions,\n  anyOption,\n  yearOptions,\n  seasonOptions,\n  formatOptions,\n  statusOptions,\n  sortOptions,\n} from '../../index';\n\ninterface StateProps {\n  data: {\n    label: string;\n  };\n  isSelected: boolean;\n  isFocused: boolean;\n}\n\nconst selectStyles: any = {\n  placeholder: (provided: object) => ({\n    ...provided,\n    color: 'var(--global-text-muted)',\n  }),\n  singleValue: (provided: object, state: StateProps) => ({\n    ...provided,\n    color:\n      state.data.label === 'Popularity' || state.data.label === 'Any'\n        ? 'var(--global-text-muted)'\n        : 'var(--primary-accent)',\n  }),\n  control: (provided: object) => ({\n    ...provided,\n    width: '11.5rem',\n    backgroundColor: 'var(--global-secondary-bg)',\n    borderColor: 'transparent',\n    color: 'var(--global-text)',\n    boxShadow: 'none',\n    '&:hover': {\n      borderColor: 'var(--primary-accent)',\n    },\n    '@media (max-width: 500px)': {\n      width: '10rem',\n    },\n  }),\n  menu: (provided: object) => ({\n    ...provided,\n    zIndex: 5,\n    padding: '0.25rem',\n    backgroundColor: 'var(--global-secondary-bg)',\n    borderColor: 'var(--global-border)',\n    color: 'var(--global-text)',\n  }),\n  option: (provided: object, state: StateProps) => ({\n    ...provided,\n    backgroundColor:\n      state.isSelected || state.isFocused\n        ? 'var(--global-tertiary-bg)'\n        : 'var(--global-secondary-bg)',\n    color:\n      state.isSelected || state.isFocused\n        ? 'var(--primary-accent)'\n        : 'var(--global-text)',\n    borderRadius: 'var(--global-border-radius)',\n    '&:hover': {\n      backgroundColor: 'var(--global-tertiary-bg)',\n      color: 'var(--primary-accent)',\n    },\n    marginBottom: '0.25rem',\n  }),\n  multiValue: (provided: object) => ({\n    ...provided,\n    backgroundColor: 'var(--global-genre-button-bg)',\n  }),\n  multiValueLabel: (provided: object) => ({\n    ...provided,\n    color: 'var(--global-text)',\n  }),\n  multiValueRemove: (provided: object) => ({\n    ...provided,\n    '&:hover': {\n      backgroundColor: 'var(--primary-accent)',\n      color: 'var(--global-secondary-bg)',\n    },\n  }),\n};\n\nconst InputContainer = styled.div`\n  display: flex;\n  max-width: 10.4rem;\n  flex: 1;\n  align-items: center;\n  padding: 0 0.3rem;\n  border-radius: var(--global-border-radius);\n  background-color: var(--global-div);\n  @media (max-width: 500px) {\n    max-width: 100%;\n  }\n`;\n\nconst Icon = styled.div`\n  font-size: 0.8rem;\n  margin: 0;\n  padding: 0 0.25rem;\n  color: 'var(--global-text-muted)',\n  transition: opacity 0.2s;\n  max-height: 100%;\n  display: flex;\n  align-items: center;\n`;\n\nconst SearchInput = styled.input`\n  background: transparent;\n  border: none;\n  color: var(--global-text);\n  display: inline-block;\n  font-size: 0.8rem;\n  outline: 0;\n  padding: 0;\n  max-height: 100%;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  height: 2.375rem;\n\n  transition:\n    border-color 0.2s ease-in-out,\n    box-shadow 0.2s ease-in-out;\n`;\n\nconst FiltersWrapper = styled.div`\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n`;\n\nconst FiltersContainer = styled.div`\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));\n  grid-template-rows: auto;\n  margin: 0 auto;\n  position: relative;\n  gap: 1rem;\n  justify-content: left;\n  align-items: center;\n  font-size: 0.8rem;\n  font-weight: bold;\n  flex-wrap: wrap;\n\n  @media (max-width: 500px) {\n    display: flex;\n    justify-content: center;\n  }\n`;\n\nconst FilterSection = styled.div`\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n\n  gap: 0.5rem;\n`;\n\nconst FilterLabel = styled.label`\n  font-weight: bold;\n  font-size: 0.9rem;\n`;\n\nconst ButtonBase = styled.button`\n  flex: 1;\n  align-items: center;\n  justify-content: center;\n  padding: 0.6rem;\n  max-width: 4.5rem;\n  min-width: 4.5rem;\n  border: none;\n  font-weight: bold;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  background-color: var(--global-div);\n  color: var(--global-text);\n  transition:\n    background-color 0.2s ease,\n    transform 0.2s ease-in-out;\n  text-align: center;\n  &:active,\n  &:focus {\n    transform: scale(1.025);\n  }\n  &:active {\n    transform: scale(0.975);\n  }\n  svg {\n    margin-bottom: -0.1rem;\n  }\n`;\n\nconst Button = styled(ButtonBase)`\n  &.active {\n    background-color: var(--primary-accent);\n  }\n`;\n\nconst ClearFilters = styled(ButtonBase)`\n  &.active {\n    background-color: none;\n  }\n  &:hover,\n  &:active,\n  &:focus {\n    background-color: red;\n    opacity: 1;\n  }\n`;\n\nconst ButtonContainer = styled.div`\n  display: flex;\n  gap: 1rem;\n  justify-content: flex-end;\n\n  @media (max-width: 500px) {\n    justify-content: center;\n  }\n`;\n\nconst ClearButton = styled.button<{ $query: string }>`\n  background: transparent;\n  border: none;\n  color: var(--global-text);\n  font-size: 1.2rem;\n  cursor: pointer;\n  opacity: ${({ $query }) => ($query ? 0.5 : 0)};\n  visibility: ${({ $query }) => ($query ? 'visible' : 'hidden')};\n  transition:\n    color 0.2s,\n    opacity 0.2s;\n  max-height: 100%;\n  display: flex;\n  align-items: center;\n\n  &:hover,\n  &:active,\n  &:focus {\n    color: var(--global-text);\n    opacity: 1;\n  }\n`;\n\nconst animatedComponents = makeAnimated();\n\nconst FilterSelect: React.FC<FilterProps> = ({\n  label,\n  options,\n  onChange,\n  value,\n  isMulti = false,\n}) => {\n  // Local state to handle input value and debounce\n  const [inputValue, setInputValue] = useState(value);\n\n  useEffect(() => {\n    // Update local state when external value prop changes\n    setInputValue(value);\n  }, [value]);\n\n  useEffect(() => {\n    if (label === 'Search') {\n      // Set up a delay for executing the onChange handler only for the Search input\n      const handler = setTimeout(() => {\n        onChange && onChange(inputValue);\n      }, 300); // 300ms delay for debounce\n\n      // Cleanup function to clear the timeout\n      return () => {\n        clearTimeout(handler);\n      };\n    }\n  }, [inputValue, onChange, label]);\n\n  //Add Check Circle to clicked option\n  const CustomOption = (props: any) => {\n    return (\n      <components.Option {...props}>\n        <div\n          style={{\n            display: 'flex',\n            justifyContent: 'space-between',\n            alignItems: 'center',\n          }}\n        >\n          <span>{props.data.label}</span>\n          {props.isSelected && <FaCheckCircle style={{ marginLeft: '10px' }} />}\n        </div>\n      </components.Option>\n    );\n  };\n  return (\n    <FilterSection>\n      <FilterLabel>\n        {label === 'Search'}\n        {label}\n      </FilterLabel>\n      {label === 'Search' ? (\n        <InputContainer>\n          <Icon>\n            <FaSearch\n              style={{\n                marginRight: '0.25rem',\n                color: 'var(--global-text-muted)',\n              }}\n            />\n          </Icon>\n          <SearchInput\n            type='text'\n            value={inputValue} // Use the local state value here\n            onChange={(e) => setInputValue(e.target.value)} // Update local state instead of calling onChange directly\n            placeholder=''\n          />\n          <ClearButton\n            $query={inputValue}\n            onClick={() => {\n              setInputValue(''); // Reset the local state\n              onChange?.(''); // Propagate the change upwards\n            }}\n            aria-label='Clear Search'\n          >\n            <FiX />\n          </ClearButton>\n        </InputContainer>\n      ) : (\n        <Select\n          components={{\n            ...animatedComponents,\n            Option: CustomOption,\n            IndicatorSeparator: () => null,\n          }}\n          isMulti={isMulti}\n          options={options}\n          onChange={onChange}\n          value={value}\n          placeholder='Any'\n          styles={selectStyles}\n          isSearchable={false}\n        />\n      )}\n    </FilterSection>\n  );\n};\n\nexport const SearchFilters: React.FC<{\n  query: string;\n  setQuery: React.Dispatch<React.SetStateAction<string>>;\n  selectedGenres: Option[];\n  setSelectedGenres: React.Dispatch<React.SetStateAction<Option[]>>;\n  selectedYear: Option;\n  setSelectedYear: React.Dispatch<React.SetStateAction<Option>>;\n  selectedSeason: Option;\n  setSelectedSeason: React.Dispatch<React.SetStateAction<Option>>;\n  selectedFormat: Option;\n  setSelectedFormat: React.Dispatch<React.SetStateAction<Option>>;\n  selectedStatus: Option;\n  setSelectedStatus: React.Dispatch<React.SetStateAction<Option>>;\n  selectedSort: Option;\n  setSelectedSort: React.Dispatch<React.SetStateAction<Option>>;\n  sortDirection: 'DESC' | 'ASC';\n  setSortDirection: React.Dispatch<React.SetStateAction<'DESC' | 'ASC'>>;\n  updateSearchParams: () => void; // Added prop for updating search params\n}> = ({\n  query,\n  setQuery,\n  selectedGenres,\n  setSelectedGenres,\n  selectedYear,\n  setSelectedYear,\n  selectedSeason,\n  setSelectedSeason,\n  selectedFormat,\n  setSelectedFormat,\n  selectedStatus,\n  setSelectedStatus,\n  selectedSort,\n  setSelectedSort,\n  sortDirection,\n  setSortDirection,\n  updateSearchParams,\n}) => {\n  // State to track if any filter is changed from its default value\n  const [filtersChanged, setFiltersChanged] = useState(false);\n\n  const handleResetFilters = () => {\n    setSelectedGenres([]);\n    setSelectedYear(anyOption);\n    setSelectedSeason(anyOption);\n    setSelectedFormat(anyOption);\n    setSelectedStatus(anyOption);\n    setSelectedSort({ value: 'POPULARITY_DESC', label: 'Popularity' });\n    setSortDirection('DESC');\n    setQuery('');\n    updateSearchParams(); // Also reset URL parameters\n  };\n\n  useEffect(() => {\n    const hasFiltersChanged =\n      query !== '' || // Check if query is not default\n      selectedGenres.length > 0 || // Check if any genres are selected\n      selectedYear.value !== anyOption.value || // Check if year is not \"Any\"\n      selectedSeason.value !== anyOption.value || // Same for season, type, status...\n      selectedFormat.value !== anyOption.value ||\n      selectedStatus.value !== anyOption.value ||\n      selectedSort.value !== 'POPULARITY_DESC' || // Check if sort criteria is not \"Popularity\"\n      sortDirection !== 'DESC'; // Check if sort direction is not descending\n\n    setFiltersChanged(hasFiltersChanged);\n  }, [\n    query,\n    selectedGenres,\n    selectedYear,\n    selectedSeason,\n    selectedFormat,\n    selectedStatus,\n    selectedSort,\n    sortDirection,\n  ]);\n\n  const handleChange =\n    (\n      setter:\n        | React.Dispatch<React.SetStateAction<Option[]>>\n        | React.Dispatch<React.SetStateAction<Option>>\n        | React.Dispatch<React.SetStateAction<string>>,\n    ) =>\n    (\n      newValue: React.SetStateAction<Option[]> &\n        React.SetStateAction<Option> &\n        React.SetStateAction<string>,\n    ) => {\n      setter(newValue);\n      updateSearchParams();\n    };\n\n  return (\n    <FiltersWrapper>\n      <div>\n        <FiltersContainer>\n          <FilterSelect\n            label='Search'\n            value={query}\n            onChange={handleChange(setQuery)}\n          />\n          <FilterSelect\n            label='Genres'\n            options={genreOptions}\n            isMulti\n            onChange={handleChange(setSelectedGenres)}\n            value={selectedGenres}\n          />\n          <FilterSelect\n            label='Year'\n            options={yearOptions}\n            onChange={handleChange(setSelectedYear)}\n            value={selectedYear}\n          />\n          <FilterSelect\n            label='Season'\n            options={seasonOptions}\n            onChange={handleChange(setSelectedSeason)}\n            value={selectedSeason}\n          />\n          <FilterSelect\n            label='Type'\n            options={formatOptions}\n            onChange={handleChange(setSelectedFormat)}\n            value={selectedFormat}\n          />\n          <FilterSelect\n            label='Status'\n            options={statusOptions}\n            onChange={handleChange(setSelectedStatus)}\n            value={selectedStatus}\n          />\n          <FilterSelect\n            label='Sort By'\n            options={sortOptions}\n            onChange={handleChange(setSelectedSort)}\n            value={selectedSort}\n          />\n        </FiltersContainer>\n      </div>\n      <ButtonContainer>\n        <Button\n          onClick={() => {\n            setSortDirection(sortDirection === 'DESC' ? 'ASC' : 'DESC');\n            updateSearchParams(); // Ensure sort direction changes also update URL\n          }}\n        >\n          {sortDirection === 'DESC' ? (\n            <FaSortAmountDown />\n          ) : (\n            <FaSortAmountDownAlt />\n          )}\n        </Button>\n        {filtersChanged && (\n          <ClearFilters onClick={handleResetFilters}>\n            <FaTrashAlt />\n          </ClearFilters>\n        )}\n      </ButtonContainer>\n    </FiltersWrapper>\n  );\n};\n"
  },
  {
    "path": "src/components/Profile/Settings.tsx",
    "content": "import React, { useState, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { useNavigate } from 'react-router-dom';\nimport { IoArrowBack } from 'react-icons/io5';\nimport { useSettings } from '../../index';\ninterface Preferences {\n  defaultLanguage: string;\n  titleLanguage: string;\n  characterNameLanguage: string;\n  ratingSource: string;\n  openKeyboardShortcuts: string;\n  autoskipIntroOutro: string;\n  autoPlay: string;\n  autoNext: string;\n  defaultServers: string;\n  restoreDefaultPreferences: string;\n  clearContinueWatching: string;\n  openButton: string;\n}\n\nconst Goback = styled.div`\n  border-radius: var(--global-border-radius);\n  display: flex;\n  cursor: pointer;\n  justify-content: center;\n  align-items: center;\n  background-color: var(--global-div);\n  color: var(--global-text);\n  width: 3rem;\n  margin-right: 0.75rem;\n  &:active {\n    transform: scale(0.975);\n  }\n`;\n\nconst SettingsDiv = styled.div`\n  gap: 1rem;\n  max-width: 45rem;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  margin: auto; /* This centers the div horizontally */\n`;\n\nconst PreferencesTable = styled.table`\n  background-color: var(--global-div-tr);\n  border-radius: var(--global-border-radius);\n  border-collapse: collapse;\n  width: 100%;\n`;\n\nconst TableRow = styled.tr``;\n\nconst TableCell = styled.td`\n  padding: 1rem;\n`;\n\nconst Title = styled.h2`\n  display: flex;\n  color: var(--global-text);\n  font-size: 1.5rem;\n  margin: 0rem;\n  margin-top: 1rem;\n`;\n\nconst SectionTitle = styled.h3`\n  color: var(--global-text);\n  font-size: 1.25rem;\n  margin: 1rem;\n`;\n\nconst Divider = styled.hr`\n  border: none;\n  height: 1px;\n  background-color: var(--global-secondary-bg);\n  margin-top: 1rem;\n  margin-bottom: 1rem;\n`;\n\nconst StyledButton = styled.button<{ isSelected: boolean }>`\n  background: var(--global-div);\n  color: var(--global-text);\n  padding: 0.4rem;\n  cursor: pointer;\n  border: none;\n  border-radius: var(--global-border-radius);\n  transition: background-color 0.2s ease-in-out;\n`;\n\nconst StyledSelect = styled.select`\n  background: var(--global-div);\n  color: var(--global-text);\n  padding: 0.25rem;\n  cursor: pointer;\n  border: none;\n  border-radius: var(--global-border-radius);\n  transition: background-color 0.2s ease-in-out;\n`;\n\nexport const Settings: React.FC = () => {\n  const navigate = useNavigate();\n  const { settings, setSettings } = useSettings();\n\n  const [preferences, setPreferences] = useState<Preferences>({\n    defaultLanguage: settings.defaultLanguage,\n    titleLanguage: 'Romaji',\n    characterNameLanguage: 'Romaji',\n    ratingSource: 'Anilist',\n    openKeyboardShortcuts: 'Open',\n    autoskipIntroOutro: settings.autoSkip ? 'Enabled' : 'Disabled',\n    autoPlay: settings.autoPlay ? 'Enabled' : 'Disabled',\n    autoNext: settings.autoNext ? 'Enabled' : 'Disabled',\n    defaultServers: 'Default',\n    restoreDefaultPreferences: 'Restore',\n    clearContinueWatching: 'Clear',\n    openButton: 'Open',\n  });\n\n  useEffect(() => {\n    setPreferences((prev) => ({\n      ...prev,\n      defaultLanguage: settings.defaultLanguage,\n      autoskipIntroOutro: settings.autoSkip ? 'Enabled' : 'Disabled',\n      autoPlay: settings.autoPlay ? 'Enabled' : 'Disabled',\n      autoNext: settings.autoNext ? 'Enabled' : 'Disabled',\n    }));\n  }, [settings]);\n\n  const getOptionsForPreference = (key: string): string[] => {\n    switch (key) {\n      case 'defaultLanguage':\n        return ['Sub', 'Dub'];\n      case 'titleLanguage':\n        return [\n          'English (Attack on Titan)',\n          'Romaji (Shingeki no Kyojin)',\n          'Native (進撃の巨人)',\n        ];\n      case 'characterNameLanguage':\n        return ['Romaji (Zoldyck Killua)', 'Native (キルア=ゾルディック)'];\n      case 'ratingSource':\n        return ['Anilist', 'IMDb', 'MyAnimeList'];\n      case 'autoskipIntroOutro':\n        return ['Enabled', 'Disabled'];\n      case 'autoPlay':\n        return ['Enabled', 'Disabled'];\n      case 'defaultServers':\n        return ['Default', 'Vidstreaming', 'Gogo'];\n      case 'autoNext':\n        return ['Enabled', 'Disabled'];\n      default:\n        return [];\n    }\n  };\n\n  const handlePreferenceChange = (\n    preferenceName: keyof Preferences,\n    value: string,\n  ) => {\n    setPreferences((prev) => ({\n      ...prev,\n      [preferenceName]: value,\n    }));\n\n    switch (preferenceName) {\n      case 'autoskipIntroOutro':\n        setSettings({ autoSkip: value === 'Enabled' });\n        break;\n      case 'autoPlay':\n        setSettings({ autoPlay: value === 'Enabled' });\n        break;\n      case 'autoNext':\n        setSettings({ autoNext: value === 'Enabled' });\n        break;\n      case 'defaultLanguage':\n        setSettings({ defaultLanguage: value });\n        break;\n      case 'defaultServers':\n        setSettings({ defaultServers: value });\n        break;\n    }\n  };\n\n  const formatPreferenceName = (key: string) => {\n    return key\n      .replace(/([A-Z])/g, ' $1')\n      .trim()\n      .toLowerCase()\n      .replace(/^\\w/, (c) => c.toUpperCase());\n  };\n\n  const handleGoback = () => {\n    navigate('/profile');\n  };\n\n  // Profile Page Document Title\n  useEffect(() => {\n    document.title = `Settings | Profile`;\n  });\n\n  return (\n    <SettingsDiv>\n      <Title>\n        <Goback onClick={handleGoback}>\n          <IoArrowBack />\n        </Goback>\n        Settings\n      </Title>\n      <PreferencesTable>\n        <SectionTitle>General</SectionTitle>\n        <tbody>\n          {[\n            'titleLanguage',\n            'characterNameLanguage',\n            'ratingSource',\n            'openKeyboardShortcuts',\n          ].map((key) => (\n            <TableRow key={key}>\n              <TableCell>{formatPreferenceName(key)}</TableCell>\n              <TableCell>\n                {key === 'openKeyboardShortcuts' ? (\n                  <StyledButton isSelected={true} disabled={true}>\n                    {preferences[key as keyof Preferences]}\n                  </StyledButton>\n                ) : (\n                  <StyledSelect\n                    value={preferences[key as keyof Preferences]}\n                    onChange={(e) =>\n                      handlePreferenceChange(\n                        key as keyof Preferences,\n                        e.target.value,\n                      )\n                    }\n                  >\n                    {getOptionsForPreference(key).map((option) => (\n                      <option key={option} value={option}>\n                        {option}\n                      </option>\n                    ))}\n                  </StyledSelect>\n                )}\n              </TableCell>\n            </TableRow>\n          ))}\n        </tbody>\n        <Divider />\n        <SectionTitle>Media</SectionTitle>\n        <tbody>\n          {[\n            'defaultLanguage',\n            'defaultServers',\n            'autoskipIntroOutro',\n            'autoPlay',\n            'autoNext',\n          ].map((key) => (\n            <TableRow key={key}>\n              <TableCell>{formatPreferenceName(key)}</TableCell>\n              <TableCell>\n                <StyledSelect\n                  value={preferences[key as keyof Preferences]}\n                  onChange={(e) =>\n                    handlePreferenceChange(\n                      key as keyof Preferences,\n                      e.target.value,\n                    )\n                  }\n                >\n                  {getOptionsForPreference(key).map((option) => (\n                    <option key={option} value={option}>\n                      {option}\n                    </option>\n                  ))}\n                </StyledSelect>\n              </TableCell>\n            </TableRow>\n          ))}\n        </tbody>\n        <Divider />\n        <SectionTitle>Other</SectionTitle>\n        <tbody>\n          {[\n            { key: 'restoreDefaultPreferences', text: 'Restore' },\n            { key: 'clearContinueWatching', text: 'Clear' },\n          ].map(({ key, text }) => (\n            <TableRow key={key}>\n              <TableCell>{formatPreferenceName(key)}</TableCell>\n              <TableCell>\n                <StyledButton\n                  isSelected={true}\n                  onClick={() =>\n                    handlePreferenceChange(key as keyof Preferences, text)\n                  }\n                >\n                  {text}\n                </StyledButton>\n              </TableCell>\n            </TableRow>\n          ))}\n        </tbody>\n      </PreferencesTable>\n    </SettingsDiv>\n  );\n};\n"
  },
  {
    "path": "src/components/Profile/SettingsProvider.tsx",
    "content": "import React, {\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  ReactNode,\n} from 'react';\n\n// Define the type for the context state\ninterface SettingsContextType {\n  settings: {\n    autoSkip: boolean;\n    autoPlay: boolean;\n    autoNext: boolean;\n    defaultLanguage: string;\n    defaultServers: string;\n  };\n  setSettings: (settings: Partial<SettingsContextType['settings']>) => void;\n}\n\n// Create the context with a default value\nconst SettingsContext = createContext<SettingsContextType | undefined>(\n  undefined,\n);\n\nexport function useSettings() {\n  const context = useContext(SettingsContext);\n  if (context === undefined) {\n    throw new Error('useSettings must be used within a SettingsProvider');\n  }\n  return context;\n}\n\ninterface SettingsProviderProps {\n  children: ReactNode;\n}\n\nexport const SettingsProvider: React.FC<SettingsProviderProps> = ({\n  children,\n}) => {\n  const [settings, setSettingsState] = useState({\n    autoSkip: localStorage.getItem('autoSkip') === 'true',\n    autoPlay: localStorage.getItem('autoPlay') === 'true',\n    autoNext: localStorage.getItem('autoNext') === 'true',\n    defaultLanguage: localStorage.getItem('defaultLanguage') || 'sub',\n    defaultServers: localStorage.getItem('defaultServers') || 'default',\n  });\n\n  useEffect(() => {\n    // This useEffect will ensure that any changes to the settings state are reflected in local storage\n    // console.log('Settings updated:', settings);\n    localStorage.setItem('autoSkip', settings.autoSkip ? 'true' : 'false');\n    localStorage.setItem('autoPlay', settings.autoPlay ? 'true' : 'false');\n    localStorage.setItem('autoNext', settings.autoNext ? 'true' : 'false');\n    localStorage.setItem('defaultLanguage', settings.defaultLanguage);\n    localStorage.setItem('defaultServers', settings.defaultServers);\n  }, [settings]);\n\n  const setSettings = (\n    newSettings: Partial<SettingsContextType['settings']>,\n  ) => {\n    setSettingsState((prev) => {\n      const updatedSettings = { ...prev, ...newSettings };\n      return updatedSettings;\n    });\n  };\n\n  return (\n    <SettingsContext.Provider value={{ settings, setSettings }}>\n      {children}\n    </SettingsContext.Provider>\n  );\n};\n"
  },
  {
    "path": "src/components/Profile/WatchingAnilist.tsx",
    "content": "import { useState, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { useAuth, useUserAnimeList, MediaListStatus } from '../../index';\nimport { CardGrid } from '../../index';\n\nconst Container = styled.div`\n  margin-top: 1rem;\n  margin-bottom: 1rem;\n`;\n\nconst NoEntriesMessage = styled.div`\n  margin: 1.5rem;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  font-size: 1.5rem;\n  font-weight: bold;\n`;\n\nconst NotLoggedIn = styled.div`\n  margin: 5rem;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  font-size: 1.5rem;\n  font-weight: bold;\n`;\n\nconst StatusDropdown = styled.select`\n  margin-left: 1rem;\n  margin-bottom: 1.5rem;\n  padding: 0.75rem;\n  border-radius: var(--global-border-radius);\n  background-color: var(--global-secondary-bg);\n  color: var(--global-text);\n  border: none;\n`;\n\nconst statusLabels = {\n  CURRENT: 'Watching',\n  PLANNING: 'Plan to Watch',\n  COMPLETED: 'Completed',\n  REPEATING: 'Re-watching',\n  PAUSED: 'Paused',\n  DROPPED: 'Dropped',\n};\n\nconst apiStatusToUserFriendly = {\n  FINISHED: 'Completed',\n  RELEASING: 'Ongoing',\n  NOT_YET_RELEASED: 'Not yet aired',\n  CANCELLED: 'Cancelled',\n  HIATUS: 'Paused',\n};\n\nexport const WatchingAnilist = () => {\n  const { isLoggedIn, userData } = useAuth();\n  const [selectedStatus, setSelectedStatus] = useState<string>(\n    localStorage.getItem('selectedStatus') || 'CURRENT',\n  );\n\n  useEffect(() => {\n    if (isLoggedIn && userData) {\n      console.log('User is logged in, username:', userData.name);\n    } else {\n      console.log('User is not logged in or userData is not available');\n    }\n  }, [isLoggedIn, userData]);\n\n  const { animeList, loading, error } = useUserAnimeList(\n    userData?.name,\n    selectedStatus as MediaListStatus,\n  );\n\n  if (!isLoggedIn)\n    return <NotLoggedIn>Please Log in to view your AniList.</NotLoggedIn>;\n  if (loading) return <NoEntriesMessage>Loading...</NoEntriesMessage>;\n  if (error)\n    return (\n      <NoEntriesMessage>\n        Error loading anime list: {error.message}\n      </NoEntriesMessage>\n    );\n\n  const animeData = animeList.lists.flatMap((list) =>\n    list.entries.map((entry) => ({\n      id: entry.media.id,\n      image: entry.media.coverImage.large,\n      title: {\n        romaji: entry.media.title.romaji,\n        english: entry.media.title.english || entry.media.title.romaji,\n      },\n      status: apiStatusToUserFriendly[entry.media.status] || 'Unknown',\n      rating: entry.media.averageScore,\n      releaseDate: entry.media.startDate.year,\n      totalEpisodes: entry.media.episodes,\n      color: entry.media.coverImage.color,\n      type: entry.media.format,\n    })),\n  );\n\n  const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {\n    const newStatus = e.target.value;\n    setSelectedStatus(newStatus);\n    localStorage.setItem('selectedStatus', newStatus);\n  };\n\n  return (\n    <Container>\n      <h3>\n        AniList\n        <StatusDropdown value={selectedStatus} onChange={handleStatusChange}>\n          {Object.values(MediaListStatus).map((status) => (\n            <option key={status} value={status}>\n              {statusLabels[status] || status}\n            </option>\n          ))}\n        </StatusDropdown>\n      </h3>\n      {animeData.length > 0 ? (\n        <CardGrid\n          animeData={animeData}\n          hasNextPage={false}\n          onLoadMore={() => {}}\n        />\n      ) : (\n        <NoEntriesMessage>No Results</NoEntriesMessage>\n      )}\n    </Container>\n  );\n};\n"
  },
  {
    "path": "src/components/ShortcutsPopup.tsx",
    "content": "import styled from 'styled-components';\nimport { useState, useEffect } from 'react';\nimport { FaTimes } from 'react-icons/fa';\n\nconst Overlay = styled.table`\n  font-size: 0.85rem;\n  animation: fadeIn 0.3s ease-in-out;\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background: rgba(0, 0, 0, 0.5);\n  backdrop-filter: blur(10px);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  z-index: 1000;\n`;\n\nconst TableCell = styled.td`\n  padding: 0.5rem;\n  border-bottom: 1px solid rgba(128, 128, 128, 0.3);\n`;\n\nconst Column1 = styled(TableCell)`\n  padding-right: 15rem;\n  opacity: 0.7;\n`;\n\nconst Column2 = styled(TableCell)`\n  padding-right: 5rem;\n`;\n\nconst CloseButton = styled.button`\n  position: absolute;\n  top: 1.25rem;\n  right: 1.25rem;\n  padding: 0.5rem;\n  padding-left: 0.6rem;\n  background-color: var(--global-primary-bg-tr);\n  color: var(--global-text);\n  border: none;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  outline: none;\n  transition: 0.1s ease-in-out;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n\n  &:hover {\n    transform: scale(1.06);\n    svg {\n      padding-bottom: 0.1rem;\n    }\n  }\n\n  &:active,\n  &:focus {\n    transform: scale(0.94);\n  }\n`;\n\nconst PopUp = styled.thead`\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  animation: slideUp 0.3s ease-in-out;\n  position: relative;\n  border-radius: var(--global-border-radius);\n  padding: 1rem;\n  line-height: 1.8rem;\n  background: var(--global-primary-bg);\n  z-index: 1100;\n  overflow: auto;\n  max-height: 90vh;\n  max-width: 90vw;\n`;\n\nconst KeyboardShortcutsPopup = ({ onClose }: { onClose: () => void }) => {\n  return (\n    <Overlay onClick={onClose}>\n      <PopUp className='popup-content' onClick={(e) => e.stopPropagation()}>\n        <div\n          style={{\n            background: 'var(--global-div)',\n            borderRadius: 'var(--global-border-radius)',\n            padding: '0.5rem',\n          }}\n        >\n          <tr>\n            <td>\n              <CloseButton onClick={onClose}>\n                <FaTimes size={'1rem'} />\n              </CloseButton>\n            </td>\n          </tr>\n          <tr>\n            <td>\n              <strong>Keyboard Shortcuts</strong>(shift+/)\n            </td>\n          </tr>\n        </div>\n        <div\n          style={{\n            background: 'var(--global-div)',\n            borderRadius: 'var(--global-border-radius)',\n            padding: '0.5rem',\n          }}\n        >\n          <tr>\n            <Column1>Play/Pause Toggle</Column1>\n            <Column2>K / Space</Column2>\n          </tr>\n          <tr>\n            <Column1>Seek Backward 10 Seconds</Column1>\n            <Column2>J</Column2>\n          </tr>\n          <tr>\n            <Column1>Seek Forward 10 Seconds</Column1>\n            <Column2>L</Column2>\n          </tr>\n          <tr>\n            <Column1>Toggle Fullscreen</Column1>\n            <Column2>F</Column2>\n          </tr>\n          <tr>\n            <Column1>Toggle Mute</Column1>\n            <Column2>M</Column2>\n          </tr>\n          <tr>\n            <Column1>Previous Episode</Column1>\n            <Column2>(SHIFT+P)</Column2>\n          </tr>\n          <tr>\n            <Column1>Next Episode</Column1>\n            <Column2>(SHIFT+N)</Column2>\n          </tr>\n          <tr>\n            <Column1>Increase Volume</Column1>\n            <Column2>Arrow Up</Column2>\n          </tr>\n          <tr>\n            <Column1>Decrease Volume</Column1>\n            <Column2>Arrow Down</Column2>\n          </tr>\n          <tr>\n            <Column1>Seek Forward 5 Seconds</Column1>\n            <Column2>Arrow Right</Column2>\n          </tr>\n          <tr>\n            <Column1>Seek Backward 5 Seconds</Column1>\n            <Column2>Arrow Left</Column2>\n          </tr>\n          <tr>\n            <Column1>Increase Playback Speed</Column1>\n            <Column2>&gt; (SHIFT+,)</Column2>\n          </tr>\n          <tr>\n            <Column1>Decrease Playback Speed</Column1>\n            <Column2>&lt; (SHIFT+.)</Column2>\n          </tr>\n          <tr>\n            <Column1>Jump to Percentage (0-90%)</Column1>\n            <Column2>0-9</Column2>\n          </tr>\n        </div>\n      </PopUp>\n    </Overlay>\n  );\n};\n\nexport const ShortcutsPopup = () => {\n  const [showPopup, setShowPopup] = useState(false);\n\n  useEffect(() => {\n    const togglePopupWithShortcut = (e: KeyboardEvent) => {\n      if (\n        e.target &&\n        ['INPUT', 'TEXTAREA', 'SELECT'].includes((e.target as Element).tagName)\n      ) {\n        return;\n      }\n\n      if (e.shiftKey && e.key === '?') {\n        e.preventDefault();\n        setShowPopup(!showPopup);\n      } else if (e.key === 'Escape') {\n        setShowPopup(false);\n      }\n    };\n\n    window.addEventListener('keydown', togglePopupWithShortcut);\n\n    return () => {\n      window.removeEventListener('keydown', togglePopupWithShortcut);\n    };\n  }, [showPopup]);\n\n  const togglePopup = () => setShowPopup(!showPopup);\n\n  return (\n    <div className='App'>\n      {showPopup && <KeyboardShortcutsPopup onClose={togglePopup} />}\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/components/Skeletons/Skeletons.tsx",
    "content": "import React from 'react';\nimport styled, { keyframes, css } from 'styled-components';\n\nconst pulseAnimation = keyframes`\n  0%, 100% { background-color: var(--global-primary-skeleton); }\n  50% { background-color: var(--global-secondary-skeleton); }\n`;\n\nconst popInAnimation = keyframes`\n  0%, 100% { opacity: 0; transform: scale(0.95); }\n  50% { opacity: 1; transform: scale(1); }\n  75% { opacity: 0.5; transform: scale(1); }\n`;\n\nconst playerPopInAnimation = keyframes`\n  0% { opacity: 0; transform: scale(0.9); }\n  100% { opacity: 1; transform: scale(1); }\n`;\n\nconst SkeletonPulse = keyframes`\n  0%, 100% { background-color: var(--global-primary-skeleton); }\n  25%, 75% { background-color: var(--global-secondary-skeleton); }\n  50% { background-color: var(--global-primary-skeleton); }\n`;\n\nconst animationMixin = css`\n  animation:\n    ${pulseAnimation} 1s infinite,\n    ${popInAnimation} 1s infinite;\n`;\n\nconst BaseSkeleton = styled.div`\n  background: var(--global-primary-skeleton);\n  border-radius: var(--global-border-radius);\n`;\n\nconst SkeletonCards = styled(BaseSkeleton)`\n  width: 100%;\n  height: 0;\n  padding-top: calc(100% * 184 / 133);\n  margin-bottom: 5.1rem;\n  ${animationMixin};\n`;\n\nconst SkeletonTitle = styled(BaseSkeleton)`\n  height: 1.4rem;\n  margin: 0.5rem 0 0.3rem;\n  ${animationMixin};\n`;\n\nconst SkeletonDetails = styled(SkeletonTitle)`\n  height: 1.3rem;\n  width: 80%;\n`;\n\nexport const SkeletonCard = React.memo(() => (\n  <SkeletonCards>\n    <SkeletonTitle />\n    <SkeletonDetails />\n    <SkeletonDetails />\n  </SkeletonCards>\n));\n\nconst SkeletonSlides = styled(BaseSkeleton)<{ loading?: boolean }>`\n  width: 100%;\n  height: 24rem;\n  ${({ loading }) => !loading && animationMixin}\n  @media (max-width: 1000px) {\n    height: 20rem;\n  }\n  @media (max-width: 500px) {\n    height: 18rem;\n  }\n`;\n\nexport const SkeletonSlide: React.FC<{ loading?: boolean }> = React.memo(\n  ({ loading }) => (\n    <SkeletonSlides loading={loading}>\n      <SkeletonImage />\n    </SkeletonSlides>\n  ),\n);\n\nconst SkeletonContainer = styled.div`\n  display: flex;\n  flex-direction: column;\n  gap: 0.2rem;\n`;\n\nconst PlayerSkeleton = styled(BaseSkeleton)`\n  position: relative;\n  padding-top: 56.25%;\n  width: 100%;\n  height: 0;\n  animation:\n    ${SkeletonPulse} 2.5s ease-in-out infinite,\n    ${playerPopInAnimation} 0.5s ease-in-out;\n`;\n\nconst PlayerButtons = styled(BaseSkeleton)`\n  position: relative;\n  height: 23px;\n  width: 100%;\n  animation:\n    ${SkeletonPulse} 2.5s ease-in-out infinite,\n    ${playerPopInAnimation} 0.5s ease-in-out;\n`;\n\nexport const SkeletonPlayer = React.memo(() => (\n  <SkeletonContainer>\n    <PlayerSkeleton />\n    <PlayerButtons />\n  </SkeletonContainer>\n));\n\nconst SkeletonImage = styled(BaseSkeleton)`\n  width: 100%;\n  height: 100%;\n`;\n"
  },
  {
    "path": "src/components/ThemeContext.tsx",
    "content": "import React, {\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  ReactNode,\n} from 'react';\n\ntype ThemeContextType = {\n  isDarkMode: boolean;\n  toggleTheme: () => void;\n};\n\nconst ThemeContext = createContext<ThemeContextType | undefined>(undefined);\n\nexport const useTheme = () => useContext(ThemeContext)!;\n\ntype ThemeProviderProps = {\n  children: ReactNode;\n};\n\nexport const ThemeProvider = ({ children }: ThemeProviderProps) => {\n  const [isDarkMode, setIsDarkMode] = useState<boolean>(() =>\n    getInitialThemePreference(),\n  );\n\n  useEffect(() => {\n    document.documentElement.classList.toggle('dark-mode', isDarkMode);\n    localStorage.setItem('themePreference', isDarkMode ? 'dark' : 'light');\n  }, [isDarkMode]);\n\n  const toggleTheme = () => {\n    setIsDarkMode(!isDarkMode);\n  };\n\n  return (\n    <ThemeContext.Provider value={{ isDarkMode, toggleTheme }}>\n      {children}\n    </ThemeContext.Provider>\n  );\n};\n\nconst getInitialThemePreference = (): boolean => {\n  const storedThemePreference = localStorage.getItem('themePreference');\n  if (storedThemePreference) {\n    return storedThemePreference === 'dark';\n  }\n  return (\n    window.matchMedia &&\n    window.matchMedia('(prefers-color-scheme: dark)').matches\n  );\n};\n"
  },
  {
    "path": "src/components/Watch/AnimeDataList.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\nimport { Link } from 'react-router-dom';\nimport { TbCards } from 'react-icons/tb';\nimport { FaStar } from 'react-icons/fa';\nimport { Anime, StatusIndicator } from '../../index';\n\nconst Sidebar = styled.div`\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  transition: 0.2s ease-in-out;\n  .Section-Title {\n    margin: 0;\n    padding: 0 0 0.5rem 0;\n    color: var(--global-text);\n    font-size: 1.25rem;\n    font-weight: bold;\n  }\n`;\n\nconst SidebarContainer = styled.div`\n  padding: 0.75rem;\n  background-color: var(--global-div-tr);\n  border-radius: var(--global-border-radius);\n`;\n\nconst Card = styled.div`\n  display: flex;\n  background-color: var(--global-div);\n  border-radius: var(--global-border-radius);\n  align-items: center;\n  overflow: hidden;\n  gap: 0.5rem;\n  cursor: pointer;\n  margin-bottom: 0.5rem;\n  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);\n  animation: slideUp 0.5s ease-in-out;\n  animation-fill-mode: backwards;\n  transition:\n    background-color 0s ease-in-out,\n    margin-left 0.2s ease-in-out 0.1s;\n  &:hover,\n  &:active,\n  &:focus {\n    background-color: var(--global-div-tr);\n    margin-left: 0.35rem;\n    @media (max-width: 500px) {\n      margin-left: unset;\n    }\n`;\n\nconst AnimeImage = styled.img`\n  width: 4.25rem;\n  height: 6rem;\n  object-fit: cover;\n  border-radius: var(--global-border-radius);\n`;\n\nconst Info = styled.div``;\n\nconst TitleWithDot = styled.div`\n  display: flex;\n  align-items: center;\n  padding: 0.5rem;\n  margin-top: 0.35rem;\n  gap: 0.4rem;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  transition: background 0.2s ease;\n`;\n\nconst Title = styled.p`\n  top: 0;\n  margin-bottom: 0.5rem;\n  display: -webkit-box;\n  -webkit-line-clamp: 2;\n  -webkit-box-orient: vertical;\n  overflow: hidden;\n  font-size: 0.9rem;\n  margin: 0;\n`;\n\nconst Details = styled.p`\n  font-size: 0.75rem;\n  margin: 0;\n  color: rgba(102, 102, 102, 0.75);\n  svg {\n    margin-left: 0.4rem;\n  }\n`;\n\nexport const AnimeDataList: React.FC<{ animeData: Anime }> = ({\n  animeData,\n}) => {\n  const filteredRecommendations = animeData.recommendations.filter((rec) =>\n    ['OVA', 'SPECIAL', 'TV', 'MOVIE', 'ONA', 'NOVEL'].includes(rec.type || ''),\n  );\n\n  const filteredRelations = animeData.relations.filter((rel) =>\n    ['OVA', 'SPECIAL', 'TV', 'MOVIE', 'ONA', 'NOVEL', 'MANGA'].includes(\n      rel.type || '',\n    ),\n  );\n\n  return (\n    <Sidebar>\n      {filteredRelations.length > 0 && (\n        <SidebarContainer>\n          <>\n            <p className='Section-Title'>RELATED</p>\n            {filteredRelations\n              .slice(0, window.innerWidth > 500 ? 5 : 3)\n              .map((relation, index) => (\n                <Link\n                  to={`/watch/${relation.id}`}\n                  key={relation.id}\n                  style={{ textDecoration: 'none', color: 'inherit' }}\n                  title={`${relation.title.userPreferred}`}\n                  aria-label={`Watch ${relation.title.userPreferred}`}\n                >\n                  <Card style={{ animationDelay: `${index * 0.1}s` }}>\n                    <AnimeImage\n                      src={relation.image}\n                      alt={relation.title.userPreferred}\n                      loading='lazy'\n                    />\n                    <Info>\n                      <TitleWithDot>\n                        <StatusIndicator status={relation.status} />\n                        <Title>\n                          {relation.title.english ??\n                            relation.title.romaji ??\n                            relation.title.userPreferred}\n                        </Title>\n                      </TitleWithDot>\n                      <Details\n                        aria-label={`Details about ${relation.title.userPreferred}`}\n                      >\n                        {/* Conditionally render each piece of detail only if it's not null or empty */}\n                        {relation.type && `${relation.type} `}\n                        {relation.episodes && (\n                          <>\n                            <TbCards aria-hidden='true' />{' '}\n                            {`${relation.episodes} `}\n                          </>\n                        )}\n                        {relation.rating && (\n                          <>\n                            <FaStar aria-hidden='true' />{' '}\n                            {`${relation.rating} `}\n                          </>\n                        )}\n                      </Details>\n                    </Info>\n                  </Card>\n                </Link>\n              ))}\n          </>\n        </SidebarContainer>\n      )}\n      {filteredRecommendations.length > 0 && (\n        <SidebarContainer>\n          <>\n            <p className='Section-Title'>RECOMMENDED</p>\n            {filteredRecommendations\n              .slice(0, window.innerWidth > 500 ? 5 : 3)\n              .map((recommendation, index) => (\n                <Link\n                  to={`/watch/${recommendation.id}`}\n                  key={recommendation.id}\n                  style={{ textDecoration: 'none', color: 'inherit' }}\n                  title={`Watch ${recommendation.title.userPreferred}`}\n                >\n                  <Card style={{ animationDelay: `${index * 0.1}s` }}>\n                    <AnimeImage\n                      src={recommendation.image}\n                      alt={recommendation.title.userPreferred}\n                      loading='lazy'\n                    />\n                    <Info>\n                      <TitleWithDot>\n                        <StatusIndicator status={recommendation.status} />\n                        <Title>\n                          {recommendation.title.english ??\n                            recommendation.title.romaji ??\n                            recommendation.title.userPreferred}\n                        </Title>\n                      </TitleWithDot>\n                      <Details\n                        aria-label={`Details about ${recommendation.title.userPreferred}`}\n                      >\n                        {/* Similar conditional rendering for recommendation details */}\n                        {recommendation.type && `${recommendation.type} `}\n                        {recommendation.episodes && (\n                          <>\n                            <TbCards aria-hidden='true' />{' '}\n                            {`${recommendation.episodes} `}\n                          </>\n                        )}\n                        {recommendation.rating && (\n                          <>\n                            <FaStar aria-hidden='true' />{' '}\n                            {`${recommendation.rating} `}\n                          </>\n                        )}\n                      </Details>\n                    </Info>\n                  </Card>\n                </Link>\n              ))}\n          </>\n        </SidebarContainer>\n      )}\n    </Sidebar>\n  );\n};\n"
  },
  {
    "path": "src/components/Watch/EpisodeList.tsx",
    "content": "import React, {\n  useState,\n  useMemo,\n  useCallback,\n  useEffect,\n  useRef,\n} from 'react';\nimport styled from 'styled-components';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport {\n  faPlay,\n  faThList,\n  faTh,\n  faSearch,\n  faImage,\n} from '@fortawesome/free-solid-svg-icons';\nimport { Episode } from '../../index';\n\ninterface Props {\n  animeId: string | undefined;\n  episodes: Episode[];\n  selectedEpisodeId: string;\n  onEpisodeSelect: (id: string) => void;\n  maxListHeight: string;\n}\n\n// Styled components for the episode list\nconst ListContainer = styled.div<{ $maxHeight: string }>`\n  background-color: var(--global-secondary-bg);\n  color: var(--global-text);\n  border-radius: var(--global-border-radius);\n  overflow: hidden;\n  flex-grow: 1;\n  display: flex;\n  flex-direction: column;\n  max-height: ${({ $maxHeight }) => $maxHeight};\n  @media (max-width: 1000px) {\n    max-height: 18rem;\n  }\n  @media (max-width: 500px) {\n    max-height: ${({ $maxHeight }) => $maxHeight};\n  }\n`;\n\nconst EpisodeGrid = styled.div<{ $isRowLayout: boolean }>`\n  display: grid;\n  grid-template-columns: ${({ $isRowLayout }) =>\n    $isRowLayout ? '1fr' : 'repeat(auto-fill, minmax(4rem, 1fr))'};\n  gap: 0.29rem;\n  padding: 0.4rem;\n  overflow-y: auto;\n  flex-grow: 1;\n`;\n\nconst EpisodeImage = styled.img`\n  max-width: 250px;\n  max-height: 150px;\n  height: auto;\n  margin-top: 0.5rem;\n  border-radius: var(--global-border-radius);\n  @media (max-width: 500px) {\n    max-width: 125px;\n    max-height: 80px;\n  }\n`;\n\nconst ListItem = styled.button<{\n  $isSelected: boolean;\n  $isRowLayout: boolean;\n  $isWatched: boolean;\n}>`\n  transition:\n    padding 0.3s ease-in-out,\n    transform 0.3s ease-in-out;\n  animation: popIn 0.3s ease-in-out;\n  background-color: ${({ $isSelected, $isWatched }) =>\n    $isSelected\n      ? $isWatched\n        ? 'var(--primary-accent)' // Selected and watched\n        : 'var(--primary-accent-bg)' // Selected but not watched\n      : $isWatched\n        ? 'var(--primary-accent-bg); filter: brightness(0.8);' // Not selected but watched\n        : 'var(--global-tertiary-bg)'};\n\n  border: none;\n  border-radius: var(--global-border-radius);\n  color: ${({ $isSelected, $isWatched }) =>\n    $isSelected\n      ? $isWatched\n        ? 'var(--global-text)' // Selected and watched\n        : 'var(--global-text)' // Selected but not watched\n      : $isWatched\n        ? 'var(--primary-accent); filter: brightness(0.8);' // Not selected but watched\n        : 'grey'}; // Not selected and not watched\n\n  padding: ${({ $isRowLayout }) =>\n    $isRowLayout ? '0.6rem 0.5rem' : '0.4rem 0'};\n  text-align: ${({ $isRowLayout }) => ($isRowLayout ? 'left' : 'center')};\n  cursor: pointer;\n  justify-content: ${({ $isRowLayout }) =>\n    $isRowLayout ? 'space-between' : 'center'};\n  align-items: center;\n\n  &:hover,\n  &:active,\n  &:focus {\n    ${({ $isSelected, $isWatched }) =>\n      $isSelected\n        ? $isWatched\n          ? 'filter: brightness(1.1)' // Selected and watched\n          : 'filter: brightness(1.1)' // Selected but not watched\n        : $isWatched\n          ? 'filter: brightness(1.1)' // Not selected but watched\n          : 'background-color: var(--global-button-hover-bg); filter: brightness(1.05); color: #FFFFFF'};\n    padding-left: ${({ $isRowLayout }) => ($isRowLayout ? '1rem' : '')};\n  }\n`;\n\nconst ControlsContainer = styled.div`\n  display: flex;\n  align-items: center;\n  background-color: var(--global-secondary-bg);\n  border-bottom: 1px solid var(--global-shadow);\n  padding: 0.25rem 0;\n`;\n\nconst SelectInterval = styled.select`\n  padding: 0.5rem;\n  background-color: var(--global-secondary-bg);\n  color: var(--global-text);\n  border: none;\n  border-radius: var(--global-border-radius);\n`;\n\nconst LayoutToggle = styled.button`\n  background-color: var(--global-secondary-bg);\n  border: 1px solid var(--global-shadow);\n  padding: 0.5rem;\n  margin-right: 0.5rem;\n  cursor: pointer;\n  color: var(--global-text);\n  border-radius: var(--global-border-radius);\n  transition:\n    background-color 0.15s,\n    color 0.15s;\n\n  &:hover,\n  &:active,\n  &:focus {\n    background-color: var(--global-button-hover-bg);\n  }\n`;\n\nconst SearchContainer = styled.div`\n  display: flex;\n  align-items: center;\n  background-color: var(--global-secondary-bg);\n  border: 1px solid var(--global-shadow);\n  padding: 0.5rem;\n  gap: 0.25rem;\n  margin: 0 0.5rem;\n  border-radius: var(--global-border-radius);\n  transition:\n    background-color 0.15s,\n    color 0.15s;\n\n  &:hover,\n  &:active,\n  &:focus {\n    background-color: var(--global-button-hover-bg);\n  }\n`;\n\nconst SearchInput = styled.input`\n  border: none;\n  background-color: transparent;\n  color: var(--global-text);\n  outline: none;\n  width: 100%;\n\n  &::placeholder {\n    color: var(--global-placeholder);\n  }\n`;\n\nconst Icon = styled.div`\n  color: var(--global-text);\n  opacity: 0.5;\n  font-size: 0.8rem;\n  transition: opacity 0.2s;\n\n  @media (max-width: 768px) {\n    display: none; /* Hide on mobile */\n  }\n`;\n\nconst EpisodeNumber = styled.span``;\nconst EpisodeTitle = styled.span`\n  padding: 0.5rem;\n`;\n\n// The updated EpisodeList component\nexport const EpisodeList: React.FC<Props> = ({\n  animeId,\n  episodes,\n  selectedEpisodeId,\n  onEpisodeSelect,\n  maxListHeight,\n}) => {\n  // State for interval, layout, user layout preference, search term, and watched episodes\n  const episodeGridRef = useRef<HTMLDivElement>(null);\n  const episodeRefs = useRef<{ [key: string]: HTMLButtonElement | null }>({});\n  const [interval, setInterval] = useState<[number, number]>([0, 99]);\n  const [isRowLayout, setIsRowLayout] = useState(true);\n  const [userLayoutPreference, setUserLayoutPreference] = useState<\n    boolean | null\n  >(null);\n  const [searchTerm, setSearchTerm] = useState('');\n  const [watchedEpisodes, setWatchedEpisodes] = useState<Episode[]>([]);\n  const defaultLayoutMode = episodes.every((episode) => episode.title)\n    ? 'list'\n    : 'grid';\n  const [displayMode, setDisplayMode] = useState<'list' | 'grid' | 'imageList'>(\n    () => {\n      const savedMode = animeId\n        ? localStorage.getItem(`listLayout-[${animeId}]`)\n        : null;\n      return (savedMode as 'list' | 'grid' | 'imageList') || defaultLayoutMode;\n    },\n  );\n\n  const [selectionInitiatedByUser, setSelectionInitiatedByUser] =\n    useState(false);\n  // Update local storage when watched episodes change\n  useEffect(() => {\n    if (animeId && watchedEpisodes.length > 0) {\n      localStorage.setItem(\n        `watched-episodes-${animeId}`,\n        JSON.stringify(watchedEpisodes),\n      );\n    }\n  }, [animeId, watchedEpisodes]);\n  // Load watched episodes from local storage when animeId changes\n  useEffect(() => {\n    if (animeId) {\n      localStorage.setItem(`listLayout-[${animeId}]`, displayMode);\n      const watched = localStorage.getItem('watched-episodes');\n      if (watched) {\n        const watchedEpisodesObject = JSON.parse(watched);\n        const watchedEpisodesForAnime = watchedEpisodesObject[animeId];\n        if (watchedEpisodesForAnime) {\n          setWatchedEpisodes(watchedEpisodesForAnime);\n        }\n      }\n    }\n  }, [animeId]);\n\n  // Function to handle episode selection\n  // Function to mark an episode as watched\n  const markEpisodeAsWatched = useCallback(\n    (id: string) => {\n      if (animeId) {\n        setWatchedEpisodes((prevWatchedEpisodes) => {\n          const updatedWatchedEpisodes = [...prevWatchedEpisodes];\n          const selectedEpisodeIndex = updatedWatchedEpisodes.findIndex(\n            (episode) => episode.id === id,\n          );\n          if (selectedEpisodeIndex === -1) {\n            const selectedEpisode = episodes.find(\n              (episode) => episode.id === id,\n            );\n            if (selectedEpisode) {\n              updatedWatchedEpisodes.push(selectedEpisode);\n              // Update the watched episodes object in local storage\n              localStorage.setItem(\n                'watched-episodes',\n                JSON.stringify({\n                  ...JSON.parse(\n                    localStorage.getItem('watched-episodes') || '{}',\n                  ),\n                  [animeId]: updatedWatchedEpisodes,\n                }),\n              );\n              return updatedWatchedEpisodes;\n            }\n          }\n          return prevWatchedEpisodes;\n        });\n      }\n    },\n    [episodes, animeId],\n  );\n  const handleEpisodeSelect = useCallback(\n    (id: string) => {\n      setSelectionInitiatedByUser(true);\n      markEpisodeAsWatched(id); // Mark the episode as watched\n      onEpisodeSelect(id);\n    },\n    [onEpisodeSelect, markEpisodeAsWatched],\n  );\n\n  // Update watched episodes when a new episode is selected or visited\n  useEffect(() => {\n    if (selectedEpisodeId && !selectionInitiatedByUser) {\n      markEpisodeAsWatched(selectedEpisodeId);\n    }\n  }, [selectedEpisodeId, selectionInitiatedByUser, markEpisodeAsWatched]);\n\n  // Generate interval options\n  const intervalOptions = useMemo(() => {\n    return episodes.reduce<{ start: number; end: number }[]>(\n      (options, _, index) => {\n        if (index % 100 === 0) {\n          const start = index;\n          const end = Math.min(index + 99, episodes.length - 1);\n          options.push({ start, end });\n        }\n        return options;\n      },\n      [],\n    );\n  }, [episodes]);\n\n  // Handle interval change\n  const handleIntervalChange = useCallback(\n    (e: React.ChangeEvent<HTMLSelectElement>) => {\n      const [start, end] = e.target.value.split('-').map(Number);\n      setInterval([start, end]);\n    },\n    [],\n  );\n\n  // Toggle layout preference\n  const toggleLayoutPreference = useCallback(() => {\n    setDisplayMode((prevMode) => {\n      const nextMode =\n        prevMode === 'list'\n          ? 'grid'\n          : prevMode === 'grid'\n            ? 'imageList'\n            : 'list';\n      if (animeId) {\n        localStorage.setItem(`listLayout-[${animeId}]`, nextMode);\n      }\n      return nextMode;\n    });\n  }, [animeId]);\n\n  // Filter episodes based on search input\n  const filteredEpisodes = useMemo(() => {\n    const searchQuery = searchTerm.toLowerCase();\n    return episodes.filter(\n      (episode) =>\n        episode.title?.toLowerCase().includes(searchQuery) ||\n        episode.number.toString().includes(searchQuery),\n    );\n  }, [episodes, searchTerm]);\n\n  // Apply the interval to the filtered episodes\n  const displayedEpisodes = useMemo(() => {\n    if (!searchTerm) {\n      // If there's no search term, apply interval to all episodes\n      return episodes.slice(interval[0], interval[1] + 1);\n    }\n    // If there is a search term, display filtered episodes without applying interval\n    return filteredEpisodes;\n  }, [episodes, filteredEpisodes, interval, searchTerm]);\n\n  // Determine layout based on episodes and user preference\n  useEffect(() => {\n    const allTitlesNull = episodes.every((episode) => episode.title === null);\n    const defaultLayout = episodes.length <= 26 && !allTitlesNull;\n\n    setIsRowLayout(\n      userLayoutPreference !== null ? userLayoutPreference : defaultLayout,\n    );\n\n    // Find the selected episode\n    if (!selectionInitiatedByUser) {\n      const selectedEpisode = episodes.find(\n        (episode) => episode.id === selectedEpisodeId,\n      );\n      if (selectedEpisode) {\n        // Find the interval containing the selected episode\n        for (let i = 0; i < intervalOptions.length; i++) {\n          const { start, end } = intervalOptions[i];\n          if (\n            selectedEpisode.number >= start + 1 &&\n            selectedEpisode.number <= end + 1\n          ) {\n            setInterval([start, end]);\n            break;\n          }\n        }\n      }\n    }\n  }, [\n    episodes,\n    userLayoutPreference,\n    selectedEpisodeId,\n    intervalOptions,\n    selectionInitiatedByUser,\n  ]);\n\n  useEffect(() => {\n    const timer = setTimeout(() => {\n      if (\n        selectedEpisodeId &&\n        episodeRefs.current[selectedEpisodeId] &&\n        episodeGridRef.current &&\n        !selectionInitiatedByUser\n      ) {\n        const episodeElement = episodeRefs.current[selectedEpisodeId];\n        const container = episodeGridRef.current;\n\n        // Ensure episodeElement is not null before proceeding\n        if (episodeElement && container) {\n          // Calculate episode's top position relative to the container\n          const episodeTop =\n            episodeElement.getBoundingClientRect().top -\n            container.getBoundingClientRect().top;\n\n          // Calculate the desired scroll position to center the episode in the container\n          const episodeHeight = episodeElement.offsetHeight;\n          const containerHeight = container.offsetHeight;\n          const desiredScrollPosition =\n            episodeTop + episodeHeight / 2 - containerHeight / 2;\n\n          container.scrollTo({\n            top: desiredScrollPosition,\n            behavior: 'smooth',\n          });\n\n          setSelectionInitiatedByUser(false);\n        }\n      }\n    }, 100); // A delay ensures the layout has stabilized, especially after dynamic content loading.\n\n    return () => clearTimeout(timer);\n  }, [selectedEpisodeId, episodes, displayMode, selectionInitiatedByUser]);\n\n  // Render the EpisodeList component\n  return (\n    <ListContainer $maxHeight={maxListHeight}>\n      <ControlsContainer>\n        <SelectInterval\n          onChange={handleIntervalChange}\n          value={`${interval[0]}-${interval[1]}`}\n        >\n          {intervalOptions.map(({ start, end }, index) => (\n            <option key={index} value={`${start}-${end}`}>\n              Episodes {start + 1} - {end + 1}\n            </option>\n          ))}\n        </SelectInterval>\n\n        <SearchContainer>\n          <Icon>\n            <FontAwesomeIcon icon={faSearch} />\n          </Icon>\n          <SearchInput\n            type='text'\n            placeholder='Search episodes...'\n            value={searchTerm}\n            onChange={(e) => setSearchTerm(e.target.value)}\n          />\n        </SearchContainer>\n        <LayoutToggle onClick={toggleLayoutPreference}>\n          {displayMode === 'list' && <FontAwesomeIcon icon={faThList} />}\n          {displayMode === 'grid' && <FontAwesomeIcon icon={faTh} />}\n          {displayMode === 'imageList' && <FontAwesomeIcon icon={faImage} />}\n        </LayoutToggle>\n      </ControlsContainer>\n      <EpisodeGrid\n        key={`episode-grid-${displayMode}`}\n        $isRowLayout={displayMode === 'list' || displayMode === 'imageList'}\n        ref={episodeGridRef}\n      >\n        {displayedEpisodes.map((episode) => {\n          const $isSelected = episode.id === selectedEpisodeId;\n          const $isWatched = watchedEpisodes.some((e) => e.id === episode.id);\n\n          return (\n            <ListItem\n              key={episode.id}\n              $isSelected={$isSelected}\n              $isRowLayout={\n                displayMode === 'list' || displayMode === 'imageList'\n              }\n              $isWatched={$isWatched}\n              onClick={() => handleEpisodeSelect(episode.id)}\n              aria-selected={$isSelected}\n              ref={(el) => (episodeRefs.current[episode.id] = el)} // Reference to each episode's button\n            >\n              {displayMode === 'imageList' ? (\n                <>\n                  <div>\n                    <EpisodeNumber>{episode.number}. </EpisodeNumber>\n                    <EpisodeTitle>{episode.title}</EpisodeTitle>\n                  </div>\n                  <EpisodeImage\n                    src={episode.image}\n                    alt={`Episode ${episode.number} - ${episode.title}`}\n                  />\n                </>\n              ) : displayMode === 'grid' ? (\n                <>\n                  <div\n                    style={{\n                      display: 'flex',\n                      flexDirection: 'column',\n                      justifyContent: 'center',\n                      alignItems: 'center',\n                      height: '100%',\n                    }}\n                  >\n                    {$isSelected ? (\n                      <FontAwesomeIcon icon={faPlay} />\n                    ) : (\n                      <EpisodeNumber>{episode.number}</EpisodeNumber>\n                    )}\n                  </div>\n                </>\n              ) : (\n                // Render for 'list' layout\n                <>\n                  <EpisodeNumber>{episode.number}. </EpisodeNumber>\n                  <EpisodeTitle>{episode.title}</EpisodeTitle>\n                  {$isSelected && <FontAwesomeIcon icon={faPlay} />}\n                </>\n              )}\n            </ListItem>\n          );\n        })}\n      </EpisodeGrid>\n    </ListContainer>\n  );\n};\n"
  },
  {
    "path": "src/components/Watch/Seasons.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\nimport { Link } from 'react-router-dom';\nimport { Relation } from '../../index';\n\nconst SeasonCardContainer = styled.div`\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: left;\n  gap: 1rem;\n  margin-top: 1rem;\n  margin-bottom: 1rem;\n  @media (max-width: 500px) {\n    justify-content: center;\n  }\n`;\n\nconst SeasonCard = styled(Link)`\n  background-size: cover;\n  background-position: center;\n  padding: 0.9rem;\n  height: 6rem;\n  width: 20rem;\n  @media (max-width: 500px) {\n    height: 3rem;\n    width: 8rem;\n    padding: 1.3rem;\n  }\n  position: relative;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  border-radius: 0.3rem;\n  box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);\n  overflow: hidden;\n  cursor: pointer;\n  text-decoration: none;\n\n  &::before {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    background-color: rgba(0, 0, 0, 0.5);\n    border-radius: var(--global-border-radius);\n    z-index: 1;\n  }\n  transition: transform 0.2s ease-in-out;\n\n  &:hover,\n  &:active &:focus {\n    transform: translateY(-5px);\n    @media (max-width: 500px) {\n      transform: none;\n    }\n  }\n`;\n\nconst Content = styled.div`\n  position: relative;\n  z-index: 2;\n`;\n\nconst SeasonName = styled.div`\n  font-size: 0.9rem;\n  @media (max-width: 500px) {\n    display: none;\n    width: 8rem;\n    font-size: 0.8rem;\n  }\n  color: white;\n  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);\n`;\n\nconst RelationType = styled.div`\n  font-size: 1.3rem;\n  @media (max-width: 500px) {\n    font-size: 1.1rem;\n    width: 8rem;\n    margin-bottom: 0.25rem;\n  }\n  font-weight: bold;\n  color: white;\n  border-radius: var(--global-border-radius);\n  text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);\n  margin-bottom: 0.75rem;\n`;\n\nexport const Seasons: React.FC<{ relations: Relation[] }> = ({ relations }) => {\n  const sortedRelations = relations.sort((a, b) => {\n    if (a.relationType === 'PREQUEL' && b.relationType !== 'PREQUEL') {\n      return -1;\n    }\n    if (a.relationType !== 'PREQUEL' && b.relationType === 'PREQUEL') {\n      return 1;\n    }\n    return 0;\n  });\n\n  return (\n    <SeasonCardContainer>\n      {sortedRelations.map((relation) => (\n        <SeasonCard\n          key={relation.id}\n          to={`/watch/${relation.id}`}\n          title={`Watch ${relation.title.english || relation.title.romaji || relation.title.userPreferred}`}\n          aria-label={`Watch ${relation.title.english || relation.title.romaji || relation.title.userPreferred}`}\n          style={{ backgroundImage: `url(${relation.image})` }}\n        >\n          <img\n            src={relation.image}\n            alt={`${relation.title.english || relation.title.romaji || relation.title.userPreferred} Cover`}\n            style={{ display: 'none' }}\n          />\n          <Content>\n            <RelationType>{relation.relationType}</RelationType>\n            <SeasonName>\n              {relation.title.english ||\n                relation.title.romaji ||\n                relation.title.userPreferred}\n            </SeasonName>\n          </Content>\n        </SeasonCard>\n      ))}\n    </SeasonCardContainer>\n  );\n};\n"
  },
  {
    "path": "src/components/Watch/Video/EmbedPlayer.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\n\nconst Container = styled.div``;\n\nconst Iframe = styled.iframe`\n  border-radius: var(--global-border-radius);\n  border: none;\n  min-height: 16.24rem;\n`;\n\nexport const EmbedPlayer: React.FC<{ src: string }> = ({ src }) => {\n  return (\n    <Container>\n      <Iframe src={src} allowFullScreen />\n    </Container>\n  );\n};\n"
  },
  {
    "path": "src/components/Watch/Video/MediaSource.tsx",
    "content": "import React, { useState } from 'react';\nimport styled from 'styled-components';\nimport {\n  FaMicrophone,\n  FaClosedCaptioning,\n  FaBell,\n  FaDownload,\n  FaShare,\n} from 'react-icons/fa';\n\n// Props interface\ninterface MediaSourceProps {\n  sourceType: string;\n  setSourceType: (sourceType: string) => void;\n  language: string;\n  setLanguage: (language: string) => void;\n  downloadLink: string;\n  episodeId?: string;\n  airingTime?: string;\n  nextEpisodenumber?: string;\n}\n\n// Adjust the Container for responsive layout\nconst UpdatedContainer = styled.div`\n  justify-content: center;\n  margin-top: 1rem;\n  gap: 1rem;\n  display: flex;\n  @media (max-width: 1000px) {\n    flex-direction: column;\n  }\n`;\n\nconst Table = styled.table`\n  font-size: 0.9rem;\n  border-collapse: collapse;\n  font-weight: bold;\n  margin-left: auto;\n  margin-right: auto;\n`;\n\nconst TableRow = styled.tr``;\n\nconst TableCell = styled.td`\n  padding: 0.35rem;\n  @media (max-width: 500px) {\n    text-align: center;\n    font-size: 0.8rem;\n  }\n  svg {\n    margin-bottom: -0.1rem;\n    @media (max-width: 500px) {\n      margin-bottom: 0rem;\n    }\n  }\n`;\n\nconst ButtonWrapper = styled.div`\n  width: 90px; // Or a specific pixel width, if preferred\n  display: flex;\n  justify-content: center;\n  gap: 0.5rem;\n`;\n\nconst ButtonBase = styled.button`\n  flex: 1; // Make the button expand to fill the wrapper\n  padding: 0.5rem;\n  border: none;\n  font-weight: bold;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  background-color: var(--global-div);\n  color: var(--global-text);\n  transition:\n    background-color 0.2s ease,\n    transform 0.2s ease-in-out;\n  text-align: center;\n\n  &:hover {\n    background-color: var(--primary-accent);\n    transform: scale(1.025);\n  }\n  &:active {\n    transform: scale(0.975);\n  }\n`;\n\nconst Button = styled(ButtonBase)`\n  &.active {\n    background-color: var(--primary-accent);\n  }\n`;\n\nconst DownloadLink = styled.a`\n  display: inline-flex; // Use inline-flex to easily center the icon\n  align-items: center; // Align the icon vertically center\n  margin-left: 0.5rem;\n  padding: 0.5rem;\n  gap: 0.25rem;\n  font-size: 0.9rem;\n  font-weight: bold;\n  border: none;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  background-color: var(--global-div);\n  color: var(--global-text);\n  text-align: center;\n  text-decoration: none;\n  transition:\n    background-color 0.3s ease,\n    transform 0.2s ease-in-out;\n\n  svg {\n    font-size: 0.85rem; // Adjust icon size\n  }\n\n  &:hover {\n    background-color: var(--primary-accent);\n    transform: scale(1.025);\n  }\n  &:active {\n    transform: scale(0.975);\n  }\n`;\n\nconst ShareButton = styled(ButtonBase)`\n  display: inline-flex; // Align items in a row\n  align-items: center; // Center items vertically\n  margin-left: 0.5rem;\n  padding: 0.5rem;\n  gap: 0.25rem;\n  font-size: 0.9rem;\n  border: none;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  background-color: var(--global-div);\n  color: var(--global-text);\n  text-decoration: none;\n  svg {\n    font-size: 0.85rem; // Adjust icon size\n  }\n`;\n\nconst ResponsiveTableContainer = styled.div`\n  background-color: var(--global-div-tr);\n  padding: 0.75rem;\n  border-radius: var(--global-border-radius);\n  @media (max-width: 500px) {\n    display: block;\n  }\n`;\n\nconst EpisodeInfoColumn = styled.div`\n  flex-grow: 1;\n  display: block;\n  background-color: var(--global-div-tr);\n  border-radius: var(--global-border-radius);\n  padding: 0.75rem;\n  @media (max-width: 1000px) {\n    display: block;\n    margin-right: 0rem;\n  }\n  p {\n    font-size: 0.9rem;\n    margin: 0;\n  }\n  h4 {\n    margin: 0rem;\n    font-size: 1.15rem;\n    margin-bottom: 1rem;\n  }\n  @media (max-width: 500px) {\n    p {\n      font-size: 0.8rem;\n      margin: 0rem;\n    }\n    h4 {\n      font-size: 1rem;\n      margin-bottom: 0rem;\n    }\n  }\n`;\n\nexport const MediaSource: React.FC<MediaSourceProps> = ({\n  sourceType,\n  setSourceType,\n  language,\n  setLanguage,\n  downloadLink,\n  episodeId,\n  airingTime,\n  nextEpisodenumber,\n}) => {\n  const [isCopied, setIsCopied] = useState(false);\n\n  const handleShareClick = () => {\n    navigator.clipboard.writeText(window.location.href);\n    setIsCopied(true);\n    setTimeout(() => {\n      setIsCopied(false);\n    }, 2000);\n  };\n\n  return (\n    <UpdatedContainer>\n      <EpisodeInfoColumn>\n        {episodeId ? (\n          <>\n            You're watching <strong>Episode {episodeId}</strong>\n            <DownloadLink\n              href={downloadLink}\n              target='_blank'\n              rel='noopener noreferrer'\n            >\n              <FaDownload />\n            </DownloadLink>\n            <ShareButton onClick={handleShareClick}>\n              <FaShare />\n            </ShareButton>\n            {isCopied && <p>Copied to clipboard!</p>}\n            <br />\n            <br />\n            <p>If current servers don't work, please try other servers.</p>\n          </>\n        ) : (\n          'Loading episode information...'\n        )}\n        {airingTime && (\n          <>\n            <p>\n              Episode <strong>{nextEpisodenumber}</strong> will air in{' '}\n              <FaBell />\n              <strong> {airingTime}</strong>.\n            </p>\n          </>\n        )}\n      </EpisodeInfoColumn>\n      <ResponsiveTableContainer>\n        <Table>\n          <tbody>\n            <TableRow>\n              <TableCell>\n                <FaClosedCaptioning /> Sub\n              </TableCell>\n              <TableCell>\n                <ButtonWrapper>\n                  <Button\n                    className={\n                      sourceType === 'default' && language === 'sub'\n                        ? 'active'\n                        : ''\n                    }\n                    onClick={() => {\n                      setSourceType('default');\n                      setLanguage('sub');\n                    }}\n                  >\n                    Default\n                  </Button>\n                </ButtonWrapper>\n              </TableCell>\n              <TableCell>\n                <ButtonWrapper>\n                  <Button\n                    className={\n                      sourceType === 'vidstreaming' && language === 'sub'\n                        ? 'active'\n                        : ''\n                    }\n                    onClick={() => {\n                      setSourceType('vidstreaming');\n                      setLanguage('sub');\n                    }}\n                  >\n                    Vidstream\n                  </Button>\n                </ButtonWrapper>\n              </TableCell>\n              <TableCell>\n                <ButtonWrapper>\n                  <Button\n                    className={\n                      sourceType === 'gogo' && language === 'sub'\n                        ? 'active'\n                        : ''\n                    }\n                    onClick={() => {\n                      setSourceType('gogo');\n                      setLanguage('sub');\n                    }}\n                  >\n                    Gogo\n                  </Button>\n                </ButtonWrapper>\n              </TableCell>\n            </TableRow>\n            <TableRow>\n              <TableCell>\n                <FaMicrophone /> Dub\n              </TableCell>\n              <TableCell>\n                <ButtonWrapper>\n                  <Button\n                    className={\n                      sourceType === 'default' && language === 'dub'\n                        ? 'active'\n                        : ''\n                    }\n                    onClick={() => {\n                      setSourceType('default');\n                      setLanguage('dub');\n                    }}\n                  >\n                    Default\n                  </Button>\n                </ButtonWrapper>\n              </TableCell>\n              <TableCell>\n                <ButtonWrapper>\n                  <Button\n                    className={\n                      sourceType === 'vidstreaming' && language === 'dub'\n                        ? 'active'\n                        : ''\n                    }\n                    onClick={() => {\n                      setSourceType('vidstreaming');\n                      setLanguage('dub');\n                    }}\n                  >\n                    Vidstream\n                  </Button>\n                </ButtonWrapper>\n              </TableCell>\n              <TableCell>\n                <ButtonWrapper>\n                  <Button\n                    className={\n                      sourceType === 'gogo' && language === 'dub'\n                        ? 'active'\n                        : ''\n                    }\n                    onClick={() => {\n                      setSourceType('gogo');\n                      setLanguage('dub');\n                    }}\n                  >\n                    Gogo\n                  </Button>\n                </ButtonWrapper>\n              </TableCell>\n            </TableRow>\n          </tbody>\n        </Table>\n      </ResponsiveTableContainer>\n    </UpdatedContainer>\n  );\n};\n"
  },
  {
    "path": "src/components/Watch/Video/Player.tsx",
    "content": "import { useEffect, useRef, useState } from 'react';\nimport './PlayerStyles.css';\nimport {\n  isHLSProvider,\n  MediaPlayer,\n  MediaProvider,\n  Poster,\n  Track,\n  type MediaProviderAdapter,\n  type MediaProviderChangeEvent,\n  type MediaPlayerInstance,\n} from '@vidstack/react';\nimport styled from 'styled-components';\nimport {\n  fetchSkipTimes,\n  fetchAnimeStreamingLinks,\n  useSettings,\n} from '../../../index';\nimport {\n  DefaultAudioLayout,\n  defaultLayoutIcons,\n  DefaultVideoLayout,\n} from '@vidstack/react/player/layouts/default';\nimport { TbPlayerTrackPrev, TbPlayerTrackNext } from 'react-icons/tb';\nimport { FaCheck } from 'react-icons/fa6';\nimport { RiCheckboxBlankFill } from 'react-icons/ri';\n\nconst Button = styled.button<{ $autoskip?: boolean }>`\n  padding: 0.25rem;\n  font-size: 0.8rem;\n  border: none;\n  margin-right: 0.25rem;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  background-color: var(--global-div);\n  color: var(--global-text);\n  svg {\n    margin-bottom: -0.1rem;\n    color: grey;\n  }\n  @media (max-width: 500px) {\n    font-size: 0.7rem;\n  }\n\n  &.active {\n    background-color: var(--primary-accent);\n  }\n  ${({ $autoskip }) =>\n    $autoskip &&\n    `\n    color: #d69e00; \n    svg {\n      color: #d69e00; \n    }\n  `}\n`;\n\ntype PlayerProps = {\n  episodeId: string;\n  banner?: string;\n  malId?: string;\n  updateDownloadLink: (link: string) => void;\n  onEpisodeEnd: () => Promise<void>;\n  onPrevEpisode: () => void;\n  onNextEpisode: () => void;\n  animeTitle?: string;\n};\n\ntype StreamingSource = {\n  url: string;\n  quality: string;\n};\n\ntype SkipTime = {\n  interval: {\n    startTime: number;\n    endTime: number;\n  };\n  skipType: string;\n};\n\ntype FetchSkipTimesResponse = {\n  results: SkipTime[];\n};\n\nexport function Player({\n  episodeId,\n  banner,\n  malId,\n  updateDownloadLink,\n  onEpisodeEnd,\n  onPrevEpisode,\n  onNextEpisode,\n  animeTitle,\n}: PlayerProps) {\n  const player = useRef<MediaPlayerInstance>(null);\n  const [src, setSrc] = useState<string>('');\n  const [vttUrl, setVttUrl] = useState<string>('');\n  const [currentTime, setCurrentTime] = useState<number>(0);\n  const [skipTimes, setSkipTimes] = useState<SkipTime[]>([]);\n  const [totalDuration, setTotalDuration] = useState<number>(0);\n  const [vttGenerated, setVttGenerated] = useState<boolean>(false);\n  const episodeNumber = getEpisodeNumber(episodeId);\n  const animeVideoTitle = animeTitle;\n\n  const { settings, setSettings } = useSettings();\n  const { autoPlay, autoNext, autoSkip } = settings;\n\n  useEffect(() => {\n    setCurrentTime(parseFloat(localStorage.getItem('currentTime') || '0'));\n\n    fetchAndSetAnimeSource();\n    fetchAndProcessSkipTimes();\n    return () => {\n      if (vttUrl) URL.revokeObjectURL(vttUrl);\n    };\n  }, [episodeId, malId, updateDownloadLink]);\n\n  useEffect(() => {\n    if (autoPlay && player.current) {\n      player.current\n        .play()\n        .catch((e) =>\n          console.log('Playback failed to start automatically:', e),\n        );\n    }\n  }, [autoPlay, src]);\n\n  useEffect(() => {\n    if (player.current && currentTime) {\n      player.current.currentTime = currentTime;\n    }\n  }, [currentTime]);\n\n  function onProviderChange(\n    provider: MediaProviderAdapter | null,\n    _nativeEvent: MediaProviderChangeEvent,\n  ) {\n    if (isHLSProvider(provider)) {\n      provider.config = {};\n    }\n  }\n\n  function onLoadedMetadata() {\n    if (player.current) {\n      setTotalDuration(player.current.duration);\n    }\n  }\n\n  function onTimeUpdate() {\n    if (player.current) {\n      const currentTime = player.current.currentTime;\n      const duration = player.current.duration || 1;\n      const playbackPercentage = (currentTime / duration) * 100;\n      const playbackInfo = {\n        currentTime,\n        playbackPercentage,\n      };\n      const allPlaybackInfo = JSON.parse(\n        localStorage.getItem('all_episode_times') || '{}',\n      );\n      allPlaybackInfo[episodeId] = playbackInfo;\n      localStorage.setItem(\n        'all_episode_times',\n        JSON.stringify(allPlaybackInfo),\n      );\n\n      if (autoSkip && skipTimes.length) {\n        const skipInterval = skipTimes.find(\n          ({ interval }) =>\n            currentTime >= interval.startTime && currentTime < interval.endTime,\n        );\n        if (skipInterval) {\n          player.current.currentTime = skipInterval.interval.endTime;\n        }\n      }\n    }\n  }\n\n  function generateWebVTTFromSkipTimes(\n    skipTimes: FetchSkipTimesResponse,\n    totalDuration: number,\n  ): string {\n    let vttString = 'WEBVTT\\n\\n';\n    let previousEndTime = 0;\n\n    const sortedSkipTimes = skipTimes.results.sort(\n      (a, b) => a.interval.startTime - b.interval.startTime,\n    );\n\n    sortedSkipTimes.forEach((skipTime, index) => {\n      const { startTime, endTime } = skipTime.interval;\n      const skipType =\n        skipTime.skipType.toUpperCase() === 'OP' ? 'Opening' : 'Outro';\n\n      // Insert default title chapter before this skip time if there's a gap\n      if (previousEndTime < startTime) {\n        vttString += `${formatTime(previousEndTime)} --> ${formatTime(startTime)}\\n`;\n        vttString += `${animeVideoTitle} - Episode ${episodeNumber}\\n\\n`;\n      }\n\n      // Insert this skip time\n      vttString += `${formatTime(startTime)} --> ${formatTime(endTime)}\\n`;\n      vttString += `${skipType}\\n\\n`;\n      previousEndTime = endTime;\n\n      // Insert default title chapter after the last skip time\n      if (index === sortedSkipTimes.length - 1 && endTime < totalDuration) {\n        vttString += `${formatTime(endTime)} --> ${formatTime(totalDuration)}\\n`;\n        vttString += `${animeVideoTitle} - Episode ${episodeNumber}\\n\\n`;\n      }\n    });\n\n    return vttString;\n  }\n\n  async function fetchAndProcessSkipTimes() {\n    if (malId && episodeId) {\n      const episodeNumber = getEpisodeNumber(episodeId);\n      try {\n        const response: FetchSkipTimesResponse = await fetchSkipTimes({\n          malId: malId.toString(),\n          episodeNumber,\n        });\n        const filteredSkipTimes = response.results.filter(\n          ({ skipType }) => skipType === 'op' || skipType === 'ed',\n        );\n        if (!vttGenerated) {\n          const vttContent = generateWebVTTFromSkipTimes(\n            { results: filteredSkipTimes },\n            totalDuration,\n          );\n          const blob = new Blob([vttContent], { type: 'text/vtt' });\n          const vttBlobUrl = URL.createObjectURL(blob);\n          setVttUrl(vttBlobUrl);\n          setSkipTimes(filteredSkipTimes);\n          setVttGenerated(true);\n        }\n      } catch (error) {\n        console.error('Failed to fetch skip times', error);\n      }\n    }\n  }\n\n  async function fetchAndSetAnimeSource() {\n    try {\n      const response = await fetchAnimeStreamingLinks(episodeId);\n      const backupSource = response.sources.find(\n        (source: StreamingSource) => source.quality === 'default',\n      );\n      if (backupSource) {\n        setSrc(backupSource.url);\n        updateDownloadLink(response.download);\n      } else {\n        console.error('Backup source not found');\n      }\n    } catch (error) {\n      console.error('Failed to fetch anime streaming links', error);\n    }\n  }\n\n  function formatTime(seconds: number): string {\n    const minutes = Math.floor(seconds / 60);\n    const remainingSeconds = Math.floor(seconds % 60);\n    return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;\n  }\n\n  function getEpisodeNumber(id: string): string {\n    const parts = id.split('-');\n    return parts[parts.length - 1];\n  }\n\n  const toggleAutoPlay = () =>\n    setSettings({ ...settings, autoPlay: !autoPlay });\n  const toggleAutoNext = () =>\n    setSettings({ ...settings, autoNext: !autoNext });\n  const toggleAutoSkip = () =>\n    setSettings({ ...settings, autoSkip: !autoSkip });\n\n  const handlePlaybackEnded = async () => {\n    if (!autoNext) return;\n\n    try {\n      player.current?.pause();\n\n      await new Promise((resolve) => setTimeout(resolve, 200)); // Delay for transition\n      await onEpisodeEnd();\n    } catch (error) {\n      console.error('Error moving to the next episode:', error);\n    }\n  };\n\n  return (\n    <div style={{ animation: 'popIn 0.25s ease-in-out' }}>\n      <MediaPlayer\n        className='player'\n        title={`${animeVideoTitle} - Episode ${episodeNumber}`}\n        src={src}\n        autoplay={autoPlay}\n        crossorigin\n        playsinline\n        onLoadedMetadata={onLoadedMetadata}\n        onProviderChange={onProviderChange}\n        onTimeUpdate={onTimeUpdate}\n        ref={player}\n        aspectRatio='16/9'\n        load='eager'\n        posterLoad='eager'\n        streamType='on-demand'\n        storage='storage-key'\n        keyTarget='player'\n        onEnded={handlePlaybackEnded}\n      >\n        <MediaProvider>\n          <Poster className='vds-poster' src={banner} alt='' />\n          {vttUrl && (\n            <Track kind='chapters' src={vttUrl} default label='Skip Times' />\n          )}\n        </MediaProvider>\n        <DefaultAudioLayout icons={defaultLayoutIcons} />\n        <DefaultVideoLayout icons={defaultLayoutIcons} />\n      </MediaPlayer>\n      <div\n        className='player-menu'\n        style={{\n          backgroundColor: 'var(--global-div-tr)',\n          borderRadius: 'var(--global-border-radius)',\n        }}\n      >\n        <Button onClick={toggleAutoPlay}>\n          {autoPlay ? <FaCheck /> : <RiCheckboxBlankFill />} Autoplay\n        </Button>\n        <Button $autoskip onClick={toggleAutoSkip}>\n          {autoSkip ? <FaCheck /> : <RiCheckboxBlankFill />} Auto Skip\n        </Button>\n        <Button onClick={onPrevEpisode}>\n          <TbPlayerTrackPrev /> Prev\n        </Button>\n        <Button onClick={onNextEpisode}>\n          <TbPlayerTrackNext /> Next\n        </Button>\n        <Button onClick={toggleAutoNext}>\n          {autoNext ? <FaCheck /> : <RiCheckboxBlankFill />} Auto Next\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/Watch/Video/PlayerStyles.css",
    "content": "@import '@vidstack/react/player/styles/default/theme.css';\n@import '@vidstack/react/player/styles/default/layouts/audio.css';\n@import '@vidstack/react/player/styles/default/layouts/video.css';\n\n.player {\n  --brand-color: #f5f5f5;\n  --focus-color: #4e9cf6;\n\n  --audio-brand: var(--brand-color);\n  --audio-focus-ring-color: var(--focus-color);\n  --audio-border-radius: var(--global-border-radius);\n\n  --video-brand: var(--brand-color);\n  --video-focus-ring-color: var(--focus-color);\n  --video-border-radius: var(--global-border-radius);\n  --video-border: none;\n\n  /* 👉 https://vidstack.io/docs/player/components/layouts/default#css-variables for more. */\n}\n\n.player[data-view-type='audio'] .vds-poster {\n  display: none;\n  border-radius: 5rem;\n}\n\n.player[data-view-type='video'] {\n  aspect-ratio: 16 /9;\n}\n\n.src-buttons {\n  display: flex;\n  align-items: center;\n  justify-content: space-evenly;\n  margin-top: 40px;\n  margin-inline: auto;\n  max-width: 300px;\n}\n\n.vds-poster {\n  object-fit: cover;\n}\n"
  },
  {
    "path": "src/components/Watch/WatchAnimeData.tsx",
    "content": "import React, { useState, useEffect } from 'react';\nimport styled from 'styled-components';\nimport { Seasons, Anime } from '../../index';\nimport { SiMyanimelist, SiAnilist } from 'react-icons/si';\n\nconst AnimeDataContainer = styled.div`\n  margin-bottom: 1.5rem;\n\n  @media (max-width: 1000px) {\n    margin-bottom: 0rem;\n  }\n`;\n\nconst AnimeDataContainerTop = styled.div`\n  border-radius: var(--global-border-radius);\n  background-color: var(--global-div-tr);\n  margin: 1rem 0;\n  padding: 0.75rem;\n  color: var(--global-text);\n  align-items: center;\n  flex-direction: row;\n  align-items: flex-start;\n  display: flex;\n`;\nconst AnimeDataContainerMiddle = styled.div`\n  border-radius: var(--global-border-radius);\n  padding-top: 0.6rem;\n  color: var(--global-text);\n  align-items: center;\n  flex-direction: row;\n  align-items: flex-start;\n  display: flex;\n  @media (max-width: 500px) {\n    padding-top: 0.4rem;\n  }\n`;\n\nconst AnimeDataContainerBottom = styled.div`\n  margin-top: 0.6rem;\n  @media (max-width: 750px) {\n    margin-top: 0rem;\n  }\n`;\n\nconst ParentContainer = styled.div`\n  display: grid;\n  grid-template-columns: 1fr; // Default to single column for narrow screens\n  @media (min-width: 750px) {\n    grid-template-columns: 1.2fr 1fr; // Switch to two columns on wider screens\n  }\n  @media (min-width: 1500px) {\n    grid-template-columns: 1.25fr 1fr; // Switch to two columns on wider screens\n  }\n`;\n\nconst AnimeDataText = styled.div`\n  text-align: left;\n  font-size: 0.8rem;\n  .anime-title {\n    line-height: 1.6rem;\n    font-size: 1.5rem;\n    font-weight: bold;\n    color: var(--global-text);\n    margin-bottom: 0.5rem;\n    @media (max-width: 500px) {\n      font-size: 1.25rem;\n      margin-bottom: 0.2rem;\n    }\n  }\n  .anime-title-romaji {\n    font-style: italic;\n    margin-top: 0rem;\n    line-height: 0.6rem;\n    margin-bottom: 0.5rem;\n    @media (max-width: 500px) {\n      line-height: 1rem;\n      margin-bottom: 0.25rem;\n    }\n  }\n  p {\n    color: #828181;\n    margin-top: 0rem;\n    margin-bottom: 0.2rem;\n    line-height: 1.3rem;\n    @media (max-width: 500px) {\n      line-height: 1rem;\n    }\n  }\n  .Description {\n    line-height: 1rem;\n    max-width: 50rem;\n    font-size: 0.9rem;\n  }\n  strong {\n    color: var(--global-text);\n  }\n  .Seasons-Sections-Titles {\n    color: var(--global-text);\n    margin-top: 1rem;\n    font-size: 1.25rem;\n    font-weight: bold;\n  }\n`;\n\nconst AnimeInfoImage = styled.img`\n  border-radius: var(--global-border-radius);\n  max-height: 15rem;\n  width: 10.5rem;\n  margin-right: 1rem;\n  margin-bottom: 0.5rem;\n  @media (max-width: 500px) {\n    max-height: 12rem;\n    width: 8.5rem;\n  }\n`;\n\nconst Button = styled.button`\n  padding: 0.5rem 0.6rem;\n  background-color: var(--primary-accent);\n  color: white;\n  border: none;\n  border-radius: var(--global-border-radius);\n  cursor: pointer;\n  transition: background-color 0.3s ease;\n  outline: none;\n\n  &:hover,\n  &:active,\n  &:focus {\n    background-color: var(--primary-accent-bg);\n  }\n\n  @media (max-width: 1000px) {\n    display: block;\n    margin: 0 auto;\n    margin-bottom: 0.5rem;\n  }\n`;\n\nconst ShowTrailerButton = styled(Button)`\n  margin-right: 1rem;\n  padding: 0rem;\n  width: 10.5rem; //same as anime picture width.\n  background-color: var(--global-div);\n  transition:\n    background-color 0.3s ease,\n    transform 0.2s ease-in-out;\n  color: var(--global-text);\n  font-size: 0.85rem;\n  margin-bottom: 0.5rem;\n  &:hover,\n  &:active,\n  &:focus {\n    background-color: var(--primary-accent);\n    z-index: 2;\n  }\n  @media (max-width: 500px) {\n    font-size: 0.8rem;\n    width: 8.5rem;\n  }\n`;\nconst MalAniContainer = styled.div`\n  display: flex; /* or grid */\n  gap: 0.5rem;\n  margin-right: 1rem;\n`;\n\nconst MalAnilistSvg = styled.div`\n  height: 2.5rem;\n  width: 5rem;\n  border-radius: var(--global-border-radius);\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  background-color: var(--global-div);\n  color: var(--global-text);\n  transition: 0.1s ease-in-out;\n\n  &:hover,\n  &:active,\n  &:focus {\n    transform: scale(1.05);\n  }\n\n  &:active {\n    transform: scale(0.975);\n  }\n\n  @media (max-width: 500px) {\n    width: 4rem;\n    height: 2rem;\n  }\n`;\n\nconst ShowMoreButton = styled.button`\n  background-color: var(--global-div);\n  color: #828181;\n  display: flex;\n  border: none;\n  padding: 0.5rem;\n  border-radius: var(--global-border-radius);\n  margin: 0.5rem 0;\n  text-align: left;\n  &:hover,\n  &:active,\n  &:focus {\n    background-color: var(--global-div);\n  }\n  transition:\n    color 0.3s ease,\n    transform 0.2s ease-in-out;\n  @media (max-width: 500px) {\n    margin: 0rem;\n    margin-top: 1rem;\n  }\n`;\n\nconst IframeTrailer = styled.iframe`\n  aspect-ratio: 16/9;\n  margin-bottom: 2rem;\n  position: relative;\n  border: none;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n\n  @media (max-width: 1000px) {\n    width: 100%;\n    height: 100%;\n  }\n`;\n\nconst TrailerOverlay = styled.div`\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background-color: rgba(0, 0, 0, 0.5);\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  z-index: 1000;\n  backdrop-filter: blur(10px);\n  -webkit-backdrop-filter: blur(10px);\n  animation: fadeIn 0.3s ease-in-out;\n  animation: slideUp 0.3s ease-in-out;\n  aspect-ratio: 16 / 9; // Maintain a 16:9 aspect ratio\n`;\n\nconst TrailerOverlayContent = styled.div`\n  width: 60%; // Adjusted width for better visibility\n  aspect-ratio: 16 / 9; // Maintain a 16:9 aspect ratio\n  background: white;\n  border-radius: var(--global-border-radius);\n  overflow: hidden;\n  background-color: var(--global-div);\n  @media (max-width: 500px) {\n    width: 95%;\n  }\n`;\n\nexport const WatchAnimeData: React.FC<{ animeData: Anime }> = ({\n  animeData,\n}) => {\n  const [isDescriptionExpanded, setDescriptionExpanded] = useState(false);\n  const [showTrailer, setShowTrailer] = useState(false);\n\n  const getAnimeIdFromUrl = () => {\n    const pathParts = window.location.pathname.split('/');\n    return pathParts[2];\n  };\n\n  const toggleDescription = () => {\n    setDescriptionExpanded(!isDescriptionExpanded);\n  };\n\n  useEffect(() => {\n    setDescriptionExpanded(false);\n  }, [getAnimeIdFromUrl()]);\n\n  const removeHTMLTags = (description: string): string => {\n    return description.replace(/<[^>]+>/g, '').replace(/\\([^)]*\\)/g, '');\n  };\n\n  const toggleTrailer = () => {\n    setShowTrailer(!showTrailer);\n  };\n\n  useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (event.key === 'Escape' && showTrailer) {\n        setShowTrailer(false);\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n    };\n  }, [showTrailer]);\n\n  function capitalizeFirstLetter(str: string) {\n    if (!str) return str;\n    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();\n  }\n\n  const isScreenUnder500px = () => window.innerWidth < 500;\n\n  return (\n    <>\n      {animeData && (\n        <AnimeDataContainer>\n          <AnimeDataContainerTop>\n            <div\n              style={{\n                display: 'flex',\n                flexDirection: 'column',\n                alignItems: 'center',\n              }}\n            >\n              <AnimeInfoImage src={animeData.image} alt='Anime Title Image' />\n              {animeData.trailer && animeData.status !== 'Not yet aired' && (\n                <ShowTrailerButton onClick={toggleTrailer}>\n                  <p>\n                    <strong>TRAILER</strong>\n                  </p>\n                </ShowTrailerButton>\n              )}\n              {showTrailer && (\n                <TrailerOverlay onClick={toggleTrailer}>\n                  <TrailerOverlayContent onClick={(e) => e.stopPropagation()}>\n                    <IframeTrailer\n                      src={`https://www.youtube.com/embed/${animeData.trailer.id}`}\n                      allowFullScreen\n                    />\n                  </TrailerOverlayContent>\n                </TrailerOverlay>\n              )}\n              <MalAniContainer>\n                {animeData.id && (\n                  <a\n                    href={`https://anilist.co/${!animeData.type ? 'anime' : animeData.type.toLowerCase() === 'manga' || animeData.type.toLowerCase() === 'novel' ? 'manga' : 'anime'}/${animeData.id}`}\n                    target='_blank'\n                    rel='noopener noreferrer'\n                  >\n                    <MalAnilistSvg>\n                      <SiAnilist size={'1.5rem'} />\n                    </MalAnilistSvg>\n                  </a>\n                )}\n                {animeData.malId && (\n                  <a\n                    href={`https://myanimelist.net/${!animeData.type ? 'anime' : animeData.type.toLowerCase() === 'manga' || animeData.type.toLowerCase() === 'novel' ? 'manga' : 'anime'}/${animeData.malId}`}\n                    target='_blank'\n                    rel='noopener noreferrer'\n                  >\n                    <MalAnilistSvg>\n                      <SiMyanimelist size={'2.75rem'} />\n                    </MalAnilistSvg>\n                  </a>\n                )}\n              </MalAniContainer>\n            </div>\n            <AnimeDataText>\n              <>\n                <p className='anime-title'>\n                  {animeData.title.english\n                    ? animeData.title.english\n                    : animeData.title.romaji}\n                </p>\n                <p\n                  className='anime-title-romaji'\n                  style={{ color: animeData.color }}\n                >\n                  {animeData.title.romaji\n                    ? animeData.title.romaji\n                    : animeData.title.native}\n                </p>\n              </>\n              {!isScreenUnder500px() && animeData.description && (\n                <AnimeDataText>\n                  <p className='Description'>\n                    <ShowMoreButton onClick={toggleDescription}>\n                      {isDescriptionExpanded\n                        ? removeHTMLTags(animeData.description)\n                        : `${removeHTMLTags(animeData.description).substring(0, 100)}...`}\n                      {isDescriptionExpanded ? '[Show Less]' : '[Show More]'}\n                    </ShowMoreButton>\n                  </p>\n                </AnimeDataText>\n              )}\n              <ParentContainer>\n                <AnimeDataContainerMiddle>\n                  <AnimeDataText>\n                    {animeData.type ? (\n                      <p>\n                        Type: <strong>{animeData.type}</strong>\n                      </p>\n                    ) : (\n                      <p>\n                        Type: <strong>Unknown</strong>\n                      </p>\n                    )}\n                    {animeData.releaseDate ? (\n                      <p>\n                        Year: <strong>{animeData.releaseDate}</strong>\n                      </p>\n                    ) : (\n                      <p>\n                        Year: <strong>Unknown</strong>\n                      </p>\n                    )}\n                    {animeData.status && (\n                      <p>\n                        Status:{' '}\n                        <strong>\n                          {animeData.status === 'Completed'\n                            ? 'Finished'\n                            : animeData.status === 'Ongoing'\n                              ? 'Airing'\n                              : animeData.status}\n                        </strong>\n                      </p>\n                    )}\n                    {animeData.rating ? (\n                      <p>\n                        Rating: <strong>{animeData.rating}</strong>\n                      </p>\n                    ) : (\n                      <p>\n                        Rating: <strong>Unknown</strong>\n                      </p>\n                    )}\n                    {animeData.studios && animeData.studios.length > 0 ? (\n                      <p>\n                        Studios: <strong>{animeData.studios}</strong>\n                      </p>\n                    ) : (\n                      <p>\n                        Studios: <strong>Unknown</strong>\n                      </p>\n                    )}\n                  </AnimeDataText>\n                </AnimeDataContainerMiddle>\n                <AnimeDataContainerBottom>\n                  <AnimeDataText>\n                    {animeData.totalEpisodes !== null ? (\n                      <p>\n                        Episodes: <strong>{animeData.totalEpisodes}</strong>\n                      </p>\n                    ) : (\n                      <p>\n                        Episodes: <strong>Unknown</strong>\n                      </p>\n                    )}\n                    {animeData.duration ? (\n                      <p>\n                        Duration: <strong>{animeData.duration} min</strong>\n                      </p>\n                    ) : (\n                      <p>\n                        Duration: <strong>Unknown</strong>\n                      </p>\n                    )}\n                    {animeData.season ? (\n                      <p>\n                        Season:{' '}\n                        <strong>\n                          {capitalizeFirstLetter(animeData.season)}\n                        </strong>\n                      </p>\n                    ) : (\n                      <p>\n                        Season: <strong>Unknown</strong>\n                      </p>\n                    )}\n                    {animeData.countryOfOrigin && (\n                      <p>\n                        Country: <strong>{animeData.countryOfOrigin}</strong>\n                      </p>\n                    )}\n                    {/* timeUntilAiring */}\n                    {/* {animeData.nextAiringEpisode && (\n                      <p>\n                        AiringTime:{\" \"}\n                        <strong>\n                          {animeData.nextAiringEpisode.timeUntilAiring}\n                        </strong>\n                      </p>\n                    )} */}\n                    {/* {animeData.startDate && (\n                      <p>\n                        Date aired:\n                        <strong>\n                          {' '}\n                          {getDateString(animeData.startDate)}\n                          {animeData.endDate\n                            ? ` to ${\n                                animeData.endDate.month &&\n                                animeData.endDate.year\n                                  ? getDateString(animeData.endDate)\n                                  : '?'\n                              }`\n                            : animeData.status === 'Ongoing'\n                              ? ''\n                              : ' to ?'}\n                        </strong>\n                      </p>\n                    )} */}\n                    {animeData.genres && animeData.genres.length > 0 ? (\n                      <p>\n                        Genres: <strong>{animeData.genres.join(', ')}</strong>\n                      </p>\n                    ) : (\n                      <p>\n                        Genres: <strong>Unknown</strong>\n                      </p>\n                    )}\n                  </AnimeDataText>\n                </AnimeDataContainerBottom>\n              </ParentContainer>\n            </AnimeDataText>\n          </AnimeDataContainerTop>\n          {isScreenUnder500px() && animeData.description && (\n            <AnimeDataText>\n              <p className='Description'>\n                <strong>Description: </strong>\n                <ShowMoreButton onClick={toggleDescription}>\n                  {isDescriptionExpanded\n                    ? removeHTMLTags(animeData.description)\n                    : `${removeHTMLTags(animeData.description).substring(0, 150)}...`}\n                  {isDescriptionExpanded ? '[Show Less]' : '[Show More]'}\n                </ShowMoreButton>\n              </p>\n            </AnimeDataText>\n          )}\n        </AnimeDataContainer>\n      )}\n      {animeData.relations &&\n        animeData.relations.some(\n          (relation: any) =>\n            relation.relationType.toUpperCase() === 'PREQUEL' ||\n            relation.relationType.toUpperCase() === 'SEQUEL',\n        ) && (\n          <>\n            <AnimeDataText>\n              <p className='Seasons-Sections-Titles'>SEASONS</p>\n              <Seasons\n                relations={animeData.relations.filter(\n                  (relation: any) =>\n                    relation.relationType.toUpperCase() === 'PREQUEL' ||\n                    relation.relationType.toUpperCase() === 'SEQUEL',\n                )}\n              />\n            </AnimeDataText>\n          </>\n        )}\n    </>\n  );\n};\n"
  },
  {
    "path": "src/components/shared/StatusIndicator.tsx",
    "content": "import styled from 'styled-components';\nimport React, { useMemo } from 'react';\n\nconst IndicatorDot = styled.div`\n  width: 0.5rem;\n  height: 0.5rem;\n  border-radius: 50%;\n  margin: 0rem;\n  flex-shrink: 0;\n`;\n\nconst OngoingIndicator = styled(IndicatorDot)`\n  background-color: var(--ongoing-dot-color);\n`;\n\nconst CompletedIndicator = styled(IndicatorDot)`\n  background-color: var(--completed-indicator-color);\n`;\n\nconst CancelledIndicator = styled(IndicatorDot)`\n  background-color: var(--cancelled-indicator-color);\n`;\n\nconst NotYetAiredIndicator = styled(IndicatorDot)`\n  background-color: var(--not-yet-aired-indicator-color);\n`;\n\nconst DefaultIndicator = styled(IndicatorDot)`\n  background-color: var(--default-indicator-color);\n`;\n\nexport const StatusIndicator: React.FC<{ status: string }> = ({ status }) => {\n  const handleStatusCheck = useMemo(() => {\n    switch (status) {\n      case 'Ongoing':\n        return <OngoingIndicator />;\n      case 'Completed':\n        return <CompletedIndicator />;\n      case 'Cancelled':\n        return <CancelledIndicator />;\n      case 'Not yet aired':\n        return <NotYetAiredIndicator />;\n      default:\n        return <DefaultIndicator />;\n    }\n  }, [status]); // Ensure all dependencies are correctly listed\n\n  return <>{handleStatusCheck}</>;\n};\n"
  },
  {
    "path": "src/hooks/animeInterface.ts",
    "content": "export interface Title {\n  romaji: string;\n  english: string;\n  native: string;\n  userPreferred: string;\n}\n\nexport interface Trailer {\n  id: string;\n  site: string;\n  thumbnail: string;\n  thumbnailHash: string;\n}\n\nexport interface VoiceActor {\n  id: string;\n  language: string;\n  name: Title;\n  image: string;\n  imageHash: string;\n}\n\nexport interface Recommendation {\n  id: string;\n  malId: string;\n  title: Title;\n  status: string;\n  episodes: number;\n  image: string;\n  imageHash: string;\n  cover: string;\n  coverHash: string;\n  rating: number;\n  type: string;\n}\n\nexport interface Character {\n  id: string;\n  role: string;\n  name: Title;\n  image: string;\n  imageHash: string;\n  voiceActors: VoiceActor[];\n}\n\nexport interface Relation {\n  id: string;\n  malId: string;\n  relationType: string;\n  title: Title;\n  status: string;\n  episodes: number;\n  image: string;\n  imageHash: string;\n  cover: string;\n  coverHash: string;\n  rating: number;\n  type: string;\n}\n\nexport interface Mapping {\n  id: string;\n  providerId: string;\n  similarity: number;\n  providerType: string;\n}\n\nexport interface Artwork {\n  img: string;\n  type: string;\n  providerId: string;\n}\n\nexport interface Episode {\n  id: string;\n  title: string;\n  description: string | null;\n  number: number;\n  image: string;\n  imageHash: string;\n  airDate: string | null;\n}\n\nexport interface Anime {\n  id: string;\n  title: Title;\n  malId: string;\n  trailer: Trailer;\n  synonyms: string[];\n  isLicensed: boolean;\n  isAdult: boolean;\n  countryOfOrigin: string;\n  image: string;\n  imageHash: string;\n  cover: string;\n  coverHash: string;\n  description: string;\n  status: string;\n  releaseDate: number;\n  totalEpisodes: number;\n  currentEpisode: number;\n  rating: number;\n  duration: number;\n  genres: string[];\n  studios: string[];\n  subOrDub: string;\n  season: string;\n  popularity: number;\n  type: string;\n  startDate: {\n    year: number;\n    month: number;\n    day: number;\n  };\n  endDate: {\n    year: number;\n    month: number;\n    day: number;\n  };\n  recommendations: Recommendation[];\n  characters: Character[];\n  relations: Relation[];\n  mappings: Mapping[];\n  artwork: Artwork[];\n  episodes: Episode[];\n  color: string;\n}\n\nexport interface Paging {\n  currentPage: number;\n  hasNextPage: boolean;\n  totalPages: number;\n  totalResults: number;\n  results: Anime[];\n}\n"
  },
  {
    "path": "src/hooks/useApi.ts",
    "content": "import axios from 'axios';\nimport { year, getCurrentSeason, getNextSeason } from '../index';\n\n// Utility function to ensure URL ends with a slash\nfunction ensureUrlEndsWithSlash(url: string): string {\n  return url.endsWith('/') ? url : `${url}/`;\n}\n\n// Adjusting environment variables to ensure they end with a slash\nconst BASE_URL = ensureUrlEndsWithSlash(\n  import.meta.env.VITE_BACKEND_URL as string,\n);\nconst SKIP_TIMES = ensureUrlEndsWithSlash(\n  import.meta.env.VITE_SKIP_TIMES as string,\n);\nlet PROXY_URL = import.meta.env.VITE_PROXY_URL; // Default to an empty string if no proxy URL is provided\n// Check if the proxy URL is provided and ensure it ends with a slash\nif (PROXY_URL) {\n  PROXY_URL = ensureUrlEndsWithSlash(import.meta.env.VITE_PROXY_URL as string);\n}\n\nconst API_KEY = import.meta.env.VITE_API_KEY as string;\n\n// Axios instance\nconst axiosInstance = axios.create({\n  baseURL: PROXY_URL || undefined,\n  timeout: 10000,\n  headers: {\n    'X-API-Key': API_KEY, // Assuming your API expects the key in this header\n  },\n});\n\n// Error handling function\n// Function to handle errors and throw appropriately\nfunction handleError(error: any, context: string) {\n  let errorMessage = 'An error occurred';\n\n  // Handling CORS errors (Note: This is a simplification. Real CORS errors are hard to catch in JS)\n  if (error.message && error.message.includes('Access-Control-Allow-Origin')) {\n    errorMessage = 'A CORS error occurred';\n  }\n\n  switch (context) {\n    case 'data':\n      errorMessage = 'Error fetching data';\n      break;\n    case 'anime episodes':\n      errorMessage = 'Error fetching anime episodes';\n      break;\n    // Extend with other cases as needed\n  }\n\n  if (error.response) {\n    // Extend with more nuanced handling based on HTTP status codes\n    const status = error.response.status;\n    if (status >= 500) {\n      errorMessage += ': Server error';\n    } else if (status >= 400) {\n      errorMessage += ': Client error';\n    }\n    // Include server-provided error message if available\n    errorMessage += `: ${error.response.data.message || 'Unknown error'}`;\n  } else if (error.message) {\n    errorMessage += `: ${error.message}`;\n  }\n\n  console.error(`${errorMessage}`, error);\n  throw new Error(errorMessage);\n}\n\n// Cache key generator\n// Function to generate cache key from arguments\nfunction generateCacheKey(...args: string[]) {\n  return args.join('-');\n}\n\ninterface CacheItem {\n  value: any; // Replace 'any' with a more specific type if possible\n  timestamp: number;\n}\n\n// Session storage cache creation\n// Function to create a cache in session storage\nfunction createOptimizedSessionStorageCache(\n  maxSize: number,\n  maxAge: number,\n  cacheKey: string,\n) {\n  const cache = new Map<string, CacheItem>(\n    JSON.parse(sessionStorage.getItem(cacheKey) || '[]'),\n  );\n  const keys = new Set<string>(cache.keys());\n\n  function isItemExpired(item: CacheItem) {\n    return Date.now() - item.timestamp > maxAge;\n  }\n\n  function updateSessionStorage() {\n    sessionStorage.setItem(\n      cacheKey,\n      JSON.stringify(Array.from(cache.entries())),\n    );\n  }\n\n  return {\n    get(key: string) {\n      if (cache.has(key)) {\n        const item = cache.get(key);\n        if (!isItemExpired(item!)) {\n          keys.delete(key);\n          keys.add(key);\n          return item!.value;\n        }\n        cache.delete(key);\n        keys.delete(key);\n      }\n      return undefined;\n    },\n    set(key: string, value: any) {\n      if (cache.size >= maxSize) {\n        const oldestKey = keys.values().next().value;\n        cache.delete(oldestKey);\n        keys.delete(oldestKey);\n      }\n      keys.add(key);\n      cache.set(key, { value, timestamp: Date.now() });\n      updateSessionStorage();\n    },\n  };\n}\n\n// Constants for cache configuration\n// Cache size and max age constants\nconst CACHE_SIZE = 20;\nconst CACHE_MAX_AGE = 24 * 60 * 60 * 1000; // 24 hours in milliseconds\n\n// Factory function for cache creation\n// Function to create cache with given cache key\nfunction createCache(cacheKey: string) {\n  return createOptimizedSessionStorageCache(\n    CACHE_SIZE,\n    CACHE_MAX_AGE,\n    cacheKey,\n  );\n}\n\ninterface FetchOptions {\n  type?: string;\n  season?: string;\n  format?: string;\n  sort?: string[];\n  genres?: string[];\n  id?: string;\n  year?: string;\n  status?: string;\n}\n\n// Individual caches for different types of data\n// Creating caches for anime data, anime info, and video sources\nconst advancedSearchCache = createCache('Advanced Search');\nconst animeDataCache = createCache('Data');\nconst animeInfoCache = createCache('Info');\nconst animeEpisodesCache = createCache('Episodes');\nconst fetchAnimeEmbeddedEpisodesCache = createCache('Video Embedded Sources');\nconst videoSourcesCache = createCache('Video Sources');\n\n// Fetch data from proxy with caching\n// Function to fetch data from proxy with caching\nasync function fetchFromProxy(url: string, cache: any, cacheKey: string) {\n  try {\n    // Attempt to retrieve the cached response using the cacheKey\n    const cachedResponse = cache.get(cacheKey);\n    if (cachedResponse) {\n      return cachedResponse; // Return the cached response if available\n    }\n\n    // Adjust request parameters based on PROXY_URL's availability\n    const requestConfig = PROXY_URL\n      ? { params: { url } } // If PROXY_URL is defined, send the original URL as a parameter\n      : {}; // If PROXY_URL is not defined, make a direct request\n\n    // Proceed with the network request\n    const response = await axiosInstance.get(PROXY_URL ? '' : url, requestConfig);\n\n    // After obtaining the response, verify it for errors or empty data\n    if (\n      response.status !== 200 ||\n      (response.data.statusCode && response.data.statusCode >= 400)\n    ) {\n      const errorMessage = response.data.message || 'Unknown server error';\n      throw new Error(\n        `Server error: ${response.data.statusCode || response.status\n        } ${errorMessage}`,\n      );\n    }\n\n    // Assuming response data is valid, store it in the cache\n    cache.set(cacheKey, response.data);\n\n    return response.data; // Return the newly fetched data\n  } catch (error) {\n    handleError(error, 'data');\n    throw error; // Rethrow the error for the caller to handle\n  }\n}\n\n// Function to fetch anime data\nexport async function fetchAdvancedSearch(\n  searchQuery: string = '',\n  page: number = 1,\n  perPage: number = 20,\n  options: FetchOptions = {},\n) {\n  const queryParams = new URLSearchParams({\n    ...(searchQuery && { query: searchQuery }),\n    page: page.toString(),\n    perPage: perPage.toString(),\n    type: options.type ?? 'ANIME',\n    ...(options.season && { season: options.season }),\n    ...(options.format && { format: options.format }),\n    ...(options.id && { id: options.id }),\n    ...(options.year && { year: options.year }),\n    ...(options.status && { status: options.status }),\n    ...(options.sort && { sort: JSON.stringify(options.sort) }),\n  });\n\n  if (options.genres && options.genres.length > 0) {\n    // Correctly encode genres as a JSON array\n    queryParams.set('genres', JSON.stringify(options.genres));\n  }\n  const url = `${BASE_URL}meta/anilist/advanced-search?${queryParams.toString()}`;\n  const cacheKey = generateCacheKey('advancedSearch', queryParams.toString());\n\n  return fetchFromProxy(url, advancedSearchCache, cacheKey);\n}\n\n// Fetch Anime DATA Function\nexport async function fetchAnimeData(\n  animeId: string,\n  provider: string = 'gogoanime',\n) {\n  const params = new URLSearchParams({ provider });\n  const url = `${BASE_URL}meta/anilist/data/${animeId}?${params.toString()}`;\n  const cacheKey = generateCacheKey('animeData', animeId, provider);\n\n  return fetchFromProxy(url, animeDataCache, cacheKey);\n}\n\n// Fetch Anime INFO Function\nexport async function fetchAnimeInfo(\n  animeId: string,\n  provider: string = 'gogoanime',\n) {\n  const params = new URLSearchParams({ provider });\n  const url = `${BASE_URL}meta/anilist/info/${animeId}?${params.toString()}`;\n  const cacheKey = generateCacheKey('animeInfo', animeId, provider);\n\n  return fetchFromProxy(url, animeInfoCache, cacheKey);\n}\n\n// Function to fetch list of anime based on type (TopRated, Trending, Popular)\nasync function fetchList(\n  type: string,\n  page: number = 1,\n  perPage: number = 16,\n  options: FetchOptions = {},\n) {\n  let cacheKey: string;\n  let url: string;\n  const params = new URLSearchParams({\n    page: page.toString(),\n    perPage: perPage.toString(),\n  });\n\n  if (\n    ['TopRated', 'Trending', 'Popular', 'TopAiring', 'Upcoming'].includes(type)\n  ) {\n    cacheKey = generateCacheKey(\n      `${type}Anime`,\n      page.toString(),\n      perPage.toString(),\n    );\n    url = `${BASE_URL}meta/anilist/${type.toLowerCase()}`;\n\n    if (type === 'TopRated') {\n      options = {\n        type: 'ANIME',\n        sort: ['[\"SCORE_DESC\"]'],\n      };\n      url = `${BASE_URL}meta/anilist/advanced-search?type=${options.type}&sort=${options.sort}&`;\n    } else if (type === 'Popular') {\n      options = {\n        type: 'ANIME',\n        sort: ['[\"POPULARITY_DESC\"]'],\n      };\n      url = `${BASE_URL}meta/anilist/advanced-search?type=${options.type}&sort=${options.sort}&`;\n    } else if (type === 'Upcoming') {\n      const season = getNextSeason(); // This will set the season based on the current month\n      options = {\n        type: 'ANIME',\n        season: season,\n        year: year.toString(),\n        status: 'NOT_YET_RELEASED',\n        sort: ['[\"POPULARITY_DESC\"]'],\n      };\n      url = `${BASE_URL}meta/anilist/advanced-search?type=${options.type}&status=${options.status}&sort=${options.sort}&season=${options.season}&year=${options.year}&`;\n    } else if (type === 'TopAiring') {\n      const season = getCurrentSeason(); // This will set the season based on the current month\n      options = {\n        type: 'ANIME',\n        season: season,\n        year: year.toString(),\n        status: 'RELEASING',\n        sort: ['[\"POPULARITY_DESC\"]'],\n      };\n      url = `${BASE_URL}meta/anilist/advanced-search?type=${options.type}&status=${options.status}&sort=${options.sort}&season=${options.season}&year=${options.year}&`;\n    }\n  } else {\n    cacheKey = generateCacheKey(\n      `${type}Anime`,\n      page.toString(),\n      perPage.toString(),\n    );\n    url = `${BASE_URL}meta/anilist/${type.toLowerCase()}`;\n    // params already defined above\n  }\n\n  const specificCache = createCache(`${type}`);\n  return fetchFromProxy(`${url}?${params.toString()}`, specificCache, cacheKey);\n}\n\n// Functions to fetch top, trending, and popular anime\nexport const fetchTopAnime = (page: number, perPage: number) =>\n  fetchList('TopRated', page, perPage);\nexport const fetchTrendingAnime = (page: number, perPage: number) =>\n  fetchList('Trending', page, perPage);\nexport const fetchPopularAnime = (page: number, perPage: number) =>\n  fetchList('Popular', page, perPage);\nexport const fetchTopAiringAnime = (page: number, perPage: number) =>\n  fetchList('TopAiring', page, perPage);\nexport const fetchUpcomingSeasons = (page: number, perPage: number) =>\n  fetchList('Upcoming', page, perPage);\n\n// Fetch Anime Episodes Function\nexport async function fetchAnimeEpisodes(\n  animeId: string,\n  provider: string = 'gogoanime',\n  dub: boolean = false,\n) {\n  const params = new URLSearchParams({ provider, dub: dub ? 'true' : 'false' });\n  const url = `${BASE_URL}meta/anilist/episodes/${animeId}?${params.toString()}`;\n  const cacheKey = generateCacheKey(\n    'animeEpisodes',\n    animeId,\n    provider,\n    dub ? 'dub' : 'sub',\n  );\n\n  return fetchFromProxy(url, animeEpisodesCache, cacheKey);\n}\n\n// Fetch Embedded Anime Episodes Servers\nexport async function fetchAnimeEmbeddedEpisodes(episodeId: string) {\n  const url = `${BASE_URL}meta/anilist/servers/${episodeId}`;\n  const cacheKey = generateCacheKey('animeEmbeddedServers', episodeId);\n\n  return fetchFromProxy(url, fetchAnimeEmbeddedEpisodesCache, cacheKey);\n}\n\n// Function to fetch anime streaming links\nexport async function fetchAnimeStreamingLinks(episodeId: string) {\n  const url = `${BASE_URL}meta/anilist/watch/${episodeId}`;\n  const cacheKey = generateCacheKey('animeStreamingLinks', episodeId);\n\n  return fetchFromProxy(url, videoSourcesCache, cacheKey);\n}\n\n// Function to fetch skip times for an anime episode\ninterface FetchSkipTimesParams {\n  malId: string;\n  episodeNumber: string;\n  episodeLength?: string;\n}\n\n// Function to fetch skip times for an anime episode\nexport async function fetchSkipTimes({\n  malId,\n  episodeNumber,\n  episodeLength = '0',\n}: FetchSkipTimesParams) {\n  // Constructing the URL with query parameters\n  const types = ['ed', 'mixed-ed', 'mixed-op', 'op', 'recap'];\n  const url = new URL(`${SKIP_TIMES}v2/skip-times/${malId}/${episodeNumber}`);\n  url.searchParams.append('episodeLength', episodeLength.toString());\n  types.forEach((type) => url.searchParams.append('types[]', type));\n\n  const cacheKey = generateCacheKey(\n    'skipTimes',\n    malId,\n    episodeNumber,\n    episodeLength || '',\n  );\n\n  // Use the fetchFromProxy function to make the request and handle caching\n  return fetchFromProxy(url.toString(), createCache('SkipTimes'), cacheKey);\n}\n\n// Fetch Recent Anime Episodes Function\nexport async function fetchRecentEpisodes(\n  page: number = 1,\n  perPage: number = 18,\n  provider: string = 'gogoanime',\n) {\n  // Construct the URL with query parameters for fetching recent episodes\n  const params = new URLSearchParams({\n    page: page.toString(),\n    perPage: perPage.toString(),\n    provider: provider, // Default to 'gogoanime' if no provider is specified\n  });\n\n  // Using the BASE_URL defined at the top of your file\n  const url = `${BASE_URL}meta/anilist/recent-episodes?${params.toString()}`;\n  const cacheKey = generateCacheKey(\n    'recentEpisodes',\n    page.toString(),\n    perPage.toString(),\n    provider,\n  );\n\n  // Utilize the existing fetchFromProxy function to handle the request and caching logic\n  return fetchFromProxy(url, createCache('RecentEpisodes'), cacheKey);\n}\n"
  },
  {
    "path": "src/hooks/useCountdown.ts",
    "content": "// /hooks/useCountdown.ts\nimport { useState, useEffect } from 'react';\n\nexport const useCountdown = (targetDate: number | null): string => {\n  const [timeLeft, setTimeLeft] = useState('');\n\n  useEffect(() => {\n    if (!targetDate) {\n      return; // Early exit if targetDate is null or undefined\n    }\n\n    const timer = setInterval(() => {\n      const now = Date.now();\n      const distance = targetDate - now;\n      if (distance < 0) {\n        clearInterval(timer);\n        setTimeLeft('Airing now or aired');\n        return;\n      }\n\n      const days = Math.floor(distance / (1000 * 60 * 60 * 24));\n      const hours = Math.floor(\n        (distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60),\n      );\n      const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));\n      const seconds = Math.floor((distance % (1000 * 60)) / 1000);\n\n      setTimeLeft(\n        `${days} days, ${hours} hours, ${minutes} minutes, ${seconds} seconds`,\n      );\n    }, 1000);\n\n    return () => clearInterval(timer);\n  }, [targetDate]);\n\n  return timeLeft;\n};\n"
  },
  {
    "path": "src/hooks/useFilters.ts",
    "content": "import { year as currentYear } from '../index';\n\nexport interface Option {\n  value: string;\n  label: string;\n}\n\nexport interface FilterProps {\n  label: string;\n  options?: Option[];\n  onChange?: (value: any) => void;\n  value?: any;\n  isMulti?: boolean;\n}\n\nexport const anyOption: Option = { value: '', label: 'Any' };\n\nexport const genreOptions: Option[] = [\n  { value: 'Action', label: 'Action' },\n  { value: 'Adventure', label: 'Adventure' },\n  { value: 'Comedy', label: 'Comedy' },\n  { value: 'Drama', label: 'Drama' },\n  { value: 'Fantasy', label: 'Fantasy' },\n  { value: 'Horror', label: 'Horror' },\n  { value: 'Mahou Shoujo', label: 'Mahou Shoujo' },\n  { value: 'Mecha', label: 'Mecha' },\n  { value: 'Music', label: 'Music' },\n  { value: 'Mystery', label: 'Mystery' },\n  { value: 'Psychological', label: 'Psychological' },\n  { value: 'Romance', label: 'Romance' },\n  { value: 'Sci-Fi', label: 'Sci-Fi' },\n  { value: 'Slice of Life', label: 'Slice of Life' },\n  { value: 'Sports', label: 'Sports' },\n  { value: 'Supernatural', label: 'Supernatural' },\n  { value: 'Thriller', label: 'Thriller' },\n];\n\nexport const yearOptions: Option[] = [\n  anyOption,\n  { value: String(currentYear + 1), label: String(currentYear + 1) },\n  ...Array.from({ length: currentYear - 1939 }, (_, i) => ({\n    value: String(currentYear - i),\n    label: String(currentYear - i),\n  })),\n];\n\nexport const seasonOptions: Option[] = [\n  anyOption,\n  { value: 'WINTER', label: 'Winter' },\n  { value: 'SPRING', label: 'Spring' },\n  { value: 'SUMMER', label: 'Summer' },\n  { value: 'FALL', label: 'Fall' },\n];\n\nexport const formatOptions: Option[] = [\n  anyOption,\n  { value: 'TV', label: 'TV' },\n  { value: 'TV_SHORT', label: 'TV Short' },\n  { value: 'OVA', label: 'OVA' },\n  { value: 'ONA', label: 'ONA' },\n  { value: 'MOVIE', label: 'Movie' },\n  { value: 'SPECIAL', label: 'Special' },\n  { value: 'MUSIC', label: 'Music' },\n];\n\nexport const statusOptions: Option[] = [\n  anyOption,\n  { value: 'RELEASING', label: 'Airing' },\n  { value: 'NOT_YET_RELEASED', label: 'Not Yet Aired' },\n  { value: 'FINISHED', label: 'Finished' },\n  { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nexport const sortOptions: Option[] = [\n  { value: 'POPULARITY_DESC', label: 'Popularity' },\n  { value: 'TRENDING_DESC', label: 'Trending' },\n  { value: 'SCORE_DESC', label: 'Rating' },\n  { value: 'FAVOURITES_DESC', label: 'Favorites' },\n  { value: 'EPISODES_DESC', label: 'Episodes' },\n  { value: 'ID_DESC', label: 'ID' },\n  { value: 'UPDATED_AT_DESC', label: 'Last Updated' },\n  { value: 'START_DATE_DESC', label: 'Start Date' },\n  { value: 'END_DATE_DESC', label: 'End Date' },\n  { value: 'TITLE_ROMAJI_DESC', label: 'Title (Romaji)' },\n  { value: 'TITLE_ENGLISH_DESC', label: 'Title (English)' },\n  { value: 'TITLE_NATIVE_DESC', label: 'Title (Native)' },\n];\n"
  },
  {
    "path": "src/hooks/useScroll.ts",
    "content": "import { useEffect, useRef } from 'react';\nimport { useLocation } from 'react-router-dom';\n\nexport function ScrollToTop() {\n  const location = useLocation();\n  const prevPathnameRef = useRef<string | null>(null);\n\n  useEffect(() => {\n    const restoreScrollPosition = () => {\n      const savedPosition = sessionStorage.getItem(location.pathname);\n      if (savedPosition) {\n        window.scrollTo(0, parseInt(savedPosition, 10));\n      }\n    };\n\n    const saveScrollPosition = () =>\n      sessionStorage.setItem(location.pathname, window.scrollY.toString());\n\n    window.addEventListener('beforeunload', saveScrollPosition);\n    window.addEventListener('popstate', restoreScrollPosition);\n\n    const ignoreRoutePattern = /^\\/watch\\/[^/]+\\/[^/]+\\/[^/]+$/;\n    if (\n      prevPathnameRef.current !== location.pathname &&\n      !ignoreRoutePattern.test(location.pathname)\n    ) {\n      if (location.state?.preserveScroll) {\n        restoreScrollPosition();\n      } else {\n        window.scrollTo(0, 0);\n      }\n    }\n\n    prevPathnameRef.current = location.pathname;\n\n    return () => {\n      window.removeEventListener('beforeunload', saveScrollPosition);\n      window.removeEventListener('popstate', restoreScrollPosition);\n    };\n  }, [location]);\n\n  return null;\n}\n\nexport const usePreserveScrollOnReload = () => {\n  useEffect(() => {\n    const savedScrollPosition = sessionStorage.getItem('scrollPosition');\n    if (savedScrollPosition) {\n      window.scrollTo(0, parseInt(savedScrollPosition, 10));\n    }\n\n    const handleBeforeUnload = () => {\n      sessionStorage.setItem('scrollPosition', window.scrollY.toString());\n    };\n\n    window.addEventListener('beforeunload', handleBeforeUnload);\n\n    return () => {\n      window.removeEventListener('beforeunload', handleBeforeUnload);\n    };\n  }, []);\n};\n"
  },
  {
    "path": "src/hooks/useTIme.ts",
    "content": "export const date = new Date();\n\nexport const time = new Date().getTime();\n\nexport const year = new Date().getFullYear();\n\nexport const month = new Date().getMonth();\n\nexport const getCurrentSeason = (): string => {\n  if (month >= 2 && month <= 4) {\n    return 'SPRING';\n  } else if (month >= 5 && month <= 7) {\n    return 'SUMMER';\n  } else if (month >= 8 && month <= 10) {\n    return 'FALL';\n  } else {\n    return 'WINTER';\n  }\n};\n\nexport const getNextSeason = (): string => {\n  const currentSeason = getCurrentSeason();\n  switch (currentSeason) {\n    case 'SPRING':\n      return 'SUMMER';\n    case 'SUMMER':\n      return 'FALL';\n    case 'FALL':\n      return 'WINTER';\n    case 'WINTER':\n      return 'SPRING';\n    default:\n      return 'UNKNOWN'; // Should never be reached\n  }\n};\n"
  },
  {
    "path": "src/index.ts",
    "content": "// * ==== Components ====\n// TODO Shared components\nexport { StatusIndicator } from './components/shared/StatusIndicator';\n\n// TODO Basic UI Components\nexport { Navbar } from './components/Navigation/Navbar';\nexport { Footer } from './components/Navigation/Footer';\nexport { DropDownSearch } from './components/Navigation/DropSearch';\nexport { SearchFilters } from './components/Navigation/SearchFilters';\nexport { ShortcutsPopup } from './components/ShortcutsPopup';\nexport { ThemeProvider, useTheme } from './components/ThemeContext';\n\n// TODO Cards\nexport * from './components/Cards/CardGrid';\nexport { CardItem } from './components/Cards/CardItem';\n\n// TODO Home Page Specific\nexport { EpisodeCard } from './components/Home/EpisodeCard';\nexport { HomeCarousel } from './components/Home/HomeCarousel';\nexport { HomeSideBar } from './components/Home/HomeSideBar';\n\n// TODO Skeletons for Loading States\nexport {\n  SkeletonCard,\n  SkeletonSlide,\n  SkeletonPlayer,\n} from './components/Skeletons/Skeletons';\n\n// TODO Watching Anime Functionality\nexport { EpisodeList } from './components/Watch/EpisodeList';\nexport { EmbedPlayer } from './components/Watch/Video/EmbedPlayer';\nexport { Player } from './components/Watch/Video/Player'; // Notice: This is not a default export\nexport { MediaSource } from './components/Watch/Video/MediaSource';\nexport { WatchAnimeData } from './components/Watch/WatchAnimeData';\nexport { AnimeDataList } from './components/Watch/AnimeDataList';\nexport { Seasons } from './components/Watch/Seasons';\n\n// TODO User Components\nexport { Settings } from './components/Profile/Settings';\nexport {\n  SettingsProvider,\n  useSettings,\n} from './components/Profile/SettingsProvider';\nexport { WatchingAnilist } from './components/Profile/WatchingAnilist';\n\n// * ==== Hooks ====\n// TODO Utilizing API and Other Functionalities\nexport * from './hooks/useApi';\nexport * from './hooks/animeInterface';\nexport * from './hooks/useScroll';\nexport * from './hooks/useTIme';\nexport * from './hooks/useFilters';\nexport * from './hooks/useCountdown';\n\n// * ==== Client ====\nexport { ApolloClientProvider } from './client/ApolloClient';\nexport * from './client/userInfoTypes';\nexport * from './client/authService';\nexport * from './client/useAuth';\n\n// * ==== Pages ====\n// TODO Main Pages of the Application\nexport { default as Home } from './pages/Home';\nexport { default as Search } from './pages/Search';\nexport { default as Watch } from './pages/Watch';\nexport { default as Profile } from './pages/Profile';\nexport { default as About } from './pages/About';\nexport { default as PolicyTerms } from './pages/PolicyTerms';\nexport { default as Page404 } from './pages/404';\nexport { default as Callback } from './pages/Callback';\n"
  },
  {
    "path": "src/main.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\n\nconst rootElement = document.getElementById('root');\nif (!rootElement) {\n  throw new Error('Root element not found');\n}\n\nconst root = ReactDOM.createRoot(rootElement);\nroot.render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>,\n);\n"
  },
  {
    "path": "src/pages/404.tsx",
    "content": "import React, { useEffect } from 'react';\nimport styled from 'styled-components';\nimport Image404URL from '/src/assets/404.webp';\n\n// Styled component for Centered Content\nconst CenteredContent = styled.div`\n  display: flex;\n  padding-top: 5rem;\n  margin-bottom: 5rem;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  font-size: 1.5rem;\n\n  img {\n    max-width: 100%;\n    box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); /* Add shadow */\n    border-radius: var(--global-border-radius);\n    animation: fadeIn 0.5s ease; /* Apply fade-in animation */\n  }\n\n  @media (max-width: 550px) {\n    img {\n      max-width: 80%;\n    }\n  }\n`;\n\nconst Idkwhattonamethis = styled.div``;\n\nconst NotFound: React.FC = () => {\n  useEffect(() => {\n    const previousTitle = document.title;\n    document.title = '404 | Page Not Found';\n    return () => {\n      document.title = previousTitle;\n    };\n  }, []);\n\n  return (\n    <CenteredContent>\n      <Idkwhattonamethis>\n        <p>\n          <strong>404</strong> | Page Not Found\n        </p>\n        <img src={Image404URL} alt='404 Error' />\n      </Idkwhattonamethis>\n    </CenteredContent>\n  );\n};\n\nexport default NotFound;\n"
  },
  {
    "path": "src/pages/About.tsx",
    "content": "import styled from 'styled-components';\nimport { useEffect } from 'react';\nimport { FaCheckCircle } from 'react-icons/fa';\nconst SplashContainer = styled.div`\n  margin-top: -2rem;\n`;\n\nconst Keyword = styled.span`\n  font-weight: bold;\n  color: var(--your-custom-color);\n  position: relative;\n  margin-right: 0.2rem;\n\n  ::before {\n    content: '\\u25A0';\n    font-size: 0.8rem;\n    position: absolute;\n    top: 0;\n    left: -0.5rem;\n    color: var(--your-custom-color);\n  }\n`;\n\nconst Paragraph = styled.p`\n  font-size: 1rem;\n  margin-bottom: 1rem;\n  line-height: 1.6;\n  color: var(--global-text);\n`;\n\nconst MainContent = styled.div`\n  max-width: 50rem;\n  margin: 0 auto;\n  padding: 1rem;\n  color: var(--global-text);\n  font-size: 1rem;\n  line-height: 1.6;\n`;\n\nconst sections = [\n  {\n    title: 'About',\n    title2: \"What's Miruro?\",\n    content: (\n      <Paragraph>\n        Miruro is an anime streaming site where you can watch anime online in HD\n        quality with English subtitles or dubbing. You can also download any\n        anime you want without registration.\n      </Paragraph>\n    ),\n  },\n  {\n    title2: 'Is Miruro safe?',\n    content: (\n      <Paragraph>\n        Yes. We started this site to improve UX and are committed to keeping our\n        users safe. We encourage all our users to notify us if anything looks\n        suspicious. Please understand that we do have to run advertisements to\n        maintain the site.\n      </Paragraph>\n    ),\n  },\n  {\n    title2: 'Why Miruro?',\n    content: (\n      <>\n        <Paragraph>\n          <strong>\n            <FaCheckCircle /> Content Library:\n          </strong>{' '}\n          We have a vast collection of both old and new anime, making us one of\n          the largest anime libraries on the web.\n        </Paragraph>\n        <Paragraph>\n          <strong>\n            <FaCheckCircle /> Streaming Experience:\n          </strong>{' '}\n          Enjoy <Keyword>fast and reliable</Keyword> streaming with our{' '}\n          <Keyword>top-of-the-line servers</Keyword>.\n        </Paragraph>\n        <Paragraph>\n          <strong>\n            <FaCheckCircle /> Quality/Resolution:\n          </strong>{' '}\n          Our videos are available in <Keyword>high resolution</Keyword>, and we\n          offer quality settings to suit your internet speed.\n        </Paragraph>\n        <Paragraph>\n          <strong>\n            <FaCheckCircle /> Frequent Updates:\n          </strong>{' '}\n          Our content is updated hourly to provide you with the{' '}\n          <Keyword>latest releases</Keyword>.\n        </Paragraph>\n        <Paragraph>\n          <strong>\n            <FaCheckCircle /> User-Friendly Interface:\n          </strong>{' '}\n          We focus on <Keyword>simplicity and ease of use</Keyword>.\n        </Paragraph>\n        <Paragraph>\n          <strong>\n            <FaCheckCircle /> Device Compatibility:\n          </strong>{' '}\n          Miruro works seamlessly on both{' '}\n          <Keyword>desktop and mobile devices</Keyword>.\n        </Paragraph>\n        <Paragraph>\n          <strong>\n            <FaCheckCircle /> Community:\n          </strong>{' '}\n          Join our active <Keyword>community of anime lovers</Keyword>.\n        </Paragraph>\n      </>\n    ),\n  },\n];\n\nfunction About() {\n  useEffect(() => {\n    const previousTitle = document.title;\n    document.title = 'About | Miruro'; // Set the title when the component mounts\n    return () => {\n      // Reset the title to the previous one when the component unmounts\n      document.title = previousTitle;\n    };\n  }, []);\n\n  return (\n    <SplashContainer>\n      <MainContent>\n        <br />\n        {sections.map((section, index) => (\n          <span key={index}>\n            {section.title && <h1 className='title-style'>{section.title}</h1>}\n            {section.title2 && (\n              <h3 className='title-style'>{section.title2}</h3>\n            )}\n            {section.content}\n          </span>\n        ))}\n      </MainContent>\n    </SplashContainer>\n  );\n}\n\nexport default About;\n"
  },
  {
    "path": "src/pages/Callback.tsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useLocation, useNavigate } from 'react-router-dom';\nimport axios from 'axios';\nimport styled from 'styled-components';\n\nconst Message = styled.div`\n  text-align: center;\n  margin-top: 5rem;\n  font-size: 1.25rem;\n  font-weight: bold;\n`;\n\nconst Callback = () => {\n  const location = useLocation();\n  const navigate = useNavigate();\n  const [errorMessage, setErrorMessage] = useState(''); // State to store the error message\n\n  useEffect(() => {\n    const queryParams = new URLSearchParams(location.search);\n    const error = queryParams.get('error');\n    const code = queryParams.get('code');\n    const PLATFORM = import.meta.env.VITE_DEPLOY_PLATFORM; // This will be set as 'VERCEL' or 'CLOUDFLARE'\n\n    // Check if there was an access denied error\n    if (error === 'access_denied') {\n      setErrorMessage(\n        'Authorization revoked. Please click \"Authorize\" to grant access.',\n      );\n      navigate('/callback', { replace: true });\n      return;\n    }\n\n    // Determine the endpoint based on the platform\n    const apiEndpoint =\n      PLATFORM === 'VERCEL' ? '/api/exchange-token' : '/exchange-token';\n\n    if (code) {\n      axios\n        .post(apiEndpoint, { code })\n        .then((response) => {\n          // Store the access token in localStorage\n          localStorage.setItem('accessToken', response.data.accessToken);\n          // After setting the token, navigate and force a refresh\n          navigate('/profile');\n          window.location.reload(); // Force a full page reload to refresh state\n        })\n        .catch((error) => {\n          const errMsg = error.response?.data?.error || 'Error logging in :(';\n          console.error('Error in token exchange:', errMsg);\n          setErrorMessage(errMsg); // Store the error message\n          navigate('/callback', { replace: true });\n        });\n    }\n  }, [location, navigate]);\n\n  return (\n    <Message>{errorMessage ? `${errorMessage}` : 'Logging in...'}</Message>\n  );\n};\n\nexport default Callback;\n"
  },
  {
    "path": "src/pages/Home.tsx",
    "content": "import { useState, useEffect } from 'react';\nimport styled from 'styled-components';\nimport {\n  HomeCarousel,\n  CardGrid,\n  StyledCardGrid,\n  SkeletonSlide,\n  SkeletonCard,\n  fetchTrendingAnime,\n  fetchPopularAnime,\n  fetchTopAnime,\n  fetchTopAiringAnime,\n  fetchUpcomingSeasons,\n  HomeSideBar,\n  EpisodeCard,\n  getNextSeason,\n  time,\n  Paging,\n  Anime,\n  Episode,\n} from '../index';\n\nconst SimpleLayout = styled.div`\n  gap: 1rem;\n  margin: 0 auto;\n  max-width: 125rem;\n  border-radius: var(--global-border-radius);\n  display: flex;\n  flex-direction: column;\n`;\n\nconst ContentSidebarLayout = styled.div`\n  display: flex;\n  flex-direction: column;\n  gap: 2rem;\n  width: 100%;\n\n  @media (min-width: 1000px) {\n    flex-direction: row;\n    justify-content: space-between;\n  }\n`;\n\nconst TabContainer = styled.div`\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: center;\n  gap: 0.5rem;\n  border-radius: var(--global-border-radius);\n  width: 100%;\n`;\n\nconst Tab = styled.div<{ $isActive: boolean }>`\n  background: ${({ $isActive }) =>\n    $isActive ? 'var(--primary-accent)' : 'transparent'};\n  border-radius: var(--global-border-radius);\n  border: none;\n  cursor: pointer;\n  font-weight: bold;\n  color: var(--global-text);\n  position: relative;\n  overflow: hidden;\n  margin: 0;\n  font-size: 0.8rem;\n  padding: 1rem;\n\n  transition: background-color 0.3s ease;\n\n  &:hover,\n  &:active,\n  &:focus {\n    background: var(--primary-accent);\n  }\n\n  @media (max-width: 500px) {\n    padding: 0.5rem;\n  }\n`;\n\nconst Section = styled.section`\n  padding: 0rem;\n  border-radius: var(--global-border-radius);\n`;\n\nconst ErrorMessage = styled.div`\n  padding: 1rem;\n  margin: 1rem 0;\n  background-color: #ffdddd;\n  border-left: 4px solid #f44336;\n  color: #f44336;\n  border-radius: var(--global-border-radius);\n\n  p {\n    margin: 0;\n    font-weight: bold;\n  }\n`;\n\nconst Home = () => {\n  const [itemsCount, setItemsCount] = useState(\n    window.innerWidth > 500 ? 24 : 15,\n  );\n\n  // Reduced active time to 5mins\n  const [activeTab, setActiveTab] = useState(() => {\n    const time = Date.now();\n    const savedData = localStorage.getItem('home tab');\n    if (savedData) {\n      const { tab, timestamp } = JSON.parse(savedData);\n      if (time - timestamp < 300000) {\n        return tab;\n      } else {\n        localStorage.removeItem('home tab');\n      }\n    }\n    return 'trending';\n  });\n\n  const [state, setState] = useState({\n    watchedEpisodes: [] as Episode[],\n    trendingAnime: [] as Anime[],\n    popularAnime: [] as Anime[],\n    topAnime: [] as Anime[],\n    topAiring: [] as Anime[],\n    Upcoming: [] as Anime[],\n    error: null as string | null,\n    loading: {\n      trending: true,\n      popular: true,\n      topRated: true,\n      topAiring: true,\n      Upcoming: true,\n    },\n  });\n\n  useEffect(() => {\n    const handleResize = () => {\n      setItemsCount(window.innerWidth > 500 ? 24 : 15);\n    };\n\n    window.addEventListener('resize', handleResize);\n    handleResize();\n\n    return () => {\n      window.removeEventListener('resize', handleResize);\n    };\n  }, []);\n\n  useEffect(() => {\n    const fetchWatchedEpisodes = () => {\n      const watchedEpisodesData = localStorage.getItem('watched-episodes');\n      if (watchedEpisodesData) {\n        const allEpisodes = JSON.parse(watchedEpisodesData);\n        const latestEpisodes: Episode[] = [];\n        Object.keys(allEpisodes).forEach((animeId) => {\n          const episodes = allEpisodes[animeId];\n          const latestEpisode = episodes[episodes.length - 1];\n          latestEpisodes.push(latestEpisode);\n        });\n        setState((prevState) => ({\n          ...prevState,\n          watchedEpisodes: latestEpisodes,\n        }));\n      }\n    };\n\n    fetchWatchedEpisodes();\n  }, []);\n\n  useEffect(() => {\n    const fetchCount = Math.ceil(itemsCount * 1.4);\n    const fetchData = async () => {\n      try {\n        setState((prevState) => ({ ...prevState, error: null }));\n        const [trending, popular, topRated, topAiring, Upcoming] =\n          await Promise.all([\n            fetchTrendingAnime(1, fetchCount),\n            fetchPopularAnime(1, fetchCount),\n            fetchTopAnime(1, fetchCount),\n            fetchTopAiringAnime(1, 6),\n            fetchUpcomingSeasons(1, 6),\n          ]);\n        setState((prevState) => ({\n          ...prevState,\n          trendingAnime: filterAndTrimAnime(trending),\n          popularAnime: filterAndTrimAnime(popular),\n          topAnime: filterAndTrimAnime(topRated),\n          topAiring: filterAndTrimAnime(topAiring),\n          Upcoming: filterAndTrimAnime(Upcoming),\n        }));\n      } catch (fetchError) {\n        setState((prevState) => ({\n          ...prevState,\n          error: 'An unexpected error occurred',\n        }));\n      } finally {\n        setState((prevState) => ({\n          ...prevState,\n          loading: {\n            trending: false,\n            popular: false,\n            topRated: false,\n            topAiring: false,\n            Upcoming: false,\n          },\n        }));\n      }\n    };\n\n    fetchData();\n  }, [itemsCount]);\n\n  useEffect(() => {\n    document.title = `Miruro | Watch Anime Online, Free Anime Streaming`;\n  }, [activeTab]);\n\n  useEffect(() => {\n    const tabData = JSON.stringify({ tab: activeTab, timestamp: time });\n    localStorage.setItem('home tab', tabData);\n  }, [activeTab]);\n\n  const filterAndTrimAnime = (animeList: Paging) =>\n    animeList.results\n      /*       .filter(\n              (anime: Anime) =>\n                anime.totalEpisodes !== null &&\n                anime.duration !== null &&\n                anime.releaseDate !== null,\n            ) */\n      .slice(0, itemsCount);\n\n  const renderCardGrid = (\n    animeData: Anime[],\n    isLoading: boolean,\n    hasError: boolean,\n  ) => (\n    <Section>\n      {isLoading || hasError ? (\n        <StyledCardGrid>\n          {Array.from({ length: itemsCount }, (_, index) => (\n            <SkeletonCard key={index} />\n          ))}\n        </StyledCardGrid>\n      ) : (\n        <CardGrid\n          animeData={animeData}\n          hasNextPage={false}\n          onLoadMore={() => {}}\n        />\n      )}\n    </Section>\n  );\n\n  const handleTabClick = (tabName: string) => {\n    setActiveTab(tabName);\n  };\n\n  const SEASON = getNextSeason();\n\n  return (\n    <SimpleLayout>\n      {state.error && (\n        <ErrorMessage title='Error Message'>\n          <p>ERROR: {state.error}</p>\n        </ErrorMessage>\n      )}\n      {state.loading.trending || state.error ? (\n        <SkeletonSlide />\n      ) : (\n        <HomeCarousel\n          data={state.trendingAnime}\n          loading={state.loading.trending}\n          error={state.error}\n        />\n      )}\n      <EpisodeCard />\n      <ContentSidebarLayout>\n        <div\n          style={{\n            display: 'flex',\n            flexDirection: 'column',\n            flexGrow: 1,\n            gap: '1rem',\n          }}\n        >\n          <TabContainer>\n            <Tab\n              title='Trending Tab'\n              $isActive={activeTab === 'trending'}\n              onClick={() => handleTabClick('trending')}\n            >\n              TRENDING\n            </Tab>\n            <Tab\n              title='Popular Tab'\n              $isActive={activeTab === 'popular'}\n              onClick={() => handleTabClick('popular')}\n            >\n              POPULAR\n            </Tab>\n            <Tab\n              title='Top Rated Tab'\n              $isActive={activeTab === 'topRated'}\n              onClick={() => handleTabClick('topRated')}\n            >\n              TOP RATED\n            </Tab>\n          </TabContainer>\n          <div>\n            {activeTab === 'trending' &&\n              renderCardGrid(\n                state.trendingAnime,\n                state.loading.trending,\n                !!state.error,\n              )}\n            {activeTab === 'popular' &&\n              renderCardGrid(\n                state.popularAnime,\n                state.loading.popular,\n                !!state.error,\n              )}\n            {activeTab === 'topRated' &&\n              renderCardGrid(\n                state.topAnime,\n                state.loading.topRated,\n                !!state.error,\n              )}\n          </div>\n        </div>\n        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>\n          <div\n            style={{\n              fontSize: '1.25rem',\n              fontWeight: 'bold',\n              padding: '0.75rem 0',\n            }}\n          >\n            TOP AIRING\n          </div>\n          <HomeSideBar animeData={state.topAiring} />\n          <div\n            style={{\n              fontSize: '1.25rem',\n              fontWeight: 'bold',\n              padding: '0.75rem 0',\n            }}\n          >\n            UPCOMING {SEASON}\n          </div>\n          <HomeSideBar animeData={state.Upcoming} />\n        </div>\n      </ContentSidebarLayout>\n    </SimpleLayout>\n  );\n};\n\nexport default Home;\n"
  },
  {
    "path": "src/pages/PolicyTerms.tsx",
    "content": "import styled from 'styled-components';\nimport { useEffect } from 'react';\nconst colors = {\n  textColor: 'var(--global-text)',\n  buttonBackground: 'var(--global-button-bg)',\n  buttonText: 'var(--global-button-text)',\n  buttonHoverBackground: 'var(--global-button-hover-bg)',\n  adBackground: 'var(--global-div)',\n  customColor: 'var(--your-custom-color)',\n  paddingSize: '1rem',\n};\n\nconst StyledLink = styled.a`\n  color: #744aff;\n  text-decoration: none;\n  font-weight: bold;\n  &:hover,\n  &:active,\n  &:focus {\n    text-decoration: underline;\n  }\n`;\n\nconst SplashContainer = styled.div`\n  margin-top: -2rem;\n`;\nconst Paragraph = styled.p`\n  font-size: 1rem;\n  margin-bottom: ${colors.paddingSize};\n  line-height: 1.6;\n  color: ${colors.textColor};\n`;\n\nconst MainContent = styled.div`\n  max-width: 50rem;\n  margin: 0 auto;\n  padding: ${colors.paddingSize};\n  color: ${colors.textColor};\n  font-size: 1rem;\n  line-height: 1.6;\n`;\n\nconst sections = [\n  {\n    title: 'Privacy Policy',\n    content: (\n      <Paragraph>\n        <strong>Data Collection</strong>: We collect minimal user data necessary\n        for the functioning of Miruro, such as account information and user\n        preferences.\n        <br></br>\n        <br></br>\n        <strong>Use of Data</strong>: The data collected is used to improve\n        service quality and user experience. We do not share personal data with\n        third parties except as required by law.\n        <br></br>\n        <br></br>\n        <strong>Cookies and Tracking</strong>: Miruro uses cookies and similar\n        tracking technologies to enhance the user experience like caching video\n        timestamps and tracking watched content.\n        <br></br>\n        <br></br>\n        <strong>Third-Party Services</strong>: Embedded videos from third-party\n        sites may have their own privacy policies, and we advise users to read\n        these policies on the respective sites.\n        <br></br>\n        <br></br>\n        <strong>Security</strong>: We are committed to ensuring your data is\n        secure but remind users that no method of transmission over the Internet\n        is 100% secure.\n        <br></br>\n        <br></br>\n        <strong>Changes to Privacy Policy</strong>: We may update our Privacy\n        Policy from time to time. We will notify users of any changes by posting\n        the new policy on this page.\n        <br></br>\n        <br></br>\n        <strong>Contact Us</strong>: If you have any questions about these\n        terms, please contact us at{' '}\n        <StyledLink href='mailto:miruro@proton.me'>\n          miruro@proton.me.\n        </StyledLink>\n      </Paragraph>\n    ),\n  },\n  {\n    title: 'Terms of Service',\n    content: (\n      <Paragraph>\n        <strong>Acceptance of Terms</strong>: By using Miruro, you agree to\n        these Terms of Service and acknowledge that they affect your legal\n        rights and obligations.\n        <br></br>\n        <br></br>\n        <strong>Content</strong>: Miruro does not host video content but embeds\n        videos from various third-party sources. We are not responsible for the\n        content, quality, or the policies of these external sites.\n        <br></br>\n        <br></br>\n        <strong>Use of Site</strong>: The service is provided \"as is\" and is\n        used at the user’s own risk. Users must not misuse the service in any\n        way that breaches laws or regulations.\n        <br></br>\n        <br></br>\n        <strong>User Content</strong>: Users may share content, such as comments\n        or reviews, responsibly. We reserve the right to remove any content that\n        violates our policies or is deemed inappropriate.\n        <br></br>\n        <br></br>\n        <strong>Intellectual Property</strong>: The intellectual property rights\n        of the embedded videos remain with their respective owners. Miruro\n        respects these rights and does not claim ownership of this content.\n        <br></br>\n        <br></br>\n        <strong>Changes to Terms of Service</strong>: We reserve the right to\n        modify these terms at any time. Continued use of the site after changes\n        constitutes acceptance of the new terms.\n        <br></br>\n        <br></br>\n        <strong>Termination</strong>: We may terminate or suspend access to our\n        service immediately, without prior notice, for any breach of these\n        Terms.\n      </Paragraph>\n    ),\n  },\n];\n\nfunction PolicyTerms() {\n  useEffect(() => {\n    const previousTitle = document.title;\n    document.title = 'Policy & Terms | Miruro'; // Set the title when the component mounts\n    return () => {\n      // Reset the title to the previous one when the component unmounts\n      document.title = previousTitle;\n    };\n  }, []);\n  return (\n    <SplashContainer>\n      <MainContent>\n        <br />\n        {sections.map((section, index) => (\n          <span key={index}>\n            {section.title && <h1 className='title-style'>{section.title}</h1>}\n            {section.content}\n          </span>\n        ))}\n      </MainContent>\n    </SplashContainer>\n  );\n}\n\nexport default PolicyTerms;\n"
  },
  {
    "path": "src/pages/Profile.tsx",
    "content": "import React, { useEffect } from 'react';\nimport styled from 'styled-components';\nimport { IoLogOutOutline } from 'react-icons/io5';\nimport { useAuth, EpisodeCard, WatchingAnilist } from '../index';\nimport { SiAnilist } from 'react-icons/si';\nimport { CgProfile } from 'react-icons/cg';\nimport { useNavigate } from 'react-router-dom';\nimport { FiSettings } from 'react-icons/fi';\n\nconst TopContainer = styled.div`\n  display: flex;\n  flex-direction: column;\n  align-items: stretch;\n  width: 100%;\n  gap: 1rem;\n\n  @media (min-width: 1000px) {\n    flex-direction: row;\n    justify-content: space-between;\n  }\n`;\n\nconst UserInfoContainer = styled.div`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  height: 100%;\n`;\n\nconst ProfileContainer = styled.div`\n  position: relative;\n  padding: 0.5rem;\n  background-color: var(--global-div-tr);\n  border-radius: var(--global-border-radius);\n  text-align: center;\n  font-size: 0.9rem;\n  flex: 1;\n  justify-content: center;\n  align-items: center;\n  p {\n    margin: 0.75rem;\n  }\n  img {\n    border-radius: var(--global-border-radius);\n    width: 100px;\n  }\n`;\n\nconst PreferencesContainer = styled.div`\n  max-width: 80rem;\n  margin: auto;\n  padding: 0.25rem;\n`;\n\nconst Loginbutton = styled.div`\n  border-radius: var(--global-border-radius);\n  display: flex;\n  cursor: pointer;\n  padding: 0.75rem;\n  justify-content: center;\n  align-items: center;\n  background-color: var(--global-div);\n  color: var(--global-text);\n  transition: 0.1s ease-in-out;\n  width: 10rem; // Fixed width\n  margin: 0 auto; // Center horizontally\n  &:hover,\n  &:active,\n  &:focus {\n    transform: scale(1.025);\n  }\n  &:active {\n    transform: scale(0.975);\n  }\n\n  .svg-wrapper {\n    margin-bottom: -0.2rem;\n    margin-left: 0.5rem;\n    font-size: 1.25rem;\n  }\n`;\n// Profile component\nexport const Profile: React.FC = () => {\n  const navigate = useNavigate();\n  const { isLoggedIn, userData, login, logout } = useAuth();\n\n  // Profile Page Document Title\n  useEffect(() => {\n    document.title =\n      isLoggedIn && userData ? `${userData.name} | Profile` : 'Profile';\n  }, [isLoggedIn, userData]);\n\n  const handleSettingsClick = () => {\n    navigate('/profile/settings');\n  };\n\n  return (\n    <PreferencesContainer>\n      <TopContainer>\n        <ProfileContainer>\n          {/* <Loginbutton\n            onClick={handleSettingsClick}\n            style={{\n              position: 'absolute',\n              top: '0.5rem',\n              right: '0.5rem',\n              maxWidth: '2.25rem',\n            }}\n          >\n            <FiSettings size={22} />\n          </Loginbutton> */}\n          {isLoggedIn && userData ? (\n            <>\n              <img\n                src={userData.avatar.large}\n                alt={`${userData.name}'s avatar`}\n              />\n              <p>\n                Welcome, <b>{userData.name}</b>\n              </p>\n              {userData.statistics && (\n                <>\n                  <p>\n                    Anime watched: <b>{userData.statistics.anime.count}</b>\n                  </p>\n                  <p>\n                    Total episodes watched:{' '}\n                    <b>{userData.statistics.anime.episodesWatched}</b>\n                  </p>\n                  <p>\n                    Total minutes watched:{' '}\n                    <b>{userData.statistics.anime.minutesWatched}</b>\n                  </p>\n                  <p>\n                    Average score:{' '}\n                    <b>{userData.statistics.anime.meanScore.toFixed(2)}</b>\n                  </p>\n                </>\n              )}\n              <Loginbutton onClick={logout}>\n                <b>Log out </b>\n                <span className='svg-wrapper'>\n                  <IoLogOutOutline />\n                </span>\n              </Loginbutton>\n            </>\n          ) : (\n            <UserInfoContainer>\n              <CgProfile size={'5rem'} style={{ marginBottom: '1rem' }} />\n              <p>Guest</p>\n              <p>Please log in to view your profile and AniList</p>\n              <a onClick={login}>\n                <Loginbutton>\n                  <b>Log in with </b>\n                  <span className='svg-wrapper'>\n                    <SiAnilist />\n                  </span>\n                </Loginbutton>\n              </a>\n            </UserInfoContainer>\n          )}\n        </ProfileContainer>\n      </TopContainer>\n      <EpisodeCard />\n      <WatchingAnilist />\n    </PreferencesContainer>\n  );\n};\n\nexport default Profile;\n"
  },
  {
    "path": "src/pages/Search.tsx",
    "content": "import { useState, useEffect, useRef, useCallback } from 'react';\nimport styled from 'styled-components';\nimport { useSearchParams } from 'react-router-dom';\nimport {\n  SearchFilters,\n  CardGrid,\n  StyledCardGrid,\n  fetchAdvancedSearch,\n  SkeletonCard,\n} from '../index';\nimport { Paging } from '../index';\n\nconst Container = styled.div`\n  display: flex;\n  flex-direction: column;\n  gap: 1.5rem;\n\n  @media (min-width: 1500px) {\n    margin-left: 8rem;\n    margin-right: 8rem;\n    margin-top: 2rem;\n  }\n`;\n\nconst Search = () => {\n  const [searchParams, setSearchParams] = useSearchParams();\n  const sortParam = searchParams.get('sort');\n  // Directly initialize state from URL parameters\n  const initialQuery = searchParams.get('query') || '';\n  // Adjusting initialization to ensure non-null values\n  let initialSortDirection: 'DESC' | 'ASC' = 'DESC'; // Default to 'DESC'\n  if (sortParam) {\n    initialSortDirection = sortParam.endsWith('_DESC') ? 'DESC' : 'ASC';\n  }\n  const initialSortValue = sortParam\n    ? sortParam.replace(/(_DESC|_ASC)$/, '')\n    : 'POPULARITY_DESC';\n\n  const initialSort = {\n    value: initialSortValue,\n    label:\n      initialSortValue.replace('_DESC', '').charAt(0) +\n      initialSortValue.replace('_DESC', '').slice(1).toLowerCase(),\n  };\n  const genresParam = searchParams.get('genres');\n  const initialGenres = genresParam\n    ? genresParam.split(',').map((value) => ({ value, label: value }))\n    : [];\n\n  const initialYear = {\n    value: searchParams.get('year') || '',\n    label: searchParams.get('year') || 'Any',\n  };\n\n  const initialSeason = {\n    value: searchParams.get('season') || '',\n    label: searchParams.get('season') || 'Any',\n  };\n\n  const initialFormat = {\n    value: searchParams.get('format') || '',\n    label: searchParams.get('format') || 'Any',\n  };\n\n  const initialStatus = {\n    value: searchParams.get('status') || '',\n    label: searchParams.get('status') || 'Any',\n  };\n\n  // State hooks\n  const [query, setQuery] = useState(initialQuery);\n  const [selectedGenres, setSelectedGenres] = useState(initialGenres);\n  const [selectedYear, setSelectedYear] = useState(initialYear);\n  const [selectedSeason, setSelectedSeason] = useState(initialSeason);\n  const [selectedFormat, setSelectedFormat] = useState(initialFormat);\n  const [selectedStatus, setSelectedStatus] = useState(initialStatus);\n  const [selectedSort, setSelectedSort] = useState(initialSort);\n  const [sortDirection, setSortDirection] = useState<'DESC' | 'ASC'>(\n    initialSortDirection,\n  );\n\n  //Other logic\n  const [animeData, setAnimeData] = useState<Paging[]>([]);\n  const [isLoading, setIsLoading] = useState<boolean>(true);\n  const [hasNextPage, setHasNextPage] = useState<boolean>(false);\n  const [page, setPage] = useState<number>(1);\n  const delayTimeout = useRef<number | null>(null);\n\n  useEffect(() => {\n    const previousTitle = document.title;\n    document.title = `${query} | Search Results`;\n    return () => {\n      document.title = previousTitle;\n    };\n  }, [query]);\n\n  const updateSearchParams = () => {\n    const params = new URLSearchParams();\n    params.set('query', query);\n    if (selectedGenres.length > 0) {\n      params.set('genres', selectedGenres.map((g) => g.value).join(','));\n    }\n    if (selectedYear.value) params.set('year', selectedYear.value);\n    if (selectedSeason.value) params.set('season', selectedSeason.value);\n    if (selectedFormat.value) params.set('format', selectedFormat.value);\n    if (selectedStatus.value) params.set('status', selectedStatus.value);\n    const sortBase = selectedSort.value.replace(/(_DESC|_ASC)$/, '');\n    const sortParam =\n      sortDirection === 'DESC' ? `${sortBase}_DESC` : `${sortBase}_ASC`;\n    params.set('sort', sortParam);\n\n    setSearchParams(params, { replace: true });\n  };\n\n  useEffect(() => {\n    setPage(1);\n\n    const scrollToTopWithDelay = () => {\n      setTimeout(() => {\n        window.scrollTo({ top: 0, behavior: 'smooth' });\n      }, 350);\n    };\n\n    scrollToTopWithDelay();\n  }, [\n    query,\n    selectedGenres,\n    selectedYear,\n    selectedSeason,\n    selectedFormat,\n    selectedStatus,\n    selectedSort,\n    sortDirection,\n  ]);\n\n  const initiateFetchAdvancedSearch = useCallback(async () => {\n    setIsLoading(true);\n    const sortBase = selectedSort.value.replace('_DESC', '');\n    const sortParam = sortDirection === 'DESC' ? `${sortBase}_DESC` : sortBase;\n    try {\n      const fetchedData = await fetchAdvancedSearch(query, page, 17, {\n        genres: selectedGenres.map((g) => g.value),\n        year: selectedYear.value,\n        season: selectedSeason.value,\n        format: selectedFormat.value,\n        status: selectedStatus.value,\n        sort: [sortParam], // Ensure this is correctly formatted\n      });\n      setAnimeData(\n        page === 1\n          ? fetchedData.results\n          : [...animeData, ...fetchedData.results],\n      );\n      setHasNextPage(fetchedData.hasNextPage);\n    } catch (err) {\n      console.error('Error fetching data:', err);\n    } finally {\n      setIsLoading(false);\n    }\n  }, [\n    query,\n    page,\n    selectedGenres,\n    selectedYear,\n    selectedSeason,\n    selectedFormat,\n    selectedStatus,\n    selectedSort,\n    sortDirection,\n  ]);\n\n  const handleLoadMore = () => {\n    setPage((prevPage) => prevPage + 1);\n  };\n\n  useEffect(() => {\n    const newQuery = searchParams.get('query') || '';\n    if (newQuery !== query) {\n      setQuery(newQuery);\n    }\n  }, [searchParams]);\n\n  useEffect(() => {\n    // Clear existing timeout to ensure no double fetches\n    if (delayTimeout.current !== null) clearTimeout(delayTimeout.current);\n\n    // Debounce to minimize fetches during rapid state changes\n    delayTimeout.current = window.setTimeout(() => {\n      initiateFetchAdvancedSearch();\n    }, 0);\n\n    // Cleanup timeout on unmount or before executing a new fetch\n    return () => {\n      if (delayTimeout.current !== null) clearTimeout(delayTimeout.current);\n    };\n  }, [initiateFetchAdvancedSearch]); // Include all dependencies here\n\n  return (\n    <Container>\n      <SearchFilters\n        query={query}\n        setQuery={setQuery}\n        selectedGenres={selectedGenres}\n        setSelectedGenres={setSelectedGenres}\n        selectedYear={selectedYear}\n        setSelectedYear={setSelectedYear}\n        selectedSeason={selectedSeason}\n        setSelectedSeason={setSelectedSeason}\n        selectedFormat={selectedFormat}\n        setSelectedFormat={setSelectedFormat}\n        selectedStatus={selectedStatus}\n        setSelectedStatus={setSelectedStatus}\n        selectedSort={selectedSort}\n        setSelectedSort={setSelectedSort}\n        sortDirection={sortDirection}\n        setSortDirection={setSortDirection}\n        updateSearchParams={updateSearchParams}\n      />\n\n      <div>\n        {(isLoading && page === 1) ||\n        (isLoading && page === 1 && animeData.length === 0) ? (\n          <StyledCardGrid>\n            {Array.from({ length: 17 }).map((_, index) => (\n              <SkeletonCard key={index} />\n            ))}\n          </StyledCardGrid>\n        ) : (\n          <CardGrid\n            animeData={animeData}\n            hasNextPage={hasNextPage}\n            onLoadMore={handleLoadMore}\n          />\n        )}\n        {!isLoading && animeData.length === 0 && (\n          <div\n            style={{\n              display: 'flex',\n              justifyContent: 'center',\n              alignItems: 'center',\n              height: '10vh',\n              fontWeight: 'bold',\n              fontSize: '1.5rem',\n            }}\n          >\n            No Results\n          </div>\n        )}\n      </div>\n    </Container>\n  );\n};\n\nexport default Search;\n"
  },
  {
    "path": "src/pages/Watch.tsx",
    "content": "import React, { useEffect, useState, useCallback, useRef } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport { FaBell } from 'react-icons/fa';\nimport styled from 'styled-components';\nimport Image404URL from '/src/assets/404.webp';\nimport {\n  EpisodeList,\n  Player,\n  EmbedPlayer,\n  WatchAnimeData as AnimeData,\n  AnimeDataList,\n  MediaSource,\n  fetchAnimeEmbeddedEpisodes,\n  fetchAnimeEpisodes,\n  fetchAnimeData,\n  fetchAnimeInfo,\n  SkeletonPlayer,\n  useCountdown,\n} from '../index';\nimport { Episode } from '../index';\n\nconst WatchContainer = styled.div``;\n\nconst WatchWrapper = styled.div`\n  font-size: 0.9rem;\n  gap: 1rem;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  background-color: var(--global-primary-bg);\n  color: var(--global-text);\n\n  @media (min-width: 1000px) {\n    flex-direction: row;\n    align-items: flex-start;\n  }\n`;\n\nconst DataWrapper = styled.div`\n  display: grid;\n  gap: 1rem;\n  grid-template-columns: 1fr 1fr; // TODO Aim for a 3:1 ratio\n  width: 100%; // TODO Make sure this container can expand enough\n  @media (max-width: 1000px) {\n    grid-template-columns: auto;\n  }\n`;\n\nconst SourceAndData = styled.div<{ $videoPlayerWidth: string }>`\n  width: ${({ $videoPlayerWidth }) => $videoPlayerWidth};\n`;\n\nconst RalationsTable = styled.div`\n  padding: 0;\n  margin-top: 1rem;\n  @media (max-width: 1000px) {\n    margin-top: 0rem;\n  }\n`;\nconst VideoPlayerContainer = styled.div`\n  position: relative;\n  width: 100%;\n  border-radius: var(--global-border-radius);\n\n  @media (min-width: 1000px) {\n    flex: 1 1 auto;\n  }\n`;\n\nconst EpisodeListContainer = styled.div`\n  width: 100%;\n  max-height: 100%;\n\n  @media (min-width: 1000px) {\n    flex: 1 1 500px;\n    max-height: 100%;\n  }\n\n  @media (max-width: 1000px) {\n    padding-left: 0rem;\n  }\n`;\n\nconst NoEpsFoundDiv = styled.div`\n  text-align: center;\n  margin-top: 7.5rem;\n  margin-bottom: 10rem;\n  @media (max-width: 1000px) {\n    margin-top: 2.5rem;\n    margin-bottom: 6rem;\n  }\n`;\n\nconst NoEpsImage = styled.div`\n  margin-bottom: 3rem;\n  max-width: 100%;\n\n  img {\n    border-radius: var(--global-border-radius);\n    max-width: 100%;\n    @media (max-width: 500px) {\n      max-width: 70%;\n    }\n  }\n`;\n\nconst StyledHomeButton = styled.button`\n  color: white;\n  border-radius: var(--global-border-radius);\n  border: none;\n  background-color: var(--primary-accent);\n  margin-top: 0.5rem;\n  font-weight: bold;\n  padding: 1rem;\n  position: absolute;\n  transform: translate(-50%, -50%);\n  transition: transform 0.2s ease-in-out;\n  &:hover,\n  &:active,\n  &:focus {\n    transform: translate(-50%, -50%) scale(1.05);\n  }\n  &:active {\n    transform: translate(-50%, -50%) scale(0.95);\n  }\n`;\n\nconst IframeTrailer = styled.iframe`\n  position: relative;\n  border-radius: var(--global-border-radius);\n  border: none;\n  top: 0;\n  left: 0;\n  width: 70%;\n  height: 100%;\n  text-items: center;\n  @media (max-width: 1000px) {\n    width: 100%;\n    height: 100%;\n  }\n`;\n\nconst LOCAL_STORAGE_KEYS = {\n  LAST_WATCHED_EPISODE: 'last-watched-',\n  WATCHED_EPISODES: 'watched-episodes-',\n  LAST_ANIME_VISITED: 'last-anime-visited',\n};\n\n// TODO Main Component\nconst Watch: React.FC = () => {\n  const videoPlayerContainerRef = useRef<HTMLDivElement>(null);\n  const [videoPlayerWidth, setVideoPlayerWidth] = useState('100%');\n  const getSourceTypeKey = (animeId: string | undefined) =>\n    `source-[${animeId}]`;\n  const getLanguageKey = (animeId: string | undefined) =>\n    `subOrDub-[${animeId}]`;\n  const updateVideoPlayerWidth = useCallback(() => {\n    if (videoPlayerContainerRef.current) {\n      const width = `${videoPlayerContainerRef.current.offsetWidth}px`;\n      setVideoPlayerWidth(width);\n    }\n  }, [setVideoPlayerWidth, videoPlayerContainerRef]);\n  const [maxEpisodeListHeight, setMaxEpisodeListHeight] =\n    useState<string>('100%');\n  const { animeId, animeTitle, episodeNumber } = useParams<{\n    animeId?: string;\n    animeTitle?: string;\n    episodeNumber?: string;\n  }>();\n  const STORAGE_KEYS = {\n    SOURCE_TYPE: `source-[${animeId}]`,\n    LANGUAGE: `subOrDub-[${animeId}]`,\n  };\n  const navigate = useNavigate();\n  const [selectedBackgroundImage, setSelectedBackgroundImage] =\n    useState<string>('');\n  const [episodes, setEpisodes] = useState<Episode[]>([]);\n  const [currentEpisode, setCurrentEpisode] = useState<Episode>({\n    id: '0',\n    number: 1,\n    title: '',\n    image: '',\n    description: '',\n    imageHash: '',\n    airDate: '',\n  });\n  const [animeInfo, setAnimeInfo] = useState<any>(null);\n  const [loading, setLoading] = useState(true);\n  const [isEpisodeChanging, setIsEpisodeChanging] = useState(false);\n  const [showNoEpisodesMessage, setShowNoEpisodesMessage] = useState(false);\n  const [lastKeypressTime, setLastKeypressTime] = useState(0);\n  const [sourceType, setSourceType] = useState(\n    () => localStorage.getItem(STORAGE_KEYS.SOURCE_TYPE) || 'default',\n  );\n  const [embeddedVideoUrl, setEmbeddedVideoUrl] = useState('');\n  const [language, setLanguage] = useState(\n    () => localStorage.getItem(STORAGE_KEYS.LANGUAGE) || 'sub',\n  );\n  const [downloadLink, setDownloadLink] = useState('');\n  const nextEpisodeAiringTime =\n    animeInfo && animeInfo.nextAiringEpisode\n      ? animeInfo.nextAiringEpisode.airingTime * 1000\n      : null;\n  const nextEpisodenumber = animeInfo?.nextAiringEpisode?.episode;\n  const countdown = useCountdown(nextEpisodeAiringTime);\n  const currentEpisodeIndex = episodes.findIndex(\n    (ep) => ep.id === currentEpisode.id,\n  );\n  const [languageChanged, setLanguageChanged] = useState(false);\n\n  //----------------------------------------------MORE VARIABLES----------------------------------------------\n  const GoToHomePageButton = () => {\n    const navigate = useNavigate();\n\n    const handleClick = () => {\n      navigate('/home');\n    };\n\n    return (\n      <StyledHomeButton onClick={handleClick}>Go back Home</StyledHomeButton>\n    );\n  };\n  // TODO FETCH VIDSTREAMING VIDEO\n  const fetchVidstreamingUrl = async (episodeId: string) => {\n    try {\n      const embeddedServers = await fetchAnimeEmbeddedEpisodes(episodeId);\n      if (embeddedServers && embeddedServers.length > 0) {\n        const vidstreamingServer = embeddedServers.find(\n          (server: any) => server.name === 'Vidstreaming',\n        );\n        const selectedServer = vidstreamingServer || embeddedServers[0];\n        setEmbeddedVideoUrl(selectedServer.url);\n      }\n    } catch (error) {\n      console.error(\n        'Error fetching Vidstreaming servers for episode ID:',\n        episodeId,\n        error,\n      );\n    }\n  };\n\n  // TODO FETCH GOGO VIDEO\n  const fetchEmbeddedUrl = async (episodeId: string) => {\n    try {\n      const embeddedServers = await fetchAnimeEmbeddedEpisodes(episodeId);\n      if (embeddedServers && embeddedServers.length > 0) {\n        const gogoServer = embeddedServers.find(\n          (server: any) => server.name === 'Gogo server',\n        );\n        const selectedServer = gogoServer || embeddedServers[0];\n        setEmbeddedVideoUrl(selectedServer.url);\n      }\n    } catch (error) {\n      console.error(\n        'Error fetching gogo servers for episode ID:',\n        episodeId,\n        error,\n      );\n    }\n  };\n\n  // TODO SAVE TO LOCAL STORAGE NAVIGATED/CLICKED EPISODES\n  const updateWatchedEpisodes = (episode: Episode) => {\n    const watchedEpisodesJson = localStorage.getItem(\n      LOCAL_STORAGE_KEYS.WATCHED_EPISODES + animeId,\n    );\n    const watchedEpisodes: Episode[] = watchedEpisodesJson\n      ? JSON.parse(watchedEpisodesJson)\n      : [];\n    if (!watchedEpisodes.some((ep) => ep.id === episode.id)) {\n      watchedEpisodes.push(episode);\n      localStorage.setItem(\n        LOCAL_STORAGE_KEYS.WATCHED_EPISODES + animeId,\n        JSON.stringify(watchedEpisodes),\n      );\n    }\n  };\n\n  // TODO UPDATES CURRENT EPISODE INFORMATION, UPDATES WATCHED EPISODES AND NAVIGATES TO NEW URL\n  const handleEpisodeSelect = useCallback(\n    async (selectedEpisode: Episode) => {\n      setIsEpisodeChanging(true);\n      const animeTitle = selectedEpisode.id.split('-episode')[0];\n      setCurrentEpisode({\n        id: selectedEpisode.id,\n        number: selectedEpisode.number,\n        image: selectedEpisode.image,\n        title: selectedEpisode.title,\n        description: selectedEpisode.description,\n        imageHash: selectedEpisode.imageHash,\n        airDate: selectedEpisode.airDate,\n      });\n\n      localStorage.setItem(\n        LOCAL_STORAGE_KEYS.LAST_WATCHED_EPISODE + animeId,\n        JSON.stringify({\n          id: selectedEpisode.id,\n          title: selectedEpisode.title,\n          number: selectedEpisode.number,\n        }),\n      );\n      updateWatchedEpisodes(selectedEpisode);\n\n      navigate(\n        `/watch/${animeId}/${encodeURI(animeTitle)}/${selectedEpisode.number}`,\n        {\n          replace: true,\n        },\n      );\n      await new Promise((resolve) => setTimeout(resolve, 100));\n      setIsEpisodeChanging(false);\n    },\n    [animeId, navigate],\n  );\n\n  // TODO UPDATE DOWNLOAD LINK WHEN EPISODE ID CHANGES\n  const updateDownloadLink = useCallback((link: string) => {\n    setDownloadLink(link);\n  }, []);\n\n  // TODO AUTOPLAY BUTTON TOGGLE PROPS\n  const handleEpisodeEnd = async () => {\n    const nextEpisodeIndex = currentEpisodeIndex + 1;\n    if (nextEpisodeIndex >= episodes.length) {\n      console.log('No more episodes.');\n      return;\n    }\n    handleEpisodeSelect(episodes[nextEpisodeIndex]);\n  };\n\n  // TODO NAVIGATE TO NEXT AND PREVIOUS EPISODES WITH SHIFT+N/P KEYBOARD COMBINATIONS (500MS DELAY)\n  const onPrevEpisode = () => {\n    const prevIndex = currentEpisodeIndex - 1;\n    if (prevIndex >= 0) {\n      handleEpisodeSelect(episodes[prevIndex]);\n    }\n  };\n  const onNextEpisode = () => {\n    const nextIndex = currentEpisodeIndex + 1;\n    if (nextIndex < episodes.length) {\n      handleEpisodeSelect(episodes[nextIndex]);\n    }\n  };\n\n  //----------------------------------------------USEFFECTS----------------------------------------------\n  // TODO SETS DEFAULT SOURCE TYPE AND LANGUGAE TO DEFAULT AND SUB\n  useEffect(() => {\n    const defaultSourceType = 'default';\n    const defaultLanguage = 'sub';\n    setSourceType(\n      localStorage.getItem(getSourceTypeKey(animeId || '')) ||\n        defaultSourceType,\n    );\n    setLanguage(\n      localStorage.getItem(getLanguageKey(animeId || '')) || defaultLanguage,\n    );\n  }, [animeId]);\n\n  // TODO SAVES LANGUAGE PREFERENCE TO LOCAL STORAGE\n  useEffect(() => {\n    localStorage.setItem(getLanguageKey(animeId), language);\n  }, [language, animeId]);\n\n  //FETCHES ANIME DATA AND ANIME INFO AS BACKUP\n  useEffect(() => {\n    let isMounted = true;\n    const fetchInfo = async () => {\n      if (!animeId) {\n        console.error('Anime ID is null.');\n        setLoading(false);\n        return;\n      }\n      setLoading(true);\n      try {\n        const info = await fetchAnimeData(animeId);\n        if (isMounted) {\n          setAnimeInfo(info);\n        }\n      } catch (error) {\n        console.error(\n          'Failed to fetch anime data, trying fetchAnimeInfo as a fallback:',\n          error,\n        );\n        try {\n          const fallbackInfo = await fetchAnimeInfo(animeId);\n          if (isMounted) {\n            setAnimeInfo(fallbackInfo);\n          }\n        } catch (fallbackError) {\n          console.error(\n            'Also failed to fetch anime info as a fallback:',\n            fallbackError,\n          );\n        } finally {\n          if (isMounted) setLoading(false);\n        }\n      }\n    };\n\n    fetchInfo();\n\n    return () => {\n      isMounted = false;\n    };\n  }, [animeId]);\n\n  // TODO FETCHES ANIME EPISODES BASED ON LANGUAGE, ANIME ID AND UPDATES COMPONENTS\n  useEffect(() => {\n    let isMounted = true;\n    const fetchData = async () => {\n      setLoading(true);\n      if (!animeId) return;\n      try {\n        const isDub = language === 'dub';\n        const animeData = await fetchAnimeEpisodes(animeId, undefined, isDub);\n        if (isMounted && animeData) {\n          const transformedEpisodes = animeData\n            .filter((ep: any) => ep.id.includes('-episode-')) // TODO Continue excluding entries without '-episode-'\n            .map((ep: any) => {\n              const episodePart = ep.id.split('-episode-')[1];\n              // TODO New regex to capture the episode number including cases like \"7-5\"\n              const episodeNumberMatch = episodePart.match(/^(\\d+(?:-\\d+)?)/);\n              return {\n                ...ep,\n                number: episodeNumberMatch ? episodeNumberMatch[0] : ep.number,\n                id: ep.id,\n                title: ep.title,\n                image: ep.image,\n              };\n            });\n          setEpisodes(transformedEpisodes);\n          const navigateToEpisode = (() => {\n            if (languageChanged) {\n              const currentEpisodeNumber =\n                episodeNumber || currentEpisode.number;\n              return (\n                transformedEpisodes.find(\n                  (ep: any) => ep.number === currentEpisodeNumber,\n                ) || transformedEpisodes[transformedEpisodes.length - 1]\n              );\n            } else if (animeTitle && episodeNumber) {\n              const episodeId = `${animeTitle}-episode-${episodeNumber}`;\n              return (\n                transformedEpisodes.find((ep: any) => ep.id === episodeId) ||\n                navigate(`/watch/${animeId}`, { replace: true })\n              );\n            } else {\n              const savedEpisodeData = localStorage.getItem(\n                LOCAL_STORAGE_KEYS.LAST_WATCHED_EPISODE + animeId,\n              );\n              const savedEpisode = savedEpisodeData\n                ? JSON.parse(savedEpisodeData)\n                : null;\n              return savedEpisode\n                ? transformedEpisodes.find(\n                    (ep: any) => ep.number === savedEpisode.number,\n                  ) || transformedEpisodes[0]\n                : transformedEpisodes[0];\n            }\n          })();\n\n          if (navigateToEpisode) {\n            setCurrentEpisode({\n              id: navigateToEpisode.id,\n              number: navigateToEpisode.number,\n              image: navigateToEpisode.image,\n              title: navigateToEpisode.title,\n              description: navigateToEpisode.description,\n              imageHash: navigateToEpisode.imageHash,\n              airDate: navigateToEpisode.airDate,\n            });\n\n            const newAnimeTitle = navigateToEpisode.id.split('-episode-')[0];\n            navigate(\n              `/watch/${animeId}/${newAnimeTitle}/${navigateToEpisode.number}`,\n              { replace: true },\n            );\n            setLanguageChanged(false); // TODO Reset the languageChanged flag after handling the navigation\n          }\n        }\n      } catch (error) {\n        console.error('Failed to fetch episodes:', error);\n      } finally {\n        if (isMounted) setLoading(false);\n      }\n    };\n\n    // TODO Last visited cache to order continue watching\n    const updateLastVisited = () => {\n      if (!animeInfo || !animeId) return; // TODO Ensure both animeInfo and animeId are available\n\n      const lastVisited = localStorage.getItem(\n        LOCAL_STORAGE_KEYS.LAST_ANIME_VISITED,\n      );\n      const lastVisitedData = lastVisited ? JSON.parse(lastVisited) : {};\n      lastVisitedData[animeId] = {\n        timestamp: Date.now(),\n        titleEnglish: animeInfo.title.english, // TODO Assuming animeInfo contains the title in English\n        titleRomaji: animeInfo.title.romaji, // TODO Assuming animeInfo contains the title in Romaji\n      };\n\n      localStorage.setItem(\n        LOCAL_STORAGE_KEYS.LAST_ANIME_VISITED,\n        JSON.stringify(lastVisitedData),\n      );\n    };\n\n    if (animeId) {\n      updateLastVisited();\n    }\n\n    fetchData();\n\n    return () => {\n      isMounted = false;\n    };\n  }, [\n    animeId,\n    animeTitle,\n    episodeNumber,\n    navigate,\n    language,\n    languageChanged,\n    currentEpisode.number,\n  ]);\n\n  // TODO FETCH EMBEDDED EPISODES IF VIDSTREAMING OR GOGO HAVE BEEN SELECTED\n  useEffect(() => {\n    if (sourceType === 'vidstreaming' && currentEpisode.id) {\n      fetchVidstreamingUrl(currentEpisode.id).catch(console.error);\n    } else if (sourceType === 'gogo' && currentEpisode.id) {\n      fetchEmbeddedUrl(currentEpisode.id).catch(console.error);\n    }\n  }, [sourceType, currentEpisode.id]);\n\n  // TODO UPDATE BACKGROUND IMAGE TO ANIME BANNER IF WIDTH IS UNDER 500PX / OR USE ANIME COVER IF NO BANNER FOUND\n  useEffect(() => {\n    const updateBackgroundImage = () => {\n      const episodeImage = currentEpisode.image;\n      const bannerImage = animeInfo?.cover || animeInfo?.artwork[3].img;\n      if (episodeImage && episodeImage !== animeInfo.image) {\n        const img = new Image();\n        img.onload = () => {\n          if (img.width > 500) {\n            setSelectedBackgroundImage(episodeImage);\n          } else {\n            setSelectedBackgroundImage(bannerImage);\n          }\n        };\n        img.onerror = () => {\n          setSelectedBackgroundImage(bannerImage);\n        };\n        img.src = episodeImage;\n      } else {\n        setSelectedBackgroundImage(bannerImage);\n      }\n    };\n    if (animeInfo && currentEpisode.id !== '0') {\n      updateBackgroundImage();\n    }\n  }, [animeInfo, currentEpisode]);\n\n  // TODO UPDATES VIDEOPLAYER WIDTH WHEN WINDOW GETS RESIZED\n  useEffect(() => {\n    updateVideoPlayerWidth();\n    const handleResize = () => {\n      updateVideoPlayerWidth();\n    };\n    window.addEventListener('resize', handleResize);\n    return () => window.removeEventListener('resize', handleResize);\n  }, [updateVideoPlayerWidth]);\n\n  // TODO UPDATES EPISODE LIST MAX HEIGHT BASED ON VIDEO PLAYER CURRENT HEIGHT\n  useEffect(() => {\n    const updateMaxHeight = () => {\n      if (videoPlayerContainerRef.current) {\n        const height = videoPlayerContainerRef.current.offsetHeight;\n        setMaxEpisodeListHeight(`${height}px`);\n      }\n    };\n    updateMaxHeight();\n    window.addEventListener('resize', updateMaxHeight);\n    return () => window.removeEventListener('resize', updateMaxHeight);\n  }, []);\n\n  // TODO SAVES SOURCE TYPE PREFERENCE TO LOCAL STORAGE\n  useEffect(() => {\n    localStorage.setItem(getSourceTypeKey(animeId), sourceType);\n  }, [sourceType, animeId]);\n\n  // TODO NAVIGATE TO NEXT AND PREVIOUS EPISODES WITH SHIFT+N/P KEYBOARD COMBINATIONS (500MS DELAY)\n  useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      const targetTagName = (event.target as HTMLElement).tagName.toLowerCase();\n      if (targetTagName === 'input' || targetTagName === 'textarea') {\n        return;\n      }\n      if (!event.shiftKey || !['N', 'P'].includes(event.key.toUpperCase()))\n        return;\n      const now = Date.now();\n      if (now - lastKeypressTime < 200) return;\n      setLastKeypressTime(now);\n      const currentIndex = episodes.findIndex(\n        (ep) => ep.id === currentEpisode.id,\n      );\n      if (\n        event.key.toUpperCase() === 'N' &&\n        currentIndex < episodes.length - 1\n      ) {\n        const nextEpisode = episodes[currentIndex + 1];\n        handleEpisodeSelect(nextEpisode);\n      } else if (event.key.toUpperCase() === 'P' && currentIndex > 0) {\n        const prevEpisode = episodes[currentIndex - 1];\n        handleEpisodeSelect(prevEpisode);\n      }\n    };\n    document.addEventListener('keydown', handleKeyDown);\n    return () => document.removeEventListener('keydown', handleKeyDown);\n  }, [episodes, currentEpisode, handleEpisodeSelect, lastKeypressTime]);\n\n  // TODO SET PAGE TITLE TO MIRURO + ANIME TITLE\n  useEffect(() => {\n    if (animeInfo && animeInfo.title) {\n      document.title =\n        'Watch ' +\n        (animeInfo.title.english ||\n          animeInfo.title.romaji ||\n          animeInfo.title.romaji ||\n          '') +\n        ' | Miruro';\n    }\n  }, [animeInfo]);\n\n  // TODO No idea\n  useEffect(() => {\n    let isMounted = true;\n    const fetchInfo = async () => {\n      if (!animeId) {\n        console.error('Anime ID is undefined.');\n        return;\n      }\n      try {\n        const info = await fetchAnimeData(animeId);\n        if (isMounted) {\n          setAnimeInfo(info);\n        }\n      } catch (error) {\n        console.error('Failed to fetch anime info:', error);\n      }\n    };\n    fetchInfo();\n    return () => {\n      isMounted = false;\n    };\n  }, [animeId]);\n\n  // TODO SHOW NO EPISODES DIV IF NO RESPONSE AFTER 10 SECONDS\n  useEffect(() => {\n    const timeoutId = setTimeout(() => {\n      if (!episodes || episodes.length === 0) {\n        setShowNoEpisodesMessage(true);\n      }\n    }, 10000);\n    return () => clearTimeout(timeoutId);\n  }, [loading, episodes]);\n\n  // TODO SHOW NO EPISODES DIV IF NOT LOADING AND NO EPISODES FOUND\n  useEffect(() => {\n    if (!loading && episodes.length === 0) {\n      setShowNoEpisodesMessage(true);\n    } else {\n      setShowNoEpisodesMessage(false);\n    }\n  }, [loading, episodes]);\n\n  return (\n    <WatchContainer>\n      {animeInfo &&\n      animeInfo.status === 'Not yet aired' &&\n      animeInfo.trailer ? (\n        <div style={{ textAlign: 'center' }}>\n          <strong>\n            <h2>Time Remaining:</h2>\n          </strong>\n          {animeInfo &&\n          animeInfo.nextAiringEpisode &&\n          countdown !== 'Airing now or aired' ? (\n            <p>\n              <FaBell /> {countdown}\n            </p>\n          ) : (\n            <p>Unknown</p>\n          )}\n          {animeInfo.trailer && (\n            <IframeTrailer\n              src={`https://www.youtube.com/embed/${animeInfo.trailer.id}`}\n              allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'\n              allowFullScreen\n            />\n          )}\n        </div>\n      ) : showNoEpisodesMessage ? (\n        <NoEpsFoundDiv>\n          <h2>No episodes found {':('}</h2>\n          <NoEpsImage>\n            <img src={Image404URL} alt='404 Error'></img>\n          </NoEpsImage>\n          <GoToHomePageButton />\n        </NoEpsFoundDiv>\n      ) : (\n        <WatchWrapper>\n          {!showNoEpisodesMessage && (\n            <>\n              <VideoPlayerContainer ref={videoPlayerContainerRef}>\n                {loading ? (\n                  <SkeletonPlayer />\n                ) : sourceType === 'default' ? (\n                  <Player\n                    episodeId={currentEpisode.id}\n                    malId={animeInfo?.malId}\n                    banner={selectedBackgroundImage}\n                    updateDownloadLink={updateDownloadLink}\n                    onEpisodeEnd={handleEpisodeEnd}\n                    onPrevEpisode={onPrevEpisode}\n                    onNextEpisode={onNextEpisode}\n                    animeTitle={\n                      animeInfo?.title?.english || animeInfo?.title?.romaji\n                    }\n                  />\n                ) : (\n                  <EmbedPlayer src={embeddedVideoUrl} />\n                )}\n              </VideoPlayerContainer>\n              <EpisodeListContainer style={{ maxHeight: maxEpisodeListHeight }}>\n                {loading ? (\n                  <SkeletonPlayer />\n                ) : (\n                  <EpisodeList\n                    animeId={animeId}\n                    episodes={episodes}\n                    selectedEpisodeId={currentEpisode.id}\n                    onEpisodeSelect={(episodeId: string) => {\n                      const episode = episodes.find((e) => e.id === episodeId);\n                      if (episode) {\n                        handleEpisodeSelect(episode);\n                      }\n                    }}\n                    maxListHeight={maxEpisodeListHeight}\n                  />\n                )}\n              </EpisodeListContainer>\n            </>\n          )}\n        </WatchWrapper>\n      )}\n      <DataWrapper>\n        <SourceAndData $videoPlayerWidth={videoPlayerWidth}>\n          {animeInfo && animeInfo.status !== 'Not yet aired' && (\n            <MediaSource\n              sourceType={sourceType}\n              setSourceType={setSourceType}\n              language={language}\n              setLanguage={setLanguage}\n              downloadLink={downloadLink}\n              episodeId={currentEpisode.number.toString()}\n              airingTime={\n                animeInfo && animeInfo.status === 'Ongoing'\n                  ? countdown\n                  : undefined\n              }\n              nextEpisodenumber={nextEpisodenumber}\n            />\n          )}\n          {animeInfo && <AnimeData animeData={animeInfo} />}\n        </SourceAndData>\n        <RalationsTable>\n          {animeInfo && <AnimeDataList animeData={animeInfo} />}\n        </RalationsTable>\n      </DataWrapper>\n    </WatchContainer>\n  );\n};\n\nexport default Watch;\n"
  },
  {
    "path": "src/styles/animations.css",
    "content": "@keyframes slideUp {\n  0% {\n    opacity: 0;\n    transform: translateY(10px);\n  }\n  100% {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n@keyframes slideDown {\n  0% {\n    opacity: 0;\n    transform: translateY(-20px);\n  }\n  100% {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n@keyframes slideRight {\n  0% {\n    opacity: 0;\n    transform: translateX(-10px);\n  }\n  100% {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n@keyframes dropDown {\n  0% {\n    max-height: 0;\n  }\n  100% {\n    max-height: 500px;\n  }\n}\n\n@keyframes slideDropDown {\n  0% {\n    opacity: 0;\n    transform: translateY(-20px);\n    max-height: 0;\n  }\n  100% {\n    opacity: 1;\n    transform: translateY(0);\n    max-height: 500px;\n  }\n}\n\n@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n\n@keyframes popIn {\n  0% {\n    opacity: 0;\n    transform: scale(0.98);\n  }\n  100% {\n    opacity: 1;\n    transform: scale(1);\n  }\n}\n"
  },
  {
    "path": "src/styles/globals.css",
    "content": "@import url('animations.css');\n@import url('themes.css');\n\n/* Basic Styles for the App */\nbody {\n  font-family:\n    ui-sans-serif,\n    system-ui,\n    sans-serif,\n    Apple Color Emoji,\n    Segoe UI Emoji,\n    Segoe UI Symbol,\n    Noto Color Emoji;\n  margin: 0 auto; /* Center the body content */\n  padding: 4.5rem 1rem 1.5rem; /* Top, Horizontal, Bottom padding */\n  max-width: 105rem; /* Max width to constrain content size */\n  background-color: var(\n    --global-primary-bg\n  ); /* Background color from variable */\n  color: var(--global-text); /* Text color from variable */\n  transition: 0.2s ease; /* Smooth transition for changes */\n}\n\n/* Responsive Styles */\n@media (max-width: 31.25rem) {\n  /* 500px */\n  body {\n    padding: 4rem 0.5rem 0.5rem; /* Adjust padding for small screens */\n  }\n}\n\n/* Text Selection Color */\n::selection {\n  background-color: var(\n    --primary-accent-bg\n  ); /* Background color when text is selected */\n  color: var(--primary-accent); /* Text color when selected */\n}\n\n/* Custom Scrollbar Styles */\n/* Main scrollbar styling */\n::-webkit-scrollbar {\n  width: 0.3125rem; /* 5px */\n}\n\n/* Track of the scrollbar, set to be transparent */\n::-webkit-scrollbar-track {\n  background: transparent;\n}\n\n/* Handle of the scrollbar */\n::-webkit-scrollbar-thumb {\n  background: #888; /* Gray color for the handle */\n  border-radius: 0.2rem; /* Rounded handle */\n}\n\n/* Handle color on hover */\n::-webkit-scrollbar-thumb:hover {\n  background: #555; /* Darker gray when hovering */\n}\n"
  },
  {
    "path": "src/styles/themes.css",
    "content": "/* Light Mode  */\n:root {\n  --global-primary-bg: #f5f5f5;\n  --global-primary-bg-tr: rgba(245, 245, 245, 0.8);\n  --global-secondary-bg: #e0e0e0;\n  --global-tertiary-bg: #eaeaea;\n  --global-div: #e0e0e0;\n  --global-div-tr: rgba(224, 224, 224, 0.5);\n  --global-border: rgba(8, 8, 8, 0.1);\n  --global-text: #333;\n  --global-text-muted: #888;\n  --global-card-bg: #fff;\n  --global-card-title-bg: #e8e8e8;\n  --global-card-shadow: rgba(0, 0, 0, 0.2);\n  --global-primary-skeleton: rgba(165, 165, 165, 0.1);\n  --global-secondary-skeleton: rgba(165, 165, 165, 0.3);\n  --global-button-bg: #e0e0e0;\n  --global-button-hover-bg: #c8c8c8;\n  --global-button-text: #333;\n  --global-button-shadow: rgba(255, 255, 255, 0.5);\n  --global-genre-button-bg: #d4d4d4;\n  --global-shadow: rgba(0, 0, 0, 0.1);\n  --global-border-radius: 0.3rem;\n  --primary-accent: #8080cf;\n  --primary-accent-bg: #595991;\n  --logo-text-transparent: url('/src/assets/miruro-text-transparent-black.webp');\n  --logo-transparent: url('/src/assets/miruro-transparent-black.webp');\n  --ongoing-dot-color: #aaff00;\n  --completed-indicator-color: #00aaff;\n  --cancelled-indicator-color: #ff0000;\n  --not-yet-aired-indicator-color: #ffa500;\n  --default-indicator-color: #808080;\n}\n\n/* Dark Mode  */\n:root.dark-mode {\n  --global-primary-bg: #080808;\n  --global-primary-bg-tr: rgba(8, 8, 8, 0.9);\n  --global-secondary-bg: #141414;\n  --global-tertiary-bg: #222222;\n  --global-div: #141414;\n  --global-div-tr: rgba(20, 20, 20, 0.5);\n  --global-border: rgba(245, 245, 245, 0.1);\n  --global-text: #e8e8e8;\n  --global-text-muted: #696969;\n  --global-card-bg: #181818;\n  --global-card-title-bg: #151515;\n  --global-card-shadow: rgba(0, 0, 0, 0.6);\n  --global-primary-skeleton: rgba(85, 85, 85, 0.1);\n  --global-secondary-skeleton: rgba(85, 85, 85, 0.3);\n  --global-button-bg: #202020;\n  --global-button-hover-bg: #292929;\n  --global-button-shadow: rgba(0, 0, 0, 0.6);\n  --global-button-text: #ebebeb;\n  --global-genre-button-bg: #222222;\n  --global-shadow: rgba(255, 255, 255, 0.08);\n  --logo-text-transparent: url('/src/assets/miruro-text-transparent-white.webp');\n  --logo-transparent: url('/src/assets/miruro-transparent-white.webp');\n}\n"
  },
  {
    "path": "src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\n      \"ES2020\",\n      \"DOM\",\n      \"DOM.Iterable\"\n    ],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\n    \"src\",\n    \"api\"\n  ],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.node.json\"\n    }\n  ]\n}"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "vercel.json",
    "content": "{\n  \"rewrites\": [{ \"source\": \"/(.*)\", \"destination\": \"/\" }]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { defineConfig, loadEnv, UserConfigExport, ConfigEnv } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n// This is a TypeScript Vite Config file.\n// Comments are retained from both original configurations for clarity.\nexport default ({ mode }: ConfigEnv): UserConfigExport => {\n  // Load environment variables and merge them with process.env\n  // You can access Vite specific env variables here like VITE_NAME using process.env.VITE_NAME\n  process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };\n\n  return defineConfig({\n    plugins: [react()], // Using React plugin from Vite\n\n    build: {\n      chunkSizeWarningLimit: 2000, // Control the size before showing a warning for chunk size\n      outDir: 'dist', // Specify your desired output directory\n      rollupOptions: {\n        output: {\n          manualChunks: {\n            lodash: ['lodash'], // Manually define chunk for lodash\n            vendor: ['react', 'react-dom'], // Manually define chunk for React and ReactDOM\n          },\n        },\n      },\n    },\n\n    server: {\n      port: parseInt(process.env.VITE_PORT || '5173'), // Use VITE_PORT from .env.local, default to 5173\n      open: true, // Automatically open the default browser when starting the server\n    },\n  });\n};\n"
  }
]