diff --git a/.env.example b/.env.example
index 706ee2c..995c7d7 100644
--- a/.env.example
+++ b/.env.example
@@ -1,2 +1,15 @@
-# Your Self Hosted AniWatch API URL - replace with your own API endpoint
-ANIWATCH_API=https://your-api-url.com/api/v2/hianime
\ No newline at end of file
+#Refer https://github.com/itzzzme/anime-api to host your backend API
+VITE_API_URL=/api
+
+#Refer this gist to setup proxy server https://gist.github.com/itzzzme/180813be2c7b45eedc8ce8344c8dea3b
+VITE_PROXY_URL=/?url=
+
+#Refer https://github.com/itzzzme/m3u8proxy to host you m3u8 proxy server though it's optional but if you don't set it up you may get CORS error for some servers if you set up from the given repo then only the url structure will look like this
+VITE_M3U8_PROXY_URL=/m3u8-proxy?url=
+
+#totaly optional / if you don't want to setup worker just change the code of getQtip.utils.js following the pattern of any other utils file
+VITE_WORKER_URL=https://worker1.workers.dev,https://worker2.workers.dev,https://worker3.workers.dev,...
+
+VITE_BASE_IFRAME_URL=https://megaplay.buzz/stream/s-2
+
+VITE_BASE_IFRAME_URL_2=https://vidwish.live/stream/s-2
diff --git a/.gitignore b/.gitignore
index e72b4d6..4855fb9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,41 +1,133 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.*
-.yarn/*
-!.yarn/patches
-!.yarn/plugins
-!.yarn/releases
-!.yarn/versions
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-
-# debug
+# Logs
+logs
+*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+lerna-debug.log*
.pnpm-debug.log*
-# env files (can opt-in for committing if needed)
-.env
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
-# vercel
-.vercel
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
-# typescript
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# lock json files
+package-lock.json
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
*.tsbuildinfo
-next-env.d.ts
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 38b7740..85f6de2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,47 +1,21 @@
-Business Source License 1.1
+MIT License
-Terms
+Copyright (c) 2024 Sayan
-The Licensor hereby grants you the right to copy, modify, create derivative
-works, redistribute, and make non-production use of the Licensed Work. The
-Licensor may make an Additional Use Grant, above, permitting limited
-production use.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-Effective on the Change Date, or the fourth anniversary of the first publicly
-available distribution of a specific version of the Licensed Work under this
-License, whichever comes first, the Licensor hereby grants you rights under
-the terms of the Change License, and the rights granted in the paragraph
-above terminate.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
-If your use of the Licensed Work does not comply with the requirements
-currently in effect as described in this License, you must purchase a
-commercial license from the Licensor, its affiliated entities, or authorized
-resellers, or you must refrain from using the Licensed Work.
-
-All copies of the original and modified Licensed Work, and derivative works
-of the Licensed Work, are subject to this License. This License applies
-separately for each version of the Licensed Work and the Change Date may vary
-for each version of the Licensed Work released by Licensor.
-
-You must conspicuously display this License on each original or modified copy
-of the Licensed Work. If you receive the Licensed Work in original or
-modified form from a third party, the terms and conditions set forth in this
-License apply to your use of that work.
-
-Any use of the Licensed Work in violation of this License will automatically
-terminate your rights under this License for the current and all other
-versions of the Licensed Work.
-
-This License does not grant you any right in any trademark or logo of
-Licensor or its affiliates (provided that you may use a trademark or logo of
-Licensor as expressly required by this License).
-
-TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
-AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
-EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
-TITLE.
-
-Change Date: 2027-04-01
-
-On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license GPL-3.0.
\ No newline at end of file
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 554fe61..1d2b28f 100644
--- a/README.md
+++ b/README.md
@@ -1,74 +1,119 @@
-
-
-
+
+ Zenime - Ad free anime streaming platform
+
+
+
+
+
+
+
+
+
-JustAnime
-
-A sleek anime streaming platform with a modern UI
-
- About •
- Features •
- Quick Start •
- Development
-
+ Zenime is an open-source anime streaming service that uses custom API, built using ReactJS with javascript and Tailwind CSS. It lets you easily find any anime with intuitive search & suggestion feature and stream without any ads.
+
-
-
-
-
-
-
-
-
-
-
-
+
+View more Features
-## What is JustAnime?
+### General
-Welcome to **JustAnime**, your premier destination for all things anime! Explore a comprehensive collection of high-definition anime with a seamless and user-friendly interface powered by **[aniwatch-api](https://github.com/ghoshRitesh12/aniwatch-api)**.
+- Sub Anime support
+- Dub Anime support
+- User-friendly interface
+- Mobile responsive
+- Fast page load
+- Character & Voice Actors
-Built using **Next.js** and **React**, JustAnime 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, JustAnime has you covered with an ad-free streaming experience that supports both English subtitles and dubbed versions. Additionally, you can easily keep track of your watched episodes without the hassle of creating an account, making your viewing experience as convenient as possible.
+### Watch Page
-## Features
+- Related Animes
+- Recommended Animes
+- Available seasons
+- Estimated schedule of upcoming episodes
+- **Player**
+ - Autoplay
+ - Autoskip intro/outro
+ - Autonext
-### General:
+
-* Sub/Dub Support - Switch between subbed and dubbed versions
-* Responsive Design - Optimized for all devices from mobile to desktop
-* Continue Watching - Resume from where you left off
-* Advanced Search - With real-time suggestions as you type
+## Previews
-### Player Experience:
+
-* Autoplay - Seamlessly continue to the next episode
-* Quality Selection - Choose your preferred streaming quality
-* Multiple Servers - Switch between different streaming servers
-* Subtitles - Toggle subtitles on/off
-* Playback Speed - Adjust video playback speed
-* Audio Controls - Volume adjustment and audio boost option
+## Installation and Local Development
-## Quick Start
+### 1. Make sure you have node installed on your device
+
+### 2. Run the following code to clone the repository and install all required dependencies
```bash
-# Clone the repository & Navigate to projetc directory
-git clone https://github.com/tejaspanchall/JustAnime.git
-cd JustAnime
-
-# Install dependencies
-npm install
-
-# Set up environment variables
-cp .env.example .env
-
-# Start development server
-npm run dev
+git clone https://github.com/itzzzme/zenime.git
+cd zenime
+npm install # or yarn
```
-Visit [http://localhost:3000](http://localhost:3000) to see the application in action.
+### 3. Refer the .env.example to set your .env file up
-## Development
+## Start the server
-Pull requests and stars are always welcome. If you encounter any bug or want to add a new feature to this api, consider creating a new [issue](https://github.com/tejaspanchall/JustAnime/issues).
+```bash
+npm start # or npm run dev (to run develepment server)
+```
+## Live Deployment
+
+### Vercel
+
+Host your own instance of Zenime on vercel
+
+[](https://vercel.com/new/clone?repository-url=https://github.com/itzzzme/zenime)
+
+### Render
+
+Host your own instance of Zenime on Render.
+
+[](https://render.com/deploy?repo=https://github.com/itzzzme/zenime)
+
+### Pull Requests
+
+- Pull requests are welcomed that address bug fixes, improvements, or new features.
+- Fork the repository and create a new branch for your changes.
+- Ensure your code follows our coding standards.
+- Include tests if applicable.
+- Describe your changes clearly in the pull request, explaining the problem and solution.
+
+ ### Reporting Issues
+
+If you discover any issues or have suggestions for improvement, please open an issue. Provide a clear and concise description of the problem, steps to reproduce it, and any relevant information about your environment.
+
+### Support
+
+ If you like the project feel free to drop a star ✨. Your appreciation means a lot.
+
+Made by itzzzme
+ 🫰
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..d16853e
--- /dev/null
+++ b/components.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": false,
+ "tailwind": {
+ "config": "tailwind.config.js",
+ "css": "src/index.css",
+ "baseColor": "zinc",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ }
+}
\ No newline at end of file
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..238d2e4
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,38 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import react from 'eslint-plugin-react'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+
+export default [
+ { ignores: ['dist'] },
+ {
+ files: ['**/*.{js,jsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ settings: { react: { version: '18.3' } },
+ plugins: {
+ react,
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...js.configs.recommended.rules,
+ ...react.configs.recommended.rules,
+ ...react.configs['jsx-runtime'].rules,
+ ...reactHooks.configs.recommended.rules,
+ 'react/jsx-no-target-blank': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+]
diff --git a/eslint.config.mjs b/eslint.config.mjs
deleted file mode 100644
index 348c45a..0000000
--- a/eslint.config.mjs
+++ /dev/null
@@ -1,14 +0,0 @@
-import { dirname } from "path";
-import { fileURLToPath } from "url";
-import { FlatCompat } from "@eslint/eslintrc";
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-const compat = new FlatCompat({
- baseDirectory: __dirname,
-});
-
-const eslintConfig = [...compat.extends("next/core-web-vitals")];
-
-export default eslintConfig;
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..ea57811
--- /dev/null
+++ b/index.html
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JustAnime | Free Anime Streaming Platform
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jsconfig.json b/jsconfig.json
index b8d6842..0d81cf7 100644
--- a/jsconfig.json
+++ b/jsconfig.json
@@ -1,7 +1,9 @@
{
- "compilerOptions": {
- "paths": {
- "@/*": ["./src/*"]
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./*"]
+ }
}
}
-}
+
\ No newline at end of file
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..b20bf01
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,6 @@
+import { clsx } from "clsx";
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs) {
+ return twMerge(clsx(inputs));
+}
diff --git a/next.config.js b/next.config.js
deleted file mode 100644
index 7e98866..0000000
--- a/next.config.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/** @type {import('next').NextConfig} */
-const nextConfig = {
- reactStrictMode: true,
- env: {
- // Environment variables here
- },
- images: {
- domains: [
- 'via.placeholder.com',
- 'gogocdn.net',
- 'cdnjs.cloudflare.com',
- 'img.zorores.com',
- 'poster.zoros.to',
- 'cdn.myanimelist.net',
- 's4.anilist.co',
- 'artworks.thetvdb.com',
- 'image.tmdb.org',
- 'justanimeapi.vercel.app',
- 'consumet.org',
- 'api.consumet.org',
- 'img.flixhq.to',
- 'img.bflix.to',
- ],
- remotePatterns: [
- {
- protocol: 'https',
- hostname: '**',
- },
- ],
- unoptimized: true,
- },
- experimental: {
- scrollRestoration: true,
- },
- serverExternalPackages: ['puppeteer-core'],
- async rewrites() {
- // Get the API URL from environment variable or use default
- const apiUrl = process.env.ANIWATCH_API;
- // Extract the base URL without the /api/v2/hianime path
- const baseUrl = apiUrl.replace('/api/v2/hianime', '');
-
- return [
- {
- source: '/api/v2/hianime/:path*',
- destination: `${apiUrl}/:path*`
- },
- {
- source: '/api/anime/:path*',
- destination: `${apiUrl}/anime/:path*`
- }
- ]
- },
- async headers() {
- return [
- {
- source: '/api/:path*',
- headers: [
- { key: 'Access-Control-Allow-Credentials', value: 'true' },
- { key: 'Access-Control-Allow-Origin', value: '*' },
- { key: 'Access-Control-Allow-Methods', value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT' },
- { key: 'Access-Control-Allow-Headers', value: 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, Authorization' },
- { key: 'Referrer-Policy', value: 'no-referrer-when-downgrade' },
- { key: 'Cross-Origin-Resource-Policy', value: 'cross-origin' },
- { key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
- ],
- },
- {
- source: '/:path*',
- headers: [
- { key: 'Referrer-Policy', value: 'no-referrer-when-downgrade' },
- { key: 'Cross-Origin-Resource-Policy', value: 'cross-origin' },
- { key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
- ]
- }
- ];
- },
- webpack(config) {
- config.module.rules.push({
- test: /\.svg$/,
- use: [{ loader: '@svgr/webpack', options: { icon: true } }],
- });
- return config;
- },
-};
-
-module.exports = nextConfig;
\ No newline at end of file
diff --git a/next.config.mjs b/next.config.mjs
deleted file mode 100644
index 1f50146..0000000
--- a/next.config.mjs
+++ /dev/null
@@ -1,17 +0,0 @@
-/** @type {import('next').NextConfig} */
-const nextConfig = {
- env: {
- // Environment variables here
- },
- images: {
- unoptimized: true,
- remotePatterns: [
- {
- protocol: 'https',
- hostname: '**',
- },
- ],
- }
-};
-
-export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
index f7a61b5..83e3160 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,38 +1,58 @@
{
"name": "justanime",
- "version": "0.1.0",
+ "version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "justanime",
- "version": "0.1.0",
+ "version": "0.0.0",
"dependencies": {
- "@heroicons/react": "^2.2.0",
- "@vercel/analytics": "^1.5.0",
- "@vercel/speed-insights": "^1.2.0",
- "hls.js": "^1.5.7",
- "next": "latest",
- "proxy-from-env": "^1.1.0",
- "react": "latest",
- "react-dom": "latest",
- "swiper": "^11.2.6"
+ "@fortawesome/fontawesome-svg-core": "^6.6.0",
+ "@fortawesome/free-brands-svg-icons": "^6.7.2",
+ "@fortawesome/free-solid-svg-icons": "^6.6.0",
+ "@fortawesome/react-fontawesome": "^0.2.2",
+ "@radix-ui/react-icons": "^1.3.0",
+ "artplayer": "^5.2.3",
+ "artplayer-plugin-chapter": "^1.0.0",
+ "artplayer-plugin-hls-control": "^1.0.1",
+ "axios": "^1.7.7",
+ "cheerio": "^1.0.0",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.1",
+ "hls.js": "^1.5.17",
+ "lucide-react": "^0.447.0",
+ "react": "^18.3.1",
+ "react-content-loader": "^7.0.2",
+ "react-dom": "^18.3.1",
+ "react-icons": "^5.3.0",
+ "react-lazy-load": "^4.0.1",
+ "react-router-dom": "^6.26.2",
+ "styled-components": "^6.1.13",
+ "swiper": "^11.2.5",
+ "tailwind-merge": "^2.5.3",
+ "tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
- "@eslint/eslintrc": "^3",
- "@tailwindcss/postcss": "^4",
- "autoprefixer": "latest",
- "eslint": "^9",
- "eslint-config-next": "15.2.5",
- "postcss": "latest",
- "tailwindcss": "^4"
+ "@eslint/js": "^9.9.0",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^9.9.0",
+ "eslint-plugin-react": "^7.35.0",
+ "eslint-plugin-react-hooks": "^5.1.0-rc.0",
+ "eslint-plugin-react-refresh": "^0.4.9",
+ "globals": "^15.9.0",
+ "postcss": "^8.4.47",
+ "tailwindcss": "^3.4.13",
+ "vite": "^5.4.1"
}
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -41,43 +61,718 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@emnapi/core": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.0.tgz",
- "integrity": "sha512-H+N/FqT07NmLmt6OFFtDfwe8PNygprzBikrEMyQfgqSmT0vzE515Pz7R8izwB9q/zsH/MA64AKoul3sA6/CzVg==",
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
+ "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
+ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.27.3",
+ "@babel/helpers": "^7.27.6",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
+ "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
+ "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz",
+ "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
+ "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
+ "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+ "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+ "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
"optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.0.1",
- "tslib": "^2.4.0"
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/@emnapi/runtime": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz",
- "integrity": "sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/wasi-threads": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz",
- "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==",
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
"optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
}
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.5.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz",
- "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==",
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -117,9 +812,9 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.20.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
- "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -132,9 +827,9 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz",
- "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==",
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
+ "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -142,9 +837,9 @@
}
},
"node_modules/@eslint/core": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
- "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
+ "version": "0.15.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
+ "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -178,14 +873,30 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@eslint/js": {
- "version": "9.24.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz",
- "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==",
+ "version": "9.31.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
+ "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
}
},
"node_modules/@eslint/object-schema": {
@@ -199,39 +910,75 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.2.8",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
- "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz",
+ "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.13.0",
+ "@eslint/core": "^0.15.1",
"levn": "^0.4.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
- "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
- "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
+ "node_modules/@fortawesome/fontawesome-common-types": {
+ "version": "6.7.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz",
+ "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==",
+ "license": "MIT",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=6"
}
},
- "node_modules/@heroicons/react": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
- "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
+ "node_modules/@fortawesome/fontawesome-svg-core": {
+ "version": "6.7.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz",
+ "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==",
"license": "MIT",
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.7.2"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-brands-svg-icons": {
+ "version": "6.7.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz",
+ "integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==",
+ "license": "(CC-BY-4.0 AND MIT)",
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.7.2"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-solid-svg-icons": {
+ "version": "6.7.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz",
+ "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==",
+ "license": "(CC-BY-4.0 AND MIT)",
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.7.2"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/react-fontawesome": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.3.tgz",
+ "integrity": "sha512-HlJco8RDY8NrzFVjy23b/7mNS4g9NegcrBG3n7jinwpc2x/AmSVk53IhWniLYM4szYLxRAFTAGwGn0EIlclDeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "prop-types": "^15.8.1"
+ },
"peerDependencies": {
- "react": ">= 16 || ^19.0.0-rc"
+ "@fortawesome/fontawesome-svg-core": "~1 || ~6 || ~7",
+ "react": "^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@humanfs/core": {
@@ -287,9 +1034,9 @@
}
},
"node_modules/@humanwhocodes/retry": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
- "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -300,529 +1047,62 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@img/sharp-darwin-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
- "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
- "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
- "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
- "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
- "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
- "cpu": [
- "arm"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
- "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
- "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
- "cpu": [
- "s390x"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
- "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
- "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
- "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-linux-arm": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
- "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
- "cpu": [
- "arm"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.0.5"
- }
- },
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
- "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linux-s390x": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
- "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
- "cpu": [
- "s390x"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linux-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
- "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
- "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
- "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-wasm32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
- "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
- "cpu": [
- "wasm32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
- "optional": true,
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "license": "ISC",
"dependencies": {
- "@emnapi/runtime": "^1.2.0"
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
+ "node": ">=12"
}
},
- "node_modules/@img/sharp-win32-ia32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
- "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
- "cpu": [
- "ia32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
- "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@napi-rs/wasm-runtime": {
- "version": "0.2.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.8.tgz",
- "integrity": "sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==",
- "dev": true,
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"license": "MIT",
- "optional": true,
"dependencies": {
- "@emnapi/core": "^1.4.0",
- "@emnapi/runtime": "^1.4.0",
- "@tybys/wasm-util": "^0.9.0"
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
}
},
- "node_modules/@next/env": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.5.tgz",
- "integrity": "sha512-uWkCf9C8wKTyQjqrNk+BA7eL3LOQdhL+xlmJUf2O85RM4lbzwBwot3Sqv2QGe/RGnc3zysIf1oJdtq9S00pkmQ==",
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
"license": "MIT"
},
- "node_modules/@next/eslint-plugin-next": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.2.5.tgz",
- "integrity": "sha512-Q1ncASVFKSy+AbabimYxr/2HH/h+qlKlwu1fYV48xUefGzVimS3i3nKwYsM2w+rLdpMFdJyoVowrYyjKu47rBw==",
- "dev": true,
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.29",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"license": "MIT",
"dependencies": {
- "fast-glob": "3.3.1"
- }
- },
- "node_modules/@next/swc-darwin-arm64": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.5.tgz",
- "integrity": "sha512-4OimvVlFTbgzPdA0kh8A1ih6FN9pQkL4nPXGqemEYgk+e7eQhsst/p35siNNqA49eQA6bvKZ1ASsDtu9gtXuog==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-darwin-x64": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.5.tgz",
- "integrity": "sha512-ohzRaE9YbGt1ctE0um+UGYIDkkOxHV44kEcHzLqQigoRLaiMtZzGrA11AJh2Lu0lv51XeiY1ZkUvkThjkVNBMA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.5.tgz",
- "integrity": "sha512-FMSdxSUt5bVXqqOoZCc/Seg4LQep9w/fXTazr/EkpXW2Eu4IFI9FD7zBDlID8TJIybmvKk7mhd9s+2XWxz4flA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.5.tgz",
- "integrity": "sha512-4ZNKmuEiW5hRKkGp2HWwZ+JrvK4DQLgf8YDaqtZyn7NYdl0cHfatvlnLFSWUayx9yFAUagIgRGRk8pFxS8Qniw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-gnu": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.5.tgz",
- "integrity": "sha512-bE6lHQ9GXIf3gCDE53u2pTl99RPZW5V1GLHSRMJ5l/oB/MT+cohu9uwnCK7QUph2xIOu2a6+27kL0REa/kqwZw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-musl": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.5.tgz",
- "integrity": "sha512-y7EeQuSkQbTAkCEQnJXm1asRUuGSWAchGJ3c+Qtxh8LVjXleZast8Mn/rL7tZOm7o35QeIpIcid6ufG7EVTTcA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.5.tgz",
- "integrity": "sha512-gQMz0yA8/dskZM2Xyiq2FRShxSrsJNha40Ob/M2n2+JGRrZ0JwTVjLdvtN6vCxuq4ByhOd4a9qEf60hApNR2gQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-x64-msvc": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.5.tgz",
- "integrity": "sha512-tBDNVUcI7U03+3oMvJ11zrtVin5p0NctiuKmTGyaTIEAVj9Q77xukLXGXRnWxKRIIdFG4OTA2rUVGZDYOwgmAA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
@@ -836,7 +1116,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@@ -846,7 +1125,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
@@ -856,85 +1134,59 @@
"node": ">= 8"
}
},
- "node_modules/@nolyfill/is-core-module": {
- "version": "1.0.39",
- "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
- "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
- "dev": true,
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"license": "MIT",
+ "optional": true,
"engines": {
- "node": ">=12.4.0"
+ "node": ">=14"
}
},
- "node_modules/@rtsao/scc": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
- "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+ "node_modules/@radix-ui/react-icons": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz",
+ "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.23.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
+ "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
"dev": true,
"license": "MIT"
},
- "node_modules/@rushstack/eslint-patch": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz",
- "integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@swc/counter": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
- "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
- "license": "Apache-2.0"
- },
- "node_modules/@swc/helpers": {
- "version": "0.5.15",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
- "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@tailwindcss/node": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.3.tgz",
- "integrity": "sha512-H/6r6IPFJkCfBJZ2dKZiPJ7Ueb2wbL592+9bQEl2r73qbX6yGnmQVIfiUvDRB2YI0a3PWDrzUwkvQx1XW1bNkA==",
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz",
+ "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "enhanced-resolve": "^5.18.1",
- "jiti": "^2.4.2",
- "lightningcss": "1.29.2",
- "tailwindcss": "4.1.3"
- }
+ "optional": true,
+ "os": [
+ "android"
+ ]
},
- "node_modules/@tailwindcss/oxide": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.3.tgz",
- "integrity": "sha512-t16lpHCU7LBxDe/8dCj9ntyNpXaSTAgxWm1u2XQP5NiIu4KGSyrDJJRlK9hJ4U9yJxx0UKCVI67MJWFNll5mOQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.1.3",
- "@tailwindcss/oxide-darwin-arm64": "4.1.3",
- "@tailwindcss/oxide-darwin-x64": "4.1.3",
- "@tailwindcss/oxide-freebsd-x64": "4.1.3",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.3",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.3",
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.3",
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.3",
- "@tailwindcss/oxide-linux-x64-musl": "4.1.3",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.3",
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.3"
- }
- },
- "node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.3.tgz",
- "integrity": "sha512-cxklKjtNLwFl3mDYw4XpEfBY+G8ssSg9ADL4Wm6//5woi3XGqlxFsnV5Zb6v07dxw1NvEX2uoqsxO/zWQsgR+g==",
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz",
+ "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==",
"cpu": [
"arm64"
],
@@ -943,15 +1195,12 @@
"optional": true,
"os": [
"android"
- ],
- "engines": {
- "node": ">= 10"
- }
+ ]
},
- "node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.3.tgz",
- "integrity": "sha512-mqkf2tLR5VCrjBvuRDwzKNShRu99gCAVMkVsaEOFvv6cCjlEKXRecPu9DEnxp6STk5z+Vlbh1M5zY3nQCXMXhw==",
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz",
+ "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==",
"cpu": [
"arm64"
],
@@ -960,15 +1209,12 @@
"optional": true,
"os": [
"darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
+ ]
},
- "node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.3.tgz",
- "integrity": "sha512-7sGraGaWzXvCLyxrc7d+CCpUN3fYnkkcso3rCzwUmo/LteAl2ZGCDlGvDD8Y/1D3ngxT8KgDj1DSwOnNewKhmg==",
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz",
+ "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==",
"cpu": [
"x64"
],
@@ -977,461 +1223,26 @@
"optional": true,
"os": [
"darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
+ ]
},
- "node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.3.tgz",
- "integrity": "sha512-E2+PbcbzIReaAYZe997wb9rId246yDkCwAakllAWSGqe6VTg9hHle67hfH6ExjpV2LSK/siRzBUs5wVff3RW9w==",
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz",
+ "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==",
"cpu": [
- "x64"
+ "arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.3.tgz",
- "integrity": "sha512-GvfbJ8wjSSjbLFFE3UYz4Eh8i4L6GiEYqCtA8j2Zd2oXriPuom/Ah/64pg/szWycQpzRnbDiJozoxFU2oJZyfg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.3.tgz",
- "integrity": "sha512-35UkuCWQTeG9BHcBQXndDOrpsnt3Pj9NVIB4CgNiKmpG8GnCNXeMczkUpOoqcOhO6Cc/mM2W7kaQ/MTEENDDXg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.3.tgz",
- "integrity": "sha512-dm18aQiML5QCj9DQo7wMbt1Z2tl3Giht54uVR87a84X8qRtuXxUqnKQkRDK5B4bCOmcZ580lF9YcoMkbDYTXHQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.3.tgz",
- "integrity": "sha512-LMdTmGe/NPtGOaOfV2HuO7w07jI3cflPrVq5CXl+2O93DCewADK0uW1ORNAcfu2YxDUS035eY2W38TxrsqngxA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.3.tgz",
- "integrity": "sha512-aalNWwIi54bbFEizwl1/XpmdDrOaCjRFQRgtbv9slWjmNPuJJTIKPHf5/XXDARc9CneW9FkSTqTbyvNecYAEGw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.3.tgz",
- "integrity": "sha512-PEj7XR4OGTGoboTIAdXicKuWl4EQIjKHKuR+bFy9oYN7CFZo0eu74+70O4XuERX4yjqVZGAkCdglBODlgqcCXg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.3.tgz",
- "integrity": "sha512-T8gfxECWDBENotpw3HR9SmNiHC9AOJdxs+woasRZ8Q/J4VHN0OMs7F+4yVNZ9EVN26Wv6mZbK0jv7eHYuLJLwA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/postcss": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.3.tgz",
- "integrity": "sha512-6s5nJODm98F++QT49qn8xJKHQRamhYHfMi3X7/ltxiSQ9dyRsaFSfFkfaMsanWzf+TMYQtbk8mt5f6cCVXJwfg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@alloc/quick-lru": "^5.2.0",
- "@tailwindcss/node": "4.1.3",
- "@tailwindcss/oxide": "4.1.3",
- "postcss": "^8.4.41",
- "tailwindcss": "4.1.3"
- }
- },
- "node_modules/@tybys/wasm-util": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
- "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
- "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json5": {
- "version": "0.0.29",
- "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
- "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.29.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz",
- "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.29.1",
- "@typescript-eslint/type-utils": "8.29.1",
- "@typescript-eslint/utils": "8.29.1",
- "@typescript-eslint/visitor-keys": "8.29.1",
- "graphemer": "^1.4.0",
- "ignore": "^5.3.1",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.0.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/parser": {
- "version": "8.29.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz",
- "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.29.1",
- "@typescript-eslint/types": "8.29.1",
- "@typescript-eslint/typescript-estree": "8.29.1",
- "@typescript-eslint/visitor-keys": "8.29.1",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.29.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz",
- "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.29.1",
- "@typescript-eslint/visitor-keys": "8.29.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.29.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz",
- "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/typescript-estree": "8.29.1",
- "@typescript-eslint/utils": "8.29.1",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.0.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "8.29.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz",
- "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.29.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz",
- "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.29.1",
- "@typescript-eslint/visitor-keys": "8.29.1",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.0.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "8.29.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz",
- "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "8.29.1",
- "@typescript-eslint/types": "8.29.1",
- "@typescript-eslint/typescript-estree": "8.29.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.29.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz",
- "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.29.1",
- "eslint-visitor-keys": "^4.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@unrs/resolver-binding-darwin-arm64": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.4.1.tgz",
- "integrity": "sha512-8Tv+Bsd0BjGwfEedIyor4inw8atppRxM5BdUnIt+3mAm/QXUm7Dw74CHnXpfZKXkp07EXJGiA8hStqCINAWhdw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
]
},
- "node_modules/@unrs/resolver-binding-darwin-x64": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.4.1.tgz",
- "integrity": "sha512-X8c3PhWziEMKAzZz+YAYWfwawi5AEgzy/hmfizAB4C70gMHLKmInJcp1270yYAOs7z07YVFI220pp50z24Jk3A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@unrs/resolver-binding-freebsd-x64": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.4.1.tgz",
- "integrity": "sha512-UUr/nREy1UdtxXQnmLaaTXFGOcGxPwNIzeJdb3KXai3TKtC1UgNOB9s8KOA4TaxOUBR/qVgL5BvBwmUjD5yuVA==",
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz",
+ "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==",
"cpu": [
"x64"
],
@@ -1442,10 +1253,10 @@
"freebsd"
]
},
- "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.4.1.tgz",
- "integrity": "sha512-e3pII53dEeS8inkX6A1ad2UXE0nuoWCqik4kOxaDnls0uJUq0ntdj5d9IYd+bv5TDwf9DSge/xPOvCmRYH+Tsw==",
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz",
+ "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==",
"cpu": [
"arm"
],
@@ -1456,10 +1267,10 @@
"linux"
]
},
- "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.4.1.tgz",
- "integrity": "sha512-e/AKKd9gR+HNmVyDEPI/PIz2t0DrA3cyonHNhHVjrkxe8pMCiYiqhtn1+h+yIpHUtUlM6Y1FNIdivFa+r7wrEQ==",
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz",
+ "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==",
"cpu": [
"arm"
],
@@ -1470,10 +1281,10 @@
"linux"
]
},
- "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.4.1.tgz",
- "integrity": "sha512-vtIu34luF1jRktlHtiwm2mjuE8oJCsFiFr8hT5+tFQdqFKjPhbJXn83LswKsOhy0GxAEevpXDI4xxEwkjuXIPA==",
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz",
+ "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==",
"cpu": [
"arm64"
],
@@ -1484,10 +1295,10 @@
"linux"
]
},
- "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.4.1.tgz",
- "integrity": "sha512-H3PaOuGyhFXiyJd+09uPhGl4gocmhyi1BRzvsP8Lv5AQO3p3/ZY7WjV4t2NkBksm9tMjf3YbOVHyPWi2eWsNYw==",
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz",
+ "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==",
"cpu": [
"arm64"
],
@@ -1498,10 +1309,24 @@
"linux"
]
},
- "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.4.1.tgz",
- "integrity": "sha512-4+GmJcaaFntCi1S01YByqp8wLMjV/FyQyHVGm0vedIhL1Vfx7uHkz/sZmKsidRwokBGuxi92GFmSzqT2O8KcNA==",
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz",
+ "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz",
+ "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==",
"cpu": [
"ppc64"
],
@@ -1512,10 +1337,38 @@
"linux"
]
},
- "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.4.1.tgz",
- "integrity": "sha512-6RDQVCmtFYTlhy89D5ixTqo9bTQqFhvNN0Ey1wJs5r+01Dq15gPHRXv2jF2bQATtMrOfYwv+R2ZR9ew1N1N3YQ==",
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz",
+ "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz",
+ "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz",
+ "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==",
"cpu": [
"s390x"
],
@@ -1526,10 +1379,10 @@
"linux"
]
},
- "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.4.1.tgz",
- "integrity": "sha512-XpU9uzIkD86+19NjCXxlVPISMUrVXsXo5htxtuG+uJ59p5JauSRZsIxQxzzfKzkxEjdvANPM/lS1HFoX6A6QeA==",
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz",
+ "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==",
"cpu": [
"x64"
],
@@ -1540,10 +1393,10 @@
"linux"
]
},
- "node_modules/@unrs/resolver-binding-linux-x64-musl": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.4.1.tgz",
- "integrity": "sha512-3CDjG/spbTKCSHl66QP2ekHSD+H34i7utuDIM5gzoNBcZ1gTO0Op09Wx5cikXnhORRf9+HyDWzm37vU1PLSM1A==",
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz",
+ "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==",
"cpu": [
"x64"
],
@@ -1554,27 +1407,10 @@
"linux"
]
},
- "node_modules/@unrs/resolver-binding-wasm32-wasi": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.4.1.tgz",
- "integrity": "sha512-50tYhvbCTnuzMn7vmP8IV2UKF7ITo1oihygEYq9wW2DUb/Y+QMqBHJUSCABRngATjZ4shOK6f2+s0gQX6ElENQ==",
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@napi-rs/wasm-runtime": "^0.2.8"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.4.1.tgz",
- "integrity": "sha512-KyJiIne/AqV4IW0wyQO34wSMuJwy3VxVQOfIXIPyQ/Up6y/zi2P/WwXb78gHsLiGRUqCA9LOoCX+6dQZde0g1g==",
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz",
+ "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==",
"cpu": [
"arm64"
],
@@ -1585,10 +1421,10 @@
"win32"
]
},
- "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.4.1.tgz",
- "integrity": "sha512-y2NUD7pygrBolN2NoXUrwVqBpKPhF8DiSNE5oB5/iFO49r2DpoYqdj5HPb3F42fPBH5qNqj6Zg63+xCEzAD2hw==",
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz",
+ "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==",
"cpu": [
"ia32"
],
@@ -1599,10 +1435,10 @@
"win32"
]
},
- "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.4.1.tgz",
- "integrity": "sha512-hVXaObGI2lGFmrtT77KSbPQ3I+zk9IU500wobjk0+oX59vg/0VqAzABNtt3YSQYgXTC2a/LYxekLfND/wlt0yQ==",
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz",
+ "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==",
"cpu": [
"x64"
],
@@ -1613,83 +1449,124 @@
"win32"
]
},
- "node_modules/@vercel/analytics": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.5.0.tgz",
- "integrity": "sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==",
- "license": "MPL-2.0",
- "peerDependencies": {
- "@remix-run/react": "^2",
- "@sveltejs/kit": "^1 || ^2",
- "next": ">= 13",
- "react": "^18 || ^19 || ^19.0.0-rc",
- "svelte": ">= 4",
- "vue": "^3",
- "vue-router": "^4"
- },
- "peerDependenciesMeta": {
- "@remix-run/react": {
- "optional": true
- },
- "@sveltejs/kit": {
- "optional": true
- },
- "next": {
- "optional": true
- },
- "react": {
- "optional": true
- },
- "svelte": {
- "optional": true
- },
- "vue": {
- "optional": true
- },
- "vue-router": {
- "optional": true
- }
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
}
},
- "node_modules/@vercel/speed-insights": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@vercel/speed-insights/-/speed-insights-1.2.0.tgz",
- "integrity": "sha512-y9GVzrUJ2xmgtQlzFP2KhVRoCglwfRQgjyfY607aU0hh0Un6d0OUyrJkjuAlsV18qR4zfoFPs/BiIj9YDS6Wzw==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
+ "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.23",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
+ "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
"peerDependencies": {
- "@sveltejs/kit": "^1 || ^2",
- "next": ">= 13",
- "react": "^18 || ^19 || ^19.0.0-rc",
- "svelte": ">= 4",
- "vue": "^3",
- "vue-router": "^4"
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/stylis": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==",
+ "license": "MIT"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
},
- "peerDependenciesMeta": {
- "@sveltejs/kit": {
- "optional": true
- },
- "next": {
- "optional": true
- },
- "react": {
- "optional": true
- },
- "svelte": {
- "optional": true
- },
- "vue": {
- "optional": true
- },
- "vue-router": {
- "optional": true
- }
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
"node_modules/acorn": {
- "version": "8.14.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
- "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -1726,11 +1603,22 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
@@ -1742,6 +1630,31 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "license": "MIT"
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -1749,16 +1662,6 @@
"dev": true,
"license": "Python-2.0"
},
- "node_modules/aria-query": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
- "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/array-buffer-byte-length": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
@@ -1777,18 +1680,20 @@
}
},
"node_modules/array-includes": {
- "version": "3.1.8",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
- "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
+ "version": "3.1.9",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
+ "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
"define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.4",
- "is-string": "^1.0.7"
+ "es-abstract": "^1.24.0",
+ "es-object-atoms": "^1.1.1",
+ "get-intrinsic": "^1.3.0",
+ "is-string": "^1.1.1",
+ "math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
@@ -1818,28 +1723,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/array.prototype.findlastindex": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz",
- "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.9",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "es-shim-unscopables": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/array.prototype.flat": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
@@ -1917,11 +1800,25 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/ast-types-flow": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
- "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
- "dev": true,
+ "node_modules/artplayer": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/artplayer/-/artplayer-5.2.3.tgz",
+ "integrity": "sha512-WaOZQrpZn/L+GgI2f0TEsoAL3Wb+v16Mu0JmWh7qKFYuvr11WNt3dWhWeIaCfoHy3NtkCWM9jTP+xwwsxdElZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "option-validator": "^2.0.6"
+ }
+ },
+ "node_modules/artplayer-plugin-chapter": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/artplayer-plugin-chapter/-/artplayer-plugin-chapter-1.0.1.tgz",
+ "integrity": "sha512-opXKGN/AdUkzhJeOJu7Pp7ExjDI9HhFbzEXmjvhLfDwZY0zSd3PpcBA5ZWPxKMqA9qxdbInCkLtWbPEhi3ZSxA==",
+ "license": "MIT"
+ },
+ "node_modules/artplayer-plugin-hls-control": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/artplayer-plugin-hls-control/-/artplayer-plugin-hls-control-1.0.1.tgz",
+ "integrity": "sha512-rbOeH/mzqgZuosOtxJ9NERil6siOLd9K7nsCRWARrSyT+zH3xdDo0WunYgDQiATniNySicxZ//ex/pPLxYECUg==",
"license": "MIT"
},
"node_modules/async-function": {
@@ -1934,6 +1831,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
"node_modules/autoprefixer": {
"version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
@@ -1988,37 +1891,45 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/axe-core": {
- "version": "4.10.3",
- "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
- "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==",
- "dev": true,
- "license": "MPL-2.0",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/axobject-query": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
- "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">= 0.4"
+ "node_modules/axios": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
+ "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "license": "ISC"
+ },
"node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2030,7 +1941,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
@@ -2040,9 +1950,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.24.4",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
- "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "version": "4.25.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
+ "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
"dev": true,
"funding": [
{
@@ -2060,10 +1970,10 @@
],
"license": "MIT",
"dependencies": {
- "caniuse-lite": "^1.0.30001688",
- "electron-to-chromium": "^1.5.73",
+ "caniuse-lite": "^1.0.30001726",
+ "electron-to-chromium": "^1.5.173",
"node-releases": "^2.0.19",
- "update-browserslist-db": "^1.1.1"
+ "update-browserslist-db": "^1.1.3"
},
"bin": {
"browserslist": "cli.js"
@@ -2072,17 +1982,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/busboy": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
- "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
- "dependencies": {
- "streamsearch": "^1.1.0"
- },
- "engines": {
- "node": ">=10.16.0"
- }
- },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -2106,7 +2005,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -2143,10 +2041,29 @@
"node": ">=6"
}
},
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-lite": {
- "version": "1.0.30001712",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz",
- "integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==",
+ "version": "1.0.30001727",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
+ "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
+ "dev": true,
"funding": [
{
"type": "opencollective",
@@ -2180,31 +2097,109 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/client-only": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
- "license": "MIT"
- },
- "node_modules/color": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
- "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "node_modules/cheerio": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz",
+ "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==",
"license": "MIT",
- "optional": true,
"dependencies": {
- "color-convert": "^2.0.1",
- "color-string": "^1.9.0"
+ "cheerio-select": "^2.1.0",
+ "dom-serializer": "^2.0.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.2.2",
+ "encoding-sniffer": "^0.2.1",
+ "htmlparser2": "^10.0.0",
+ "parse5": "^7.3.0",
+ "parse5-htmlparser2-tree-adapter": "^7.1.0",
+ "parse5-parser-stream": "^7.1.2",
+ "undici": "^7.12.0",
+ "whatwg-mimetype": "^4.0.0"
},
"engines": {
- "node": ">=12.5.0"
+ "node": ">=20.18.1"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
+ }
+ },
+ "node_modules/cheerio-select": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
+ "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-select": "^5.1.0",
+ "css-what": "^6.1.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
@@ -2217,18 +2212,27 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "devOptional": true,
"license": "MIT"
},
- "node_modules/color-string": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
- "optional": true,
"dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
}
},
"node_modules/concat-map": {
@@ -2238,11 +2242,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -2253,12 +2263,71 @@
"node": ">= 8"
}
},
- "node_modules/damerau-levenshtein": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
- "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
- "dev": true,
- "license": "BSD-2-Clause"
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-select": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-what": "^6.1.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "nth-check": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "node_modules/css-what": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
},
"node_modules/data-view-buffer": {
"version": "1.0.2",
@@ -2315,9 +2384,9 @@
}
},
"node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2375,16 +2444,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/detect-libc": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
- "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
- "devOptional": true,
- "license": "Apache-2.0",
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=0.4.0"
}
},
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "license": "MIT"
+ },
"node_modules/doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -2398,11 +2478,65 @@
"node": ">=0.10.0"
}
},
+ "node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -2413,10 +2547,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "license": "MIT"
+ },
"node_modules/electron-to-chromium": {
- "version": "1.5.139",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.139.tgz",
- "integrity": "sha512-GGnRYOTdN5LYpwbIr0rwP/ZHOQSvAF6TG0LSzp28uCBb9JiXHJGmaaKw29qjNJc5bGnnp6kXJqRnGMQoELwi5w==",
+ "version": "1.5.190",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz",
+ "integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==",
"dev": true,
"license": "ISC"
},
@@ -2424,27 +2564,37 @@
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
"license": "MIT"
},
- "node_modules/enhanced-resolve": {
- "version": "5.18.1",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
- "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
- "dev": true,
+ "node_modules/encoding-sniffer": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
+ "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
"license": "MIT",
"dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
+ "iconv-lite": "^0.6.3",
+ "whatwg-encoding": "^3.1.1"
},
+ "funding": {
+ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
"engines": {
- "node": ">=10.13.0"
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es-abstract": {
- "version": "1.23.9",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
- "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
+ "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2452,18 +2602,18 @@
"arraybuffer.prototype.slice": "^1.0.4",
"available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
+ "call-bound": "^1.0.4",
"data-view-buffer": "^1.0.2",
"data-view-byte-length": "^1.0.2",
"data-view-byte-offset": "^1.0.1",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
+ "es-object-atoms": "^1.1.1",
"es-set-tostringtag": "^2.1.0",
"es-to-primitive": "^1.3.0",
"function.prototype.name": "^1.1.8",
- "get-intrinsic": "^1.2.7",
- "get-proto": "^1.0.0",
+ "get-intrinsic": "^1.3.0",
+ "get-proto": "^1.0.1",
"get-symbol-description": "^1.1.0",
"globalthis": "^1.0.4",
"gopd": "^1.2.0",
@@ -2475,21 +2625,24 @@
"is-array-buffer": "^3.0.5",
"is-callable": "^1.2.7",
"is-data-view": "^1.0.2",
+ "is-negative-zero": "^2.0.3",
"is-regex": "^1.2.1",
+ "is-set": "^2.0.3",
"is-shared-array-buffer": "^1.0.4",
"is-string": "^1.1.1",
"is-typed-array": "^1.1.15",
- "is-weakref": "^1.1.0",
+ "is-weakref": "^1.1.1",
"math-intrinsics": "^1.1.0",
- "object-inspect": "^1.13.3",
+ "object-inspect": "^1.13.4",
"object-keys": "^1.1.1",
"object.assign": "^4.1.7",
"own-keys": "^1.0.1",
- "regexp.prototype.flags": "^1.5.3",
+ "regexp.prototype.flags": "^1.5.4",
"safe-array-concat": "^1.1.3",
"safe-push-apply": "^1.0.0",
"safe-regex-test": "^1.1.0",
"set-proto": "^1.0.0",
+ "stop-iteration-iterator": "^1.1.0",
"string.prototype.trim": "^1.2.10",
"string.prototype.trimend": "^1.0.9",
"string.prototype.trimstart": "^1.0.8",
@@ -2498,7 +2651,7 @@
"typed-array-byte-offset": "^1.0.4",
"typed-array-length": "^1.0.7",
"unbox-primitive": "^1.1.0",
- "which-typed-array": "^1.1.18"
+ "which-typed-array": "^1.1.19"
},
"engines": {
"node": ">= 0.4"
@@ -2511,7 +2664,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2521,7 +2673,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2559,7 +2710,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -2572,7 +2722,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -2615,6 +2764,45 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -2639,20 +2827,20 @@
}
},
"node_modules/eslint": {
- "version": "9.24.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz",
- "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
+ "version": "9.31.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
+ "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.20.0",
- "@eslint/config-helpers": "^0.2.0",
- "@eslint/core": "^0.12.0",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.0",
+ "@eslint/core": "^0.15.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.24.0",
- "@eslint/plugin-kit": "^0.2.7",
+ "@eslint/js": "9.31.0",
+ "@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -2663,9 +2851,9 @@
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.3.0",
- "eslint-visitor-keys": "^4.2.0",
- "espree": "^10.3.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
"esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -2699,203 +2887,6 @@
}
}
},
- "node_modules/eslint-config-next": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.2.5.tgz",
- "integrity": "sha512-/aUpN5FVI3FD+OB4gY0GyD2TwIOjLk8mG0B9NxVsSn8/svNmzFaIAaS80ZO1zWaIcWxrzTy2FcPVdsCK7eiceA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@next/eslint-plugin-next": "15.2.5",
- "@rushstack/eslint-patch": "^1.10.3",
- "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
- "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
- "eslint-import-resolver-node": "^0.3.6",
- "eslint-import-resolver-typescript": "^3.5.2",
- "eslint-plugin-import": "^2.31.0",
- "eslint-plugin-jsx-a11y": "^6.10.0",
- "eslint-plugin-react": "^7.37.0",
- "eslint-plugin-react-hooks": "^5.0.0"
- },
- "peerDependencies": {
- "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0",
- "typescript": ">=3.3.1"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-import-resolver-node": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
- "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^3.2.7",
- "is-core-module": "^2.13.0",
- "resolve": "^1.22.4"
- }
- },
- "node_modules/eslint-import-resolver-node/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-import-resolver-typescript": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.0.tgz",
- "integrity": "sha512-aV3/dVsT0/H9BtpNwbaqvl+0xGMRGzncLyhm793NFGvbwGGvzyAykqWZ8oZlZuGwuHkwJjhWJkG1cM3ynvd2pQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@nolyfill/is-core-module": "1.0.39",
- "debug": "^4.4.0",
- "get-tsconfig": "^4.10.0",
- "is-bun-module": "^2.0.0",
- "stable-hash": "^0.0.5",
- "tinyglobby": "^0.2.12",
- "unrs-resolver": "^1.3.2"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint-import-resolver-typescript"
- },
- "peerDependencies": {
- "eslint": "*",
- "eslint-plugin-import": "*",
- "eslint-plugin-import-x": "*"
- },
- "peerDependenciesMeta": {
- "eslint-plugin-import": {
- "optional": true
- },
- "eslint-plugin-import-x": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-module-utils": {
- "version": "2.12.0",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
- "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^3.2.7"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependenciesMeta": {
- "eslint": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-module-utils/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-plugin-import": {
- "version": "2.31.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
- "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@rtsao/scc": "^1.1.0",
- "array-includes": "^3.1.8",
- "array.prototype.findlastindex": "^1.2.5",
- "array.prototype.flat": "^1.3.2",
- "array.prototype.flatmap": "^1.3.2",
- "debug": "^3.2.7",
- "doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.9",
- "eslint-module-utils": "^2.12.0",
- "hasown": "^2.0.2",
- "is-core-module": "^2.15.1",
- "is-glob": "^4.0.3",
- "minimatch": "^3.1.2",
- "object.fromentries": "^2.0.8",
- "object.groupby": "^1.0.3",
- "object.values": "^1.2.0",
- "semver": "^6.3.1",
- "string.prototype.trimend": "^1.0.8",
- "tsconfig-paths": "^3.15.0"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependencies": {
- "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/eslint-plugin-jsx-a11y": {
- "version": "6.10.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz",
- "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "aria-query": "^5.3.2",
- "array-includes": "^3.1.8",
- "array.prototype.flatmap": "^1.3.2",
- "ast-types-flow": "^0.0.8",
- "axe-core": "^4.10.0",
- "axobject-query": "^4.1.0",
- "damerau-levenshtein": "^1.0.8",
- "emoji-regex": "^9.2.2",
- "hasown": "^2.0.2",
- "jsx-ast-utils": "^3.3.5",
- "language-tags": "^1.0.9",
- "minimatch": "^3.1.2",
- "object.fromentries": "^2.0.8",
- "safe-regex-test": "^1.0.3",
- "string.prototype.includes": "^2.0.1"
- },
- "engines": {
- "node": ">=4.0"
- },
- "peerDependencies": {
- "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9"
- }
- },
"node_modules/eslint-plugin-react": {
"version": "7.37.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
@@ -2942,38 +2933,20 @@
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
}
},
- "node_modules/eslint-plugin-react/node_modules/resolve": {
- "version": "2.0.0-next.5",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
- "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.20",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz",
+ "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "is-core-module": "^2.13.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/eslint-plugin-react/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
+ "peerDependencies": {
+ "eslint": ">=8.40"
}
},
"node_modules/eslint-scope": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
- "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -2988,9 +2961,9 @@
}
},
"node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -3001,15 +2974,15 @@
}
},
"node_modules/espree": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
- "acorn": "^8.14.0",
+ "acorn": "^8.15.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.0"
+ "eslint-visitor-keys": "^4.2.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3072,17 +3045,16 @@
"license": "MIT"
},
"node_modules/fast-glob": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
- "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
- "dev": true,
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
- "micromatch": "^4.0.4"
+ "micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
@@ -3092,7 +3064,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
@@ -3119,27 +3090,11 @@
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
- "dev": true,
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
}
},
- "node_modules/fdir": {
- "version": "6.4.3",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
- "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -3157,7 +3112,6 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
@@ -3204,6 +3158,26 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -3220,6 +3194,38 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -3234,11 +3240,24 @@
"url": "https://github.com/sponsors/rawify"
}
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -3275,11 +3294,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -3304,7 +3332,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@@ -3332,24 +3359,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/get-tsconfig": {
- "version": "4.10.0",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz",
- "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==",
- "dev": true,
- "license": "MIT",
+ "node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "license": "ISC",
"dependencies": {
- "resolve-pkg-maps": "^1.0.0"
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
},
"funding": {
- "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ "url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
@@ -3358,10 +3391,34 @@
"node": ">=10.13.0"
}
},
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "version": "15.15.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
+ "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3392,7 +3449,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -3401,20 +3457,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/has-bigints": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -3471,7 +3513,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -3484,7 +3525,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -3500,7 +3540,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -3510,11 +3549,54 @@
}
},
"node_modules/hls.js": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.1.tgz",
- "integrity": "sha512-7GOkcqn0Y9EqU2OJZlzkwxj9Uynuln7URvr7dRjgqNJNZ5UbbjL/v1BjAvQogy57Psdd/ek1u2s6IDEFYlabrA==",
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.7.tgz",
+ "integrity": "sha512-QW2fnwDGKGc9DwQUGLbmMOz8G48UZK7PVNJPcOUql1b8jubKx4/eMHNP5mGqr6tYlJNDG1g10Lx2U/qPzL6zwQ==",
"license": "Apache-2.0"
},
+ "node_modules/htmlparser2": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz",
+ "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.2.1",
+ "entities": "^6.0.0"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3585,13 +3667,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-arrayish": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
- "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
- "license": "MIT",
- "optional": true
- },
"node_modules/is-async-function": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
@@ -3628,6 +3703,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-boolean-object": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
@@ -3645,16 +3732,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-bun-module": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz",
- "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.7.1"
- }
- },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -3672,7 +3749,6 @@
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
- "dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@@ -3723,7 +3799,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -3745,6 +3820,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-generator-function": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
@@ -3768,7 +3852,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -3790,11 +3873,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
@@ -3973,7 +4068,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
"license": "ISC"
},
"node_modules/iterator.prototype": {
@@ -3994,21 +4088,34 @@
"node": ">= 0.4"
}
},
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
"node_modules/jiti": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
- "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
- "dev": true,
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"license": "MIT",
"bin": {
- "jiti": "lib/jiti-cli.mjs"
+ "jiti": "bin/jiti.js"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -4024,6 +4131,19 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -4046,16 +4166,16 @@
"license": "MIT"
},
"node_modules/json5": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
- "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "minimist": "^1.2.0"
- },
"bin": {
"json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
}
},
"node_modules/jsx-ast-utils": {
@@ -4084,24 +4204,13 @@
"json-buffer": "3.0.1"
}
},
- "node_modules/language-subtag-registry": {
- "version": "0.3.23",
- "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
- "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
- "dev": true,
- "license": "CC0-1.0"
- },
- "node_modules/language-tags": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
- "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
- "dev": true,
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"license": "MIT",
- "dependencies": {
- "language-subtag-registry": "^0.3.20"
- },
"engines": {
- "node": ">=0.10"
+ "node": ">=0.10.0"
}
},
"node_modules/levn": {
@@ -4118,244 +4227,23 @@
"node": ">= 0.8.0"
}
},
- "node_modules/lightningcss": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
- "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==",
- "dev": true,
- "license": "MPL-2.0",
- "dependencies": {
- "detect-libc": "^2.0.3"
- },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "license": "MIT",
"engines": {
- "node": ">= 12.0.0"
+ "node": ">=14"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-darwin-arm64": "1.29.2",
- "lightningcss-darwin-x64": "1.29.2",
- "lightningcss-freebsd-x64": "1.29.2",
- "lightningcss-linux-arm-gnueabihf": "1.29.2",
- "lightningcss-linux-arm64-gnu": "1.29.2",
- "lightningcss-linux-arm64-musl": "1.29.2",
- "lightningcss-linux-x64-gnu": "1.29.2",
- "lightningcss-linux-x64-musl": "1.29.2",
- "lightningcss-win32-arm64-msvc": "1.29.2",
- "lightningcss-win32-x64-msvc": "1.29.2"
+ "url": "https://github.com/sponsors/antonk52"
}
},
- "node_modules/lightningcss-darwin-arm64": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz",
- "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-x64": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz",
- "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-freebsd-x64": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz",
- "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz",
- "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz",
- "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz",
- "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz",
- "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-musl": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz",
- "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz",
- "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz",
- "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
},
"node_modules/locate-path": {
"version": "6.0.0",
@@ -4384,7 +4272,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -4393,11 +4280,29 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.447.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.447.0.tgz",
+ "integrity": "sha512-SZ//hQmvi+kDKrNepArVkYK7/jfeZ5uFNEnYmd45RKZcbGD78KLnrcNXmgeg6m+xNHFvTG+CblszXCy4n6DN4w==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -4407,7 +4312,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@@ -4417,7 +4321,6 @@
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
@@ -4427,6 +4330,27 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -4440,14 +4364,13 @@
"node": "*"
}
},
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
}
},
"node_modules/ms": {
@@ -4457,6 +4380,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -4482,88 +4416,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/next": {
- "version": "15.2.5",
- "resolved": "https://registry.npmjs.org/next/-/next-15.2.5.tgz",
- "integrity": "sha512-LlqS8ljc7RWR3riUwxB5+14v7ULAa5EuLUyarD/sFgXPd6Hmmscg8DXcu9hDdh5atybrIDVBrFhjDpRIQo/4pQ==",
- "license": "MIT",
- "dependencies": {
- "@next/env": "15.2.5",
- "@swc/counter": "0.1.3",
- "@swc/helpers": "0.5.15",
- "busboy": "1.6.0",
- "caniuse-lite": "^1.0.30001579",
- "postcss": "8.4.31",
- "styled-jsx": "5.1.6"
- },
- "bin": {
- "next": "dist/bin/next"
- },
- "engines": {
- "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
- },
- "optionalDependencies": {
- "@next/swc-darwin-arm64": "15.2.5",
- "@next/swc-darwin-x64": "15.2.5",
- "@next/swc-linux-arm64-gnu": "15.2.5",
- "@next/swc-linux-arm64-musl": "15.2.5",
- "@next/swc-linux-x64-gnu": "15.2.5",
- "@next/swc-linux-x64-musl": "15.2.5",
- "@next/swc-win32-arm64-msvc": "15.2.5",
- "@next/swc-win32-x64-msvc": "15.2.5",
- "sharp": "^0.33.5"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.1.0",
- "@playwright/test": "^1.41.2",
- "babel-plugin-react-compiler": "*",
- "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
- "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
- "sass": "^1.3.0"
- },
- "peerDependenciesMeta": {
- "@opentelemetry/api": {
- "optional": true
- },
- "@playwright/test": {
- "optional": true
- },
- "babel-plugin-react-compiler": {
- "optional": true
- },
- "sass": {
- "optional": true
- }
- }
- },
- "node_modules/next/node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -4571,6 +4423,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
@@ -4581,16 +4442,36 @@
"node": ">=0.10.0"
}
},
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -4670,21 +4551,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/object.groupby": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
- "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/object.values": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
@@ -4704,6 +4570,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/option-validator": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/option-validator/-/option-validator-2.0.6.tgz",
+ "integrity": "sha512-tmZDan2LRIRQyhUGvkff68/O0R8UmF+Btmiiz0SmSw2ng3CfPZB9wJlIjHpe/MKUZqyIZkVIXCrwr1tIN+0Dzg==",
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^6.0.3"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -4772,6 +4647,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "license": "BlueOak-1.0.0"
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -4785,6 +4666,55 @@
"node": ">=6"
}
},
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5-htmlparser2-tree-adapter": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
+ "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
+ "license": "MIT",
+ "dependencies": {
+ "domhandler": "^5.0.3",
+ "parse5": "^7.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5-parser-stream": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
+ "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
+ "license": "MIT",
+ "dependencies": {
+ "parse5": "^7.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5/node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -4799,7 +4729,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -4809,9 +4738,30 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -4822,7 +4772,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -4831,6 +4780,24 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -4842,10 +4809,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.3",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
- "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
- "dev": true,
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
@@ -4862,7 +4828,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.8",
+ "nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -4870,11 +4836,139 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-import/node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/prelude-ls": {
@@ -4891,7 +4985,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
@@ -4919,7 +5012,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -4937,33 +5029,130 @@
"license": "MIT"
},
"node_modules/react": {
- "version": "19.1.0",
- "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
- "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/react-dom": {
- "version": "19.1.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
- "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
+ "node_modules/react-content-loader": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/react-content-loader/-/react-content-loader-7.1.1.tgz",
+ "integrity": "sha512-yNkqtd+15wXRLfDKZb5nTqDV2fPTG2kpUgeGRb+WBz43bU0j4DSGXETF0bnFr44fAoTPpm0Dya0WGdhpHSvtYA==",
"license": "MIT",
- "dependencies": {
- "scheduler": "^0.26.0"
+ "engines": {
+ "node": ">=10"
},
"peerDependencies": {
- "react": "^19.1.0"
+ "react": ">=16.0.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/react-lazy-load": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/react-lazy-load/-/react-lazy-load-4.0.1.tgz",
+ "integrity": "sha512-TnXRr79X9rlC9UcmO6iyS28rOPHrgkHIP4+b8yZPfs1tw6k/Rp2DmFY8R20BqWR45ZWkpT+4dqV1f+yci+1ozg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.30.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
+ "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.30.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
+ "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.0",
+ "react-router": "6.30.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -5009,22 +5198,19 @@
}
},
"node_modules/resolve": {
- "version": "1.22.10",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
- "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "version": "2.0.0-next.5",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
+ "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "is-core-module": "^2.16.0",
+ "is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
- "engines": {
- "node": ">= 0.4"
- },
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -5039,32 +5225,60 @@
"node": ">=4"
}
},
- "node_modules/resolve-pkg-maps": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
- "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
- }
- },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
}
},
+ "node_modules/rollup": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz",
+ "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.45.1",
+ "@rollup/rollup-android-arm64": "4.45.1",
+ "@rollup/rollup-darwin-arm64": "4.45.1",
+ "@rollup/rollup-darwin-x64": "4.45.1",
+ "@rollup/rollup-freebsd-arm64": "4.45.1",
+ "@rollup/rollup-freebsd-x64": "4.45.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.45.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.45.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.45.1",
+ "@rollup/rollup-linux-arm64-musl": "4.45.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.45.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.45.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.45.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.45.1",
+ "@rollup/rollup-linux-x64-gnu": "4.45.1",
+ "@rollup/rollup-linux-x64-musl": "4.45.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.45.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.45.1",
+ "@rollup/rollup-win32-x64-msvc": "4.45.1",
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -5139,23 +5353,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/scheduler": {
- "version": "0.26.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
- "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
"node_modules/semver": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
- "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
- "devOptional": true,
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
}
},
"node_modules/set-function-length": {
@@ -5207,51 +5427,16 @@
"node": ">= 0.4"
}
},
- "node_modules/sharp": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
- "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "optional": true,
- "dependencies": {
- "color": "^4.2.3",
- "detect-libc": "^2.0.3",
- "semver": "^7.6.3"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.33.5",
- "@img/sharp-darwin-x64": "0.33.5",
- "@img/sharp-libvips-darwin-arm64": "1.0.4",
- "@img/sharp-libvips-darwin-x64": "1.0.4",
- "@img/sharp-libvips-linux-arm": "1.0.5",
- "@img/sharp-libvips-linux-arm64": "1.0.4",
- "@img/sharp-libvips-linux-s390x": "1.0.4",
- "@img/sharp-libvips-linux-x64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
- "@img/sharp-linux-arm": "0.33.5",
- "@img/sharp-linux-arm64": "0.33.5",
- "@img/sharp-linux-s390x": "0.33.5",
- "@img/sharp-linux-x64": "0.33.5",
- "@img/sharp-linuxmusl-arm64": "0.33.5",
- "@img/sharp-linuxmusl-x64": "0.33.5",
- "@img/sharp-wasm32": "0.33.5",
- "@img/sharp-win32-ia32": "0.33.5",
- "@img/sharp-win32-x64": "0.33.5"
- }
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -5264,7 +5449,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -5346,14 +5530,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/simple-swizzle": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
- "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "is-arrayish": "^0.3.1"
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/source-map-js": {
@@ -5365,36 +5551,79 @@
"node": ">=0.10.0"
}
},
- "node_modules/stable-hash": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
- "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/streamsearch": {
+ "node_modules/stop-iteration-iterator": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
- "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/string.prototype.includes": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
- "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.3"
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
}
},
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/string.prototype.matchall": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
@@ -5493,14 +5722,41 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
- "node": ">=4"
+ "node": ">=8"
}
},
"node_modules/strip-json-comments": {
@@ -5516,27 +5772,88 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/styled-jsx": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
- "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
+ "node_modules/styled-components": {
+ "version": "6.1.19",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz",
+ "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==",
"license": "MIT",
"dependencies": {
- "client-only": "0.0.1"
+ "@emotion/is-prop-valid": "1.2.2",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.5",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.3",
+ "postcss": "8.4.49",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.2",
+ "tslib": "2.6.2"
},
"engines": {
- "node": ">= 12.0.0"
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
},
"peerDependencies": {
- "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
- },
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/postcss": {
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
},
- "babel-plugin-macros": {
- "optional": true
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
}
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+ "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==",
+ "license": "MIT"
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
}
},
"node_modules/supports-color": {
@@ -5556,7 +5873,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -5566,9 +5882,9 @@
}
},
"node_modules/swiper": {
- "version": "11.2.6",
- "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.6.tgz",
- "integrity": "sha512-8aXpYKtjy3DjcbzZfz+/OX/GhcU5h+looA6PbAzHMZT6ESSycSp9nAjPCenczgJyslV+rUGse64LMGpWE3PX9Q==",
+ "version": "11.2.10",
+ "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.10.tgz",
+ "integrity": "sha512-RMeVUUjTQH+6N3ckimK93oxz6Sn5la4aDlgPzB+rBrG/smPdCTicXyhxa+woIpopz+jewEloiEE3lKo1h9w2YQ==",
"funding": [
{
"type": "patreon",
@@ -5584,58 +5900,107 @@
"node": ">= 4.7.0"
}
},
- "node_modules/tailwindcss": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.3.tgz",
- "integrity": "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
- "dev": true,
+ "node_modules/tailwind-merge": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
+ "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
"license": "MIT",
- "engines": {
- "node": ">=6"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
}
},
- "node_modules/tinyglobby": {
- "version": "0.2.12",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
- "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
- "dev": true,
+ "node_modules/tailwindcss": {
+ "version": "3.4.17",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
+ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"license": "MIT",
"dependencies": {
- "fdir": "^6.4.3",
- "picomatch": "^4.0.2"
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.6",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
},
"engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
+ "node": ">=14.0.0"
}
},
- "node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true,
+ "node_modules/tailwindcss-animate": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
+ "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
"license": "MIT",
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || insiders"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
"engines": {
- "node": ">=12"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
@@ -5644,36 +6009,16 @@
"node": ">=8.0"
}
},
- "node_modules/ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.12"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4"
- }
- },
- "node_modules/tsconfig-paths": {
- "version": "3.15.0",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
- "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/json5": "^0.0.29",
- "json5": "^1.0.2",
- "minimist": "^1.2.6",
- "strip-bom": "^3.0.0"
- }
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "license": "Apache-2.0"
},
"node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"license": "0BSD"
},
"node_modules/type-check": {
@@ -5786,31 +6131,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/unrs-resolver": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.4.1.tgz",
- "integrity": "sha512-MhPB3wBI5BR8TGieTb08XuYlE8oFVEXdSAgat3psdlRyejl8ojQ8iqPcjh094qCZ1r+TnkxzP6BeCd/umfHckQ==",
- "dev": true,
+ "node_modules/undici": {
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.12.0.tgz",
+ "integrity": "sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==",
"license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/JounQin"
- },
- "optionalDependencies": {
- "@unrs/resolver-binding-darwin-arm64": "1.4.1",
- "@unrs/resolver-binding-darwin-x64": "1.4.1",
- "@unrs/resolver-binding-freebsd-x64": "1.4.1",
- "@unrs/resolver-binding-linux-arm-gnueabihf": "1.4.1",
- "@unrs/resolver-binding-linux-arm-musleabihf": "1.4.1",
- "@unrs/resolver-binding-linux-arm64-gnu": "1.4.1",
- "@unrs/resolver-binding-linux-arm64-musl": "1.4.1",
- "@unrs/resolver-binding-linux-ppc64-gnu": "1.4.1",
- "@unrs/resolver-binding-linux-s390x-gnu": "1.4.1",
- "@unrs/resolver-binding-linux-x64-gnu": "1.4.1",
- "@unrs/resolver-binding-linux-x64-musl": "1.4.1",
- "@unrs/resolver-binding-wasm32-wasi": "1.4.1",
- "@unrs/resolver-binding-win32-arm64-msvc": "1.4.1",
- "@unrs/resolver-binding-win32-ia32-msvc": "1.4.1",
- "@unrs/resolver-binding-win32-x64-msvc": "1.4.1"
+ "engines": {
+ "node": ">=20.18.1"
}
},
"node_modules/update-browserslist-db": {
@@ -5854,11 +6181,97 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "5.4.19",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
+ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -5969,6 +6382,113 @@
"node": ">=0.10.0"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
+ "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/package.json b/package.json
index 48b5f2e..0abc694 100644
--- a/package.json
+++ b/package.json
@@ -1,31 +1,54 @@
{
"name": "justanime",
- "version": "0.1.0",
"private": true,
+ "version": "0.0.0",
+ "type": "module",
"scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
- "lint": "next lint"
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint .",
+ "preview": "vite preview",
+ "host": "vite --host"
},
"dependencies": {
- "@heroicons/react": "^2.2.0",
- "@vercel/analytics": "^1.5.0",
- "@vercel/speed-insights": "^1.2.0",
- "hls.js": "^1.5.7",
- "next": "latest",
- "proxy-from-env": "^1.1.0",
- "react": "latest",
- "react-dom": "latest",
- "swiper": "^11.2.6"
+ "@fortawesome/fontawesome-svg-core": "^6.6.0",
+ "@fortawesome/free-brands-svg-icons": "^6.7.2",
+ "@fortawesome/free-solid-svg-icons": "^6.6.0",
+ "@fortawesome/react-fontawesome": "^0.2.2",
+ "@radix-ui/react-icons": "^1.3.0",
+ "artplayer": "^5.2.3",
+ "artplayer-plugin-chapter": "^1.0.0",
+ "artplayer-plugin-hls-control": "^1.0.1",
+ "axios": "^1.7.7",
+ "cheerio": "^1.0.0",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.1",
+ "hls.js": "^1.5.17",
+ "lucide-react": "^0.447.0",
+ "react": "^18.3.1",
+ "react-content-loader": "^7.0.2",
+ "react-dom": "^18.3.1",
+ "react-icons": "^5.3.0",
+ "react-lazy-load": "^4.0.1",
+ "react-router-dom": "^6.26.2",
+ "styled-components": "^6.1.13",
+ "swiper": "^11.2.5",
+ "tailwind-merge": "^2.5.3",
+ "tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
- "@eslint/eslintrc": "^3",
- "@tailwindcss/postcss": "^4",
- "autoprefixer": "latest",
- "eslint": "^9",
- "eslint-config-next": "15.2.5",
- "postcss": "latest",
- "tailwindcss": "^4"
+ "@eslint/js": "^9.9.0",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^9.9.0",
+ "eslint-plugin-react": "^7.35.0",
+ "eslint-plugin-react-hooks": "^5.1.0-rc.0",
+ "eslint-plugin-react-refresh": "^0.4.9",
+ "globals": "^15.9.0",
+ "postcss": "^8.4.47",
+ "tailwindcss": "^3.4.13",
+ "vite": "^5.4.1"
}
}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/postcss.config.mjs b/postcss.config.mjs
deleted file mode 100644
index c7bcb4b..0000000
--- a/postcss.config.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-const config = {
- plugins: ["@tailwindcss/postcss"],
-};
-
-export default config;
diff --git a/public/images/placeholder.png b/public/images/placeholder.png
deleted file mode 100644
index 490b3f6..0000000
Binary files a/public/images/placeholder.png and /dev/null differ
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..1e85586
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,4 @@
+User-Agent: *
+Allow: /
+
+Sitemap: https://zenime.site/sitemap.xml
\ No newline at end of file
diff --git a/public/sitemap.xml b/public/sitemap.xml
new file mode 100644
index 0000000..87923c4
--- /dev/null
+++ b/public/sitemap.xml
@@ -0,0 +1,2430 @@
+
+
+
+ https://zenime.site/
+ 2024-11-08T15:50:46+00:00
+ 1.00
+
+
+ https://zenime.site/top-upcoming
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/movie
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/tv
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/most-popular
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/top-airing
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+
+ https://zenime.site/search?keyword=Dandadan
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/search?keyword=Blue%20Box
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/search?keyword=Haikyu!!%20Movie%3A%20Battle%20of%20the%20Garbage%20Dump
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/search?keyword=One%20Piece
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/search?keyword=Dragon%20Ball%20Daima
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/search?keyword=Solo%20Leveling
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/search?keyword=Re%3AZERO%20-Starting%20Life%20in%20Another%20World-%20Season%203
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/search?keyword=Attack%20on%20Titan
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/search?keyword=Bleach%3A%20Thousand-Year%20Blood%20War%20-%20The%20Conflict
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/search?keyword=My%20Hero%20Academia%20Season%207
+ 2024-11-08T15:50:46+00:00
+ 0.80
+
+
+ https://zenime.site/basilisk-1307
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/subbed-anime
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/dubbed-anime
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/ova
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/ona
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/special
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/app-download
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/action
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/adventure
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/cars
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/comedy
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/dementia
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/demons
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/drama
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/ecchi
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/fantasy
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/game
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/harem
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/historical
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/horror
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/isekai
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/josei
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/kids
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/magic
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/marial-arts
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/mecha
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/military
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/music
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/mystery
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/parody
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/police
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/psychological
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/romance
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/samurai
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/school
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/sci-fi
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/seinen
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/shoujo
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/shoujo-ai
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/shounen
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/shounen-ai
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/slice-of-life
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/space
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/sports
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/super-power
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/supernatural
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/thriller
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genre/vampire
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/filter
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/ranma-1-2-19335
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/ranma-1-2-19335
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/the-elusive-samurai-19233
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-elusive-samurai-19233
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/no-longer-allowed-in-another-world-19247
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/no-longer-allowed-in-another-world-19247
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/why-does-nobody-remember-me-in-this-world-19240
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/why-does-nobody-remember-me-in-this-world-19240
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-piece-100
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-piece-100
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/the-strongest-magician-in-the-demon-lords-army-was-a-human-19238
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-strongest-magician-in-the-demon-lords-army-was-a-human-19238
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/pseudo-harem-19246
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/pseudo-harem-19246
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/demon-slayer-kimetsu-no-yaiba-hashira-training-arc-19107
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/demon-slayer-kimetsu-no-yaiba-hashira-training-arc-19107
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/wind-breaker-19136
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/wind-breaker-19136
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/bleach-806
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/bleach-806
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/dandadan-19319
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-lock-season-2-19318
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-box-19326
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/bleach-thousand-year-blood-war-the-conflict-19322
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/rurouni-kenshin-kyoto-disturbance-19340
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/rezero-starting-life-in-another-world-season-3-19301
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/my-star-season-2-19256
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/good-bye-dragon-life-19347
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/case-closed-323
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/naruto-shippuden-355
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/jujutsu-kaisen-2nd-season-18413
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/black-clover-2404
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/chainsaw-man-17406
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/jujutsu-kaisen-tv-534
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/demon-slayer-kimetsu-no-yaiba-swordsmith-village-arc-18056
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/most-favorite
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/look-back-19083
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/i-parry-everything-19229
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/high-card-the-flowers-bloom-19410
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/jellyfish-cant-swim-in-the-night-19124
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/level-1-demon-lord-and-one-room-hero-18465
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/completed
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/recently-updated
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/hamidashi-creative-19368?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/hamidashi-creative-19368
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/25-dimensional-seduction-19245?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/25-dimensional-seduction-19245
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/another-journey-to-the-west-19402?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/another-journey-to-the-west-19402
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/rurouni-kenshin-kyoto-disturbance-19340?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/trillion-game-19362?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/trillion-game-19362
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/good-bye-dragon-life-19347?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/365-days-to-the-wedding-19332?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/365-days-to-the-wedding-19332
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/mechanical-arms-19354?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/mechanical-arms-19354
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/look-back-19083?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/dandadan-19319?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/loner-life-in-another-world-19337?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/loner-life-in-another-world-19337
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/kinoko-inu-19373?w=latest
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/kinoko-inu-19373
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/recently-added
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/look-back-19083
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/i-parry-everything-19229
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/high-card-the-flowers-bloom-19410
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/jellyfish-cant-swim-in-the-night-19124
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/level-1-demon-lord-and-one-room-hero-18465
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/future-folktales-8383
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/future-folktales-8383
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/mission-yozakura-family-19133
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/mission-yozakura-family-19133
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/da-shen-xian-17405
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/da-shen-xian-17405
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/da-shen-xian-17526
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/da-shen-xian-17526
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/crayon-shin-chan-movie-31-chounouryoku-daikessen-tobe-tobe-temakizushi-18427
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/crayon-shin-chan-movie-31-chounouryoku-daikessen-tobe-tobe-temakizushi-18427
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/crayon-shin-chan-movie-30-mononoke-ninja-chinpuuden-19408
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/crayon-shin-chan-movie-30-mononoke-ninja-chinpuuden-19408
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/cardfight-vanguard-divinez-season-2-19206
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/cardfight-vanguard-divinez-season-2-19206
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/rakshasa-street-4th-season-19411
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/murder-mystery-of-the-dead-19405
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/kagaku-x-bouken-survival-19376
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/genjitsu-no-yohane-sunshine-in-the-mirror-movie-19394
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/give-it-all-19393
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/solo-leveling-reawakening-19392
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/attack-on-titan-the-last-attack-19391
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/gintama-on-theater-2d-kintama-hen-19389
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/beastars-final-season-19385
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/pinkfong-gwa-hogi-sae-chingu-ninimo-19384
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/pochaazu-19383
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/kumarba-season-2-19382
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/is-it-wrong-to-try-to-pick-up-girls-in-a-dungeon-v-19323
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/haikyu-movie-battle-of-the-garbage-dump-18922
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/my-hero-academia-season-7-19146
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/dragon-ball-daima-19328
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/other
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/0-9
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/A
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/B
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/C
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/D
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/E
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/F
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/G
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/H
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/I
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/J
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/K
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/L
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/M
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/N
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/O
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/P
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/Q
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/R
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/S
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/T
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/U
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/V
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/W
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/X
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/Y
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/az-list/Z
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/spy-x-family-code-white-19291
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/be-forever-yamato-rebel-3199-19192
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/idolish7-movie-live-4bit-beyond-the-period-19176
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-lock-episode-nagi-19085
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/my-oni-girl-19076
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/kuramerukagari-19075
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/rascal-does-not-dream-of-a-sister-venturing-out-18934
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/dead-dead-demons-dededede-destruction-18925
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/oomuro-ke-dear-sisters-18916
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/ya-boy-kongming-road-to-summer-sonia-18913
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/mobile-suit-gundam-seed-freedom-18910
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/psycho-pass-movie-providence-18715
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/white-snake-2-the-tribulation-of-the-green-snake-18682
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/detective-conan-movie-26-black-iron-submarine-18655
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/my-hero-academia-ua-heroes-battle-18629
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/digimon-adventure-02-movie-18562
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-imaginary-18561
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/i-am-what-i-am-18555
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-summer-18553
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/boonie-bears-back-to-earth-18549
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/boonie-bears-the-wild-life-18548
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/free-movie-5-the-final-stroke-kouhen-18504
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/maboroshi-18434
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/sound-euphonium-ensemble-contest-arc-18433
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/kaina-of-the-great-snow-sea-star-sage-18432
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/resident-evil-death-island-18429
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/detective-conan-movie-the-story-of-haibara-ai-black-iron-mystery-train-18412
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/tsurune-movie-hajimari-no-issha-18411
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/backflip-the-movie-18405
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/mobile-suit-gundam-cucuruz-doans-island-18400
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/movie?page=2
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/movie?page=3
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/movie?page=32
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-piece-log-fish-man-island-saga-19404
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/tower-of-god-season-2-workshop-battle-19400
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/punirunes-2-19381
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/okaimono-panda-19379
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/asatir-2-mirai-no-mukashi-banashi-19378
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/neko-ni-tensei-shita-ojisan-19375
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/hyakushou-kizoku-2nd-season-19374
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/puniru-is-a-kawaii-slime-19372
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/tonbo-season-2-19370
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-idolm-at-ster-shiny-colors-2nd-season-19369
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/a-herbivorous-dragon-of-5000-years-gets-unfairly-villainized-2nd-season-19367
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/a-story-of-a-girl-that-was-unable-to-become-a-mage-19366
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-prince-of-tennis-u-17-world-cup-semifinal-19365
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/kamierabi-godapp-season-2-19364
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/negative-positive-angler-19363
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/haigakura-19361
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/acro-trip-19360
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/a-terrified-teacher-at-ghoul-school-19359
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/touhai-ura-rate-mahjong-touhai-roku-19358
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/magilumiere-co-ltd-19357
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/let-this-grieving-soul-retire-19356
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/how-i-attended-an-all-guys-mixer-19355
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-most-notorious-talker-runs-the-worlds-greatest-clan-19353
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/love-live-superstar-3rd-season-19352
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/about-the-movement-of-the-earth-19351
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-wolves-of-mibu-19350
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/demon-lord-retry-r-19349
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/nina-the-starry-bride-19348
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/after-school-hanako-kun-part-2-19346
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/tv?page=2
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/tv?page=3
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/tv?page=113
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/boruto-naruto-next-generations-8143
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/naruto-677
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/spy-x-family-17977
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/solo-leveling-18718
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/hunter-x-hunter-2
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-lock-17889
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/demon-slayer-entertainment-district-arc-17483
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/demon-slayer-kimetsu-no-yaiba-47
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/attack-on-titan-112
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/my-hero-academia-season-6-18154
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/hells-paradise-18332
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/dragon-ball-z-325
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-eminence-in-shadow-17473
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/dragon-ball-super-1692
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/attack-on-titan-final-season-part-2-17753
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/death-note-60
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/mashle-magic-and-muscles-18339
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/attack-on-titan-season-3-85
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/i-got-a-cheat-skill-in-another-world-and-became-unrivaled-in-the-real-world-too-18343
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/dragon-ball-509
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/attack-on-titan-final-season-part-1-15548
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/classroom-of-the-elite-2nd-season-18076
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/fairy-tail-930
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/my-hero-academia-5th-season-15666
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/bleach-thousandyear-blood-war-the-separation-18420
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/my-star-18330
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/vinland-saga-2nd-season-18239
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/that-time-i-got-reincarnated-as-a-slime-season-3-19109
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/jujutsu-kaisen-0-movie-17763
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/most-popular?page=2
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/most-popular?page=3
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/most-popular?page=50
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/fairy-tail-100-years-quest-19253
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/super-dragon-ball-heroes-9688
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/pokemon-horizons-the-series-18397
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/seirei-gensouki-spirit-chronicles-season-2-19320
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-healer-who-was-banished-from-his-party-is-in-fact-the-strongest-19345
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/shangri-la-frontier-season-2-19324
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/as-a-reincarnated-aristocrat-ill-use-my-appraisal-skill-to-rise-in-the-world-season-2-19329
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/arifureta-from-commonplace-to-worlds-strongest-season-3-19321
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/super-dragon-ball-heroes-big-bang-mission-17970
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/yakuza-fiance-19336
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/battle-through-the-heavens-5th-season-18119
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/ill-become-a-villainess-who-goes-down-in-history-19334
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/demon-lord-2099-19339
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/you-are-ms-servant-19331
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/shin-chan-1058
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-do-over-damsel-conquers-the-dragon-emperor-19341
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/tying-the-knot-with-an-amagami-sister-19338
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/beyblade-x-18632
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/soul-land-2-peerless-tang-sect-18416
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/swallowed-star-2nd-season-18018
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/top-airing?page=2
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/top-airing?page=3
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/top-airing?page=10
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/dandadan-19319
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/dandadan-19319
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-giant-18260
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-giant-18260
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-gender-3372
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-gender-3372
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-reflection-17485
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-reflection-17485
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-box-19326
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-box-19326
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-period-17427
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-period-17427
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-lock-17889
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-lock-17889
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-dragon-6179
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-dragon-6179
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-seed-3431
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-seed-3431
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/grand-blue-dreaming-139
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/grand-blue-dreaming-139
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/perfect-blue-127
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/perfect-blue-127
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-literature-893
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-literature-893
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-wolves-of-mibu-19350
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-wolves-of-mibu-19350
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-seed-beyond-6048
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-seed-beyond-6048
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/the-blue-orchestra-18359
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-blue-orchestra-18359
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/bb-fish-10438
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/bb-fish-10438
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/sweet-blue-flowers-2862
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/sweet-blue-flowers-2862
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/armed-blue-gunvolt-8786
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/armed-blue-gunvolt-8786
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-seed-omake-4193
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-seed-omake-4193
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-exorcist-1198
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-exorcist-1198
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-drop-4694
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-drop-4694
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-blink-4540
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-blink-4540
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/sky-blue-3412
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/sky-blue-3412
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/ginga-no-uo-ursa-minor-blue-7064
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/ginga-no-uo-ursa-minor-blue-7064
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-dragon-the-seven-dragons-of-the-heavens-5453
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-dragon-the-seven-dragons-of-the-heavens-5453
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-lock-season-2-19318
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-lock-season-2-19318
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/aoki-honoo-10273
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/aoki-honoo-10273
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/801-tts-airbats-5741
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/801-tts-airbats-5741
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/ao-no-exorcist-kyoto-fujouou-hen-ova-2896
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/ao-no-exorcist-kyoto-fujouou-hen-ova-2896
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-archive-the-animation-19125
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-archive-the-animation-19125
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-lock-episode-nagi-19085
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-lock-episode-nagi-19085
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/world-war-blue-8602
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/world-war-blue-8602
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/her-blue-sky-1418
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/her-blue-sky-1418
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-legend-shoot-1822
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-legend-shoot-1822
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-legend-shoot-4518
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-legend-shoot-4518
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/combat-mecha-xabungle-2768
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/combat-mecha-xabungle-2768
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blue-exorcist-kyoto-saga-1628
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blue-exorcist-kyoto-saga-1628
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/search?keyword=Blue%20Box&page=2
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/search?keyword=Blue%20Box&page=3
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/haikyu-movie-battle-of-the-garbage-dump-18922
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/haikyu-movie-battle-of-the-garbage-dump-18922
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/knights-of-the-zodiac-saint-seiya-battle-for-sanctuary-18135
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/knights-of-the-zodiac-saint-seiya-battle-for-sanctuary-18135
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/persona-3-the-movie-1-spring-of-birth-1244
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/persona-3-the-movie-1-spring-of-birth-1244
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/persona-3-the-movie-4-winter-of-rebirth-500
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/persona-3-the-movie-4-winter-of-rebirth-500
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/haikyu-76
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/haikyu-76
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/ex-driver-the-movie-5019
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/ex-driver-the-movie-5019
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/dirty-pair-project-eden-3340
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/dirty-pair-project-eden-3340
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/the-last-naruto-the-movie-882
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/the-last-naruto-the-movie-882
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/boruto-naruto-the-movie-1391
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/boruto-naruto-the-movie-1391
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/love-live-the-school-idol-movie-572
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/love-live-the-school-idol-movie-572
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/banner-of-the-stars-945
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/banner-of-the-stars-945
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/recycle-of-the-penguindrum-17796
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/recycle-of-the-penguindrum-17796
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/haikyu-2nd-season-29
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/haikyu-2nd-season-29
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/haikyu-3rd-season-18
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/haikyu-3rd-season-18
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/my-hero-academia-the-movie-3-world-heroes-mission-17334
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/my-hero-academia-the-movie-3-world-heroes-mission-17334
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/naruto-shippuuden-movie-6-road-to-ninja-1066
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/naruto-shippuuden-movie-6-road-to-ninja-1066
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/persona-3-the-movie-3-falling-down-1230
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/persona-3-the-movie-3-falling-down-1230
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/love-live-sunshine-the-school-idol-movie-over-the-rainbow-1211
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/love-live-sunshine-the-school-idol-movie-over-the-rainbow-1211
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/banner-of-the-stars-ii-753
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/banner-of-the-stars-ii-753
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/black-butler-book-of-the-atlantic-224
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/black-butler-book-of-the-atlantic-224
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/magi-the-labyrinth-of-magic-425
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/magi-the-labyrinth-of-magic-425
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/magi-the-kingdom-of-magic-208
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/magi-the-kingdom-of-magic-208
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/patlabor-the-movie-1464
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/patlabor-the-movie-1464
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/persona-3-the-movie-2-midsummer-knights-dream-1088
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/persona-3-the-movie-2-midsummer-knights-dream-1088
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/my-hero-academia-the-movie-2-heroes-rising-383
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/my-hero-academia-the-movie-2-heroes-rising-383
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/battle-of-clay-10620
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/battle-of-clay-10620
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/tales-of-luminaria-the-fateful-crossroad-17962
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/tales-of-luminaria-the-fateful-crossroad-17962
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/growlanser-iv-wayfarer-of-time-8906
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/growlanser-iv-wayfarer-of-time-8906
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/blackfox-4434
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/blackfox-4434
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/mobile-police-patlabor-2-the-movie-696
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/mobile-police-patlabor-2-the-movie-696
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/garouden-the-way-of-the-lone-wolf-19165
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/garouden-the-way-of-the-lone-wolf-19165
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/saint-seiya-the-movie-evil-goddess-eris-4181
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/saint-seiya-the-movie-evil-goddess-eris-4181
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/kabaneri-of-the-iron-fortress-the-battle-of-unato-933
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/kabaneri-of-the-iron-fortress-the-battle-of-unato-933
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/saint-seiya-knights-of-the-zodiac-battle-for-sanctuary-part-2-19090
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/saint-seiya-knights-of-the-zodiac-battle-for-sanctuary-part-2-19090
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/soul-hunter-3019
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/soul-hunter-3019
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/highschool-of-the-dead-drifters-of-the-dead-5101
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/highschool-of-the-dead-drifters-of-the-dead-5101
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/search?keyword=Haikyu!!%20Movie:%20Battle%20of%20the%20Garbage%20Dump&page=2
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/search?keyword=Haikyu!!%20Movie:%20Battle%20of%20the%20Garbage%20Dump&page=3
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/search?keyword=Haikyu!!%20Movie:%20Battle%20of%20the%20Garbage%20Dump&page=76
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-piece-movie-1-3096
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-piece-movie-1-3096
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-piece-100
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-piece-fan-letter-19406
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-piece-fan-letter-19406
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-piece-film-red-18236
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-piece-film-red-18236
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-piece-the-movie-13-film-gold-550
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-piece-the-movie-13-film-gold-550
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-room-9215
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-room-9215
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-room-second-season-7392
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-room-second-season-7392
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-room-third-season-6959
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-room-third-season-6959
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-piece-episode-of-skypiea-3097
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-piece-episode-of-skypiea-3097
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-piece-log-fish-man-island-saga-19404
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/one-piece-log-fish-man-island-saga-19404
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+ https://zenime.site/watch/one-piece-3d-gekisou-trap-coaster-3400
+ 2024-11-08T15:50:46+00:00
+ 0.64
+
+
+
+
\ No newline at end of file
diff --git a/public/LandingPage.jpg b/public/splash.jpg
similarity index 100%
rename from public/LandingPage.jpg
rename to public/splash.jpg
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 0000000..d608b5b
--- /dev/null
+++ b/src/App.css
@@ -0,0 +1,27 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+html,
+body,
+#root {
+ margin: 0;
+ padding: 0;
+}
+
+.app-container {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ max-width: 2048px;
+ margin-inline: auto;
+}
+.content {
+ width: 100%;
+ flex-grow: 1;
+}
+
+footer {
+ margin-top: auto;
+}
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 0000000..65102a4
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,73 @@
+import { useLocation } from "react-router-dom";
+import { useEffect } from "react";
+import { Routes, Route } from "react-router-dom";
+import { HomeInfoProvider } from "./context/HomeInfoContext";
+import Home from "./pages/Home/Home";
+import AnimeInfo from "./pages/animeInfo/AnimeInfo";
+import Navbar from "./components/navbar/Navbar";
+import Footer from "./components/footer/Footer";
+import Error from "./components/error/Error";
+import Category from "./pages/category/Category";
+import AtoZ from "./pages/a2z/AtoZ";
+import { azRoute, categoryRoutes } from "./utils/category.utils";
+import "./App.css";
+import Search from "./pages/search/Search";
+import Watch from "./pages/watch/Watch";
+import Producer from "./components/producer/Producer";
+import SplashScreen from "./components/splashscreen/SplashScreen";
+
+function App() {
+ const location = useLocation();
+
+ // Scroll to top on location change
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, [location]);
+
+ // Check if the current route is for the splash screen
+ const isSplashScreen = location.pathname === "/";
+
+ return (
+
+
+
+ {!isSplashScreen && }
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ {/* Render category routes */}
+ {categoryRoutes.map((path) => (
+
+ }
+ />
+ ))}
+ {/* Render A to Z routes */}
+ {azRoute.map((path) => (
+ }
+ />
+ ))}
+ } />
+ } />
+ {/* Catch-all route for 404 */}
+ } />
+
+ {!isSplashScreen && }
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/app/anime/[id]/page.js b/src/app/anime/[id]/page.js
deleted file mode 100644
index a50221a..0000000
--- a/src/app/anime/[id]/page.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { Suspense } from 'react';
-import Link from 'next/link';
-import { fetchAnimeInfo } from '@/lib/api';
-import AnimeDetails from '@/components/AnimeDetails.js';
-
-// Loading state component
-const LoadingState = () => (
-
-
- {/* Background Placeholder */}
-
-
- {/* Content Placeholder */}
-
-
- {/* Poster Placeholder */}
-
-
- {/* Details Placeholder */}
-
-
-
-
-
-);
-
-// Error state component
-const ErrorState = ({ error }) => (
-
-
-
Error Loading Anime
-
{error}
-
- window.location.reload()}
- className="px-6 py-3 bg-[var(--primary)] text-[var(--background)] rounded-lg hover:opacity-90 transition-opacity block w-full mb-4"
- >
- Try Again
-
-
- Go Back Home
-
-
-
-
-);
-
-// Not found state component
-const NotFoundState = () => (
-
-
-
Anime Not Found
-
The anime you're looking for doesn't exist or was removed.
-
- Go Back Home
-
-
-
-);
-
-// Main anime content component
-const AnimeContent = async ({ id }) => {
- try {
- const anime = await fetchAnimeInfo(id);
-
- if (!anime || !anime.info) {
- return ;
- }
-
- return (
-
- );
- } catch (error) {
- return ;
- }
-};
-
-// Main page component with Suspense
-export default function AnimeInfoPage({ params }) {
- const { id } = params;
-
- return (
- }>
-
-
- );
-}
\ No newline at end of file
diff --git a/src/app/anime/layout.js b/src/app/anime/layout.js
deleted file mode 100644
index 9fab739..0000000
--- a/src/app/anime/layout.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import SharedLayout from '@/components/SharedLayout';
-
-export default function AnimeLayout({ children }) {
- return {children} ;
-}
\ No newline at end of file
diff --git a/src/app/contacts/page.jsx b/src/app/contacts/page.jsx
deleted file mode 100644
index 5f5ecd9..0000000
--- a/src/app/contacts/page.jsx
+++ /dev/null
@@ -1,169 +0,0 @@
-'use client';
-
-import { useState } from 'react';
-import SharedLayout from '@/components/SharedLayout';
-
-export default function ContactsPage() {
- const [formData, setFormData] = useState({
- name: '',
- email: '',
- subject: '',
- message: '',
- });
-
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [submitStatus, setSubmitStatus] = useState(null);
-
- const handleChange = (e) => {
- const { name, value } = e.target;
- setFormData((prev) => ({
- ...prev,
- [name]: value,
- }));
- };
-
- const handleSubmit = async (e) => {
- e.preventDefault();
- setIsSubmitting(true);
-
- // Simulate form submission
- try {
- // In a real application, you would send this data to your backend/API
- await new Promise(resolve => setTimeout(resolve, 1000));
- setSubmitStatus({ success: true, message: 'Your message has been sent successfully!' });
- // Reset form
- setFormData({
- name: '',
- email: '',
- subject: '',
- message: '',
- });
- } catch (error) {
- setSubmitStatus({ success: false, message: 'There was an error sending your message. Please try again.' });
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
-
-
Contact Us
-
-
-
-
-
- Have questions, suggestions, or need assistance? We're here to help.
- Fill out the form and we'll get back to you as soon as possible.
-
-
-
-
-
-
Email
-
support@justanime.com
-
-
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/app/dmca/page.jsx b/src/app/dmca/page.jsx
deleted file mode 100644
index e4c7127..0000000
--- a/src/app/dmca/page.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-'use client';
-
-import SharedLayout from '@/components/SharedLayout';
-
-export default function DmcaPage() {
- return (
-
-
-
DMCA Policy
-
-
-
-
- We take the intellectual property rights of others seriously and require that our Users do the same.
- The Digital Millennium Copyright Act (DMCA) established a process for addressing claims of copyright infringement.
- If you own a copyright or have authority to act on behalf of a copyright owner and want to report a claim that a third party is
- infringing that material on or through JustAnime's services, please submit a DMCA report as outlined below, and we will take appropriate action.
-
-
-
-
DMCA Report Requirements
-
- A description of the copyrighted work that you claim is being infringed;
- A description of the material you claim is infringing and that you want removed or access to which you want disabled and the URL or other location of that material;
- Your name, title (if acting as an agent), address, telephone number, and email address;
- The following statement: "I have a good faith belief that the use of the copyrighted material I am complaining of is not authorized by the copyright owner, its agent, or the law (e.g., as a fair use)";
- The following statement: "The information in this notice is accurate and, under penalty of perjury, I am the owner, or authorized to act on behalf of the owner, of the copyright or of an exclusive right that is allegedly infringed";
- An electronic or physical signature of the owner of the copyright or a person authorized to act on the owner's behalf.
-
-
-
-
-
- Your DMCA takedown request should be submitted through our Contact page .
-
-
-
- We will then review your DMCA request and take proper actions, including removal of the content from the website.
-
-
-
-
-
Submit a DMCA Request
-
- To submit a DMCA takedown request, please include all required information as listed above and
- contact us through our Contact page .
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
deleted file mode 100644
index 718d6fe..0000000
Binary files a/src/app/favicon.ico and /dev/null differ
diff --git a/src/app/globals.css b/src/app/globals.css
deleted file mode 100644
index 9413505..0000000
--- a/src/app/globals.css
+++ /dev/null
@@ -1,173 +0,0 @@
-@import "tailwindcss";
-
-:root {
- --background: #0a0a0a;
- --foreground: #ffffff;
- --primary: #d1d1d1;
- --secondary: #404040;
- --accent: #808080;
- --card: #131313;
- --border: #2a2a2a;
- --hover: #333333;
- --text-muted: #8a8a8a;
-}
-
-@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-primary: var(--primary);
- --color-secondary: var(--secondary);
- --color-accent: var(--accent);
- --color-card: var(--card);
- --color-border: var(--border);
- --color-hover: var(--hover);
- --color-text-muted: var(--text-muted);
- --font-sans: var(--font-geist-sans);
- --font-mono: var(--font-geist-mono);
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #f5f5f5;
- }
-}
-
-body {
- margin: 0;
- padding: 0;
- min-height: 100vh;
- background-color: var(--background);
- color: var(--foreground);
- font-family: var(--font-sans);
-}
-
-/* Custom scrollbar */
-::-webkit-scrollbar {
- width: 8px;
-}
-
-::-webkit-scrollbar-track {
- background: var(--background);
-}
-
-::-webkit-scrollbar-thumb {
- background: var(--border);
- border-radius: 4px;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: var(--secondary);
-}
-
-/* Hide scrollbar for Chrome, Safari and Opera */
-.scrollbar-hide::-webkit-scrollbar {
- display: none;
-}
-
-/* Hide scrollbar for IE, Edge and Firefox */
-.scrollbar-hide {
- -ms-overflow-style: none; /* IE and Edge */
- scrollbar-width: none; /* Firefox */
-}
-
-.custom-scrollbar::-webkit-scrollbar {
- width: 6px;
- height: 6px;
-}
-
-.custom-scrollbar::-webkit-scrollbar-track {
- background: rgba(255, 255, 255, 0.05);
- border-radius: 8px;
-}
-
-.custom-scrollbar::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.15);
- border-radius: 8px;
-}
-
-.custom-scrollbar::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.25);
-}
-
-/* Modern card styling */
-.card-hover {
- transition: transform 0.3s ease, box-shadow 0.3s ease;
-}
-
-.card-hover:hover {
- transform: translateY(-4px);
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
-}
-
-/* Gradient text */
-.gradient-text {
- background: linear-gradient(to right, var(--foreground), var(--text-muted));
- -webkit-background-clip: text;
- background-clip: text;
- -webkit-text-fill-color: transparent;
-}
-
-/* Button styles */
-.btn-primary {
- background-color: var(--foreground);
- color: var(--background);
- transition: all 0.2s ease;
-}
-
-.btn-primary:hover {
- background-color: var(--text-muted);
-}
-
-.btn-secondary {
- background-color: transparent;
- border: 1px solid var(--border);
- color: var(--foreground);
- transition: all 0.2s ease;
-}
-
-.btn-secondary:hover {
- background-color: var(--hover);
-}
-
-/* Animation utilities */
-.fade-in {
- animation: fadeIn 0.5s ease-in forwards;
-}
-
-@keyframes fadeIn {
- 0% { opacity: 0; }
- 100% { opacity: 1; }
-}
-
-.slide-up {
- animation: slideUp 0.5s ease forwards;
-}
-
-@keyframes slideUp {
- 0% { transform: translateY(20px); opacity: 0; }
- 100% { transform: translateY(0); opacity: 1; }
-}
-
-/* Radial gradient for hero sections */
-.bg-radial-gradient {
- background: radial-gradient(circle at center, rgba(20, 20, 20, 0) 0%, rgba(0, 0, 0, 0.8) 100%);
-}
-
-/* Grid Pattern for backgrounds */
-.bg-grid-pattern {
- background-size: 25px 25px;
- background-image:
- linear-gradient(to right, rgba(255, 255, 255, 0.05) 1px, transparent 1px),
- linear-gradient(to bottom, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
-}
-
-/* Hide scrollbar while maintaining scroll functionality */
-.hide-scrollbar {
- -ms-overflow-style: none; /* IE and Edge */
- scrollbar-width: none; /* Firefox */
-}
-
-.hide-scrollbar::-webkit-scrollbar {
- display: none; /* Chrome, Safari and Opera */
-}
diff --git a/src/app/home/layout.js b/src/app/home/layout.js
deleted file mode 100644
index 48bdb93..0000000
--- a/src/app/home/layout.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import SharedLayout from '@/components/SharedLayout';
-
-export default function HomeLayout({ children }) {
- return {children} ;
-}
\ No newline at end of file
diff --git a/src/app/home/page.js b/src/app/home/page.js
deleted file mode 100644
index d263a5b..0000000
--- a/src/app/home/page.js
+++ /dev/null
@@ -1,202 +0,0 @@
-import React from 'react';
-import AnimeCard from '@/components/AnimeCard';
-import TopLists from '@/components/TopLists';
-import AnimeCalendar from '@/components/AnimeCalendar';
-import GenreBar from '@/components/GenreBar';
-import SpotlightCarousel from '@/components/SpotlightCarousel';
-import AnimeTabs from '@/components/AnimeTabs';
-import TrendingList from '@/components/TrendingList';
-import Link from 'next/link';
-import {
- fetchRecentEpisodes,
- fetchMostFavorite,
- fetchSpotlightAnime,
- fetchTopToday,
- fetchTopWeek,
- fetchTopMonth,
- fetchMostPopular,
- fetchTopAiring,
- fetchLatestCompleted,
- fetchTrending
-} from '@/lib/api';
-
-// New unified section component with grid layout
-const AnimeGridSection = ({ title, animeList = [], viewMoreLink, isRecent = false }) => {
- if (!animeList || animeList.length === 0) {
- return (
-
- );
- }
-
- return (
-
-
-
{title}
- {viewMoreLink && (
-
-
View All
-
-
-
-
- )}
-
-
-
- {animeList.slice(0, 12).map((anime, index) => (
-
- ))}
-
-
- );
-};
-
-async function HomePage() {
- try {
- console.log('[HomePage] Fetching home page data');
-
- // Fetch all data in parallel
- const [
- spotlightData,
- recentEpisodes,
- mostFavorite,
- topToday,
- topWeek,
- topMonth,
- topAiring,
- popular,
- latestCompleted,
- trending
- ] = await Promise.all([
- fetchSpotlightAnime().catch(err => {
- console.error("[HomePage] Error fetching spotlight anime:", err.message);
- return [];
- }),
- fetchRecentEpisodes().catch(err => {
- console.error("[HomePage] Error fetching recent episodes:", err.message);
- return { results: [] };
- }),
- fetchMostFavorite().catch(err => {
- console.error("[HomePage] Error fetching most favorite:", err.message);
- return { results: [] };
- }),
- fetchTopToday().catch(err => {
- console.error("[HomePage] Error fetching top today:", err.message);
- return [];
- }),
- fetchTopWeek().catch(err => {
- console.error("[HomePage] Error fetching top week:", err.message);
- return [];
- }),
- fetchTopMonth().catch(err => {
- console.error("[HomePage] Error fetching top month:", err.message);
- return [];
- }),
- fetchTopAiring().catch(err => {
- console.error("[HomePage] Error fetching top airing anime:", err.message);
- return { results: [] };
- }),
- fetchMostPopular().catch(err => {
- console.error("[HomePage] Error fetching popular anime:", err.message);
- return { results: [] };
- }),
- fetchLatestCompleted().catch(err => {
- console.error("[HomePage] Error fetching latest completed anime:", err.message);
- return { results: [] };
- }),
- fetchTrending().catch(err => {
- console.error("[HomePage] Error fetching trending anime:", err.message);
- return { results: [] };
- })
- ]);
-
- console.log('[HomePage] Data fetched successfully');
-
- return (
-
-
- {/* Spotlight Carousel */}
-
-
- {/* Genre Bar */}
-
-
-
-
- {/* Main Content + Sidebar Layout */}
-
- {/* Main Content - 2/3 width on large screens */}
-
- {/* Latest Episodes Grid */}
-
-
- {/* Anime Tabs Section */}
-
-
-
- {/* Sidebar - 1/4 width on large screens */}
-
- {/* Trending List */}
-
-
- {/* Calendar Widget */}
-
-
- {/* Top Lists */}
-
-
-
-
-
- );
- } catch (error) {
- console.error('[HomePage] Error in HomePage:', error.message);
- return (
-
-
-
Unable to load content
-
There was an error loading the home page content. Please try refreshing the page.
-
window.location.reload()}
- className="px-6 py-2 bg-[var(--primary)] text-white rounded-md hover:opacity-90 transition-opacity"
- >
- Refresh Page
-
-
-
- );
- }
-}
-
-export default HomePage;
\ No newline at end of file
diff --git a/src/app/latest-completed/layout.js b/src/app/latest-completed/layout.js
deleted file mode 100644
index 37cb70c..0000000
--- a/src/app/latest-completed/layout.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import SharedLayout from '@/components/SharedLayout';
-
-export default function LatestCompletedLayout({ children }) {
- return {children} ;
-}
\ No newline at end of file
diff --git a/src/app/latest-completed/page.js b/src/app/latest-completed/page.js
deleted file mode 100644
index 8f97593..0000000
--- a/src/app/latest-completed/page.js
+++ /dev/null
@@ -1,305 +0,0 @@
-'use client';
-
-import { useState, useEffect, useRef } from 'react';
-import AnimeCard from '@/components/AnimeCard';
-import AnimeFilters from '@/components/AnimeFilters';
-import { fetchLatestCompleted } from '@/lib/api';
-
-export default function LatestCompletedPage() {
- const [animeList, setAnimeList] = useState([]);
- const [filteredList, setFilteredList] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [currentPage, setCurrentPage] = useState(1);
- const [hasNextPage, setHasNextPage] = useState(false);
- const [selectedGenre, setSelectedGenre] = useState(null);
- const [yearFilter, setYearFilter] = useState('all');
- const [sortOrder, setSortOrder] = useState('default');
- const [searchQuery, setSearchQuery] = useState('');
- const [selectedSeasons, setSelectedSeasons] = useState([]);
- const [selectedTypes, setSelectedTypes] = useState([]);
- const [selectedStatus, setSelectedStatus] = useState([]);
- const [selectedLanguages, setSelectedLanguages] = useState([]);
- const [error, setError] = useState(null);
-
- // Current year for filtering
- const currentYear = new Date().getFullYear();
-
- // Add ref to track if this is the first render
- const initialRender = useRef(true);
-
- useEffect(() => {
- const fetchData = async () => {
- setIsLoading(true);
- try {
- const data = await fetchLatestCompleted(currentPage);
-
- if (currentPage === 1) {
- setAnimeList(data.results || []);
- } else {
- setAnimeList(prev => [...prev, ...(data.results || [])]);
- }
-
- setHasNextPage(data.hasNextPage || false);
- } catch (error) {
- console.error('Error fetching latest completed anime:', error);
- setError('Failed to load anime. Please try again later.');
- } finally {
- setIsLoading(false);
- }
- };
-
- fetchData();
- }, [currentPage]);
-
- // Apply filters and sorting whenever the anime list or filter settings change
- useEffect(() => {
- // Skip the initial render effect to avoid duplicate filtering
- if (initialRender.current) {
- initialRender.current = false;
- return;
- }
-
- if (!animeList.length) {
- setFilteredList([]);
- return;
- }
-
- let result = [...animeList];
-
- // Search filter
- if (searchQuery && searchQuery.trim() !== '') {
- const query = searchQuery.toLowerCase().trim();
- result = result.filter(anime => {
- const title = (anime.title || '').toLowerCase();
- const otherNames = (anime.otherNames || '').toLowerCase();
- return title.includes(query) || otherNames.includes(query);
- });
- }
-
- // Filter by genre if selected
- if (selectedGenre) {
- result = result.filter(anime => {
- if (anime.genres && Array.isArray(anime.genres)) {
- return anime.genres.some(g =>
- g.toLowerCase() === selectedGenre.toLowerCase() ||
- (g.name && g.name.toLowerCase() === selectedGenre.toLowerCase())
- );
- } else if (anime.genre) {
- return anime.genre.toLowerCase().includes(selectedGenre.toLowerCase());
- }
- return false;
- });
- }
-
- // Filter by season
- if (selectedSeasons.length > 0) {
- result = result.filter(anime => {
- const season = getAnimeSeason(anime);
- return selectedSeasons.includes(season);
- });
- }
-
- // Filter by year
- if (yearFilter !== 'all') {
- result = result.filter(anime => {
- const animeYear = parseInt(anime.year) || 0;
- if (yearFilter === 'older') {
- return animeYear < 2000;
- } else {
- return animeYear.toString() === yearFilter;
- }
- });
- }
-
- // Filter by type
- if (selectedTypes.length > 0) {
- result = result.filter(anime =>
- selectedTypes.includes(anime.type)
- );
- }
-
- // Filter by status
- if (selectedStatus.length > 0) {
- result = result.filter(anime => {
- const status = anime.status || getDefaultStatus(anime);
- return selectedStatus.includes(status);
- });
- }
-
- // Filter by language
- if (selectedLanguages.length > 0) {
- result = result.filter(anime => {
- const language = anime.language || getDefaultLanguage(anime);
- return selectedLanguages.includes(language);
- });
- }
-
- // Apply sorting
- switch (sortOrder) {
- case 'title-asc':
- result.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
- break;
- case 'title-desc':
- result.sort((a, b) => (b.title || '').localeCompare(a.title || ''));
- break;
- case 'year-desc':
- result.sort((a, b) => (parseInt(b.year) || 0) - (parseInt(a.year) || 0));
- break;
- case 'year-asc':
- result.sort((a, b) => (parseInt(a.year) || 0) - (parseInt(b.year) || 0));
- break;
- default:
- // Default order from API
- break;
- }
-
- setFilteredList(result);
- }, [animeList, selectedGenre, yearFilter, sortOrder, searchQuery, selectedSeasons, selectedTypes, selectedStatus, selectedLanguages]);
-
- const handleLoadMore = () => {
- setCurrentPage(prev => prev + 1);
- };
-
- const handleGenreChange = (genre) => {
- setSelectedGenre(genre);
- };
-
- const handleYearChange = (year) => {
- setYearFilter(year);
- };
-
- const handleSortChange = (order) => {
- setSortOrder(order);
- };
-
- const handleSearchChange = (value) => {
- setSearchQuery(value);
- };
-
- const handleSeasonChange = (seasons) => {
- setSelectedSeasons(seasons);
- };
-
- const handleTypeChange = (types) => {
- setSelectedTypes(types);
- };
-
- const handleStatusChange = (status) => {
- setSelectedStatus(status);
- };
-
- const handleLanguageChange = (languages) => {
- setSelectedLanguages(languages);
- };
-
- // Helper function to determine anime season based on available data
- const getAnimeSeason = (anime) => {
- if (anime.season) return anime.season;
-
- const seasons = ['Winter', 'Spring', 'Summer', 'Fall'];
- // Use hash of ID to assign consistent season for demo purposes
- const hash = anime.id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
- return seasons[hash % 4];
- };
-
- // Helper function to determine anime status
- const getDefaultStatus = (anime) => {
- if (anime.status) return anime.status;
-
- // Default logic - you may need to customize this based on your actual data
- const currentYear = new Date().getFullYear();
- if (anime.year > currentYear) return 'Upcoming';
- if (anime.totalEpisodes && anime.episodes && anime.episodes >= anime.totalEpisodes) return 'Completed';
- return 'Ongoing';
- };
-
- // Helper function to determine anime language
- const getDefaultLanguage = (anime) => {
- if (anime.language) return anime.language;
-
- // Default to "Subbed" for all anime unless specifically marked
- return anime.isDub ? 'Dubbed' : 'Subbed';
- };
-
- return (
-
-
Latest Completed Anime
-
- {/* Filters */}
-
-
- {isLoading && animeList.length === 0 ? (
-
- {[...Array(14)].map((_, index) => (
-
- ))}
-
- ) : (filteredList.length > 0 || animeList.length > 0) ? (
- <>
-
- {(filteredList.length > 0 ? filteredList : animeList).map((anime) => (
-
- ))}
-
-
- {hasNextPage && (
-
-
- {isLoading ? (
-
-
-
-
-
- Loading...
-
- ) : (
- 'Load More'
- )}
-
-
- )}
- >
- ) : (
-
-
No anime found
-
- We couldn't find any anime matching your criteria. Please try different filters.
-
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/app/layout.js b/src/app/layout.js
deleted file mode 100644
index d25115b..0000000
--- a/src/app/layout.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Geist, Geist_Mono } from "next/font/google";
-import { Analytics } from "@vercel/analytics/next";
-import { SpeedInsights } from "@vercel/speed-insights/next";
-import "./globals.css";
-
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
-
-export const metadata = {
- title: "JustAnime - Watch Anime Online",
- description: "Watch the latest anime episodes for free. Stream all your favorite anime shows in HD quality.",
- keywords: "anime, streaming, watch anime, free anime, anime online, just , justanime",
-};
-
-export default function RootLayout({ children }) {
- return (
-
-
-
- {children}
-
-
-
-
-
- );
-}
diff --git a/src/app/most-popular/layout.js b/src/app/most-popular/layout.js
deleted file mode 100644
index 7daa4ab..0000000
--- a/src/app/most-popular/layout.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import SharedLayout from '@/components/SharedLayout';
-
-export default function MostPopularLayout({ children }) {
- return {children} ;
-}
\ No newline at end of file
diff --git a/src/app/most-popular/page.js b/src/app/most-popular/page.js
deleted file mode 100644
index d546f9d..0000000
--- a/src/app/most-popular/page.js
+++ /dev/null
@@ -1,305 +0,0 @@
-'use client';
-
-import { useState, useEffect, useRef } from 'react';
-import AnimeCard from '@/components/AnimeCard';
-import AnimeFilters from '@/components/AnimeFilters';
-import { fetchMostPopular } from '@/lib/api';
-
-export default function MostPopularPage() {
- const [animeList, setAnimeList] = useState([]);
- const [filteredList, setFilteredList] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [currentPage, setCurrentPage] = useState(1);
- const [hasNextPage, setHasNextPage] = useState(false);
- const [selectedGenre, setSelectedGenre] = useState(null);
- const [yearFilter, setYearFilter] = useState('all');
- const [sortOrder, setSortOrder] = useState('default');
- const [searchQuery, setSearchQuery] = useState('');
- const [selectedSeasons, setSelectedSeasons] = useState([]);
- const [selectedTypes, setSelectedTypes] = useState([]);
- const [selectedStatus, setSelectedStatus] = useState([]);
- const [selectedLanguages, setSelectedLanguages] = useState([]);
- const [error, setError] = useState(null);
-
- // Current year for filtering
- const currentYear = new Date().getFullYear();
-
- // Add ref to track if this is the first render
- const initialRender = useRef(true);
-
- useEffect(() => {
- const fetchData = async () => {
- setIsLoading(true);
- try {
- const data = await fetchMostPopular(currentPage);
-
- if (currentPage === 1) {
- setAnimeList(data.results || []);
- } else {
- setAnimeList(prev => [...prev, ...(data.results || [])]);
- }
-
- setHasNextPage(data.hasNextPage || false);
- } catch (error) {
- console.error('Error fetching most popular anime:', error);
- setError('Failed to load anime. Please try again later.');
- } finally {
- setIsLoading(false);
- }
- };
-
- fetchData();
- }, [currentPage]);
-
- // Apply filters and sorting whenever the anime list or filter settings change
- useEffect(() => {
- // Skip the initial render effect to avoid duplicate filtering
- if (initialRender.current) {
- initialRender.current = false;
- return;
- }
-
- if (!animeList.length) {
- setFilteredList([]);
- return;
- }
-
- let result = [...animeList];
-
- // Search filter
- if (searchQuery && searchQuery.trim() !== '') {
- const query = searchQuery.toLowerCase().trim();
- result = result.filter(anime => {
- const title = (anime.title || '').toLowerCase();
- const otherNames = (anime.otherNames || '').toLowerCase();
- return title.includes(query) || otherNames.includes(query);
- });
- }
-
- // Filter by genre if selected
- if (selectedGenre) {
- result = result.filter(anime => {
- if (anime.genres && Array.isArray(anime.genres)) {
- return anime.genres.some(g =>
- g.toLowerCase() === selectedGenre.toLowerCase() ||
- (g.name && g.name.toLowerCase() === selectedGenre.toLowerCase())
- );
- } else if (anime.genre) {
- return anime.genre.toLowerCase().includes(selectedGenre.toLowerCase());
- }
- return false;
- });
- }
-
- // Filter by season
- if (selectedSeasons.length > 0) {
- result = result.filter(anime => {
- const season = getAnimeSeason(anime);
- return selectedSeasons.includes(season);
- });
- }
-
- // Filter by year
- if (yearFilter !== 'all') {
- result = result.filter(anime => {
- const animeYear = parseInt(anime.year) || 0;
- if (yearFilter === 'older') {
- return animeYear < 2000;
- } else {
- return animeYear.toString() === yearFilter;
- }
- });
- }
-
- // Filter by type
- if (selectedTypes.length > 0) {
- result = result.filter(anime =>
- selectedTypes.includes(anime.type)
- );
- }
-
- // Filter by status
- if (selectedStatus.length > 0) {
- result = result.filter(anime => {
- const status = anime.status || getDefaultStatus(anime);
- return selectedStatus.includes(status);
- });
- }
-
- // Filter by language
- if (selectedLanguages.length > 0) {
- result = result.filter(anime => {
- const language = anime.language || getDefaultLanguage(anime);
- return selectedLanguages.includes(language);
- });
- }
-
- // Apply sorting
- switch (sortOrder) {
- case 'title-asc':
- result.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
- break;
- case 'title-desc':
- result.sort((a, b) => (b.title || '').localeCompare(a.title || ''));
- break;
- case 'year-desc':
- result.sort((a, b) => (parseInt(b.year) || 0) - (parseInt(a.year) || 0));
- break;
- case 'year-asc':
- result.sort((a, b) => (parseInt(a.year) || 0) - (parseInt(b.year) || 0));
- break;
- default:
- // Default order from API
- break;
- }
-
- setFilteredList(result);
- }, [animeList, selectedGenre, yearFilter, sortOrder, searchQuery, selectedSeasons, selectedTypes, selectedStatus, selectedLanguages]);
-
- const handleLoadMore = () => {
- setCurrentPage(prev => prev + 1);
- };
-
- const handleGenreChange = (genre) => {
- setSelectedGenre(genre);
- };
-
- const handleYearChange = (year) => {
- setYearFilter(year);
- };
-
- const handleSortChange = (order) => {
- setSortOrder(order);
- };
-
- const handleSearchChange = (value) => {
- setSearchQuery(value);
- };
-
- const handleSeasonChange = (seasons) => {
- setSelectedSeasons(seasons);
- };
-
- const handleTypeChange = (types) => {
- setSelectedTypes(types);
- };
-
- const handleStatusChange = (status) => {
- setSelectedStatus(status);
- };
-
- const handleLanguageChange = (languages) => {
- setSelectedLanguages(languages);
- };
-
- // Helper function to determine anime season based on available data
- const getAnimeSeason = (anime) => {
- if (anime.season) return anime.season;
-
- const seasons = ['Winter', 'Spring', 'Summer', 'Fall'];
- // Use hash of ID to assign consistent season for demo purposes
- const hash = anime.id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
- return seasons[hash % 4];
- };
-
- // Helper function to determine anime status
- const getDefaultStatus = (anime) => {
- if (anime.status) return anime.status;
-
- // Default logic - you may need to customize this based on your actual data
- const currentYear = new Date().getFullYear();
- if (anime.year > currentYear) return 'Upcoming';
- if (anime.totalEpisodes && anime.episodes && anime.episodes >= anime.totalEpisodes) return 'Completed';
- return 'Ongoing';
- };
-
- // Helper function to determine anime language
- const getDefaultLanguage = (anime) => {
- if (anime.language) return anime.language;
-
- // Default to "Subbed" for all anime unless specifically marked
- return anime.isDub ? 'Dubbed' : 'Subbed';
- };
-
- return (
-
-
Most Popular Anime
-
- {/* Filters */}
-
-
- {isLoading && animeList.length === 0 ? (
-
- {[...Array(14)].map((_, index) => (
-
- ))}
-
- ) : (filteredList.length > 0 || animeList.length > 0) ? (
- <>
-
- {(filteredList.length > 0 ? filteredList : animeList).map((anime) => (
-
- ))}
-
-
- {hasNextPage && (
-
-
- {isLoading ? (
-
-
-
-
-
- Loading...
-
- ) : (
- 'Load More'
- )}
-
-
- )}
- >
- ) : (
-
-
No anime found
-
- We couldn't find any anime matching your criteria. Please try different filters.
-
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/app/page.jsx b/src/app/page.jsx
deleted file mode 100644
index bf8e703..0000000
--- a/src/app/page.jsx
+++ /dev/null
@@ -1,321 +0,0 @@
-'use client';
-
-import Link from 'next/link';
-import { useState, useEffect, useRef } from 'react';
-import { useRouter } from 'next/navigation';
-import { fetchSearchSuggestions } from '@/lib/api';
-import Image from 'next/image';
-
-export default function LandingPage() {
- const [searchQuery, setSearchQuery] = useState('');
- const [searchSuggestions, setSearchSuggestions] = useState([]);
- const [showSuggestions, setShowSuggestions] = useState(false);
- const router = useRouter();
- const suggestionRef = useRef(null);
- const searchInputRef = useRef(null);
-
- // For FAQ dropdowns
- const [openFAQ, setOpenFAQ] = useState(null);
-
- const toggleFAQ = (index) => {
- setOpenFAQ(openFAQ === index ? null : index);
- };
-
- // Fetch search suggestions when search query changes
- useEffect(() => {
- const fetchSuggestions = async () => {
- if (searchQuery.trim().length > 2) {
- try {
- const suggestions = await fetchSearchSuggestions(searchQuery);
- // Update to use the same format as home page search
- setSearchSuggestions(suggestions || []);
- setShowSuggestions(true);
- } catch (error) {
- console.error('Error fetching search suggestions:', error);
- setSearchSuggestions([]);
- }
- } else {
- setSearchSuggestions([]);
- setShowSuggestions(false);
- }
- };
-
- const debounceTimer = setTimeout(() => {
- fetchSuggestions();
- }, 300);
-
- return () => clearTimeout(debounceTimer);
- }, [searchQuery]);
-
- // Close suggestions when clicking outside
- useEffect(() => {
- const handleClickOutside = (event) => {
- if (
- suggestionRef.current &&
- !suggestionRef.current.contains(event.target) &&
- !searchInputRef.current?.contains(event.target)
- ) {
- setShowSuggestions(false);
- }
- };
-
- document.addEventListener('mousedown', handleClickOutside);
- return () => {
- document.removeEventListener('mousedown', handleClickOutside);
- };
- }, []);
-
- const handleSearch = (e) => {
- e.preventDefault();
- if (searchQuery.trim()) {
- router.push(`/search?q=${encodeURIComponent(searchQuery)}`);
- setSearchQuery('');
- setShowSuggestions(false);
- }
- };
-
- const handleSuggestionClick = (suggestion) => {
- // Updated to handle object-based suggestions
- const query = suggestion.title || suggestion;
- router.push(`/search?q=${encodeURIComponent(query)}`);
- setSearchQuery('');
- setShowSuggestions(false);
- };
-
- return (
-
- {/* Background Image with Fade Effect */}
-
-
- {/* Ultra-smooth gradient for fade from bottom */}
-
-
-
-
- {/* Unified Content Section */}
-
- {/* Hero Content */}
-
- {/* Logo */}
-
-
-
-
- {/* Search Bar */}
-
-
-
-
setSearchQuery(e.target.value)}
- onFocus={() => searchSuggestions.length > 0 && setShowSuggestions(true)}
- />
-
-
-
-
-
-
-
-
-
- {/* Search Suggestions Dropdown */}
- {showSuggestions && searchSuggestions.length > 0 && (
-
- {searchSuggestions.map((suggestion, index) => (
-
handleSuggestionClick(suggestion)}
- >
- {suggestion.image && (
-
-
-
- )}
-
-
{suggestion.title || suggestion}
- {suggestion.jname && (
-
{suggestion.jname}
- )}
-
- {suggestion.type && (
-
- {suggestion.type}
-
- )}
-
- ))}
-
- )}
-
-
- {/* Enter Homepage Button */}
-
- Enter Homepage
→
-
-
-
- {/* FAQ Content */}
-
-
Frequently Asked Questions
-
-
- {/* FAQ Item 1 */}
-
-
toggleFAQ(0)}
- >
- Is JustAnime safe?
-
-
-
-
-
-
-
Yes. We started this site to improve UX and are committed to keeping our users safe. We encourage all our users to notify us if anything looks suspicious.
-
-
-
-
- {/* FAQ Item 2 */}
-
-
toggleFAQ(1)}
- >
- What makes JustAnime the best site to watch anime free online?
-
-
-
-
-
-
-
JustAnime offers the best user experience for anime streaming with fast loading speeds, a beautiful interface, no intrusive ads, large content library, HD quality, and weekly updates. Our clean design and extensive features set us apart from other platforms.
-
-
-
-
- {/* FAQ Item 3 */}
-
-
toggleFAQ(2)}
- >
- How do I request an anime?
-
-
-
-
-
-
-
You can request anime by visiting our Discord community or using the contact form. Our team aims to fulfill requests quickly based on availability.
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/app/recent/layout.js b/src/app/recent/layout.js
deleted file mode 100644
index 14e5824..0000000
--- a/src/app/recent/layout.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import SharedLayout from '@/components/SharedLayout';
-
-export default function RecentEpisodesLayout({ children }) {
- return {children} ;
-}
\ No newline at end of file
diff --git a/src/app/recent/page.js b/src/app/recent/page.js
deleted file mode 100644
index 7e6dec4..0000000
--- a/src/app/recent/page.js
+++ /dev/null
@@ -1,305 +0,0 @@
-'use client';
-
-import { useState, useEffect, useRef } from 'react';
-import AnimeCard from '@/components/AnimeCard';
-import AnimeFilters from '@/components/AnimeFilters';
-import { fetchRecentEpisodes } from '@/lib/api';
-
-export default function RecentEpisodesPage() {
- const [animeList, setAnimeList] = useState([]);
- const [filteredList, setFilteredList] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [currentPage, setCurrentPage] = useState(1);
- const [hasNextPage, setHasNextPage] = useState(false);
- const [selectedGenre, setSelectedGenre] = useState(null);
- const [yearFilter, setYearFilter] = useState('all');
- const [sortOrder, setSortOrder] = useState('default');
- const [searchQuery, setSearchQuery] = useState('');
- const [selectedSeasons, setSelectedSeasons] = useState([]);
- const [selectedTypes, setSelectedTypes] = useState([]);
- const [selectedStatus, setSelectedStatus] = useState([]);
- const [selectedLanguages, setSelectedLanguages] = useState([]);
- const [error, setError] = useState(null);
-
- // Current year for filtering
- const currentYear = new Date().getFullYear();
-
- // Add ref to track if this is the first render
- const initialRender = useRef(true);
-
- useEffect(() => {
- const fetchData = async () => {
- setIsLoading(true);
- try {
- const data = await fetchRecentEpisodes(currentPage);
-
- if (currentPage === 1) {
- setAnimeList(data.results || []);
- } else {
- setAnimeList(prev => [...prev, ...(data.results || [])]);
- }
-
- setHasNextPage(data.hasNextPage || false);
- } catch (error) {
- console.error('Error fetching recent episodes:', error);
- setError('Failed to load anime. Please try again later.');
- } finally {
- setIsLoading(false);
- }
- };
-
- fetchData();
- }, [currentPage]);
-
- // Apply filters and sorting whenever the anime list or filter settings change
- useEffect(() => {
- // Skip the initial render effect to avoid duplicate filtering
- if (initialRender.current) {
- initialRender.current = false;
- return;
- }
-
- if (!animeList.length) {
- setFilteredList([]);
- return;
- }
-
- let result = [...animeList];
-
- // Search filter
- if (searchQuery && searchQuery.trim() !== '') {
- const query = searchQuery.toLowerCase().trim();
- result = result.filter(anime => {
- const title = (anime.title || '').toLowerCase();
- const otherNames = (anime.otherNames || '').toLowerCase();
- return title.includes(query) || otherNames.includes(query);
- });
- }
-
- // Filter by genre if selected
- if (selectedGenre) {
- result = result.filter(anime => {
- if (anime.genres && Array.isArray(anime.genres)) {
- return anime.genres.some(g =>
- g.toLowerCase() === selectedGenre.toLowerCase() ||
- (g.name && g.name.toLowerCase() === selectedGenre.toLowerCase())
- );
- } else if (anime.genre) {
- return anime.genre.toLowerCase().includes(selectedGenre.toLowerCase());
- }
- return false;
- });
- }
-
- // Filter by season
- if (selectedSeasons.length > 0) {
- result = result.filter(anime => {
- const season = getAnimeSeason(anime);
- return selectedSeasons.includes(season);
- });
- }
-
- // Filter by year
- if (yearFilter !== 'all') {
- result = result.filter(anime => {
- const animeYear = parseInt(anime.year) || 0;
- if (yearFilter === 'older') {
- return animeYear < 2000;
- } else {
- return animeYear.toString() === yearFilter;
- }
- });
- }
-
- // Filter by type
- if (selectedTypes.length > 0) {
- result = result.filter(anime =>
- selectedTypes.includes(anime.type)
- );
- }
-
- // Filter by status
- if (selectedStatus.length > 0) {
- result = result.filter(anime => {
- const status = anime.status || getDefaultStatus(anime);
- return selectedStatus.includes(status);
- });
- }
-
- // Filter by language
- if (selectedLanguages.length > 0) {
- result = result.filter(anime => {
- const language = anime.language || getDefaultLanguage(anime);
- return selectedLanguages.includes(language);
- });
- }
-
- // Apply sorting
- switch (sortOrder) {
- case 'title-asc':
- result.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
- break;
- case 'title-desc':
- result.sort((a, b) => (b.title || '').localeCompare(a.title || ''));
- break;
- case 'year-desc':
- result.sort((a, b) => (parseInt(b.year) || 0) - (parseInt(a.year) || 0));
- break;
- case 'year-asc':
- result.sort((a, b) => (parseInt(a.year) || 0) - (parseInt(b.year) || 0));
- break;
- default:
- // Default order from API
- break;
- }
-
- setFilteredList(result);
- }, [animeList, selectedGenre, yearFilter, sortOrder, searchQuery, selectedSeasons, selectedTypes, selectedStatus, selectedLanguages]);
-
- const handleLoadMore = () => {
- setCurrentPage(prev => prev + 1);
- };
-
- const handleGenreChange = (genre) => {
- setSelectedGenre(genre);
- };
-
- const handleYearChange = (year) => {
- setYearFilter(year);
- };
-
- const handleSortChange = (order) => {
- setSortOrder(order);
- };
-
- const handleSearchChange = (value) => {
- setSearchQuery(value);
- };
-
- const handleSeasonChange = (seasons) => {
- setSelectedSeasons(seasons);
- };
-
- const handleTypeChange = (types) => {
- setSelectedTypes(types);
- };
-
- const handleStatusChange = (status) => {
- setSelectedStatus(status);
- };
-
- const handleLanguageChange = (languages) => {
- setSelectedLanguages(languages);
- };
-
- // Helper function to determine anime season based on available data
- const getAnimeSeason = (anime) => {
- if (anime.season) return anime.season;
-
- const seasons = ['Winter', 'Spring', 'Summer', 'Fall'];
- // Use hash of ID to assign consistent season for demo purposes
- const hash = anime.id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
- return seasons[hash % 4];
- };
-
- // Helper function to determine anime status
- const getDefaultStatus = (anime) => {
- if (anime.status) return anime.status;
-
- // Default logic - you may need to customize this based on your actual data
- const currentYear = new Date().getFullYear();
- if (anime.year > currentYear) return 'Upcoming';
- if (anime.totalEpisodes && anime.episodes && anime.episodes >= anime.totalEpisodes) return 'Completed';
- return 'Ongoing';
- };
-
- // Helper function to determine anime language
- const getDefaultLanguage = (anime) => {
- if (anime.language) return anime.language;
-
- // Default to "Subbed" for all anime unless specifically marked
- return anime.isDub ? 'Dubbed' : 'Subbed';
- };
-
- return (
-
-
Recent Episodes
-
- {/* Filters */}
-
-
- {isLoading && animeList.length === 0 ? (
-
- {[...Array(14)].map((_, index) => (
-
- ))}
-
- ) : (filteredList.length > 0 || animeList.length > 0) ? (
- <>
-
- {(filteredList.length > 0 ? filteredList : animeList).map((anime) => (
-
- ))}
-
-
- {hasNextPage && (
-
-
- {isLoading ? (
-
-
-
-
-
- Loading...
-
- ) : (
- 'Load More'
- )}
-
-
- )}
- >
- ) : (
-
-
No anime found
-
- We couldn't find any anime matching your criteria. Please try different filters.
-
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/app/search/[query]/page.js b/src/app/search/[query]/page.js
deleted file mode 100644
index 7c147cc..0000000
--- a/src/app/search/[query]/page.js
+++ /dev/null
@@ -1,311 +0,0 @@
-'use client';
-
-import { useState, useEffect, useCallback } from 'react';
-import { useParams, useRouter } from 'next/navigation';
-import AnimeCard from '@/components/AnimeCard';
-import AnimeFilters from '@/components/AnimeFilters';
-import { searchAnime, fetchMostPopular } from '@/lib/api';
-
-export default function SearchPage() {
- const router = useRouter();
- const { query } = useParams();
- const decodedQuery = query ? decodeURIComponent(query) : '';
-
- const [searchResults, setSearchResults] = useState([]);
- const [filteredResults, setFilteredResults] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [currentPage, setCurrentPage] = useState(1);
- const [hasNextPage, setHasNextPage] = useState(false);
- const [isEmptySearch, setIsEmptySearch] = useState(false);
-
- // Filter states
- const [selectedGenre, setSelectedGenre] = useState(null);
- const [yearFilter, setYearFilter] = useState('all');
- const [sortOrder, setSortOrder] = useState('default');
- const [selectedSeasons, setSelectedSeasons] = useState([]);
- const [selectedTypes, setSelectedTypes] = useState([]);
- const [selectedStatus, setSelectedStatus] = useState([]);
- const [selectedLanguages, setSelectedLanguages] = useState([]);
- const [error, setError] = useState(null);
-
- // Create filters object for API request
- const getFiltersForApi = useCallback(() => {
- const filters = {};
-
- if (selectedGenre) filters.genre = selectedGenre;
- if (yearFilter !== 'all') filters.year = yearFilter;
- if (sortOrder !== 'default') filters.sort = sortOrder;
-
- // Only add these filters if API supports them
- // Currently, these may need to be handled client-side
- // if (selectedSeasons.length > 0) filters.seasons = selectedSeasons;
- // if (selectedTypes.length > 0) filters.types = selectedTypes;
- // if (selectedStatus.length > 0) filters.status = selectedStatus;
- // if (selectedLanguages.length > 0) filters.languages = selectedLanguages;
-
- return filters;
- }, [selectedGenre, yearFilter, sortOrder]);
-
- // Apply client-side filters
- const applyClientSideFilters = useCallback((animeList) => {
- if (!animeList.length) return [];
-
- let result = [...animeList];
-
- // Apply season filter if selected
- if (selectedSeasons.length > 0) {
- result = result.filter(anime => {
- if (!anime.season) return false;
-
- const animeSeason = typeof anime.season === 'string'
- ? anime.season
- : anime.season?.name || '';
-
- return selectedSeasons.some(season =>
- animeSeason.toLowerCase().includes(season.toLowerCase())
- );
- });
- }
-
- // Apply type filter if selected
- if (selectedTypes.length > 0) {
- result = result.filter(anime => {
- if (!anime.type) return false;
-
- return selectedTypes.some(type =>
- anime.type.toLowerCase() === type.toLowerCase()
- );
- });
- }
-
- // Apply status filter if selected
- if (selectedStatus.length > 0) {
- result = result.filter(anime => {
- if (!anime.status) return false;
-
- return selectedStatus.some(status =>
- anime.status.toLowerCase().includes(status.toLowerCase())
- );
- });
- }
-
- // Apply language filter if selected
- if (selectedLanguages.length > 0) {
- result = result.filter(anime => {
- // If no language info, assume subbed (most common)
- const animeLanguage = anime.language || 'Subbed';
-
- return selectedLanguages.some(language =>
- animeLanguage.toLowerCase().includes(language.toLowerCase())
- );
- });
- }
-
- // Apply client-side sorting (when API sort is not supported)
- if (sortOrder !== 'default') {
- switch (sortOrder) {
- case 'title-asc':
- result.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
- break;
- case 'title-desc':
- result.sort((a, b) => (b.title || '').localeCompare(a.title || ''));
- break;
- case 'year-desc':
- result.sort((a, b) => (parseInt(b.year) || 0) - (parseInt(a.year) || 0));
- break;
- case 'year-asc':
- result.sort((a, b) => (parseInt(a.year) || 0) - (parseInt(b.year) || 0));
- break;
- // Default order from API is used when sortOrder is 'default'
- }
- }
-
- return result;
- }, [selectedSeasons, selectedTypes, selectedStatus, selectedLanguages, sortOrder]);
-
- // Fetch popular anime when search is empty
- const fetchPopularAnime = useCallback(async (page = 1) => {
- setIsLoading(true);
- setError(null);
- setIsEmptySearch(true);
-
- try {
- const data = await fetchMostPopular(page);
-
- if (page === 1) {
- setSearchResults(data.results || []);
- } else {
- setSearchResults(prev => [...prev, ...(data.results || [])]);
- }
-
- setHasNextPage(data.hasNextPage || false);
- } catch (error) {
- console.error('Error fetching popular anime:', error);
- setError('Failed to fetch popular anime. Please try again later.');
- } finally {
- setIsLoading(false);
- }
- }, []);
-
- useEffect(() => {
- // If the query param is empty, redirect to search page with empty query
- if (!decodedQuery.trim()) {
- // Fetch popular anime instead
- fetchPopularAnime(currentPage);
- return;
- }
-
- setIsEmptySearch(false);
- const fetchSearchResults = async () => {
- setIsLoading(true);
- setError(null);
-
- try {
- const filters = getFiltersForApi();
- const data = await searchAnime(decodedQuery, currentPage, filters);
-
- if (currentPage === 1) {
- setSearchResults(data.results || []);
- } else {
- setSearchResults(prev => [...prev, ...(data.results || [])]);
- }
-
- setHasNextPage(data.hasNextPage || false);
- } catch (error) {
- console.error('Error fetching search results:', error);
- setError('Failed to search anime. Please try again later.');
- } finally {
- setIsLoading(false);
- }
- };
-
- fetchSearchResults();
- }, [decodedQuery, currentPage, getFiltersForApi, fetchPopularAnime]);
-
- // Apply client-side filters whenever search results or filter settings change
- useEffect(() => {
- const filteredResults = applyClientSideFilters(searchResults);
- setFilteredResults(filteredResults);
- }, [searchResults, applyClientSideFilters]);
-
- const handleLoadMore = () => {
- setCurrentPage(prev => prev + 1);
- };
-
- // Filter handlers
- const handleGenreChange = (genre) => {
- setSelectedGenre(genre);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleYearChange = (year) => {
- setYearFilter(year);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleSortChange = (order) => {
- setSortOrder(order);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleSeasonChange = (seasons) => {
- setSelectedSeasons(seasons);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleTypeChange = (types) => {
- setSelectedTypes(types);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleStatusChange = (status) => {
- setSelectedStatus(status);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleLanguageChange = (languages) => {
- setSelectedLanguages(languages);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- return (
-
-
-
- {decodedQuery.trim() ? `Search Results for "${decodedQuery}"` : 'Popular Anime'}
-
-
- {/* Filters */}
-
{}}
- selectedSeasons={selectedSeasons}
- onSeasonChange={handleSeasonChange}
- selectedTypes={selectedTypes}
- onTypeChange={handleTypeChange}
- selectedStatus={selectedStatus}
- onStatusChange={handleStatusChange}
- selectedLanguages={selectedLanguages}
- onLanguageChange={handleLanguageChange}
- />
-
-
- {isLoading && currentPage === 1 ? (
-
- ) : error ? (
-
- ) : filteredResults.length > 0 ? (
- <>
-
- {filteredResults.map((anime) => (
-
- ))}
-
-
- {hasNextPage && (
-
-
- {isLoading ? (
-
-
-
-
-
- Loading...
-
- ) : (
- 'Load More'
- )}
-
-
- )}
- >
- ) : (
-
-
No results found
-
- We couldn't find any anime matching your search criteria. Please try different filters or a different search term.
-
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/app/search/layout.js b/src/app/search/layout.js
deleted file mode 100644
index f44e019..0000000
--- a/src/app/search/layout.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import SharedLayout from '@/components/SharedLayout';
-
-export default function SearchLayout({ children }) {
- return {children} ;
-}
\ No newline at end of file
diff --git a/src/app/search/page.js b/src/app/search/page.js
deleted file mode 100644
index dd5d63f..0000000
--- a/src/app/search/page.js
+++ /dev/null
@@ -1,433 +0,0 @@
-'use client';
-
-import { useState, useEffect, useCallback, Suspense } from 'react';
-import { useSearchParams } from 'next/navigation';
-import { searchAnime, fetchMostPopular } from '@/lib/api';
-import AnimeCard from '@/components/AnimeCard';
-import AnimeFilters from '@/components/AnimeFilters';
-
-function SearchResults() {
- const searchParams = useSearchParams();
- const queryTerm = searchParams.get('q') || '';
- const genreParam = searchParams.get('genre') || null;
-
- const [animeList, setAnimeList] = useState([]);
- const [filteredList, setFilteredList] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [currentPage, setCurrentPage] = useState(1);
- const [hasNextPage, setHasNextPage] = useState(false);
- const [selectedGenre, setSelectedGenre] = useState(genreParam);
- const [yearFilter, setYearFilter] = useState('all');
- const [sortOrder, setSortOrder] = useState('default');
- const [selectedSeasons, setSelectedSeasons] = useState([]);
- const [selectedTypes, setSelectedTypes] = useState([]);
- const [selectedStatus, setSelectedStatus] = useState([]);
- const [selectedLanguages, setSelectedLanguages] = useState([]);
- const [error, setError] = useState(null);
- const [isEmptySearch, setIsEmptySearch] = useState(false);
-
- // Current year for filtering
- const currentYear = new Date().getFullYear();
-
- // Process and augment anime data to ensure all items have year information
- const processAnimeData = useCallback((animeData) => {
- if (!animeData || !animeData.results) return animeData;
-
- // Create a copy of the data to avoid mutating the original
- const processedData = {
- ...animeData,
- results: animeData.results.map(anime => {
- const processed = { ...anime };
-
- // Extract or estimate year from various properties
- // Fallback to randomized year range between 2000-current year if no year data available
- if (!processed.year) {
- if (processed.releaseDate && !isNaN(parseInt(processed.releaseDate))) {
- processed.year = parseInt(processed.releaseDate);
- } else if (processed.date && !isNaN(parseInt(processed.date))) {
- processed.year = parseInt(processed.date);
- } else {
- // Assign a semi-random year based on anime ID to ensure consistency
- const hash = processed.id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
- processed.year = 2000 + (hash % (currentYear - 2000 + 1));
- }
- }
-
- return processed;
- })
- };
-
- return processedData;
- }, [currentYear]);
-
- // Create filters object for API request
- const getFiltersForApi = useCallback(() => {
- const filters = {};
-
- if (selectedGenre) filters.genre = selectedGenre;
- if (yearFilter !== 'all') filters.year = yearFilter;
- if (sortOrder !== 'default') filters.sort = sortOrder;
-
- // Support all client-side filters in API call when possible
- if (selectedSeasons.length > 0) filters.season = selectedSeasons.join(',');
- if (selectedTypes.length > 0) filters.type = selectedTypes.join(',');
- if (selectedStatus.length > 0) filters.status = selectedStatus.join(',');
- if (selectedLanguages.length > 0) filters.language = selectedLanguages.join(',');
-
- return filters;
- }, [selectedGenre, yearFilter, sortOrder, selectedSeasons, selectedTypes, selectedStatus, selectedLanguages]);
-
- // Apply client-side filters for things not supported by API
- const applyClientSideFilters = useCallback((animeList) => {
- if (!animeList.length) return [];
-
- let result = [...animeList];
-
- // Apply season filter if selected
- if (selectedSeasons.length > 0) {
- result = result.filter(anime => {
- if (!anime.season) return false;
-
- const animeSeason = typeof anime.season === 'string'
- ? anime.season
- : anime.season?.name || '';
-
- return selectedSeasons.some(season =>
- animeSeason.toLowerCase().includes(season.toLowerCase())
- );
- });
- }
-
- // Apply type filter if selected
- if (selectedTypes.length > 0) {
- result = result.filter(anime => {
- if (!anime.type) return false;
-
- return selectedTypes.some(type =>
- anime.type.toLowerCase() === type.toLowerCase()
- );
- });
- }
-
- // Apply status filter if selected
- if (selectedStatus.length > 0) {
- result = result.filter(anime => {
- if (!anime.status) return false;
-
- return selectedStatus.some(status =>
- anime.status.toLowerCase().includes(status.toLowerCase())
- );
- });
- }
-
- // Apply language filter if selected
- if (selectedLanguages.length > 0) {
- result = result.filter(anime => {
- // If no language info, assume subbed (most common)
- const animeLanguage = anime.language || 'Subbed';
-
- return selectedLanguages.some(language =>
- animeLanguage.toLowerCase().includes(language.toLowerCase())
- );
- });
- }
-
- // Apply client-side sorting (when API sort is not supported)
- if (sortOrder !== 'default') {
- switch (sortOrder) {
- case 'title-asc':
- result.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
- break;
- case 'title-desc':
- result.sort((a, b) => (b.title || '').localeCompare(a.title || ''));
- break;
- case 'year-desc':
- result.sort((a, b) => (parseInt(b.year) || 0) - (parseInt(a.year) || 0));
- break;
- case 'year-asc':
- result.sort((a, b) => (parseInt(a.year) || 0) - (parseInt(b.year) || 0));
- break;
- // Default order from API is used when sortOrder is 'default'
- }
- }
-
- return result;
- }, [selectedSeasons, selectedTypes, selectedStatus, selectedLanguages, sortOrder]);
-
- // Fetch most popular anime when search is empty
- const fetchPopularAnime = useCallback(async () => {
- setIsLoading(true);
- setError(null);
- setIsEmptySearch(true);
-
- try {
- const data = await fetchMostPopular(1);
- const processedData = processAnimeData(data);
-
- const results = processedData.results || [];
- setAnimeList(results);
-
- // Apply client-side filters
- const filteredResults = applyClientSideFilters(results);
- setFilteredList(filteredResults);
-
- setHasNextPage(processedData.hasNextPage || false);
- } catch (error) {
- console.error('Error fetching popular anime:', error);
- setError('Failed to fetch popular anime. Please try again later.');
- setAnimeList([]);
- setFilteredList([]);
- } finally {
- setIsLoading(false);
- }
- }, [processAnimeData, applyClientSideFilters]);
-
- // Fetch data from API when search term or main filters change
- useEffect(() => {
- const fetchData = async () => {
- if (!queryTerm.trim()) {
- // Show popular anime instead of empty results
- fetchPopularAnime();
- return;
- }
-
- setIsLoading(true);
- setError(null);
- setCurrentPage(1);
- setIsEmptySearch(false);
-
- try {
- const filters = getFiltersForApi();
- console.log(`[Search] Searching for: "${queryTerm}" with filters:`, filters);
-
- const data = await searchAnime(queryTerm, 1, filters);
-
- // If no results but no error was thrown, show empty state
- if (!data || (!data.results || data.results.length === 0)) {
- console.log('[Search] No results found for search term:', queryTerm);
- setError(`No results found for "${queryTerm}"`);
- setAnimeList([]);
- setFilteredList([]);
- setIsLoading(false);
- return;
- }
-
- const processedData = processAnimeData(data);
- const results = processedData.results || [];
- setAnimeList(results);
-
- // Only apply client-side filters for things not supported by API
- const filteredResults = applyClientSideFilters(results);
- setFilteredList(filteredResults);
-
- setHasNextPage(processedData.hasNextPage || false);
- } catch (error) {
- console.error('[Search] Error searching anime:', error);
- setError('Failed to search anime. Please try again later or check your internet connection.');
- setAnimeList([]);
- setFilteredList([]);
- } finally {
- setIsLoading(false);
- }
- };
-
- fetchData();
- }, [queryTerm, getFiltersForApi, processAnimeData, applyClientSideFilters, fetchPopularAnime]);
-
- // Handle pagination
- useEffect(() => {
- // Skip if it's the first page (already fetched in the previous effect)
- // or if no search term is provided
- if (currentPage === 1) {
- return;
- }
-
- const loadMoreData = async () => {
- setIsLoading(true);
-
- try {
- // If it's an empty search query, load more popular anime
- if (isEmptySearch) {
- const data = await fetchMostPopular(currentPage);
- const processedData = processAnimeData(data);
-
- const newResults = processedData.results || [];
- setAnimeList(prev => [...prev, ...newResults]);
-
- // Apply client-side filters to new results
- const filteredNewResults = applyClientSideFilters(newResults);
- setFilteredList(prev => [...prev, ...filteredNewResults]);
-
- setHasNextPage(processedData.hasNextPage || false);
- } else {
- // For search results, include filters
- const filters = getFiltersForApi();
- const data = await searchAnime(queryTerm, currentPage, filters);
- const processedData = processAnimeData(data);
-
- const newResults = processedData.results || [];
- setAnimeList(prev => [...prev, ...newResults]);
-
- // Only apply client-side filters for things not supported by API
- const filteredNewResults = applyClientSideFilters(newResults);
- setFilteredList(prev => [...prev, ...filteredNewResults]);
-
- setHasNextPage(processedData.hasNextPage || false);
- }
- } catch (error) {
- console.error('Error loading more anime:', error);
- setError('Failed to load more results. Please try again later.');
- } finally {
- setIsLoading(false);
- }
- };
-
- loadMoreData();
- }, [currentPage, queryTerm, isEmptySearch, getFiltersForApi, processAnimeData, applyClientSideFilters]);
-
- // Re-apply client-side filters when filters change but don't need API refetch
- useEffect(() => {
- const applyFilters = () => {
- const filteredResults = applyClientSideFilters(animeList);
- setFilteredList(filteredResults);
- };
-
- applyFilters();
- }, [selectedSeasons, selectedTypes, selectedStatus, selectedLanguages, animeList, applyClientSideFilters]);
-
- const handleLoadMore = () => {
- setCurrentPage(prev => prev + 1);
- };
-
- const handleGenreChange = (genre) => {
- setSelectedGenre(genre);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleYearChange = (year) => {
- setYearFilter(year);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleSortChange = (order) => {
- setSortOrder(order);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleSeasonChange = (seasons) => {
- setSelectedSeasons(seasons);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleTypeChange = (types) => {
- setSelectedTypes(types);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleStatusChange = (status) => {
- setSelectedStatus(status);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- const handleLanguageChange = (languages) => {
- setSelectedLanguages(languages);
- if (currentPage !== 1) setCurrentPage(1);
- };
-
- return (
-
- {/* Horizontal filters at the top */}
-
-
{}}
- selectedSeasons={selectedSeasons}
- onSeasonChange={handleSeasonChange}
- selectedTypes={selectedTypes}
- onTypeChange={handleTypeChange}
- selectedStatus={selectedStatus}
- onStatusChange={handleStatusChange}
- selectedLanguages={selectedLanguages}
- onLanguageChange={handleLanguageChange}
- />
-
-
- {/* Main content */}
-
-
-
- {queryTerm ? `Results for "${queryTerm}"` : 'Popular Anime'}
-
-
- {filteredList.length > 0 && (
- {filteredList.length} {filteredList.length === 1 ? 'result' : 'results'}
- )}
-
-
-
- {error ? (
-
- ) : isLoading && currentPage === 1 ? (
-
- {/* Loading skeleton */}
- {[...Array(24)].map((_, index) => (
-
- ))}
-
- ) : filteredList.length === 0 ? (
-
-
No anime found matching your filters.
-
- ) : (
- <>
-
- {filteredList.map((anime) => (
-
- ))}
-
-
- {/* Load more button */}
- {hasNextPage && (
-
-
- {isLoading ? (
-
-
-
-
-
- Loading...
-
- ) : (
- 'Load More'
- )}
-
-
- )}
- >
- )}
-
-
- );
-}
-
-export default function SearchPage() {
- return (
- Loading...}>
-
-
- );
-}
\ No newline at end of file
diff --git a/src/app/terms-and-services/page.jsx b/src/app/terms-and-services/page.jsx
deleted file mode 100644
index 3b80de2..0000000
--- a/src/app/terms-and-services/page.jsx
+++ /dev/null
@@ -1,191 +0,0 @@
-'use client';
-
-import SharedLayout from '@/components/SharedLayout';
-
-export default function TermsPage() {
- return (
-
-
-
Terms of Service & Privacy Policy
-
-
-
-
Terms of Service
-
-
-
-
1. Acceptance of Terms
-
- By accessing and using JustAnime, you acknowledge that you have read, understood, and agree to be bound by these Terms of Service.
- If you do not agree with any part of these terms, you may not use our services.
-
-
-
-
-
2. Service Description
-
- JustAnime is a platform that provides information and links to anime content.
- We do not host, upload, or distribute any content directly.
- Our service aggregates links to third-party websites and services that host the actual content.
-
-
-
-
-
3. User Conduct
-
- Users of JustAnime agree not to:
-
-
- Use our service for any illegal purpose or in violation of any local, state, national, or international law
- Harass, abuse, or harm another person
- Interfere with or disrupt the service or servers connected to the service
- Create multiple accounts for disruptive or abusive purposes
- Attempt to access any portion of the service that you are not authorized to access
-
-
-
-
-
4. Content Disclaimer
-
- JustAnime does not host any content on its servers. We are not responsible for the content, accuracy, or practices of third-party websites that our service may link to.
- These links are provided solely as a convenience to users, and we do not endorse the content of such third-party sites.
-
-
-
-
-
5. Intellectual Property
-
- All trademarks, logos, service marks, and trade names are the property of their respective owners.
- JustAnime respects intellectual property rights and expects users to do the same.
- If you believe content linked through our service infringes on your copyright, please contact us with details.
-
-
-
-
-
6. Modification of Terms
-
- JustAnime reserves the right to modify these Terms of Service at any time.
- We will provide notice of significant changes through our website.
- Your continued use of our service after such modifications constitutes your acceptance of the updated terms.
-
-
-
-
-
7. Termination
-
- JustAnime reserves the right to terminate or suspend your access to our service at any time, without prior notice or liability, for any reason whatsoever, including without limitation if you breach these Terms of Service.
-
-
-
-
-
-
-
Privacy Policy
-
-
-
-
1. Information We Collect
-
- JustAnime collects the following types of information:
-
-
- Information you provide: We may collect personal information such as your email address when you sign up for an account or contact us.
- Usage data: We automatically collect information about your interactions with our service, including the pages you visit and your preferences.
- Device information: We collect information about your device and internet connection, including IP address, browser type, and operating system.
-
-
-
-
-
2. How We Use Your Information
-
- We use the information we collect to:
-
-
- Provide, maintain, and improve our service
- Communicate with you about updates, support, and features
- Monitor and analyze usage patterns and trends
- Protect against, identify, and prevent fraud and other illegal activity
- Comply with legal obligations
-
-
-
-
-
3. Cookies and Similar Technologies
-
- JustAnime uses cookies and similar tracking technologies to track activity on our service and hold certain information.
- Cookies are files with a small amount of data that may include an anonymous unique identifier.
- You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent.
-
-
-
-
-
4. Data Sharing and Disclosure
-
- We may share your information in the following circumstances:
-
-
- With service providers who perform services on our behalf
- To comply with legal obligations
- To protect the rights, property, or safety of JustAnime, our users, or others
- In connection with a business transfer, such as a merger or acquisition
-
-
-
-
-
5. Data Security
-
- JustAnime takes reasonable measures to protect your information from unauthorized access, alteration, disclosure, or destruction.
- However, no method of transmission over the Internet or electronic storage is 100% secure, and we cannot guarantee absolute security.
-
-
-
-
-
6. Your Rights
-
- Depending on your location, you may have certain rights regarding your personal data, including:
-
-
- The right to access and receive a copy of your data
- The right to rectify or update your data
- The right to delete your data
- The right to restrict processing of your data
- The right to object to processing of your data
- The right to data portability
-
-
- To exercise these rights, please contact us at privacy@justanime.com.
-
-
-
-
-
7. Children's Privacy
-
- JustAnime does not knowingly collect personal information from children under 13.
- If you are a parent or guardian and you believe your child has provided us with personal information, please contact us.
-
-
-
-
-
8. Changes to This Privacy Policy
-
- We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last Updated" date.
-
-
-
-
-
9. Contact Us
-
- If you have any questions about this Privacy Policy, please contact us at privacy@justanime.com.
-
-
-
-
-
Last Updated: May 5, 2024
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/app/top-airing/layout.js b/src/app/top-airing/layout.js
deleted file mode 100644
index a55c076..0000000
--- a/src/app/top-airing/layout.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import SharedLayout from '@/components/SharedLayout';
-
-export default function TopAiringLayout({ children }) {
- return {children} ;
-}
\ No newline at end of file
diff --git a/src/app/top-airing/page.js b/src/app/top-airing/page.js
deleted file mode 100644
index afff6f5..0000000
--- a/src/app/top-airing/page.js
+++ /dev/null
@@ -1,276 +0,0 @@
-'use client';
-
-import { useState, useEffect, useRef } from 'react';
-import AnimeCard from '@/components/AnimeCard';
-import AnimeFilters from '@/components/AnimeFilters';
-import { fetchTopAiring } from '@/lib/api';
-
-export default function TopAiringPage() {
- const [animeList, setAnimeList] = useState([]);
- const [filteredList, setFilteredList] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [currentPage, setCurrentPage] = useState(1);
- const [hasNextPage, setHasNextPage] = useState(false);
- const [selectedGenre, setSelectedGenre] = useState(null);
- const [yearFilter, setYearFilter] = useState('all');
- const [sortOrder, setSortOrder] = useState('default');
- const [searchQuery, setSearchQuery] = useState('');
- const [selectedSeasons, setSelectedSeasons] = useState([]);
- const [selectedTypes, setSelectedTypes] = useState([]);
- const [selectedStatus, setSelectedStatus] = useState([]);
- const [selectedLanguages, setSelectedLanguages] = useState([]);
- const [error, setError] = useState(null);
-
- // Current year for filtering
- const currentYear = new Date().getFullYear();
-
- // Add ref to track if this is the first render
- const initialRender = useRef(true);
-
- useEffect(() => {
- const fetchData = async () => {
- setIsLoading(true);
- try {
- const data = await fetchTopAiring(currentPage);
-
- if (currentPage === 1) {
- setAnimeList(data.results || []);
- } else {
- setAnimeList(prev => [...prev, ...(data.results || [])]);
- }
-
- setHasNextPage(data.hasNextPage || false);
- } catch (error) {
- console.error('Error fetching top airing anime:', error);
- setError('Failed to load anime. Please try again later.');
- } finally {
- setIsLoading(false);
- }
- };
-
- fetchData();
- }, [currentPage]);
-
- // Apply filters and sorting whenever the anime list or filter settings change
- useEffect(() => {
- // Skip the initial render effect to avoid duplicate filtering
- if (initialRender.current) {
- initialRender.current = false;
- return;
- }
-
- if (!animeList.length) {
- setFilteredList([]);
- return;
- }
-
- let result = [...animeList];
-
- // Search filter
- if (searchQuery && searchQuery.trim() !== '') {
- const query = searchQuery.toLowerCase().trim();
- result = result.filter(anime => {
- const title = (anime.title || '').toLowerCase();
- const otherNames = (anime.otherNames || '').toLowerCase();
- return title.includes(query) || otherNames.includes(query);
- });
- }
-
- // Filter by genre if selected
- if (selectedGenre) {
- result = result.filter(anime => {
- if (anime.genres && Array.isArray(anime.genres)) {
- return anime.genres.some(g =>
- g.toLowerCase() === selectedGenre.toLowerCase() ||
- (g.name && g.name.toLowerCase() === selectedGenre.toLowerCase())
- );
- } else if (anime.genre) {
- return anime.genre.toLowerCase().includes(selectedGenre.toLowerCase());
- }
- return false;
- });
- }
-
- // Filter by season
- if (selectedSeasons.length > 0) {
- result = result.filter(anime => {
- const season = getAnimeSeason(anime);
- return selectedSeasons.includes(season);
- });
- }
-
- // Filter by year
- if (yearFilter !== 'all') {
- result = result.filter(anime => {
- const animeYear = parseInt(anime.year) || 0;
- if (yearFilter === 'older') {
- return animeYear < 2000;
- } else {
- return animeYear.toString() === yearFilter;
- }
- });
- }
-
- // Filter by type
- if (selectedTypes.length > 0) {
- result = result.filter(anime =>
- selectedTypes.includes(anime.type)
- );
- }
-
- // Filter by status
- if (selectedStatus.length > 0) {
- result = result.filter(anime => {
- const status = anime.status || getDefaultStatus(anime);
- return selectedStatus.includes(status);
- });
- }
-
- // Filter by language
- if (selectedLanguages.length > 0) {
- result = result.filter(anime => {
- const language = anime.language || getDefaultLanguage(anime);
- return selectedLanguages.includes(language);
- });
- }
-
- // Apply sorting
- switch (sortOrder) {
- case 'title-asc':
- result.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
- break;
- case 'title-desc':
- result.sort((a, b) => (b.title || '').localeCompare(a.title || ''));
- break;
- case 'year-desc':
- result.sort((a, b) => (parseInt(b.year) || 0) - (parseInt(a.year) || 0));
- break;
- case 'year-asc':
- result.sort((a, b) => (parseInt(a.year) || 0) - (parseInt(b.year) || 0));
- break;
- default:
- // Default order from API
- break;
- }
-
- setFilteredList(result);
- }, [animeList, selectedGenre, yearFilter, sortOrder, searchQuery, selectedSeasons, selectedTypes, selectedStatus, selectedLanguages]);
-
- const handleLoadMore = () => {
- setCurrentPage(prev => prev + 1);
- };
-
- const handleGenreChange = (genre) => {
- setSelectedGenre(genre);
- };
-
- const handleYearChange = (year) => {
- setYearFilter(year);
- };
-
- const handleSortChange = (order) => {
- setSortOrder(order);
- };
-
- const handleSearchChange = (value) => {
- setSearchQuery(value);
- };
-
- const handleSeasonChange = (seasons) => {
- setSelectedSeasons(seasons);
- };
-
- const handleTypeChange = (types) => {
- setSelectedTypes(types);
- };
-
- const handleStatusChange = (status) => {
- setSelectedStatus(status);
- };
-
- const handleLanguageChange = (languages) => {
- setSelectedLanguages(languages);
- };
-
- return (
-
-
Top Airing Anime
-
- {/* Filters */}
-
-
- {isLoading && animeList.length === 0 ? (
-
- {[...Array(14)].map((_, index) => (
-
- ))}
-
- ) : (filteredList.length > 0 || animeList.length > 0) ? (
- <>
-
- {(filteredList.length > 0 ? filteredList : animeList).map((anime) => (
-
- ))}
-
-
- {hasNextPage && (
-
-
- {isLoading ? (
-
-
-
-
-
- Loading...
-
- ) : (
- 'Load More'
- )}
-
-
- )}
- >
- ) : (
-
-
No anime found
-
- We couldn't find any top airing anime at this time. Please check back later.
-
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/app/watch/[episodeId]/page.js b/src/app/watch/[episodeId]/page.js
deleted file mode 100644
index a2a9d7c..0000000
--- a/src/app/watch/[episodeId]/page.js
+++ /dev/null
@@ -1,664 +0,0 @@
-'use client';
-
-import { useState, useEffect } from 'react';
-import { useParams, useRouter, usePathname, useSearchParams } from 'next/navigation';
-import Link from 'next/link';
-import Image from 'next/image';
-import VideoPlayer from '@/components/VideoPlayer';
-import EpisodeList from '@/components/EpisodeList';
-import {
- fetchEpisodeSources,
- fetchAnimeInfo,
- fetchEpisodeServers,
- fetchAnimeEpisodes
-} from '@/lib/api';
-
-export default function WatchPage() {
- const { episodeId } = useParams();
- const router = useRouter();
- const pathname = usePathname();
- const [videoSource, setVideoSource] = useState(null);
- const [anime, setAnime] = useState(null);
- const [currentEpisode, setCurrentEpisode] = useState(null);
- const [isDub, setIsDub] = useState(false);
- const [isLoading, setIsLoading] = useState(true);
- const [error, setError] = useState(null);
- const [videoHeaders, setVideoHeaders] = useState({});
- const [subtitles, setSubtitles] = useState([]);
- const [thumbnails, setThumbnails] = useState(null);
- const [animeId, setAnimeId] = useState(null);
- const [episodeData, setEpisodeData] = useState(null);
- const [isRetrying, setIsRetrying] = useState(false);
- const [currentPage, setCurrentPage] = useState(1);
- const episodesPerPage = 100;
- const [showFullSynopsis, setShowFullSynopsis] = useState(false);
- const [autoSkip, setAutoSkip] = useState(false);
- const [currentEpisodeId, setCurrentEpisodeId] = useState(episodeId);
- const [availableServers, setAvailableServers] = useState([]);
- const [selectedServer, setSelectedServer] = useState('hd-2');
- const [episodes, setEpisodes] = useState([]);
-
- // Handle URL updates when currentEpisodeId changes
- useEffect(() => {
- if (currentEpisodeId && currentEpisodeId !== episodeId) {
- const newUrl = `/watch/${currentEpisodeId}`;
- window.history.pushState({ episodeId: currentEpisodeId }, '', newUrl);
- }
- }, [currentEpisodeId, episodeId]);
-
- // Listen for popstate (browser back/forward) events
- useEffect(() => {
- const handlePopState = (event) => {
- const path = window.location.pathname;
- const match = path.match(/\/watch\/(.+)$/);
- if (match) {
- const newEpisodeId = match[1];
- setCurrentEpisodeId(newEpisodeId);
- }
- };
-
- window.addEventListener('popstate', handlePopState);
- return () => window.removeEventListener('popstate', handlePopState);
- }, []);
-
- // Extract animeId from the URL
- useEffect(() => {
- if (episodeId) {
- // Log the raw episodeId from the URL for debugging
- console.log('[Watch] Raw episodeId from URL:', episodeId);
-
- // Extract animeId from the episodeId parameter
- // The API response contains episode.id in the format "anime-id?ep=episode-number"
- let extractedAnimeId = episodeId;
-
- // If the ID contains a query parameter, extract just the anime ID
- if (episodeId.includes('?')) {
- extractedAnimeId = episodeId.split('?')[0];
- }
-
- setAnimeId(extractedAnimeId);
- console.log('[Watch] Extracted anime ID:', extractedAnimeId);
-
- setCurrentEpisodeId(episodeId);
- }
- }, [episodeId]);
-
- // First fetch episode servers to get available servers and subtitles
- useEffect(() => {
- if (!currentEpisodeId || currentEpisodeId === 'undefined') {
- setError('Invalid episode ID');
- setIsLoading(false);
- return;
- }
-
- const fetchServers = async () => {
- setIsLoading(true);
-
- try {
- console.log(`[Watch] Fetching servers for episode ${currentEpisodeId}`);
-
- // Fetch available servers from the API
- const data = await fetchEpisodeServers(currentEpisodeId);
-
- if (!data || !data.servers || data.servers.length === 0) {
- console.warn('[Watch] No servers available for this episode');
- } else {
- // Filter servers based on current audio preference (sub/dub)
- const filteredServers = data.servers.filter(server =>
- server.category === (isDub ? 'dub' : 'sub')
- );
-
- setAvailableServers(filteredServers);
- console.log(`[Watch] Available ${isDub ? 'dub' : 'sub'} servers:`, filteredServers);
-
- // Set default server if available
- // First try to find HD-1 server
- let preferredServer = filteredServers.find(server =>
- server.serverName && server.serverName.toLowerCase() === 'hd-2'
- );
-
- // If not found, look for vidstreaming
- if (!preferredServer) {
- preferredServer = filteredServers.find(server =>
- server.serverName && server.serverName.toLowerCase().includes('vidstreaming')
- );
- }
-
- if (preferredServer && preferredServer.serverName) {
- setSelectedServer(preferredServer.serverName.toLowerCase());
- console.log(`[Watch] Selected preferred server: ${preferredServer.serverName}`);
- } else if (filteredServers.length > 0 && filteredServers[0].serverName) {
- setSelectedServer(filteredServers[0].serverName.toLowerCase());
- console.log(`[Watch] Selected first available server: ${filteredServers[0].serverName}`);
- }
- }
-
- // Continue to fetch video sources with the selected server
- fetchVideoSources(currentEpisodeId, isDub, selectedServer);
-
- } catch (error) {
- console.error('[Watch] Error fetching episode servers:', error);
- // Continue to sources even if servers fail
- fetchVideoSources(currentEpisodeId, isDub, selectedServer);
- }
- };
-
- fetchServers();
- }, [currentEpisodeId, isDub]);
-
- // Fetch video sources function
- const fetchVideoSources = async (episodeId, dub, server) => {
- setIsLoading(true);
- setError(null);
- setVideoSource(null);
-
- try {
- console.log(`[Watch] Fetching video for episode ${episodeId} (dub: ${dub}, server: ${server})`);
-
- // Fetch the episode sources from the API
- const data = await fetchEpisodeSources(episodeId, dub, server);
-
- console.log('[Watch] Episode sources API response:', data);
- setEpisodeData(data);
-
- if (!data || !data.sources || data.sources.length === 0) {
- throw new Error('No video sources available for this episode');
- }
-
- // Extract headers if they exist in the response
- if (data.headers) {
- console.log('[Watch] Headers from API:', data.headers);
- setVideoHeaders(data.headers);
- } else {
- // Set default headers if none provided
- const defaultHeaders = {
- "Referer": "https://hianime.to/",
- "Origin": "https://hianime.to"
- };
- setVideoHeaders(defaultHeaders);
- }
-
- // Set subtitles if available in the sources response
- // Check both subtitles and tracks fields since API might return either
- const subtitleData = data.subtitles || data.tracks || [];
- if (subtitleData.length > 0) {
- // Filter out thumbnails from subtitles array
- const filteredSubtitles = subtitleData.filter(sub =>
- sub.lang && sub.lang.toLowerCase() !== 'thumbnails'
- );
-
- // Look for thumbnails separately
- const thumbnailTrack = subtitleData.find(sub =>
- sub.lang && sub.lang.toLowerCase() === 'thumbnails'
- );
-
- if (thumbnailTrack && thumbnailTrack.url) {
- console.log('[Watch] Found thumbnails track:', thumbnailTrack.url);
- setThumbnails(thumbnailTrack.url);
- }
-
- if (filteredSubtitles.length > 0) {
- console.log('[Watch] Found subtitles:', filteredSubtitles.length);
- setSubtitles(filteredSubtitles);
- }
- }
-
- // Try to find the best source in order of preference
- // 1. HLS (m3u8) sources
- // 2. High quality MP4 sources
- const hlsSource = data.sources.find(src => src.isM3U8);
- const mp4Source = data.sources.find(src => !src.isM3U8);
-
- let selectedSource = null;
-
- if (hlsSource && hlsSource.url) {
- console.log('[Watch] Selected HLS source:', hlsSource.url);
- selectedSource = hlsSource.url;
- } else if (mp4Source && mp4Source.url) {
- console.log('[Watch] Selected MP4 source:', mp4Source.url);
- selectedSource = mp4Source.url;
- } else if (data.sources[0] && data.sources[0].url) {
- console.log('[Watch] Falling back to first available source:', data.sources[0].url);
- selectedSource = data.sources[0].url;
- } else {
- throw new Error('No valid video URLs found');
- }
-
- setVideoSource(selectedSource);
- setIsLoading(false);
-
- } catch (error) {
- console.error('[Watch] Error fetching video sources:', error);
- setError(error.message || 'Failed to load video');
- setIsLoading(false);
-
- // If this is the first try, attempt to retry once
- if (!isRetrying) {
- console.log('[Watch] First error, attempting retry...');
- setIsRetrying(true);
- setTimeout(() => {
- console.log('[Watch] Executing retry...');
- fetchVideoSources(episodeId, dub, server);
- }, 2000);
- }
- }
- };
-
- // Effect to refetch sources when server or dub changes
- useEffect(() => {
- if (currentEpisodeId && selectedServer) {
- fetchVideoSources(currentEpisodeId, isDub, selectedServer);
- }
- }, [selectedServer, isDub]);
-
- // Fetch anime info and episodes using animeId
- useEffect(() => {
- if (animeId) {
- const fetchAnimeDetails = async () => {
- try {
- setIsRetrying(true);
- console.log(`[Watch] Fetching anime info for ID: ${animeId}`);
-
- // Fetch basic anime info
- const animeData = await fetchAnimeInfo(animeId);
- if (animeData) {
- console.log('[Watch] Anime info received:', animeData.info?.name);
- setAnime({
- id: animeId,
- title: animeData.info?.name || 'Unknown Anime',
- image: animeData.info?.poster || '',
- description: animeData.info?.description || 'No description available',
- status: animeData.moreInfo?.status || 'Unknown',
- type: animeData.info?.stats?.type || 'TV',
- totalEpisodes: animeData.info?.stats?.episodes?.sub || 0,
- genres: animeData.moreInfo?.genres || []
- });
- }
-
- // Fetch episodes separately
- const episodesData = await fetchAnimeEpisodes(animeId);
- if (episodesData && episodesData.episodes && episodesData.episodes.length > 0) {
- console.log('[Watch] Episodes found:', episodesData.episodes.length);
- setEpisodes(episodesData.episodes);
-
- // Find current episode in episode list
- const findCurrentEpisode = () => {
- // Find the episode by direct ID match
- const directMatch = episodesData.episodes.find(ep => ep.id === currentEpisodeId);
- if (directMatch) {
- console.log('[Watch] Found episode by direct ID match:', directMatch.number);
- return directMatch;
- }
-
- // If no match found, return first episode as fallback
- console.warn('[Watch] Could not find matching episode, falling back to first episode');
- return episodesData.episodes[0];
- };
-
- const episode = findCurrentEpisode();
- if (episode) {
- setCurrentEpisode(episode);
- console.log('[Watch] Current episode found:', episode.number);
- } else {
- console.warn('[Watch] Current episode not found in episode list');
- }
- } else {
- console.warn('[Watch] No episodes found for this anime');
- }
- } catch (error) {
- console.error('[Watch] Error fetching anime details:', error);
- } finally {
- setIsRetrying(false);
- }
- };
-
- fetchAnimeDetails();
- }
- }, [animeId, currentEpisodeId]);
-
- const handleDubToggle = () => {
- setIsDub(prev => {
- const newDubState = !prev;
- // Refetch servers for the new audio type
- fetchEpisodeServers(currentEpisodeId).then(data => {
- if (data && data.servers && data.servers.length > 0) {
- // Filter servers based on new audio preference
- const filteredServers = data.servers.filter(server =>
- server.category === (newDubState ? 'dub' : 'sub')
- );
-
- setAvailableServers(filteredServers);
-
- // Update selected server if needed
- // First try to find HD-1 server
- let preferredServer = filteredServers.find(server =>
- server.serverName && server.serverName.toLowerCase() === 'hd-2'
- );
-
- // If not found, look for vidstreaming
- if (!preferredServer) {
- preferredServer = filteredServers.find(server =>
- server.serverName && server.serverName.toLowerCase().includes('vidstreaming')
- );
- }
-
- if (preferredServer && preferredServer.serverName) {
- setSelectedServer(preferredServer.serverName.toLowerCase());
- console.log(`[Watch] Selected preferred server: ${preferredServer.serverName}`);
- } else if (filteredServers.length > 0 && filteredServers[0].serverName) {
- setSelectedServer(filteredServers[0].serverName.toLowerCase());
- console.log(`[Watch] Selected first available server: ${filteredServers[0].serverName}`);
- }
- }
- });
- return newDubState;
- });
- };
-
- const handleServerChange = (server) => {
- setSelectedServer(server);
- };
-
- const handleEpisodeClick = (newEpisodeId) => {
- if (newEpisodeId !== currentEpisodeId) {
- console.log(`[Watch] Episode clicked, ID: ${newEpisodeId}`);
-
- // Use the episode ID directly as it should already be in the correct format
- // from the API response (animeId?ep=episodeNumber)
-
- // Update the URL using history API
- const newUrl = `/watch/${newEpisodeId}`;
- window.history.pushState({ episodeId: newEpisodeId }, '', newUrl);
-
- // Update state to trigger video reload
- setCurrentEpisodeId(newEpisodeId);
-
- // Update current episode in state
- if (episodes) {
- const newEpisode = episodes.find(ep => ep.id === newEpisodeId);
- if (newEpisode) {
- setCurrentEpisode(newEpisode);
- }
- }
- }
- };
-
- const findAdjacentEpisodes = () => {
- if (!episodes || !currentEpisode) return { prev: null, next: null };
-
- const currentIndex = episodes.findIndex(ep => ep.number === currentEpisode.number);
- if (currentIndex === -1) return { prev: null, next: null };
-
- return {
- prev: currentIndex > 0 ? episodes[currentIndex - 1] : null,
- next: currentIndex < episodes.length - 1 ? episodes[currentIndex + 1] : null
- };
- };
-
- const { prev, next } = findAdjacentEpisodes();
-
- return (
-
-
-
- {/* Left Side - Video Player (70%) */}
-
-
- {/* Video Player Container */}
-
-
-
- {error ? (
-
-
Error: {error}
-
- The video source couldn't be loaded. Please try again or check back later.
-
-
- ) : isLoading ? (
-
- ) : videoSource ? (
-
-
-
- ) : (
-
-
No video source available
-
- Please try again or check back later.
-
-
- )}
-
-
-
-
- {/* Video Controls - Slimmer and without container background */}
-
- {/* Audio and Playback Controls */}
-
- {/* Playback Settings */}
-
-
Playback Settings
-
- {/* Auto Skip Checkbox */}
- {(episodeData?.intro || episodeData?.outro) && (
-
- setAutoSkip(e.target.checked)}
- className="w-4 h-4 text-white bg-white/10 border-none rounded cursor-pointer focus:ring-white focus:ring-offset-0 focus:ring-offset-transparent focus:ring-opacity-50"
- />
- Auto Skip
-
- )}
-
-
-
- {/* Server Selection */}
- {availableServers.length > 0 && (
-
-
Servers
-
- {availableServers.map((server) =>
- server.serverName ? (
- handleServerChange(server.serverName.toLowerCase())}
- className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all ${
- selectedServer === server.serverName.toLowerCase()
- ? 'bg-white text-black'
- : 'bg-white/5 text-gray-400 hover:text-white hover:bg-white/10 ring-1 ring-white/10'
- }`}
- >
- {server.serverName}
-
- ) : null
- )}
-
-
- )}
-
- {/* Audio Toggle */}
-
-
Audio
-
- setIsDub(false)}
- className={`px-4 py-1.5 rounded-md text-sm font-medium transition-all ${
- !isDub
- ? 'bg-white text-black'
- : 'text-gray-400 hover:text-white'
- }`}
- >
- SUB
-
- setIsDub(true)}
- className={`px-4 py-1.5 rounded-md text-sm font-medium transition-all ${
- isDub
- ? 'bg-white text-black'
- : 'text-gray-400 hover:text-white'
- }`}
- >
- DUB
-
-
-
-
-
- {/* Episode Navigation */}
-
- {episodes && episodes.length > 0 && (
- <>
-
{
- const { prev } = findAdjacentEpisodes();
- if (prev) {
- handleEpisodeClick(prev.id);
- }
- }}
- disabled={!findAdjacentEpisodes().prev}
- className="px-4 py-2 rounded-lg bg-white/5 text-white disabled:opacity-30
- disabled:cursor-not-allowed hover:bg-white/10 transition-all
- flex items-center gap-2 flex-1 justify-center ring-1 ring-white/10"
- >
-
-
-
- Previous Episode
-
-
{
- const { next } = findAdjacentEpisodes();
- if (next) {
- handleEpisodeClick(next.id);
- }
- }}
- disabled={!findAdjacentEpisodes().next}
- className="px-4 py-2 rounded-lg bg-white/5 text-white disabled:opacity-30
- disabled:cursor-not-allowed hover:bg-white/10 transition-all
- flex items-center gap-2 flex-1 justify-center ring-1 ring-white/10"
- >
- Next Episode
-
-
-
-
- >
- )}
-
-
-
- {/* Anime Info Section */}
- {anime && (
-
-
- {/* Cover Image */}
-
-
- {/* Details */}
-
-
-
- {anime.title}
-
-
-
- {/* Status Bar */}
-
- {anime.status}
- •
- {anime.type}
- •
- {anime.totalEpisodes} Episodes
-
-
- {/* Synopsis Section */}
-
-
Synopsis
-
-
- {anime.description}
-
-
setShowFullSynopsis(!showFullSynopsis)}
- className="text-white hover:text-white/80 transition-colors mt-2 text-sm font-medium"
- >
- {showFullSynopsis ? 'Show Less' : 'Read More'}
-
-
-
-
- {/* Genres */}
- {anime.genres && (
-
- {anime.genres.map((genre, index) => (
-
- {genre}
-
- ))}
-
- )}
-
-
-
- )}
-
-
-
- {/* Right Side - Episode List (30%) */}
-
- {episodes && episodes.length > 0 ? (
-
-
-
- ) : (
-
-
- {isLoading ? 'Loading episodes...' : 'No episodes available'}
-
-
- )}
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/app/watch/layout.js b/src/app/watch/layout.js
deleted file mode 100644
index 3fca793..0000000
--- a/src/app/watch/layout.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import SharedLayout from '@/components/SharedLayout';
-
-export default function WatchLayout({ children }) {
- return {children} ;
-}
\ No newline at end of file
diff --git a/src/components/AnimeCalendar.js b/src/components/AnimeCalendar.js
deleted file mode 100644
index c4e3b53..0000000
--- a/src/components/AnimeCalendar.js
+++ /dev/null
@@ -1,210 +0,0 @@
-'use client';
-
-import { useState, useEffect } from 'react';
-import Link from 'next/link';
-import Image from 'next/image';
-import { fetchSchedule } from '@/lib/api';
-
-export default function AnimeCalendar() {
- const [selectedDay, setSelectedDay] = useState(getCurrentDayIndex());
- const [scheduleData, setScheduleData] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
-
- // Add custom scrollbar styles
- useEffect(() => {
- // Add custom styles for the calendar scrollbar
- const style = document.createElement('style');
- style.textContent = `
- .schedule-scrollbar::-webkit-scrollbar {
- width: 4px;
- }
- .schedule-scrollbar::-webkit-scrollbar-track {
- background: var(--card);
- }
- .schedule-scrollbar::-webkit-scrollbar-thumb {
- background-color: var(--border);
- border-radius: 4px;
- }
- `;
- document.head.appendChild(style);
-
- // Cleanup function
- return () => {
- document.head.removeChild(style);
- };
- }, []);
-
- // Get current day index (0-6, Sunday is 0)
- function getCurrentDayIndex() {
- const dayIndex = new Date().getDay();
- return dayIndex; // Sunday is 0, Monday is 1, etc.
- }
-
- // Get current date info for the header
- const getCurrentDateInfo = () => {
- const today = new Date();
- const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
- const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
-
- // Calculate the date for the selected day
- const currentDayIndex = today.getDay();
- let daysDiff = selectedDay - currentDayIndex;
-
- // Always get the previous occurrence (or today if it's the current day)
- if (daysDiff > 0) {
- daysDiff -= 7; // Go back to previous week
- }
-
- const selectedDate = new Date(today);
- selectedDate.setDate(today.getDate() + daysDiff);
-
- return {
- day: dayNames[selectedDay],
- date: selectedDate.getDate(),
- month: monthNames[selectedDate.getMonth()]
- };
- };
-
- const dateInfo = getCurrentDateInfo();
-
- // Generate week days for the calendar
- const days = [
- { label: 'Mon', value: 1 },
- { label: 'Tue', value: 2 },
- { label: 'Wed', value: 3 },
- { label: 'Thu', value: 4 },
- { label: 'Fri', value: 5 },
- { label: 'Sat', value: 6 },
- { label: 'Sun', value: 0 },
- ];
-
- useEffect(() => {
- async function loadScheduleData() {
- setIsLoading(true);
- try {
- // Get the date for the selected day
- const today = new Date();
- const currentDayIndex = today.getDay();
- let daysDiff = selectedDay - currentDayIndex;
-
- if (daysDiff > 0) {
- daysDiff -= 7;
- }
-
- const selectedDate = new Date(today);
- selectedDate.setDate(today.getDate() + daysDiff);
-
- // Format date as YYYY-MM-DD
- const formattedDate = selectedDate.toISOString().split('T')[0];
-
- // Fetch schedule data for the selected date
- const data = await fetchSchedule(formattedDate);
-
- if (data && data.scheduledAnimes) {
- // Process and sort the scheduled animes by time
- const processedData = data.scheduledAnimes
- .map(anime => ({
- id: anime.id,
- title: anime.name,
- japaneseTitle: anime.jname,
- time: anime.time,
- airingTimestamp: anime.airingTimestamp,
- secondsUntilAiring: anime.secondsUntilAiring
- }))
- .sort((a, b) => {
- // Convert time strings to comparable values (assuming 24-hour format)
- const timeA = a.time.split(':').map(Number);
- const timeB = b.time.split(':').map(Number);
- return (timeA[0] * 60 + timeA[1]) - (timeB[0] * 60 + timeB[1]);
- });
-
- setScheduleData(processedData);
- } else {
- setScheduleData([]);
- }
- } catch (error) {
- console.error('Error loading schedule data:', error);
- setScheduleData([]);
- } finally {
- setIsLoading(false);
- }
- }
-
- loadScheduleData();
- }, [selectedDay]);
-
- return (
-
- {/* Header */}
-
-
-
Release Calendar
-
- {dateInfo.month} {dateInfo.date}
-
-
-
- {/* Day selector */}
-
- {days.map((day) => (
- setSelectedDay(day.value)}
- className={`
- flex-1 py-2 text-sm font-medium rounded-md transition-colors
- ${selectedDay === day.value
- ? 'bg-white text-[var(--background)]'
- : 'text-[var(--text-muted)] hover:text-white'
- }
- `}
- >
- {day.label}
-
- ))}
-
-
-
- {/* Schedule list */}
-
- {isLoading ? (
-
- ) : scheduleData.length > 0 ? (
-
- {scheduleData.map((anime) => (
-
-
- {/* Time */}
-
- {anime.time}
-
-
- {/* Anime info */}
-
-
- {anime.title}
-
- {anime.japaneseTitle && (
-
- {anime.japaneseTitle}
-
- )}
-
-
-
- ))}
-
- ) : (
-
- No releases scheduled
-
- )}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/AnimeCard.js b/src/components/AnimeCard.js
deleted file mode 100644
index 14952c0..0000000
--- a/src/components/AnimeCard.js
+++ /dev/null
@@ -1,158 +0,0 @@
-'use client';
-
-import React, { useState, useEffect, useRef } from 'react';
-import Image from 'next/image';
-import Link from 'next/link';
-import { fetchAnimeEpisodes } from '@/lib/api';
-
-export default function AnimeCard({ anime, isRecent }) {
- const [imageError, setImageError] = useState(false);
- const [firstEpisodeId, setFirstEpisodeId] = useState(null);
- const [isLoading, setIsLoading] = useState(false);
- const timerRef = useRef(null);
-
- // Fetch first episode ID when component mounts for recent anime
- useEffect(() => {
- const fetchFirstEpisode = async () => {
- // Only fetch for recent anime and if we don't already have the episode ID
- if (isRecent && anime?.id && !firstEpisodeId && !isLoading) {
- setIsLoading(true);
- try {
- console.log(`[AnimeCard] Fetching episodes for anime: ${anime.id}`);
- const response = await fetchAnimeEpisodes(anime.id);
- console.log(`[AnimeCard] Episodes response for ${anime.name}:`, response);
-
- if (response.episodes && response.episodes.length > 0) {
- // Check for the episode ID in the format expected by the watch page
- const firstEp = response.episodes[0];
- if (firstEp.id) {
- setFirstEpisodeId(firstEp.id);
- console.log(`[AnimeCard] First episode ID (id) for ${anime.name}: ${firstEp.id}`);
- } else if (firstEp.episodeId) {
- setFirstEpisodeId(firstEp.episodeId);
- console.log(`[AnimeCard] First episode ID (episodeId) for ${anime.name}: ${firstEp.episodeId}`);
- } else {
- // Create a fallback ID if neither id nor episodeId are available
- const fallbackId = `${anime.id}?ep=1`;
- setFirstEpisodeId(fallbackId);
- console.log(`[AnimeCard] Using fallback ID for ${anime.name}: ${fallbackId}`);
- }
- } else if (anime.id) {
- // If no episodes found, create a fallback ID
- const fallbackId = `${anime.id}?ep=1`;
- setFirstEpisodeId(fallbackId);
- console.log(`[AnimeCard] No episodes found for ${anime.name}, using fallback ID: ${fallbackId}`);
- }
- } catch (error) {
- console.error(`[AnimeCard] Error fetching episodes for ${anime.id}:`, error);
- // Even on error, try to use fallback
- if (anime.id) {
- const fallbackId = `${anime.id}?ep=1`;
- setFirstEpisodeId(fallbackId);
- console.log(`[AnimeCard] Error for ${anime.name}, using fallback ID: ${fallbackId}`);
- }
- } finally {
- setIsLoading(false);
- }
- }
- };
-
- fetchFirstEpisode();
-
- // Clean up timer if component unmounts
- return () => {
- if (timerRef.current) clearTimeout(timerRef.current);
- };
- }, [anime?.id, anime?.name, isRecent, firstEpisodeId, isLoading]);
-
- if (!anime) return null;
-
- const handleImageError = () => {
- console.log("Image error for:", anime.name);
- setImageError(true);
- };
-
- // Get image URL with fallback
- const imageSrc = imageError ? '/images/placeholder.png' : anime.poster;
-
- // Generate appropriate links
- const infoLink = `/anime/${anime.id}`;
-
- // Build the watch URL based on the first episode ID or fallback
- const watchLink = isRecent && firstEpisodeId
- ? `/watch/${firstEpisodeId}`
- : isRecent
- ? `/anime/${anime.id}` // Temporarily link to info page while loading
- : `/anime/${anime.id}`; // Non-recent anime always link to info
-
- return (
-
- {/* Image card linking to watch page for recent anime, or info page otherwise */}
-
-
- {/* Hover overlay */}
-
-
- {/* Play button triangle - appears on hover */}
-
-
-
-
- {/* Badges in bottom left */}
-
- {/* Episode badges */}
- {anime.episodes && (
- <>
- {anime.episodes.sub > 0 && (
-
- SUB {anime.episodes.sub}
-
- )}
- {anime.episodes.dub > 0 && (
-
- DUB {anime.episodes.dub}
-
- )}
- >
- )}
-
- {/* Type badge */}
- {anime.type && (
-
- {anime.type}
-
- )}
-
-
-
-
- {/* Title linking to info page */}
-
-
- {anime.name}
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/AnimeDetails.js b/src/components/AnimeDetails.js
deleted file mode 100644
index 68c2654..0000000
--- a/src/components/AnimeDetails.js
+++ /dev/null
@@ -1,585 +0,0 @@
-'use client';
-
-import { useState, useRef, useEffect } from 'react';
-import Image from 'next/image';
-import Link from 'next/link';
-import AnimeRow from './AnimeRow';
-import SeasonRow from './SeasonRow';
-import { fetchAnimeEpisodes } from '@/lib/api';
-
-export default function AnimeDetails({ anime }) {
- const [isExpanded, setIsExpanded] = useState(false);
- const [activeVideo, setActiveVideo] = useState(null);
- const [activeTab, setActiveTab] = useState('synopsis');
- const [synopsisOverflows, setSynopsisOverflows] = useState(false);
- const [firstEpisodeId, setFirstEpisodeId] = useState(null);
- const [isLoadingEpisodes, setIsLoadingEpisodes] = useState(false);
- const synopsisRef = useRef(null);
-
- // Check if synopsis overflows when component mounts or when content changes
- useEffect(() => {
- if (synopsisRef.current) {
- const element = synopsisRef.current;
- setSynopsisOverflows(element.scrollHeight > element.clientHeight);
- }
- }, [anime?.info?.description, activeTab]);
-
- // Fetch first episode ID when component mounts
- useEffect(() => {
- const fetchFirstEpisode = async () => {
- if (anime?.info?.id) {
- setIsLoadingEpisodes(true);
- try {
- console.log(`[AnimeDetails] Fetching episodes for anime: ${anime.info.id}`);
- const response = await fetchAnimeEpisodes(anime.info.id);
- console.log('[AnimeDetails] Episodes response:', response);
-
- if (response.episodes && response.episodes.length > 0) {
- // Log the first episode to check its structure
- console.log('[AnimeDetails] First episode:', response.episodes[0]);
-
- // Get the first episode's id
- const firstEp = response.episodes[0];
- if (firstEp.id) {
- setFirstEpisodeId(firstEp.id);
- console.log(`[AnimeDetails] First episode ID found: ${firstEp.id}`);
- } else if (firstEp.episodeId) {
- // Fallback to episodeId if id is not available
- setFirstEpisodeId(firstEp.episodeId);
- console.log(`[AnimeDetails] Falling back to episodeId: ${firstEp.episodeId}`);
- } else {
- // If no episode ID is found in the API response, create a fallback ID
- const fallbackId = `${anime.info.id}?ep=1`;
- setFirstEpisodeId(fallbackId);
- console.log(`[AnimeDetails] Using fallback ID: ${fallbackId}`);
- }
- } else if (anime.info.id) {
- // If no episodes found but anime ID is available, use fallback
- const fallbackId = `${anime.info.id}?ep=1`;
- setFirstEpisodeId(fallbackId);
- console.log(`[AnimeDetails] No episodes found, using fallback ID: ${fallbackId}`);
- } else {
- console.warn('[AnimeDetails] No episodes found and no anime ID available');
- }
- } catch (error) {
- console.error('[AnimeDetails] Error fetching episodes:', error);
- // Even on error, try to use fallback
- if (anime.info.id) {
- const fallbackId = `${anime.info.id}?ep=1`;
- setFirstEpisodeId(fallbackId);
- console.log(`[AnimeDetails] Error occurred, using fallback ID: ${fallbackId}`);
- }
- } finally {
- setIsLoadingEpisodes(false);
- }
- }
- };
-
- fetchFirstEpisode();
- }, [anime?.info?.id]);
-
- // Add a useEffect to debug when and why firstEpisodeId changes
- useEffect(() => {
- console.log('[AnimeDetails] firstEpisodeId changed:', firstEpisodeId);
- }, [firstEpisodeId]);
-
- if (!anime?.info) {
- return null;
- }
-
- const { info, moreInfo, relatedAnime, recommendations, seasons } = anime;
- const hasCharacters = info.characterVoiceActor?.length > 0 || info.charactersVoiceActors?.length > 0;
- const hasVideos = info.promotionalVideos && info.promotionalVideos.length > 0;
-
- // Build the watch URL based on the first episode ID
- const watchUrl = firstEpisodeId
- ? `/watch/${firstEpisodeId}`
- : ''; // Empty string if no episodes available - this shouldn't happen with our fallback
-
- // Add debug log here
- console.log('[AnimeDetails] Rendered with watchUrl:', watchUrl, 'firstEpisodeId:', firstEpisodeId);
-
- // Video modal for promotional videos
- const VideoModal = ({ video, onClose }) => {
- if (!video) return null;
-
- return (
-
- );
- };
-
- // Format status with aired date
- const getStatusWithAired = () => {
- let status = moreInfo?.status || '';
- if (moreInfo?.aired) {
- status += ` (${moreInfo.aired})`;
- }
- return status;
- };
-
- return (
-
- {/* Video Modal */}
- {activeVideo &&
setActiveVideo(null)} />}
-
- {/* Background Image with Gradient Overlay - Desktop Only */}
-
- {info.poster && (
- <>
-
-
- >
- )}
-
-
- {/* Main Content */}
-
- {/* MOBILE LAYOUT - Only visible on mobile */}
-
-
- {/* Mobile Header with Title + Rating */}
-
-
{info.name}
- {info.stats?.rating && (
-
-
-
-
-
{info.stats.rating}
-
- )}
-
-
- {/* Japanese Title */}
- {moreInfo?.japanese && (
-
{moreInfo.japanese}
- )}
-
- {/* Mobile Two-Column Layout */}
-
- {/* Left Column - Poster */}
-
-
- {/* Right Column - Info Card */}
-
- {/* Type & Episodes on same row */}
-
- {info.stats?.type && (
-
{info.stats.type}
- )}
-
- {info.stats?.episodes && (
-
- {info.stats.episodes.sub > 0 && `Sub: ${info.stats.episodes.sub}`}
- {info.stats.episodes.dub > 0 && info.stats.episodes.sub > 0 && ' • '}
- {info.stats.episodes.dub > 0 && `Dub: ${info.stats.episodes.dub}`}
-
- )}
-
-
- {/* Clean Info Layout */}
-
- {/* Status */}
- {moreInfo?.status && (
-
- Status:
- {getStatusWithAired()}
-
- )}
-
- {/* Quality */}
- {info.stats?.quality && (
-
- Quality:
- {info.stats.quality}
-
- )}
-
- {/* Duration */}
- {info.stats?.duration && (
-
- Duration:
- {info.stats.duration}
-
- )}
-
- {/* Studio */}
- {moreInfo?.studios && (
-
- Studio:
- {moreInfo.studios}
-
- )}
-
-
- {/* Mobile Genres */}
- {moreInfo?.genres && moreInfo.genres.length > 0 && (
-
- {moreInfo.genres.slice(0, 5).map((genre, index) => (
-
- {genre}
-
- ))}
- {moreInfo.genres.length > 5 && (
- +{moreInfo.genres.length - 5}
- )}
-
- )}
-
-
-
- {/* Watch Button - Mobile */}
- {firstEpisodeId && (
-
-
-
-
-
Start Watching
-
- )}
-
-
-
- {/* DESKTOP LAYOUT - Only visible on desktop */}
-
- {/* Poster */}
-
-
-
- {/* Watch Button - Desktop */}
- {firstEpisodeId && (
-
-
-
-
-
Start Watching
-
- )}
-
-
- {/* Title and Metadata */}
-
- {/* Title Section */}
-
-
- {info.name}
-
-
- {moreInfo?.japanese && (
-
{moreInfo.japanese}
- )}
-
- {/* Synonyms */}
- {moreInfo?.synonyms && (
-
- )}
-
-
- {/* Status Badges */}
-
- {info.stats?.rating && (
-
-
-
-
-
{info.stats.rating}
-
- )}
-
- {/* Status with Aired Date */}
- {moreInfo?.status && (
-
- {getStatusWithAired()}
-
- )}
-
- {info.stats?.type && (
-
- {info.stats.type}
-
- )}
-
- {info.stats?.episodes && (
-
- {info.stats.episodes.sub > 0 && `SUB ${info.stats.episodes.sub}`}
- {info.stats.episodes.dub > 0 && info.stats.episodes.sub > 0 && ' | '}
- {info.stats.episodes.dub > 0 && `DUB ${info.stats.episodes.dub}`}
-
- )}
-
- {info.stats?.quality && (
-
- {info.stats.quality}
-
- )}
-
- {info.stats?.duration && (
-
- {info.stats.duration}
-
- )}
-
-
- {/* Genres & Studios */}
-
- {/* Genres */}
- {moreInfo?.genres && moreInfo.genres.length > 0 && (
-
-
Genres
-
- {moreInfo.genres.map((genre, index) => (
-
- {genre}
-
- ))}
-
-
- )}
-
- {/* Studios */}
- {moreInfo?.studios && (
-
-
Studios
-
-
- {moreInfo.studios}
-
-
-
- )}
-
-
-
-
- {/* Tabs Section - Different for Mobile/Desktop */}
-
- {/* Tab Navigation */}
-
- {/* Synopsis Tab */}
- setActiveTab('synopsis')}
- >
- Synopsis
-
-
- {/* Characters Tab */}
- {hasCharacters && (
- setActiveTab('characters')}
- >
- Characters
-
- )}
-
- {/* Videos Tab */}
- {hasVideos && (
- setActiveTab('videos')}
- >
- Videos
-
- )}
-
-
- {/* Tab Content */}
-
- {/* Synopsis Tab */}
- {activeTab === 'synopsis' && (
-
-
- {info.description || 'No description available for this anime.'}
-
- {synopsisOverflows && (
-
setIsExpanded(!isExpanded)}
- className="text-[var(--primary)] hover:underline text-xs md:text-sm mt-2 md:mt-3 font-medium"
- >
- {isExpanded ? 'Show Less' : 'Read More'}
-
- )}
-
- )}
-
- {/* Characters Tab */}
- {activeTab === 'characters' && hasCharacters && (
-
- {(info.characterVoiceActor || info.charactersVoiceActors || []).map((item, index) => (
-
- {/* Character Image */}
-
-
-
-
- {/* Text content in the middle */}
-
-
- {/* Character Name */}
-
-
{item.character.name}
-
{item.character.cast || 'Main'}
-
-
- {/* Voice Actor Name */}
-
-
{item.voiceActor.name}
-
{item.voiceActor.cast || 'Japanese'}
-
-
-
-
- {/* Voice Actor Image */}
-
-
-
-
- ))}
-
- )}
-
- {/* Videos Tab */}
- {activeTab === 'videos' && hasVideos && (
-
- {info.promotionalVideos.map((video, index) => (
-
setActiveVideo(video)}
- >
-
-
-
- ))}
-
- )}
-
-
-
- {/* Seasons Section */}
- {seasons && seasons.length > 0 && (
-
- )}
-
- {/* Related Anime Section */}
- {relatedAnime && relatedAnime.length > 0 && (
-
- )}
-
- {/* Recommendations Section */}
- {recommendations && recommendations.length > 0 && (
-
- )}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/AnimeFilters.js b/src/components/AnimeFilters.js
deleted file mode 100644
index 2a7a507..0000000
--- a/src/components/AnimeFilters.js
+++ /dev/null
@@ -1,597 +0,0 @@
-'use client';
-
-import { useState, useEffect, useRef } from 'react';
-import { fetchGenres } from '@/lib/api';
-import { ChevronDownIcon, CheckIcon, XMarkIcon } from '@heroicons/react/24/outline';
-
-// Helper function to capitalize first letter of each word
-const capitalizeFirstLetter = (string) => {
- if (!string) return '';
- return string.split(' ').map(word =>
- word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
- ).join(' ');
-};
-
-export default function AnimeFilters({
- selectedGenre,
- onGenreChange,
- yearFilter,
- onYearChange,
- sortOrder,
- onSortChange,
- showGenreFilter = true,
- searchQuery = '',
- onSearchChange,
- selectedSeasons = [],
- onSeasonChange,
- selectedTypes = [],
- onTypeChange,
- selectedStatus = [],
- onStatusChange,
- selectedLanguages = [],
- onLanguageChange
-}) {
- const [genres, setGenres] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [error, setError] = useState(null);
- const [dropdowns, setDropdowns] = useState({
- genre: false,
- season: false,
- year: false,
- type: false,
- status: false,
- language: false,
- sort: false
- });
- const dropdownRefs = useRef({
- genre: null,
- season: null,
- year: null,
- type: null,
- status: null,
- language: null,
- sort: null
- });
-
- // Available years for filter (current year down to 2000 and 'older')
- const currentYear = new Date().getFullYear();
- const years = ['all', ...Array.from({ length: currentYear - 1999 }, (_, i) => (currentYear - i).toString()), 'older'];
-
- // Seasons data
- const seasons = ['Winter', 'Spring', 'Summer', 'Fall'];
-
- // Types data
- const types = ['TV', 'Movie', 'OVA', 'ONA', 'Special'];
-
- // Status data
- const statuses = ['Ongoing', 'Completed', 'Upcoming'];
-
- // Languages data
- const languages = ['Subbed', 'Dubbed', 'Chinese', 'English'];
-
- // Fetch genres on component mount
- useEffect(() => {
- const getGenres = async () => {
- if (!showGenreFilter) return;
-
- try {
- setIsLoading(true);
- const genreData = await fetchGenres();
- // Capitalize each genre
- const capitalizedGenres = genreData ? genreData.map(capitalizeFirstLetter) : [];
- setGenres(capitalizedGenres);
- } catch (error) {
- console.error('Error fetching genres:', error);
- setError('Failed to load genres. Please try again later.');
- } finally {
- setIsLoading(false);
- }
- };
-
- getGenres();
- }, [showGenreFilter]);
-
- // Toggle dropdown visibility
- const toggleDropdown = (dropdown) => {
- setDropdowns(prev => {
- // Close other dropdowns when opening one
- const newState = {
- genre: false,
- season: false,
- year: false,
- type: false,
- status: false,
- language: false,
- sort: false,
- [dropdown]: !prev[dropdown]
- };
- return newState;
- });
- };
-
- // Initialize refs for each dropdown
- useEffect(() => {
- dropdownRefs.current = {
- genre: dropdownRefs.current.genre,
- season: dropdownRefs.current.season,
- year: dropdownRefs.current.year,
- type: dropdownRefs.current.type,
- status: dropdownRefs.current.status,
- language: dropdownRefs.current.language,
- sort: dropdownRefs.current.sort
- };
- }, []);
-
- // Close all dropdowns when clicking outside
- useEffect(() => {
- const handleClickOutside = (event) => {
- // Check if the click was outside all dropdown containers
- let isOutside = true;
- Object.keys(dropdownRefs.current).forEach(key => {
- if (dropdownRefs.current[key] && dropdownRefs.current[key].contains(event.target)) {
- isOutside = false;
- }
- });
-
- if (isOutside) {
- setDropdowns({
- genre: false,
- season: false,
- year: false,
- type: false,
- status: false,
- language: false,
- sort: false
- });
- }
- };
-
- document.addEventListener('mousedown', handleClickOutside);
- return () => {
- document.removeEventListener('mousedown', handleClickOutside);
- };
- }, []);
-
- // Prevent dropdown from closing when selecting an item in multiselect
- const keepDropdownOpen = (e, dropdown) => {
- e.stopPropagation();
- // Don't toggle the dropdown state on item click for multi-select dropdowns
- };
-
- const handleClearGenre = (e) => {
- e.stopPropagation();
- if (onGenreChange) {
- onGenreChange(null);
- }
- };
-
- // Toggle multi-select filter
- const handleMultiSelectToggle = (type, value, onChange) => {
- if (!onChange) return;
-
- let updatedSelection;
- if (type.includes(value)) {
- updatedSelection = type.filter(item => item !== value);
- } else {
- updatedSelection = [...type, value];
- }
- onChange(updatedSelection);
- };
-
- // Modify the onClick handlers for each button to prevent event propagation
- const handleGenreSelect = (e, genre) => {
- e.stopPropagation();
- if (onGenreChange) {
- onGenreChange(genre);
- // Close genre dropdown after selection since it's a single select
- setDropdowns(prev => ({ ...prev, genre: false }));
- }
- };
-
- const handleYearSelect = (e, year) => {
- e.stopPropagation();
- if (onYearChange) {
- onYearChange(year);
- // Close year dropdown after selection since it's a single select
- setDropdowns(prev => ({ ...prev, year: false }));
- }
- };
-
- const handleSortSelect = (e, sort) => {
- e.stopPropagation();
- if (onSortChange) {
- onSortChange(sort);
- // Close sort dropdown after selection since it's a single select
- setDropdowns(prev => ({ ...prev, sort: false }));
- }
- };
-
- const handleMultiSelect = (e, type, value, onChange, dropdown) => {
- e.stopPropagation();
- let updatedSelection;
- if (type.includes(value)) {
- updatedSelection = type.filter(item => item !== value);
- } else {
- updatedSelection = [...type, value];
- }
-
- if (onChange) {
- onChange(updatedSelection);
- // Keep dropdown open for multiselect to allow multiple selections
- // Without closing the dropdown
- }
- };
-
- // Add clear filter handlers
- const clearAllFilters = (e) => {
- e.stopPropagation();
- if (onGenreChange) onGenreChange(null);
- if (onYearChange) onYearChange('all');
- if (onSortChange) onSortChange('default');
- if (onSeasonChange) onSeasonChange([]);
- if (onTypeChange) onTypeChange([]);
- if (onStatusChange) onStatusChange([]);
- if (onLanguageChange) onLanguageChange([]);
- };
-
- const clearGenre = (e) => {
- e.stopPropagation();
- if (onGenreChange) onGenreChange(null);
- };
-
- const clearYear = (e) => {
- e.stopPropagation();
- if (onYearChange) onYearChange('all');
- };
-
- const clearSort = (e) => {
- e.stopPropagation();
- if (onSortChange) onSortChange('default');
- };
-
- const clearSeasons = (e) => {
- e.stopPropagation();
- if (onSeasonChange) onSeasonChange([]);
- };
-
- const clearTypes = (e) => {
- e.stopPropagation();
- if (onTypeChange) onTypeChange([]);
- };
-
- const clearStatus = (e) => {
- e.stopPropagation();
- if (onStatusChange) onStatusChange([]);
- };
-
- const clearLanguages = (e) => {
- e.stopPropagation();
- if (onLanguageChange) onLanguageChange([]);
- };
-
- // Get display text for filters
- const getYearDisplayText = () => {
- if (yearFilter === 'all') return 'Year';
- if (yearFilter === 'older') return 'Before 2000';
- return yearFilter;
- };
-
- const getSortDisplayText = () => {
- switch (sortOrder) {
- case 'title-asc': return 'Title (A-Z)';
- case 'title-desc': return 'Title (Z-A)';
- case 'year-desc': return 'Newest First';
- case 'year-asc': return 'Oldest First';
- default: return 'Default';
- }
- };
-
- // Check if any filter is active
- const isAnyFilterActive = () => {
- return selectedGenre !== null ||
- yearFilter !== 'all' ||
- sortOrder !== 'default' ||
- selectedSeasons.length > 0 ||
- selectedTypes.length > 0 ||
- selectedStatus.length > 0 ||
- selectedLanguages.length > 0;
- };
-
- return (
-
-
- {/* Genre Filter */}
-
dropdownRefs.current.genre = el}>
-
toggleDropdown('genre')}
- className="flex items-center justify-between w-full px-4 py-2 rounded-lg bg-[#141414] hover:bg-[#1a1a1a] active:bg-[#1f1f1f] border border-white/[0.04] group transition-colors"
- >
-
- {selectedGenre ? selectedGenre : 'Genre'}
-
-
-
-
-
-
- {dropdowns.genre && (
-
-
- {genres.map((genre) => (
- handleGenreSelect(e, genre)}
- className={`w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors ${
- selectedGenre === genre ? 'text-white font-medium' : 'text-white/70'
- }`}
- >
- {genre}
-
- ))}
-
-
- )}
-
-
- {/* Year Filter */}
-
dropdownRefs.current.year = el}>
-
toggleDropdown('year')}
- className="flex items-center justify-between w-full px-4 py-2 rounded-lg bg-[#141414] hover:bg-[#1a1a1a] active:bg-[#1f1f1f] border border-white/[0.04] group transition-colors"
- >
-
- {getYearDisplayText()}
-
-
-
-
-
-
- {dropdowns.year && (
-
-
- {years.map((year) => (
- handleYearSelect(e, year)}
- className={`w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors ${
- yearFilter === year ? 'text-white font-medium' : 'text-white/70'
- }`}
- >
- {year === 'older' ? 'Before 2000' : year === 'all' ? 'All Years' : year}
-
- ))}
-
-
- )}
-
-
- {/* Season Filter */}
-
dropdownRefs.current.season = el}>
-
toggleDropdown('season')}
- className="flex items-center justify-between w-full px-4 py-2 rounded-lg bg-[#141414] hover:bg-[#1a1a1a] active:bg-[#1f1f1f] border border-white/[0.04] group transition-colors"
- >
-
- {selectedSeasons.length > 0 ? `${selectedSeasons.length} Selected` : 'Season'}
-
-
-
-
-
-
- {dropdowns.season && (
-
keepDropdownOpen(e, 'season')} className="absolute z-50 w-full mt-2 py-1 bg-[#141414] rounded-lg border border-white/[0.04] shadow-xl">
- {seasons.map((season) => (
- handleMultiSelect(e, selectedSeasons, season, onSeasonChange, 'season')}
- className="w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors flex items-center justify-between"
- >
-
- {season}
-
- {selectedSeasons.includes(season) && (
-
- )}
-
- ))}
-
- )}
-
-
- {/* Format Filter */}
-
dropdownRefs.current.type = el}>
-
toggleDropdown('type')}
- className="flex items-center justify-between w-full px-4 py-2 rounded-lg bg-[#141414] hover:bg-[#1a1a1a] active:bg-[#1f1f1f] border border-white/[0.04] group transition-colors"
- >
-
- {selectedTypes.length > 0 ? `${selectedTypes.length} Selected` : 'Format'}
-
-
-
-
-
-
- {dropdowns.type && (
-
keepDropdownOpen(e, 'type')} className="absolute z-50 w-full mt-2 py-1 bg-[#141414] rounded-lg border border-white/[0.04] shadow-xl">
- {types.map((type) => (
- handleMultiSelect(e, selectedTypes, type, onTypeChange, 'type')}
- className="w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors flex items-center justify-between"
- >
-
- {type}
-
- {selectedTypes.includes(type) && (
-
- )}
-
- ))}
-
- )}
-
-
- {/* Status Filter */}
-
dropdownRefs.current.status = el}>
-
toggleDropdown('status')}
- className="flex items-center justify-between w-full px-4 py-2 rounded-lg bg-[#141414] hover:bg-[#1a1a1a] active:bg-[#1f1f1f] border border-white/[0.04] group transition-colors"
- >
-
- {selectedStatus.length > 0 ? `${selectedStatus.length} Selected` : 'Status'}
-
-
-
-
-
-
- {dropdowns.status && (
-
keepDropdownOpen(e, 'status')} className="absolute z-50 w-full mt-2 py-1 bg-[#141414] rounded-lg border border-white/[0.04] shadow-xl">
- {statuses.map((status) => (
- handleMultiSelect(e, selectedStatus, status, onStatusChange, 'status')}
- className="w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors flex items-center justify-between"
- >
-
- {status}
-
- {selectedStatus.includes(status) && (
-
- )}
-
- ))}
-
- )}
-
-
- {/* Language Filter */}
-
dropdownRefs.current.language = el}>
-
toggleDropdown('language')}
- className="flex items-center justify-between w-full px-4 py-2 rounded-lg bg-[#141414] hover:bg-[#1a1a1a] active:bg-[#1f1f1f] border border-white/[0.04] group transition-colors"
- >
-
- {selectedLanguages.length > 0 ? `${selectedLanguages.length} Selected` : 'Language'}
-
-
-
-
-
-
- {dropdowns.language && (
-
keepDropdownOpen(e, 'language')} className="absolute z-50 w-full mt-2 py-1 bg-[#141414] rounded-lg border border-white/[0.04] shadow-xl">
- {languages.map((language) => (
- handleMultiSelect(e, selectedLanguages, language, onLanguageChange, 'language')}
- className="w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors flex items-center justify-between"
- >
-
- {language}
-
- {selectedLanguages.includes(language) && (
-
- )}
-
- ))}
-
- )}
-
-
- {/* Sort Filter */}
-
dropdownRefs.current.sort = el}>
-
toggleDropdown('sort')}
- className="flex items-center justify-between w-full px-4 py-2 rounded-lg bg-[#141414] hover:bg-[#1a1a1a] active:bg-[#1f1f1f] border border-white/[0.04] group transition-colors"
- >
-
- {getSortDisplayText()}
-
-
-
-
-
-
- {dropdowns.sort && (
-
- handleSortSelect(e, 'default')}
- className={`w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors ${
- sortOrder === 'default' ? 'text-white font-medium' : 'text-white/70'
- }`}
- >
- Default
-
- handleSortSelect(e, 'title-asc')}
- className={`w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors ${
- sortOrder === 'title-asc' ? 'text-white font-medium' : 'text-white/70'
- }`}
- >
- Title (A-Z)
-
- handleSortSelect(e, 'title-desc')}
- className={`w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors ${
- sortOrder === 'title-desc' ? 'text-white font-medium' : 'text-white/70'
- }`}
- >
- Title (Z-A)
-
- handleSortSelect(e, 'year-desc')}
- className={`w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors ${
- sortOrder === 'year-desc' ? 'text-white font-medium' : 'text-white/70'
- }`}
- >
- Newest First
-
- handleSortSelect(e, 'year-asc')}
- className={`w-full px-4 py-1.5 text-left hover:bg-white/[0.03] transition-colors ${
- sortOrder === 'year-asc' ? 'text-white font-medium' : 'text-white/70'
- }`}
- >
- Oldest First
-
-
- )}
-
-
- {/* Clear All Button - Always visible */}
-
-
- Clear All
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/AnimeRow.js b/src/components/AnimeRow.js
deleted file mode 100644
index e153fb6..0000000
--- a/src/components/AnimeRow.js
+++ /dev/null
@@ -1,131 +0,0 @@
-'use client';
-
-import { useRef, useState, useEffect } from 'react';
-import AnimeCard from './AnimeCard';
-
-export default function AnimeRow({ title, animeList }) {
- const scrollContainerRef = useRef(null);
- const contentRef = useRef(null);
- const [showLeftButton, setShowLeftButton] = useState(false);
- const [showRightButton, setShowRightButton] = useState(false);
-
- useEffect(() => {
- if (!animeList || animeList.length <= 7) {
- setShowRightButton(false);
- return;
- }
-
- setShowRightButton(true);
-
- const checkScroll = () => {
- if (!scrollContainerRef.current) return;
-
- const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
- setShowLeftButton(scrollLeft > 0);
- setShowRightButton(scrollLeft + clientWidth < scrollWidth - 10);
- };
-
- const scrollContainer = scrollContainerRef.current;
- scrollContainer.addEventListener('scroll', checkScroll);
-
- // Initial check
- checkScroll();
-
- return () => {
- if (scrollContainer) {
- scrollContainer.removeEventListener('scroll', checkScroll);
- }
- };
- }, [animeList]);
-
- const scroll = (direction) => {
- if (!scrollContainerRef.current) return;
-
- const container = scrollContainerRef.current;
- // Calculate single card width based on viewport
- const isMobile = window.innerWidth < 640; // sm breakpoint in Tailwind
- const cardsPerRow = isMobile ? 3 : 7;
- const singleCardWidth = container.clientWidth / cardsPerRow;
-
- if (direction === 'left') {
- container.scrollBy({ left: -singleCardWidth, behavior: 'smooth' });
- } else {
- container.scrollBy({ left: singleCardWidth, behavior: 'smooth' });
- }
- };
-
- if (!animeList || animeList.length === 0) return null;
-
- // Create groups of cards for pagination - 3 for mobile, 7 for larger screens
- const cardGroups = [];
- const isMobileView = typeof window !== 'undefined' && window.innerWidth < 640;
- const groupSize = isMobileView ? 3 : 7;
-
- for (let i = 0; i < animeList.length; i += groupSize) {
- cardGroups.push(animeList.slice(i, i + groupSize));
- }
-
- return (
-
-
-
{title}
-
-
-
- {showLeftButton && (
-
scroll('left')}
- className="absolute left-0 top-1/2 -translate-y-1/2 z-10 w-10 h-10 rounded-full bg-black/70 flex items-center justify-center text-white hover:bg-black shadow-lg -ml-5"
- aria-label="Scroll left"
- >
-
-
-
-
- )}
-
-
-
- {cardGroups.map((group, groupIndex) => (
-
- {group.map((anime, index) => (
-
- ))}
- {/* Add empty placeholders if needed to ensure slots are filled */}
- {Array.from({ length: (typeof window !== 'undefined' && window.innerWidth < 640) ?
- Math.max(0, 3 - group.length) :
- Math.max(0, 7 - group.length) }).map((_, index) => (
-
- ))}
-
- ))}
-
-
-
- {showRightButton && (
-
scroll('right')}
- className="absolute right-0 top-1/2 -translate-y-1/2 z-10 w-10 h-10 rounded-full bg-black/70 flex items-center justify-center text-white hover:bg-black shadow-lg -mr-5"
- aria-label="Scroll right"
- >
-
-
-
-
- )}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/AnimeTabs.js b/src/components/AnimeTabs.js
deleted file mode 100644
index 5d878cf..0000000
--- a/src/components/AnimeTabs.js
+++ /dev/null
@@ -1,86 +0,0 @@
-'use client';
-
-import React, { useState } from 'react';
-import AnimeCard from './AnimeCard';
-import Link from 'next/link';
-
-const tabs = [
- { id: 'topAiring', label: 'TOP AIRING' },
- { id: 'popular', label: 'POPULAR' },
- { id: 'latestCompleted', label: 'LATEST COMPLETED' }
-];
-
-export default function AnimeTabs({ topAiring = [], popular = [], latestCompleted = [] }) {
- const [activeTab, setActiveTab] = useState('topAiring');
-
- const getActiveList = () => {
- switch (activeTab) {
- case 'topAiring':
- return topAiring;
- case 'popular':
- return popular;
- case 'latestCompleted':
- return latestCompleted;
- default:
- return [];
- }
- };
-
- const getViewAllLink = () => {
- switch (activeTab) {
- case 'topAiring':
- return '/top-airing';
- case 'popular':
- return '/most-popular';
- case 'latestCompleted':
- return '/latest-completed';
- default:
- return '/';
- }
- };
-
- return (
-
- {/* Tabs Navigation */}
-
- {tabs.map((tab) => (
-
setActiveTab(tab.id)}
- className={`px-3 sm:px-6 py-3 text-xs sm:text-sm font-medium transition-colors relative whitespace-nowrap flex-shrink-0 ${
- activeTab === tab.id
- ? 'text-white'
- : 'text-[var(--text-muted)] hover:text-white'
- }`}
- >
- {tab.label}
- {activeTab === tab.id && (
-
- )}
-
- ))}
-
-
View All
-
-
-
-
-
-
- {/* Anime Grid */}
-
- {getActiveList().slice(0, 18).map((anime, index) => (
-
- ))}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/EpisodeList.js b/src/components/EpisodeList.js
deleted file mode 100644
index c52a8b0..0000000
--- a/src/components/EpisodeList.js
+++ /dev/null
@@ -1,239 +0,0 @@
-import { useState, useMemo, useEffect } from 'react';
-
-export default function EpisodeList({ episodes, currentEpisode, onEpisodeClick, isDub = false }) {
- const [currentPage, setCurrentPage] = useState(1);
- const [isGridView, setIsGridView] = useState(false);
- const [searchQuery, setSearchQuery] = useState('');
- const [activeEpisodeId, setActiveEpisodeId] = useState(null);
- const episodesPerPage = 100;
-
- // Update active episode when currentEpisode changes
- useEffect(() => {
- if (currentEpisode?.id) {
- setActiveEpisodeId(currentEpisode.id);
- }
- }, [currentEpisode]);
-
- // Sync with URL to identify current episode
- useEffect(() => {
- const checkCurrentEpisode = () => {
- const path = window.location.pathname;
- const match = path.match(/\/watch\/(.+)$/);
- if (match) {
- const urlEpisodeId = match[1];
- setActiveEpisodeId(urlEpisodeId);
-
- // Find the episode and update page
- const episode = episodes.find(ep => ep.id === urlEpisodeId);
-
- if (episode) {
- const pageNumber = Math.ceil(episode.number / episodesPerPage);
- setCurrentPage(pageNumber);
- }
- }
- };
-
- // Check initially
- checkCurrentEpisode();
-
- // Set up listener for URL changes using the History API
- const handleUrlChange = () => {
- checkCurrentEpisode();
- };
-
- window.addEventListener('popstate', handleUrlChange);
-
- // Clean up
- return () => {
- window.removeEventListener('popstate', handleUrlChange);
- };
- }, [episodes, episodesPerPage]);
-
- const filteredEpisodes = useMemo(() => {
- if (!searchQuery) return episodes;
- const query = searchQuery.toLowerCase();
- return episodes.filter(episode =>
- episode.number.toString().includes(query) ||
- (episode.title && episode.title.toLowerCase().includes(query))
- );
- }, [episodes, searchQuery]);
-
- const totalPages = Math.ceil(filteredEpisodes.length / episodesPerPage);
- const indexOfLastEpisode = currentPage * episodesPerPage;
- const indexOfFirstEpisode = indexOfLastEpisode - episodesPerPage;
- const currentEpisodes = filteredEpisodes.slice(indexOfFirstEpisode, indexOfLastEpisode);
-
- const getPageRange = (pageNum) => {
- const start = (pageNum - 1) * episodesPerPage + 1;
- const end = Math.min(pageNum * episodesPerPage, filteredEpisodes.length);
- return `${start}-${end}`;
- };
-
- const isCurrentEpisode = (episode) => {
- if (!episode || !episode.id || !activeEpisodeId) return false;
- return episode.id === activeEpisodeId;
- };
-
- const handleEpisodeSelect = (episode, e) => {
- e.preventDefault();
- if (onEpisodeClick && episode.id) {
- // Use the episode ID directly as it's already in the correct format from the API
- console.log(`[EpisodeList] Selected episode: ${episode.number}, ID: ${episode.id}`);
- onEpisodeClick(episode.id);
- setActiveEpisodeId(episode.id);
- }
- };
-
- // Scroll active episode into view when page changes or active episode changes
- useEffect(() => {
- if (activeEpisodeId) {
- setTimeout(() => {
- const activeElement = document.querySelector(`[data-episode-id="${activeEpisodeId}"]`);
- if (activeElement) {
- activeElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
- }
- }, 100);
- }
- }, [activeEpisodeId, currentPage]);
-
- return (
-
- {/* Header */}
-
-
-
-
{
- setSearchQuery(e.target.value);
- setCurrentPage(1);
- }}
- className="w-full bg-[#2a2a2a] text-white text-sm rounded-lg px-4 py-1.5 pl-9 focus:outline-none focus:ring-2 focus:ring-[var(--primary)] placeholder-gray-500"
- />
-
-
-
-
-
-
setCurrentPage(Number(e.target.value))}
- className="bg-[#2a2a2a] text-white text-sm rounded-lg px-2 py-1.5 border border-gray-700 focus:outline-none focus:ring-2 focus:ring-[var(--primary)] min-w-[90px]"
- >
- {[...Array(totalPages)].map((_, index) => (
-
- {getPageRange(index + 1)}
-
- ))}
-
-
setIsGridView(!isGridView)}
- className="p-1.5 rounded-lg text-gray-400 hover:text-white transition-colors bg-[#2a2a2a] hover:bg-[#333333]"
- title={isGridView ? "Switch to List View" : "Switch to Grid View"}
- >
- {isGridView ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
-
-
-
- {/* Episodes Container */}
-
-
- {isGridView ? (
- // Grid View
-
- {currentEpisodes.map((episode) => (
-
handleEpisodeSelect(episode, e)}
- className={`group relative ${
- isCurrentEpisode(episode)
- ? 'bg-[#2a2a2a] ring-2 ring-white z-30'
- : 'bg-[#2a2a2a] hover:bg-[#333333]'
- } rounded-lg transition-all duration-300 ease-out transform hover:scale-[1.02] hover:z-10`}
- >
-
- {isCurrentEpisode(episode) && (
-
- )}
-
- ))}
-
- ) : (
- // List View
-
- {currentEpisodes.map((episode) => (
-
handleEpisodeSelect(episode, e)}
- className={`group flex items-center gap-3 py-2 px-3 rounded-lg transition-all duration-300 w-full text-left ${
- isCurrentEpisode(episode)
- ? 'bg-[#2a2a2a] ring-2 ring-white z-30'
- : 'bg-[#2a2a2a] hover:bg-[#333333]'
- }`}
- >
-
- {episode.number}
-
-
-
- {episode.title || `Episode ${episode.number}`}
-
-
- {isCurrentEpisode(episode) && (
-
- )}
-
- ))}
-
- )}
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/GenreBar.js b/src/components/GenreBar.js
deleted file mode 100644
index 97543e8..0000000
--- a/src/components/GenreBar.js
+++ /dev/null
@@ -1,230 +0,0 @@
-'use client';
-
-import Link from 'next/link';
-import { useState, useEffect, useRef, useMemo } from 'react';
-import { fetchGenres } from '@/lib/api';
-
-export default function GenreBar() {
- const [genres, setGenres] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [showLeftButton, setShowLeftButton] = useState(true); // Always show left button initially
- const [showRightButton, setShowRightButton] = useState(true);
- const [isMobile, setIsMobile] = useState(false);
- const scrollContainerRef = useRef(null);
- const containerRef = useRef(null);
- const [visibleGenres, setVisibleGenres] = useState(14);
-
- // Function to capitalize first letter
- const capitalizeFirstLetter = (string) => {
- return string.charAt(0).toUpperCase() + string.slice(1);
- };
-
- // Predefined genres exactly as specified - wrapped in useMemo to prevent recreation on every render
- const defaultGenres = useMemo(() => [
- "Action", "Adventure", "Comedy", "Drama", "Ecchi", "Fantasy",
- "Horror", "Mahou Shoujo", "Mecha", "Music", "Mystery", "Psychological",
- "Romance", "Sci-Fi", "Slice of Life", "Sports", "Supernatural", "Thriller"
- ], []);
-
- // Handle long names on mobile
- const getMobileGenreName = (genre) => {
- // Abbreviate long genre names for mobile view
- switch(genre) {
- case "Psychological": return "Psycho";
- case "Mahou Shoujo": return "Mahou";
- case "Supernatural": return "Super";
- case "Slice of Life": return "SoL";
- default: return genre;
- }
- };
-
- // Detect mobile devices
- useEffect(() => {
- const checkIfMobile = () => {
- setIsMobile(window.innerWidth < 768);
- };
-
- checkIfMobile();
- window.addEventListener('resize', checkIfMobile);
-
- return () => window.removeEventListener('resize', checkIfMobile);
- }, []);
-
- // Calculate the number of genres that fit in the container
- useEffect(() => {
- const calculateVisibleGenres = () => {
- const container = containerRef.current;
- if (container) {
- const containerWidth = container.offsetWidth;
- // Approximate width of each genre button
- const genreButtonWidth = isMobile ? 72 : 88; // Slightly larger on mobile to fit text
- const visibleCount = Math.floor((containerWidth - 80) / genreButtonWidth);
- // Minimum genres visible (smaller minimum on mobile)
- setVisibleGenres(Math.max(visibleCount, isMobile ? 4 : 8));
- }
- };
-
- calculateVisibleGenres();
- window.addEventListener('resize', calculateVisibleGenres);
-
- return () => {
- window.removeEventListener('resize', calculateVisibleGenres);
- };
- }, [isMobile]);
-
- useEffect(() => {
- // Force scroll position slightly to the right initially
- // to ensure there are genres on both sides for scrolling
- setTimeout(() => {
- if (scrollContainerRef.current) {
- scrollContainerRef.current.scrollLeft = 40; // Start slightly scrolled
- // Trigger scroll event to update button states
- scrollContainerRef.current.dispatchEvent(new Event('scroll'));
- }
- }, 100);
-
- setGenres(defaultGenres);
- setIsLoading(false);
- }, [defaultGenres]);
-
- // Check scroll position to determine button visibility
- useEffect(() => {
- const handleScroll = () => {
- if (scrollContainerRef.current) {
- const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
-
- // Show left button if not at the start
- setShowLeftButton(scrollLeft > 5);
-
- // Show right button if not at the end
- setShowRightButton(scrollLeft < scrollWidth - clientWidth - 5);
- }
- };
-
- const scrollContainer = scrollContainerRef.current;
- if (scrollContainer) {
- scrollContainer.addEventListener('scroll', handleScroll);
- // Initial check
- handleScroll();
-
- return () => {
- scrollContainer.removeEventListener('scroll', handleScroll);
- };
- }
- }, []);
-
- // Scroll left/right functions
- const scrollLeft = () => {
- if (scrollContainerRef.current) {
- const scrollAmount = isMobile ? -80 : -200;
- scrollContainerRef.current.scrollBy({ left: scrollAmount, behavior: 'smooth' });
- }
- };
-
- const scrollRight = () => {
- if (scrollContainerRef.current) {
- const scrollAmount = isMobile ? 80 : 200;
- scrollContainerRef.current.scrollBy({ left: scrollAmount, behavior: 'smooth' });
- }
- };
-
- // Mobile-specific styles
- const mobileButtonStyle = {
- padding: '0.15rem 0.5rem',
- fontSize: '0.65rem',
- height: '1.5rem',
- minWidth: '4rem',
- maxWidth: '5.5rem',
- textAlign: 'center',
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center'
- };
-
- if (isLoading) {
- return (
-
-
- {[...Array(isMobile ? 5 : visibleGenres)].map((_, i) => (
-
- ))}
-
-
- );
- }
-
- return (
-
- {/* Left fade effect */}
-
-
-
- {/* Left scroll button - only visible when not at the leftmost position */}
- {showLeftButton && (
-
-
-
-
-
- )}
-
- {/* Scrollable genre container */}
-
-
- {genres.map((genre) => (
-
- {isMobile ? getMobileGenreName(genre) : genre}
-
- ))}
-
-
-
- {/* Right fade effect */}
-
-
-
- {/* Right scroll button - only visible when not at the rightmost position */}
- {showRightButton && (
-
-
-
-
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/components/GenreList.js b/src/components/GenreList.js
deleted file mode 100644
index f0c5e71..0000000
--- a/src/components/GenreList.js
+++ /dev/null
@@ -1,93 +0,0 @@
-'use client';
-
-import Link from 'next/link';
-import { useState, useEffect } from 'react';
-import { fetchGenres } from '@/lib/api';
-
-export default function GenreList() {
- const [genres, setGenres] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [showAll, setShowAll] = useState(false);
-
- useEffect(() => {
- async function loadGenres() {
- try {
- const genreData = await fetchGenres();
- setGenres(genreData || []);
- } catch (error) {
- console.error("Error fetching genres:", error);
- } finally {
- setIsLoading(false);
- }
- }
-
- loadGenres();
- }, []);
-
- // Predefined popular genres if API doesn't return them
- const defaultGenres = [
- "Action", "Adventure", "Comedy", "Drama", "Fantasy",
- "Horror", "Mystery", "Romance", "Sci-Fi", "Slice of Life",
- "Supernatural", "Thriller", "Isekai", "Mecha", "Sports"
- ];
-
- // Use fetched genres or fallback to default genres
- const displayGenres = genres.length > 0 ? genres : defaultGenres;
-
- // Show only first 12 genres if not showing all
- const visibleGenres = showAll ? displayGenres : displayGenres.slice(0, 12);
-
- if (isLoading) {
- return (
-
-
-
Genres
-
-
- {[...Array(12)].map((_, i) => (
-
- ))}
-
-
- );
- }
-
- return (
-
-
-
Genres
-
-
-
- {visibleGenres.map((genre) => (
-
- {genre}
-
- ))}
-
-
- {displayGenres.length > 12 && (
-
-
setShowAll(!showAll)}
- className="text-white/70 hover:text-white text-sm transition-colors inline-flex items-center"
- >
- {showAll ? 'Show Less' : 'Show All'}
-
-
-
-
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/components/Loader/AnimeInfo.loader.jsx b/src/components/Loader/AnimeInfo.loader.jsx
new file mode 100644
index 0000000..3a20c22
--- /dev/null
+++ b/src/components/Loader/AnimeInfo.loader.jsx
@@ -0,0 +1,58 @@
+import { Skeleton } from "@/src/components/ui/Skeleton/Skeleton";
+import CategoryCardLoader from "./CategoryCard.loader";
+import SidecardLoader from "./Sidecard.loader";
+
+const SkeletonItems = ({ count, className }) => (
+ [...Array(count)].map((_, index) => )
+);
+
+function AnimeInfoLoader() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+export default AnimeInfoLoader;
diff --git a/src/components/Loader/AtoZ.loader.jsx b/src/components/Loader/AtoZ.loader.jsx
new file mode 100644
index 0000000..8c9ba62
--- /dev/null
+++ b/src/components/Loader/AtoZ.loader.jsx
@@ -0,0 +1,26 @@
+import { Skeleton } from "../ui/Skeleton/Skeleton";
+import CategoryCardLoader from "./CategoryCard.loader";
+
+const SkeletonItems = ({ count, className }) => (
+ [...Array(count)].map((_, index) => )
+);
+
+function AtoZLoader() {
+ return (
+
+ );
+}
+
+export default AtoZLoader;
diff --git a/src/components/Loader/Cart.loader.jsx b/src/components/Loader/Cart.loader.jsx
new file mode 100644
index 0000000..2467415
--- /dev/null
+++ b/src/components/Loader/Cart.loader.jsx
@@ -0,0 +1,27 @@
+import { Skeleton } from "../ui/Skeleton/Skeleton"
+const SkeletonItems = ({ count, className }) => (
+ [...Array(count)].map((_, index) => )
+);
+function CartLoader() {
+ return (
+
+
+
+ {[...Array(5)].map((item, index) => (
+
+ ))}
+
+
+
+ )
+}
+
+export default CartLoader
\ No newline at end of file
diff --git a/src/components/Loader/Category.loader.jsx b/src/components/Loader/Category.loader.jsx
new file mode 100644
index 0000000..45a6b21
--- /dev/null
+++ b/src/components/Loader/Category.loader.jsx
@@ -0,0 +1,23 @@
+import { Skeleton } from "../ui/Skeleton/Skeleton"
+import CategoryCardLoader from "./CategoryCard.loader"
+import SidecardLoader from "./Sidecard.loader"
+
+function CategoryLoader() {
+ return (
+
+ )
+}
+
+export default CategoryLoader
\ No newline at end of file
diff --git a/src/components/Loader/CategoryCard.loader.jsx b/src/components/Loader/CategoryCard.loader.jsx
new file mode 100644
index 0000000..c170069
--- /dev/null
+++ b/src/components/Loader/CategoryCard.loader.jsx
@@ -0,0 +1,35 @@
+import { Skeleton } from "../ui/Skeleton/Skeleton";
+
+function CategoryCardLoader({ className, showLabelSkeleton = true }) {
+ return (
+
+ {showLabelSkeleton && (
+
+ )}
+
+ {[...Array(12)].map((_, index) => (
+
+ ))}
+
+
+ );
+}
+
+export default CategoryCardLoader;
diff --git a/src/components/Loader/Home.loader.jsx b/src/components/Loader/Home.loader.jsx
new file mode 100644
index 0000000..21f8491
--- /dev/null
+++ b/src/components/Loader/Home.loader.jsx
@@ -0,0 +1,32 @@
+import CartLoader from "./Cart.loader";
+import CategoryCardLoader from "./CategoryCard.loader";
+import SidecardLoader from "./Sidecard.loader";
+import SpotlightLoader from "./Spotlight.loader";
+import Trendingloader from "./Trending.loader";
+function HomeLoader() {
+ return (
+
+ );
+}
+
+export default HomeLoader;
diff --git a/src/components/Loader/Loader.jsx b/src/components/Loader/Loader.jsx
new file mode 100644
index 0000000..e6a7886
--- /dev/null
+++ b/src/components/Loader/Loader.jsx
@@ -0,0 +1,24 @@
+import AnimeInfoLoader from "./AnimeInfo.loader";
+import HomeLoader from "./Home.loader";
+import CategoryLoader from "./Category.loader";
+import AtoZLoader from "./AtoZ.loader";
+import ProducerLoader from "./Producer.loader";
+
+const Loader = ({ type }) => {
+ switch (type) {
+ case "home":
+ return ;
+ case "animeInfo":
+ return ;
+ case "category":
+ return ;
+ case "producer":
+ return ;
+ case "AtoZ":
+ return ;
+ default:
+ return
;
+ }
+};
+
+export default Loader;
diff --git a/src/components/Loader/Producer.loader.jsx b/src/components/Loader/Producer.loader.jsx
new file mode 100644
index 0000000..b48b908
--- /dev/null
+++ b/src/components/Loader/Producer.loader.jsx
@@ -0,0 +1,15 @@
+import CategoryCardLoader from "./CategoryCard.loader";
+import SidecardLoader from "./Sidecard.loader";
+
+function ProducerLoader() {
+ return (
+
+ );
+}
+
+export default ProducerLoader;
diff --git a/src/components/Loader/Sidecard.loader.jsx b/src/components/Loader/Sidecard.loader.jsx
new file mode 100644
index 0000000..8f9d7d5
--- /dev/null
+++ b/src/components/Loader/Sidecard.loader.jsx
@@ -0,0 +1,26 @@
+import { Skeleton } from "../ui/Skeleton/Skeleton";
+function SidecardLoader({ className }) {
+ return (
+
+
+
+ {[...Array(10)].map((_, index) => (
+
+ ))}
+
+
+ );
+}
+
+export default SidecardLoader;
diff --git a/src/components/Loader/Spotlight.loader.jsx b/src/components/Loader/Spotlight.loader.jsx
new file mode 100644
index 0000000..583d74c
--- /dev/null
+++ b/src/components/Loader/Spotlight.loader.jsx
@@ -0,0 +1,34 @@
+import { Skeleton } from "../ui/Skeleton/Skeleton"
+const SkeletonItems = ({ count, className }) => (
+ [...Array(count)].map((_, index) => )
+);
+function SpotlightLoader() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default SpotlightLoader
\ No newline at end of file
diff --git a/src/components/Loader/Trending.loader.jsx b/src/components/Loader/Trending.loader.jsx
new file mode 100644
index 0000000..bf777db
--- /dev/null
+++ b/src/components/Loader/Trending.loader.jsx
@@ -0,0 +1,34 @@
+import { useState, useEffect } from "react";
+import { Skeleton } from "../ui/Skeleton/Skeleton";
+
+function TrendingLoader() {
+ const [count, setCount] = useState(() => window.innerWidth < 720 ? 3 : window.innerWidth < 1300 ? 4 : 6);
+ useEffect(() => {
+ const updateCount = () => {
+ if (window.innerWidth < 720) {
+ setCount(3);
+ } else if (window.innerWidth < 1300) {
+ setCount(4);
+ } else {
+ setCount(6);
+ }
+ };
+ updateCount();
+ window.addEventListener("resize", updateCount);
+ return () => window.removeEventListener("resize", updateCount);
+ }, []);
+ return (
+
+
+
+ {[...Array(count)].map((_, index) => (
+
+
+
+ ))}
+
+
+ );
+}
+
+export default TrendingLoader;
diff --git a/src/components/Loader/VoiceActorlist.loader.jsx b/src/components/Loader/VoiceActorlist.loader.jsx
new file mode 100644
index 0000000..f4b975e
--- /dev/null
+++ b/src/components/Loader/VoiceActorlist.loader.jsx
@@ -0,0 +1,21 @@
+import { Skeleton } from "../ui/Skeleton/Skeleton"
+
+function VoiceActorlistLoader() {
+ return (
+
+ {[...Array(10)].map((_, index) => (
+
+ ))}
+
+ )
+}
+
+export default VoiceActorlistLoader
\ No newline at end of file
diff --git a/src/components/Navbar.js b/src/components/Navbar.js
deleted file mode 100644
index be281e9..0000000
--- a/src/components/Navbar.js
+++ /dev/null
@@ -1,628 +0,0 @@
-'use client';
-
-import Link from 'next/link';
-import Image from 'next/image';
-import { useState, useEffect, useRef } from 'react';
-import { useRouter } from 'next/navigation';
-import {
- fetchSearchSuggestions,
- fetchMostPopular,
- fetchTopAiring,
- fetchRecentEpisodes,
- fetchMostFavorite,
- fetchTopUpcoming
-} from '@/lib/api';
-
-export default function Navbar() {
- const [isMenuOpen, setIsMenuOpen] = useState(false);
- const [searchQuery, setSearchQuery] = useState('');
- const [searchSuggestions, setSearchSuggestions] = useState([]);
- const [showSuggestions, setShowSuggestions] = useState(false);
- const [isScrolled, setIsScrolled] = useState(false);
- const [isLoading, setIsLoading] = useState(false);
- const [isRandomLoading, setIsRandomLoading] = useState(false);
- const suggestionRef = useRef(null);
- const searchInputRef = useRef(null);
- const router = useRouter();
-
- // Track scroll position
- useEffect(() => {
- const handleScroll = () => {
- if (window.scrollY > 10) {
- setIsScrolled(true);
- } else {
- setIsScrolled(false);
- }
- };
-
- window.addEventListener('scroll', handleScroll);
- return () => window.removeEventListener('scroll', handleScroll);
- }, []);
-
- // Update suggestions when search query changes
- useEffect(() => {
- const updateSuggestions = async () => {
- // Only search if we have at least 2 characters
- if (searchQuery.trim().length >= 2) {
- setIsLoading(true);
- setShowSuggestions(true); // Always show the suggestions container when typing
-
- try {
- console.log(`Fetching suggestions for: ${searchQuery}`);
- const apiSuggestions = await fetchSearchSuggestions(searchQuery);
- console.log('API returned:', apiSuggestions);
-
- if (Array.isArray(apiSuggestions) && apiSuggestions.length > 0) {
- // Take top 5 results
- setSearchSuggestions(apiSuggestions.slice(0, 5));
- } else {
- // Create a generic suggestion based on the search query
- setSearchSuggestions([{
- id: searchQuery.toLowerCase().replace(/\s+/g, '-'),
- title: `Search for "${searchQuery}"`,
- type: "SEARCH",
- image: null
- }]);
- }
- } catch (error) {
- console.error('Error in search component:', error);
- // Create a generic suggestion
- setSearchSuggestions([{
- id: searchQuery.toLowerCase().replace(/\s+/g, '-'),
- title: `Search for "${searchQuery}"`,
- type: "SEARCH",
- image: null
- }]);
- } finally {
- setIsLoading(false);
- }
- } else {
- setSearchSuggestions([]);
- setShowSuggestions(false);
- }
- };
-
- const debounceTimer = setTimeout(() => {
- updateSuggestions();
- }, 300); // 300ms debounce time
-
- return () => clearTimeout(debounceTimer);
- }, [searchQuery]);
-
- // Close suggestions when clicking outside
- useEffect(() => {
- const handleClickOutside = (event) => {
- if (
- suggestionRef.current &&
- !suggestionRef.current.contains(event.target) &&
- !searchInputRef.current?.contains(event.target)
- ) {
- setShowSuggestions(false);
- }
- };
-
- document.addEventListener('mousedown', handleClickOutside);
- return () => {
- document.removeEventListener('mousedown', handleClickOutside);
- };
- }, []);
-
- const handleSearch = (e) => {
- e.preventDefault();
- // Navigate to search page regardless if search is empty or not
- router.push(searchQuery.trim() ? `/search?q=${encodeURIComponent(searchQuery)}` : '/search');
- setSearchQuery('');
- setShowSuggestions(false);
- setIsMenuOpen(false);
- };
-
- // Handle suggestion item click
- const handleAnimeClick = (id) => {
- router.push(`/anime/${id}`);
- setSearchQuery('');
- setShowSuggestions(false);
- setIsMenuOpen(false);
- };
-
- // Handle search by query click
- const handleSearchByQueryClick = () => {
- router.push(`/search?q=${encodeURIComponent(searchQuery)}`);
- setSearchQuery('');
- setShowSuggestions(false);
- setIsMenuOpen(false);
- };
-
- // Helper function to render clear button
- const renderClearButton = () => {
- if (searchQuery) {
- return (
- setSearchQuery('')}
- >
-
-
-
-
- );
- }
- return null;
- };
-
- // Function to handle input focus
- const handleInputFocus = () => {
- if (searchQuery.trim().length >= 2) {
- setShowSuggestions(true);
- }
- };
-
- // Function to handle random anime click
- const handleRandomAnimeClick = async () => {
- setIsRandomLoading(true);
- try {
- // Randomly select a category to fetch from
- const categories = [
- { name: 'Most Popular', fetch: fetchMostPopular },
- { name: 'Top Airing', fetch: fetchTopAiring },
- { name: 'Recent Episodes', fetch: fetchRecentEpisodes },
- { name: 'Most Favorite', fetch: fetchMostFavorite },
- { name: 'Top Upcoming', fetch: fetchTopUpcoming }
- ];
-
- // Select a random category
- const randomCategoryIndex = Math.floor(Math.random() * categories.length);
- const selectedCategory = categories[randomCategoryIndex];
-
- console.log(`Fetching random anime from: ${selectedCategory.name}`);
-
- // Fetch anime from the selected category - use a random page number to get more variety
- const randomPage = Math.floor(Math.random() * 5) + 1; // Random page between 1-5
- const animeList = await selectedCategory.fetch(randomPage);
-
- if (animeList && animeList.results && animeList.results.length > 0) {
- // Skip the first few results as they tend to be more popular
- const skipCount = Math.min(5, Math.floor(animeList.results.length / 3));
- let availableAnime = animeList.results.slice(skipCount);
-
- if (availableAnime.length === 0) {
- // If we've filtered out everything, use the original list
- availableAnime = animeList.results;
- }
-
- // Get a random index
- const randomAnimeIndex = Math.floor(Math.random() * availableAnime.length);
-
- // Get the random anime ID
- const randomAnimeId = availableAnime[randomAnimeIndex].id;
-
- console.log(`Selected random anime: ${availableAnime[randomAnimeIndex].title} (ID: ${randomAnimeId})`);
-
- // Navigate to the anime page
- router.push(`/anime/${randomAnimeId}`);
- } else {
- console.error('No anime found to select randomly from');
-
- // Fallback to most popular if the chosen category fails, but use a higher page number
- const fallbackPage = Math.floor(Math.random() * 5) + 2; // Pages 2-6 for more obscure options
- const fallbackList = await fetchMostPopular(fallbackPage);
-
- if (fallbackList && fallbackList.results && fallbackList.results.length > 0) {
- const randomIndex = Math.floor(Math.random() * fallbackList.results.length);
- const randomAnimeId = fallbackList.results[randomIndex].id;
- router.push(`/anime/${randomAnimeId}`);
- }
- }
- } catch (error) {
- console.error('Error fetching random anime:', error);
- } finally {
- setIsRandomLoading(false);
- }
- };
-
- return (
-
-
- {/* Logo */}
-
-
-
-
-
-
- {/* Search Bar - Desktop */}
-
-
-
-
setSearchQuery(e.target.value)}
- onFocus={handleInputFocus}
- />
-
-
-
-
-
-
-
- {renderClearButton()}
-
- {/* Search Suggestions Dropdown */}
- {showSuggestions && (
-
- )}
-
-
- {/* Search Button */}
-
-
-
-
-
-
-
- {/* Random Anime Button */}
-
-
-
-
- {/* Login Button - Desktop */}
-
-
- Login
-
-
-
- {/* Mobile Menu Button */}
-
-
setIsMenuOpen(!isMenuOpen)}
- className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-[#1a1a1a] focus:outline-none"
- >
-
-
-
-
-
-
-
-
-
-
- {/* Mobile menu */}
- {isMenuOpen && (
-
-
-
-
-
-
setSearchQuery(e.target.value)}
- onFocus={handleInputFocus}
- />
-
-
-
-
-
-
-
- {/* Mobile Clear Button */}
- {searchQuery && (
-
setSearchQuery('')}
- >
-
-
-
-
- )}
-
- {/* Mobile Search Suggestions */}
- {showSuggestions && (
-
- )}
-
-
- {/* Search Button - Mobile */}
-
-
-
-
-
-
-
- {/* Random Anime Button - Mobile */}
-
-
-
-
-
-
-
-
-
-
- setIsMenuOpen(false)}
- prefetch={false}
- >
- Login
-
-
-
-
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/components/SeasonCard.js b/src/components/SeasonCard.js
deleted file mode 100644
index 2123710..0000000
--- a/src/components/SeasonCard.js
+++ /dev/null
@@ -1,56 +0,0 @@
-'use client';
-
-import Image from 'next/image';
-import Link from 'next/link';
-import { useState } from 'react';
-
-export default function SeasonCard({ season }) {
- const [imageError, setImageError] = useState(false);
-
- if (!season) return null;
-
- const handleImageError = () => {
- console.log("Image error for:", season.name);
- setImageError(true);
- };
-
- // Get image URL with fallback
- const imageSrc = imageError ? '/images/placeholder.png' : season.poster;
-
- // Generate link
- const infoLink = `/anime/${season.id}`;
-
- return (
-
-
- {/* Background image with blur */}
-
-
- {/* Content overlay */}
-
-
-
- {season.title || season.name}
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/SeasonRow.js b/src/components/SeasonRow.js
deleted file mode 100644
index e54c4b7..0000000
--- a/src/components/SeasonRow.js
+++ /dev/null
@@ -1,162 +0,0 @@
-'use client';
-
-import { useRef, useState, useEffect } from 'react';
-import SeasonCard from './SeasonCard';
-
-export default function SeasonRow({ title, seasons }) {
- const scrollContainerRef = useRef(null);
- const contentRef = useRef(null);
- const [showLeftButton, setShowLeftButton] = useState(false);
- const [showRightButton, setShowRightButton] = useState(false);
-
- useEffect(() => {
- if (!seasons || seasons.length <= 7) {
- setShowRightButton(false);
- return;
- }
-
- setShowRightButton(true);
-
- const checkScroll = () => {
- if (!scrollContainerRef.current) return;
-
- const { scrollLeft, scrollWidth, clientWidth } = scrollContainerRef.current;
- setShowLeftButton(scrollLeft > 0);
- setShowRightButton(scrollLeft + clientWidth < scrollWidth - 10);
- };
-
- const scrollContainer = scrollContainerRef.current;
- scrollContainer.addEventListener('scroll', checkScroll);
-
- // Initial check
- checkScroll();
-
- return () => {
- if (scrollContainer) {
- scrollContainer.removeEventListener('scroll', checkScroll);
- }
- };
- }, [seasons]);
-
- // Updated effect to handle mobile view arrows
- useEffect(() => {
- if (!seasons) return;
-
- // Check if we're on mobile and have more than 3 seasons
- const isMobileView = typeof window !== 'undefined' && window.innerWidth < 640;
- const showArrowsOnMobile = isMobileView && seasons.length > 3;
-
- // On desktop, show arrows if more than 7 seasons
- const showArrowsOnDesktop = !isMobileView && seasons.length > 7;
-
- if (showArrowsOnMobile || showArrowsOnDesktop) {
- setShowRightButton(true);
- } else {
- setShowRightButton(false);
- }
-
- // Listen for resize events to update arrow visibility
- const handleResize = () => {
- const isMobile = window.innerWidth < 640;
- const showArrows = isMobile ? seasons.length > 3 : seasons.length > 7;
- setShowRightButton(showArrows);
- };
-
- window.addEventListener('resize', handleResize);
-
- return () => {
- window.removeEventListener('resize', handleResize);
- };
- }, [seasons]);
-
- const scroll = (direction) => {
- if (!scrollContainerRef.current) return;
-
- const container = scrollContainerRef.current;
- // Calculate single card width based on viewport
- const isMobile = window.innerWidth < 640; // sm breakpoint in Tailwind
- const cardsPerRow = isMobile ? 3 : 7;
- const singleCardWidth = container.clientWidth / cardsPerRow;
-
- if (direction === 'left') {
- container.scrollBy({ left: -singleCardWidth, behavior: 'smooth' });
- } else {
- container.scrollBy({ left: singleCardWidth, behavior: 'smooth' });
- }
- };
-
- if (!seasons || seasons.length === 0) return null;
-
- // Create groups of cards for pagination - 3 for mobile, 7 for larger screens
- const seasonGroups = [];
- const isMobileView = typeof window !== 'undefined' && window.innerWidth < 640;
- const groupSize = isMobileView ? 3 : 7;
-
- for (let i = 0; i < seasons.length; i += groupSize) {
- seasonGroups.push(seasons.slice(i, i + groupSize));
- }
-
- return (
-
-
-
{title || 'Seasons'}
-
-
-
- {showLeftButton && (
-
scroll('left')}
- className="absolute left-0 top-1/2 -translate-y-1/2 z-10 w-10 h-10 rounded-full bg-black/70 flex items-center justify-center text-white hover:bg-black shadow-lg -ml-5"
- aria-label="Scroll left"
- >
-
-
-
-
- )}
-
-
-
- {seasonGroups.map((group, groupIndex) => (
-
- {group.map((season, index) => (
-
-
-
- ))}
- {/* Add empty placeholders if needed to ensure slots are filled */}
- {Array.from({ length: (typeof window !== 'undefined' && window.innerWidth < 640) ?
- Math.max(0, 3 - group.length) :
- Math.max(0, 7 - group.length) }).map((_, index) => (
-
- ))}
-
- ))}
-
-
-
- {showRightButton && (
-
scroll('right')}
- className="absolute right-0 top-1/2 -translate-y-1/2 z-10 w-10 h-10 rounded-full bg-black/70 flex items-center justify-center text-white hover:bg-black shadow-lg -mr-5"
- aria-label="Scroll right"
- >
-
-
-
-
- )}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/SharedLayout.js b/src/components/SharedLayout.js
deleted file mode 100644
index f42d5f2..0000000
--- a/src/components/SharedLayout.js
+++ /dev/null
@@ -1,86 +0,0 @@
-'use client';
-
-import Navbar from './Navbar';
-import Image from 'next/image';
-
-const Footer = () => {
- return (
-
-
-
-
-
-
-
This website does not retain any files on its server. Rather, it solely provides links to media content hosted by third-party services.
-
-
-
-
-
-
- );
-};
-
-export default function SharedLayout({ children }) {
- return (
- <>
-
-
- {children}
-
-
- >
- );
-}
\ No newline at end of file
diff --git a/src/components/SpotlightCarousel.js b/src/components/SpotlightCarousel.js
deleted file mode 100644
index 35891c1..0000000
--- a/src/components/SpotlightCarousel.js
+++ /dev/null
@@ -1,371 +0,0 @@
-'use client';
-
-import React, { useEffect, useState, useRef } from 'react';
-import Link from 'next/link';
-import Image from 'next/image';
-import { Swiper, SwiperSlide } from 'swiper/react';
-import { Autoplay, Navigation, Pagination, EffectFade } from 'swiper/modules';
-import { fetchAnimeEpisodes } from '@/lib/api';
-
-// Import Swiper styles
-import 'swiper/css';
-import 'swiper/css/navigation';
-import 'swiper/css/pagination';
-import 'swiper/css/effect-fade';
-
-const SpotlightCarousel = ({ items = [] }) => {
- const [isClient, setIsClient] = useState(false);
- const [currentIndex, setCurrentIndex] = useState(0);
- const [autoplay, setAutoplay] = useState(true);
- const [progress, setProgress] = useState(0);
- const [episodeIds, setEpisodeIds] = useState({});
- const [loadingItems, setLoadingItems] = useState({});
- const intervalRef = useRef(null);
- const progressIntervalRef = useRef(null);
-
- // Handle hydration mismatch
- useEffect(() => {
- setIsClient(true);
- }, []);
-
- // Fetch first episode IDs for all spotlight items
- useEffect(() => {
- const fetchEpisodeData = async () => {
- // Create a copy to track what we're loading
- const newLoadingItems = { ...loadingItems };
- const episodeData = { ...episodeIds };
-
- for (const item of items) {
- // Skip if we already have the episode ID or if it's already loading
- if (item.id && !episodeData[item.id] && !newLoadingItems[item.id]) {
- newLoadingItems[item.id] = true;
- }
- }
-
- // Update loading state
- setLoadingItems(newLoadingItems);
-
- // Process items that need to be loaded
- for (const item of items) {
- if (item.id && !episodeData[item.id] && newLoadingItems[item.id]) {
- try {
- console.log(`[SpotlightCarousel] Fetching episodes for anime: ${item.id}`);
- const response = await fetchAnimeEpisodes(item.id);
- console.log(`[SpotlightCarousel] Episodes response for ${item.name}:`, response);
-
- if (response.episodes && response.episodes.length > 0) {
- // Check for episode ID in the expected format
- const firstEp = response.episodes[0];
- if (firstEp.id) {
- episodeData[item.id] = firstEp.id;
- console.log(`[SpotlightCarousel] Found episode ID (id) for ${item.name}: ${firstEp.id}`);
- } else if (firstEp.episodeId) {
- episodeData[item.id] = firstEp.episodeId;
- console.log(`[SpotlightCarousel] Found episode ID (episodeId) for ${item.name}: ${firstEp.episodeId}`);
- } else {
- // Create a fallback ID if neither id nor episodeId are available
- episodeData[item.id] = `${item.id}?ep=1`;
- console.log(`[SpotlightCarousel] Using fallback ID for ${item.name}: ${item.id}?ep=1`);
- }
- } else {
- // If no episodes, use a fallback
- episodeData[item.id] = `${item.id}?ep=1`;
- console.log(`[SpotlightCarousel] No episodes for ${item.name}, using fallback: ${item.id}?ep=1`);
- }
- } catch (error) {
- console.error(`[SpotlightCarousel] Error fetching episodes for ${item.id}:`, error);
- // Even on error, try to use fallback
- episodeData[item.id] = `${item.id}?ep=1`;
- } finally {
- // Mark as no longer loading
- newLoadingItems[item.id] = false;
- }
- }
- }
-
- // Update states
- setEpisodeIds(episodeData);
- setLoadingItems(newLoadingItems);
- };
-
- if (items && items.length > 0) {
- fetchEpisodeData();
- }
-
- // Clean up function
- return () => {
- if (intervalRef.current) clearTimeout(intervalRef.current);
- if (progressIntervalRef.current) clearInterval(progressIntervalRef.current);
- };
- }, [items, episodeIds, loadingItems]);
-
- // Autoplay functionality
- useEffect(() => {
- if (autoplay && items.length > 1) {
- // Clear any existing intervals
- if (intervalRef.current) clearInterval(intervalRef.current);
- if (progressIntervalRef.current) clearInterval(progressIntervalRef.current);
-
- // Set up new intervals
- setProgress(0);
- progressIntervalRef.current = setInterval(() => {
- setProgress(prev => {
- const newProgress = prev + 1;
- return newProgress <= 100 ? newProgress : prev;
- });
- }, 50); // Update every 50ms to get smooth progress
-
- intervalRef.current = setTimeout(() => {
- setCurrentIndex(prevIndex => (prevIndex + 1) % items.length);
- setProgress(0);
- }, 5000);
- }
-
- return () => {
- if (intervalRef.current) clearTimeout(intervalRef.current);
- if (progressIntervalRef.current) clearInterval(progressIntervalRef.current);
- };
- }, [autoplay, currentIndex, items.length]);
-
- const handleDotClick = (index) => {
- setCurrentIndex(index);
- setProgress(0);
- // Reset autoplay timer when manually changing slides
- if (intervalRef.current) clearTimeout(intervalRef.current);
- if (progressIntervalRef.current) clearInterval(progressIntervalRef.current);
- if (autoplay) {
- intervalRef.current = setTimeout(() => {
- setCurrentIndex((index + 1) % items.length);
- }, 5000);
- }
- };
-
- const handleMouseEnter = () => setAutoplay(false);
- const handleMouseLeave = () => setAutoplay(true);
-
- // If no items or not on client yet, show loading state
- if (!isClient || !items.length) {
- return (
-
- );
- }
-
- const currentItem = items[currentIndex];
-
- // Get the watch URL for the current item
- const watchUrl = episodeIds[currentItem.id]
- ? `/watch/${episodeIds[currentItem.id]}`
- : `/anime/${currentItem.id}`; // Direct to anime info if no episode ID
-
- return (
-
-
{
- setCurrentIndex(swiper.realIndex);
- setProgress(0);
- // Reset autoplay timer when manually changing slides
- if (intervalRef.current) clearTimeout(intervalRef.current);
- if (progressIntervalRef.current) clearInterval(progressIntervalRef.current);
- if (autoplay) {
- intervalRef.current = setTimeout(() => {
- setCurrentIndex((swiper.realIndex + 1) % items.length);
- }, 5000);
- }
- }}
- onMouseEnter={handleMouseEnter}
- onMouseLeave={handleMouseLeave}
- >
- {items.map((anime, index) => (
-
-
- {/* Background Image */}
-
-
- {/* Gradient Overlay */}
-
-
- {/* Content Area */}
-
-
- {/* Left Side Content */}
-
- {/* Metadata first - Minimal boxed design */}
-
- {anime.otherInfo?.map((info, i) => (
-
- {info}
-
- ))}
-
- {anime.episodes && (
- <>
- {anime.episodes.sub > 0 && (
-
- SUB {anime.episodes.sub}
-
- )}
- {anime.episodes.dub > 0 && (
-
- DUB {anime.episodes.dub}
-
- )}
- >
- )}
-
-
- {/* Title second */}
-
- {anime.name || 'Anime Title'}
-
-
- {/* Japanese Title */}
- {anime.jname && (
-
- {anime.jname}
-
- )}
-
- {/* Description third - hidden on mobile, shown on desktop with exactly 3 lines */}
-
- {anime.description || 'No description available.'}
-
-
-
- {/* Buttons - Below title on mobile, right side on desktop */}
-
- {/* Watch button - Uses episodeIds[anime.id] if available, otherwise links to anime details */}
-
-
-
-
-
WATCH NOW
-
-
-
-
-
-
-
DETAILS
-
-
-
-
-
-
- ))}
-
-
-
-
- );
-};
-
-export default SpotlightCarousel;
\ No newline at end of file
diff --git a/src/components/TopLists.js b/src/components/TopLists.js
deleted file mode 100644
index 40903ea..0000000
--- a/src/components/TopLists.js
+++ /dev/null
@@ -1,159 +0,0 @@
-'use client';
-
-import { useState, useEffect } from 'react';
-import Link from 'next/link';
-import Image from 'next/image';
-
-export default function TopLists({ topToday = [], topWeek = [], topMonth = [] }) {
- const [activeTab, setActiveTab] = useState('today');
-
- const tabs = [
- { id: 'today', label: 'Today', data: topToday },
- { id: 'week', label: 'Week', data: topWeek },
- { id: 'month', label: 'Month', data: topMonth },
- ];
-
- // Add custom scrollbar styles
- useEffect(() => {
- // Add custom styles for the toplists scrollbar
- const style = document.createElement('style');
- style.textContent = `
- .toplists-scrollbar::-webkit-scrollbar {
- width: 4px;
- }
- .toplists-scrollbar::-webkit-scrollbar-track {
- background: var(--card);
- }
- .toplists-scrollbar::-webkit-scrollbar-thumb {
- background-color: var(--border);
- border-radius: 4px;
- }
- `;
- document.head.appendChild(style);
-
- // Cleanup function
- return () => {
- document.head.removeChild(style);
- };
- }, []);
-
- // Find the active tab data
- const activeTabData = tabs.find(tab => tab.id === activeTab)?.data || [];
-
- return (
-
-
-
- {/* Tabs */}
-
- {tabs.map((tab) => (
- setActiveTab(tab.id)}
- className={`py-3 transition-colors text-sm font-medium ${
- activeTab === tab.id
- ? 'text-white bg-[var(--background)] border-b-2 border-[var(--border)]'
- : 'text-[var(--text-muted)] hover:bg-[var(--background)]'
- }`}
- >
- {tab.label}
-
- ))}
-
-
- {/* List content */}
-
- {activeTabData.length === 0 ? (
-
-
-
-
-
No data available
-
Check another tab or come back later
-
- ) : (
-
- {activeTabData.slice(0, 10).map((anime, index) => (
-
- {/* Top rank highlight for top 3 */}
- {index < 3 && (
-
- )}
-
-
- {/* Rank number with monochrome styling */}
-
-
- {anime.rank || index + 1}
-
-
-
- {/* Anime thumbnail with subtle shadow */}
-
-
-
-
- {/* Info */}
-
- {/* Title */}
-
-
- {anime.name}
-
-
-
- {/* Episodes if available */}
- {anime.episodes && (
-
- {anime.episodes.sub > 0 && (
-
- SUB {anime.episodes.sub}
-
- )}
- {anime.episodes.dub > 0 && (
-
- DUB {anime.episodes.dub}
-
- )}
-
- )}
-
-
-
- ))}
-
- )}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/TrendingList.js b/src/components/TrendingList.js
deleted file mode 100644
index a8f0bfb..0000000
--- a/src/components/TrendingList.js
+++ /dev/null
@@ -1,55 +0,0 @@
-'use client';
-
-import React from 'react';
-import Link from 'next/link';
-import Image from 'next/image';
-
-export default function TrendingList({ trendingAnime = [] }) {
- return (
-
-
-
-
-
- {trendingAnime.slice(0, 10).map((anime, index) => (
-
-
- {/* Rank number */}
-
- #{index + 1}
-
-
- {/* Anime image */}
-
-
-
-
- {/* Anime info */}
-
-
- {anime.title}
-
-
-
-
- ))}
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/VideoPlayer.js b/src/components/VideoPlayer.js
deleted file mode 100644
index 62c3d06..0000000
--- a/src/components/VideoPlayer.js
+++ /dev/null
@@ -1,1542 +0,0 @@
-'use client';
-
-import { useEffect, useRef, useState } from 'react';
-import Hls from 'hls.js';
-import NextImage from 'next/image';
-
-export default function VideoPlayer({ src, poster, headers = {}, subtitles = [], thumbnails = null, category = 'sub', intro = null, outro = null, autoSkipIntro = false, autoSkipOutro = false, episodeId }) {
- const videoRef = useRef(null);
- const [isPlaying, setIsPlaying] = useState(false);
- const [isLoading, setIsLoading] = useState(true);
- const [error, setError] = useState(null);
- const [progress, setProgress] = useState(0);
- const [currentTime, setCurrentTime] = useState(0);
- const [duration, setDuration] = useState(0);
- const [volume, setVolume] = useState(1);
- const [showControls, setShowControls] = useState(true);
- const [videoHeaders, setVideoHeaders] = useState(headers);
- const [thumbnailPreview, setThumbnailPreview] = useState(null);
- const [previewPosition, setPreviewPosition] = useState(0);
- const [showPreview, setShowPreview] = useState(false);
- const [isDubbed, setIsDubbed] = useState(category === 'dub');
- const [showSkipIntro, setShowSkipIntro] = useState(false);
- const [showSkipOutro, setShowSkipOutro] = useState(false);
-
- // New state variables
- const [playbackSpeed, setPlaybackSpeed] = useState(1);
- const [showSettings, setShowSettings] = useState(false);
- const [showSubtitles, setShowSubtitles] = useState(true);
- const [activeSubtitle, setActiveSubtitle] = useState(null);
- const [qualities, setQualities] = useState([]);
- const [currentQuality, setCurrentQuality] = useState(null);
- const [showQualityOptions, setShowQualityOptions] = useState(false);
- const [showSpeedOptions, setShowSpeedOptions] = useState(false);
- const [showSubtitleOptions, setShowSubtitleOptions] = useState(false);
- const [audioBoost, setAudioBoost] = useState(100);
- const [settingsView, setSettingsView] = useState('main'); // 'main', 'quality', 'speed', 'audio'
-
- // Add state for buffering and last buffer update
- const [isBuffering, setIsBuffering] = useState(false);
- const [lastBufferUpdate, setLastBufferUpdate] = useState(Date.now());
- const bufferCheckInterval = useRef(null);
- const [bufferedProgress, setBufferedProgress] = useState(0);
- const [isCastAvailable, setIsCastAvailable] = useState(false);
- const [castState, setCastState] = useState('NO_DEVICES_AVAILABLE');
- const [previewTime, setPreviewTime] = useState(0);
- const [thumbnailsLoaded, setThumbnailsLoaded] = useState(false);
-
- // Add state for mouse movement
- const [isMouseMoving, setIsMouseMoving] = useState(false);
- const hideControlsTimer = useRef(null);
-
- // Add isMobile state
- const [isMobile, setIsMobile] = useState(false);
-
- // Detect mobile device on mount
- useEffect(() => {
- const checkMobile = () => {
- setIsMobile(window.innerWidth < 768); // matches Tailwind's md breakpoint
- };
-
- checkMobile();
- window.addEventListener('resize', checkMobile);
-
- return () => window.removeEventListener('resize', checkMobile);
- }, []);
-
- // Reset video state when episodeId changes
- useEffect(() => {
- setIsPlaying(false);
- setIsLoading(true);
- setError(null);
- setProgress(0);
- setCurrentTime(0);
- setDuration(0);
- setBufferedProgress(0);
-
- // Reset video element
- if (videoRef.current) {
- videoRef.current.currentTime = 0;
- }
- }, [episodeId]);
-
- // Handle video click based on device type
- const handleVideoClick = () => {
- if (isMobile) {
- setShowControls(!showControls);
- } else {
- togglePlay();
- }
- };
-
- // Set headers from props when they change
- useEffect(() => {
- setVideoHeaders(headers);
- console.log('Headers updated from props:', headers);
- }, [headers]);
-
- // Format time from seconds to MM:SS
- const formatTime = (timeInSeconds) => {
- if (isNaN(timeInSeconds)) return '00:00';
- const minutes = Math.floor(timeInSeconds / 60);
- const seconds = Math.floor(timeInSeconds % 60);
- return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
- };
-
- // Event handlers
- const onTimeUpdate = () => {
- const video = videoRef.current;
- if (!video) return;
- const progress = (video.currentTime / video.duration) * 100;
- setProgress(isNaN(progress) ? 0 : progress);
- setCurrentTime(video.currentTime);
-
- // Show/hide skip intro button
- if (intro) {
- const isInIntro = video.currentTime >= intro.start && video.currentTime < intro.end;
- setShowSkipIntro(isInIntro);
-
- // Auto skip intro if enabled
- if (isInIntro && autoSkipIntro) {
- video.currentTime = intro.end;
- }
- }
-
- // Show/hide skip outro button
- if (outro) {
- const isInOutro = video.currentTime >= outro.start && video.currentTime < outro.end;
- setShowSkipOutro(isInOutro);
-
- // Auto skip outro if enabled
- if (isInOutro && autoSkipOutro) {
- video.currentTime = outro.end;
- }
- }
- };
-
- const onDurationChange = () => {
- const video = videoRef.current;
- if (!video) return;
- setDuration(video.duration);
- };
-
- const onLoadedData = () => {
- setIsLoading(false);
- };
-
- const onError = () => {
- setError('Error playing video');
- setIsLoading(false);
- };
-
- const onEnded = () => {
- setIsPlaying(false);
- };
-
- // Toggle play/pause
- const togglePlay = () => {
- const video = videoRef.current;
- if (!video) return;
-
- if (isPlaying) {
- video.pause();
- } else {
- video.play();
- }
- setIsPlaying(!isPlaying);
- };
-
- // Change current time on seek
- const handleSeek = (e) => {
- const video = videoRef.current;
- if (!video) return;
-
- const rect = e.currentTarget.getBoundingClientRect();
- const percent = (e.clientX - rect.left) / rect.width;
- video.currentTime = percent * video.duration;
- };
-
- // Change volume
- const handleVolumeChange = (e) => {
- const video = videoRef.current;
- if (!video) return;
-
- const newVolume = parseFloat(e.target.value);
- video.volume = newVolume;
- setVolume(newVolume);
- };
-
- // Function to get highest quality level
- const getHighestQuality = () => {
- if (!qualities.length) return -1;
- const sortedQualities = [...qualities].sort((a, b) => (b.height || 0) - (a.height || 0));
- return sortedQualities[0].id;
- };
-
- // Function to get next lower quality level
- const getNextLowerQuality = (currentId) => {
- if (!qualities.length) return -1;
- const sortedQualities = [...qualities].sort((a, b) => (b.height || 0) - (a.height || 0));
- const currentIndex = sortedQualities.findIndex(q => q.id === currentId);
- if (currentIndex < sortedQualities.length - 1) {
- return sortedQualities[currentIndex + 1].id;
- }
- return currentId;
- };
-
- // Set up HLS.js for M3U8 streams
- useEffect(() => {
- if (!src) return;
-
- const video = videoRef.current;
- if (!video) return;
-
- let hls = null;
-
- const setupHls = async () => {
- try {
- setIsLoading(true);
- setError(null);
-
- // Check if the source is an M3U8 stream
- const isHlsStream = src.includes('.m3u8') || src.includes('application/vnd.apple.mpegurl');
-
- if (isHlsStream && Hls.isSupported()) {
- console.log('[VideoPlayer] HLS is supported, initializing');
-
- hls = new Hls({
- debug: true,
- maxBufferLength: 30,
- maxMaxBufferLength: 60,
- enableWorker: false, // Disable worker for easier debugging
- startLevel: -1, // Start with Auto quality
- autoLevelEnabled: true, // Enable auto quality switching
- abrEwmaDefaultEstimate: 500000, // Start with a higher bandwidth estimate
- abrBandWidthFactor: 0.95, // Be more aggressive with quality upgrades
- abrBandWidthUpFactor: 0.7, // Be more conservative with quality downgrades
- xhrSetup: function(xhr, url) {
- console.log('HLS attempting to load:', url);
-
- // Apply headers directly to all requests
- if (!url.startsWith('blob:') && url.includes('://')) {
- Object.entries(videoHeaders).forEach(([key, value]) => {
- xhr.setRequestHeader(key, value);
- });
- }
- }
- });
-
- window.hls = hls; // Save reference for debugging
-
- // Bind HLS to video element
- const proxiedSrc = src;
- console.log('[VideoPlayer] Loading proxied source:', proxiedSrc);
- hls.loadSource(proxiedSrc);
- hls.attachMedia(video);
-
- // Handle HLS events
- hls.on(Hls.Events.MANIFEST_PARSED, () => {
- console.log('[VideoPlayer] HLS manifest parsed');
- // Get available qualities
- const levels = hls.levels.map((level, index) => ({
- id: index,
- label: `${level.height}p`,
- height: level.height,
- selected: index === hls.currentLevel
- }));
-
- console.log('[VideoPlayer] Available qualities:', levels);
- setQualities(levels);
- setCurrentQuality(hls.currentLevel);
-
- // Auto-play when ready
- if (isPlaying) {
- video.play().catch(err => {
- console.error('[VideoPlayer] Autoplay error:', err);
- });
- }
- });
-
- hls.on(Hls.Events.ERROR, (event, data) => {
- console.error('[VideoPlayer] HLS error:', event, data);
-
- if (data.fatal) {
- switch (data.type) {
- case Hls.ErrorTypes.NETWORK_ERROR:
- console.error('[VideoPlayer] HLS network error, attempting recovery');
- hls.startLoad();
- break;
- case Hls.ErrorTypes.MEDIA_ERROR:
- console.error('[VideoPlayer] HLS media error, attempting recovery');
- hls.recoverMediaError();
- break;
- default:
- console.error('[VideoPlayer] HLS fatal error, cannot recover');
- setError('Failed to load video - please try another server');
- break;
- }
- }
- });
- } else {
- // For non-HLS streams, use native video player
- console.log('[VideoPlayer] Using native player for source:', src);
-
- // Set headers for direct video requests
- const fetchOptions = {
- headers: videoHeaders
- };
-
- try {
- const response = await fetch(src, fetchOptions);
- if (!response.ok) {
- console.error('[VideoPlayer] Failed to fetch video:', response.status, response.statusText);
- throw new Error('Failed to load video');
- }
-
- const blob = await response.blob();
- const url = URL.createObjectURL(blob);
- video.src = url;
-
- // Auto-play when ready
- if (isPlaying) {
- video.play().catch(err => {
- console.error('[VideoPlayer] Autoplay error:', err);
- });
- }
- } catch (error) {
- console.error('[VideoPlayer] Error loading video:', error);
- setError('Failed to load video - please try another server');
- }
- }
-
- setIsLoading(false);
- } catch (error) {
- console.error('[VideoPlayer] Error setting up video:', error);
- setError('Failed to load video - please try another server');
- setIsLoading(false);
- }
- };
-
- setupHls();
-
- // Cleanup
- return () => {
- if (hls) {
- hls.destroy();
- }
- };
- }, [src, videoHeaders, isPlaying]);
-
- // Add event listeners
- useEffect(() => {
- const video = videoRef.current;
- if (!src || src === 'undefined' || src.includes('undefined')) {
- setError('No valid video source provided');
- setIsLoading(false);
- return;
- }
-
- setIsLoading(true);
-
- const setupHls = async () => {
- if (Hls.isSupported()) {
- if (hls) {
- hls.destroy();
- }
-
- hls = new Hls({
- debug: true,
- maxBufferLength: 30,
- maxMaxBufferLength: 60,
- enableWorker: false, // Disable worker for easier debugging
- startLevel: -1, // Start with Auto quality
- autoLevelEnabled: true, // Enable auto quality switching
- abrEwmaDefaultEstimate: 500000, // Start with a higher bandwidth estimate
- abrBandWidthFactor: 0.95, // Be more aggressive with quality upgrades
- abrBandWidthUpFactor: 0.7, // Be more conservative with quality downgrades
- xhrSetup: function(xhr, url) {
- console.log('HLS attempting to load:', url);
-
- // Apply headers directly to all requests
- if (!url.startsWith('blob:') && url.includes('://')) {
- Object.entries(videoHeaders).forEach(([key, value]) => {
- xhr.setRequestHeader(key, value);
- });
- }
- }
- });
-
- // Make hls instance globally accessible for quality switching
- window.hls = hls;
-
- hls.on(Hls.Events.MEDIA_ATTACHED, () => {
- console.log('Video and HLS attached');
- });
-
- hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
- console.log('Manifest parsed successfully');
- setIsLoading(false);
-
- // Extract available quality levels
- if (data && data.levels && data.levels.length > 0) {
- const availableQualities = data.levels.map((level, index) => ({
- id: index,
- height: level.height,
- width: level.width,
- bitrate: level.bitrate,
- name: level.height ? `${level.height}p` : `Quality ${index + 1}`
- }));
-
- setQualities(availableQualities);
- setCurrentQuality({ id: -1, name: 'Auto' }); // Set to Auto by default
- console.log('Available qualities:', availableQualities);
-
- // Start with highest quality in Auto mode
- const highestQuality = getHighestQuality();
- if (highestQuality !== -1) {
- hls.nextLevel = highestQuality;
- }
- }
-
- video.play().catch(e => {
- console.warn('Auto-play was prevented:', e);
- setIsPlaying(false);
- });
- });
-
- // Monitor buffering state
- hls.on(Hls.Events.FRAG_BUFFERED, () => {
- setIsBuffering(false);
- setLastBufferUpdate(Date.now());
- });
-
- hls.on(Hls.Events.BUFFER_STALLED, () => {
- setIsBuffering(true);
- });
-
- hls.on(Hls.Events.ERROR, (event, data) => {
- console.error('HLS error details:', data);
-
- if (data.fatal) {
- switch (data.type) {
- case Hls.ErrorTypes.NETWORK_ERROR:
- console.error('Network error:', data.details);
- console.error('Error with URL:', data.url);
-
- if (data.response && data.response.code === 403) {
- setError(`Access denied (403 Forbidden). Please check your headers.`);
- setIsLoading(false);
- return;
- }
-
- if (data.response && data.response.code === 404) {
- setError(`Video not found (404). Please try another server.`);
- setIsLoading(false);
- return;
- }
-
- console.log('Network error, trying to recover...');
- hls.startLoad();
- break;
- case Hls.ErrorTypes.MEDIA_ERROR:
- console.log('Media error, trying to recover...');
- hls.recoverMediaError();
- break;
- default:
- setError('Failed to load video: ' + (data.details || 'Unknown error'));
- setIsLoading(false);
- break;
- }
- }
- });
-
- console.log('Loading source:', proxiedSrc);
- hls.loadSource(proxiedSrc);
- hls.attachMedia(video);
-
- } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
- // For Safari on iOS/Mac
- console.log('Using native HLS support');
- video.src = proxiedSrc;
-
- video.addEventListener('loadedmetadata', () => {
- setIsLoading(false);
- video.play().catch(e => {
- console.warn('Auto-play was prevented:', e);
- setIsPlaying(false);
- });
- });
-
- } else {
- // Direct fallback for non-HLS browsers
- console.log('No HLS support, trying direct playback');
- try {
- video.src = proxiedSrc;
- setIsLoading(false);
- } catch (e) {
- console.error('Error setting video source:', e);
- setError('Your browser cannot play this video');
- setIsLoading(false);
- }
- }
- };
-
- setupHls();
-
- // Set up buffer monitoring
- bufferCheckInterval.current = setInterval(() => {
- if (isBuffering && Date.now() - lastBufferUpdate > 5000) {
- // If buffering for more than 5 seconds, try switching to lower quality
- if (window.hls && currentQuality?.id !== -1) {
- const nextLowerQuality = getNextLowerQuality(window.hls.currentLevel);
- if (nextLowerQuality !== window.hls.currentLevel) {
- window.hls.nextLevel = nextLowerQuality;
- console.log('Switching to lower quality due to buffering:', nextLowerQuality);
- }
- }
- }
- }, 1000);
-
- // Cleanup function
- return () => {
- if (hls) {
- hls.destroy();
- }
- if (bufferCheckInterval.current) {
- clearInterval(bufferCheckInterval.current);
- }
- };
- }, [src, videoHeaders]);
-
- // Add event listeners
- useEffect(() => {
- const video = videoRef.current;
- if (!video) return;
-
- video.addEventListener('timeupdate', onTimeUpdate);
- video.addEventListener('durationchange', onDurationChange);
- video.addEventListener('loadeddata', onLoadedData);
- video.addEventListener('error', onError);
- video.addEventListener('ended', onEnded);
-
- return () => {
- video.removeEventListener('timeupdate', onTimeUpdate);
- video.removeEventListener('durationchange', onDurationChange);
- video.removeEventListener('loadeddata', onLoadedData);
- video.removeEventListener('error', onError);
- video.removeEventListener('ended', onEnded);
- };
- }, []);
-
- // Toggle fullscreen
- const toggleFullscreen = () => {
- const container = videoRef.current?.parentElement;
- if (!container) return;
-
- if (document.fullscreenElement) {
- document.exitFullscreen();
- } else {
- container.requestFullscreen().catch((err) => {
- console.error('Error attempting to enable fullscreen:', err);
- });
- }
- };
-
- // Skip forward 10 seconds
- const skipForward = () => {
- const video = videoRef.current;
- if (!video) return;
- video.currentTime = Math.min(video.currentTime + 10, video.duration);
- };
-
- // Skip backward 10 seconds
- const skipBackward = () => {
- const video = videoRef.current;
- if (!video) return;
- video.currentTime = Math.max(video.currentTime - 10, 0);
- };
-
- // Change playback speed
- const changePlaybackSpeed = (speed) => {
- const video = videoRef.current;
- if (!video) return;
- video.playbackRate = speed;
- setPlaybackSpeed(speed);
- setShowSpeedOptions(false);
- };
-
- // Toggle subtitles
- const toggleSubtitles = () => {
- setShowSubtitles(!showSubtitles);
- };
-
- // Initialize subtitle tracks when the video element is created
- useEffect(() => {
- const video = videoRef.current;
- if (!video || !activeSubtitle) return;
-
- console.log('[VideoPlayer] Initializing subtitle tracks on mount');
-
- // Apply the active subtitle
- if (activeSubtitle && showSubtitles) {
- // First remove any existing tracks
- const existingTracks = video.querySelectorAll('track');
- existingTracks.forEach(track => track.remove());
-
- // Create and append the track element
- const track = document.createElement('track');
- track.kind = 'subtitles';
- track.label = activeSubtitle.label || activeSubtitle.lang || 'Default';
- track.srclang = activeSubtitle.lang || 'en';
-
- // Format subtitle URL correctly - it might be in different formats
- let subtitleUrl = activeSubtitle.src || activeSubtitle.url;
-
- // Use subtitle URL directly
- track.src = subtitleUrl;
- track.default = true;
-
- console.log('[VideoPlayer] Adding track on mount with src:', track.src);
- video.appendChild(track);
-
- // Force the track to be active after a short delay
- setTimeout(() => {
- if (video.textTracks && video.textTracks.length > 0) {
- console.log('[VideoPlayer] Activating text track');
- video.textTracks[0].mode = 'showing';
- }
- }, 500);
- }
- }, [videoRef.current, activeSubtitle, showSubtitles]);
-
- // This function manually applies the selected subtitle to the video
- const applySubtitle = (subtitle) => {
- console.log('[VideoPlayer] Applying subtitle:', JSON.stringify(subtitle));
- if (!videoRef.current || !subtitle) return;
-
- // Ensure we have valid URL
- let subtitleUrl = subtitle.src || subtitle.url;
- if (!subtitleUrl) {
- console.error('[VideoPlayer] No valid URL found in subtitle:', JSON.stringify(subtitle));
- return;
- }
-
- console.log('[VideoPlayer] Final subtitle URL:', subtitleUrl);
-
- // Remove all existing tracks
- const video = videoRef.current;
- const existingTracks = video.querySelectorAll('track');
- existingTracks.forEach(track => track.remove());
-
- // Create a new track element
- const track = document.createElement('track');
- track.kind = 'subtitles';
- track.label = subtitle.label || subtitle.lang || 'Unknown';
- track.srclang = subtitle.lang || 'en';
- track.src = subtitleUrl;
- track.default = true;
-
- // Add track to video
- video.appendChild(track);
-
- // Force enable the track
- setTimeout(() => {
- if (video.textTracks && video.textTracks.length > 0) {
- video.textTracks[0].mode = 'showing';
- console.log('[VideoPlayer] Track enabled, mode:', video.textTracks[0].mode);
- } else {
- console.error('[VideoPlayer] No text tracks available after adding track');
- }
- }, 100);
- };
-
- // Handle subtitle change from UI
- const changeSubtitle = (subtitle) => {
- console.log('[VideoPlayer] changeSubtitle called with:', JSON.stringify(subtitle));
-
- if (!subtitle) {
- setShowSubtitles(false);
- setActiveSubtitle(null);
-
- // Remove all tracks
- if (videoRef.current) {
- const existingTracks = videoRef.current.querySelectorAll('track');
- existingTracks.forEach(track => track.remove());
- }
-
- console.log('[VideoPlayer] Subtitles turned off');
- return;
- }
-
- // Set state and apply the subtitle
- setActiveSubtitle(subtitle);
- setShowSubtitles(true);
- applySubtitle(subtitle);
- };
-
- // Change quality function update
- const changeQuality = (quality) => {
- const video = videoRef.current;
- if (!video || !window.hls) return;
-
- // Save current time
- const currentTime = video.currentTime;
-
- if (quality.id === -1) {
- // Auto mode
- window.hls.currentLevel = -1;
- window.hls.nextLevel = getHighestQuality(); // Start with highest quality in Auto mode
- window.hls.autoLevelEnabled = true;
- } else {
- // Manual mode
- window.hls.currentLevel = quality.id;
- window.hls.autoLevelEnabled = false;
- }
-
- setCurrentQuality(quality);
- console.log(`Changed quality to ${quality.name}`);
- };
-
- // Toggle settings menu
- const toggleSettings = () => {
- setShowSettings(!showSettings);
- setShowQualityOptions(false);
- setShowSpeedOptions(false);
- setShowSubtitleOptions(false);
- };
-
- // Handle mouse movement and auto-hide controls
- const handleMouseMove = () => {
- setIsMouseMoving(true);
- setShowControls(true);
-
- // Clear existing timer
- if (hideControlsTimer.current) {
- clearTimeout(hideControlsTimer.current);
- }
-
- // Set new timer to hide controls after 3 seconds
- hideControlsTimer.current = setTimeout(() => {
- if (isPlaying) {
- setShowControls(false);
- }
- }, 3000);
- };
-
- // Clear timer on unmount
- useEffect(() => {
- return () => {
- if (hideControlsTimer.current) {
- clearTimeout(hideControlsTimer.current);
- }
- };
- }, []);
-
- // Update timer when play state changes
- useEffect(() => {
- if (!isPlaying) {
- setShowControls(true);
- if (hideControlsTimer.current) {
- clearTimeout(hideControlsTimer.current);
- }
- } else {
- // Start hide timer when video starts playing
- handleMouseMove();
- }
- }, [isPlaying]);
-
- // Set default subtitle when subtitles change
- useEffect(() => {
- console.log('[VideoPlayer] Subtitles prop received:', JSON.stringify(subtitles));
-
- if (subtitles && subtitles.length > 0) {
- // Set the first subtitle as active (should be English after our sorting)
- console.log('[VideoPlayer] Setting active subtitle to:', JSON.stringify(subtitles[0]));
- setActiveSubtitle(subtitles[0]);
- setShowSubtitles(true);
-
- // Apply the subtitle if video is already loaded
- if (videoRef.current) {
- setTimeout(() => {
- applySubtitle(subtitles[0]);
- }, 500);
- }
- } else {
- console.log('[VideoPlayer] No subtitles available, setting activeSubtitle to null');
- setActiveSubtitle(null);
- }
- }, [subtitles]);
-
- // Function to format time for thumbnail preview
- const formatPreviewTime = (seconds) => {
- const hours = Math.floor(seconds / 3600);
- const minutes = Math.floor((seconds % 3600) / 60);
- const secs = Math.floor(seconds % 60);
-
- if (hours > 0) {
- return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
- }
- return `${minutes}:${secs.toString().padStart(2, '0')}`;
- };
-
- // Handle timeline hover
- const handleTimelineHover = (e) => {
- if (!thumbnails) return;
-
- const timeline = e.currentTarget;
- const rect = timeline.getBoundingClientRect();
- const offsetX = e.clientX - rect.left;
- const percentage = offsetX / rect.width;
-
- // Calculate preview position and time
- const previewTimeValue = percentage * (videoRef.current?.duration || 0);
- setPreviewTime(previewTimeValue);
-
- // Position the preview element
- // Ensure the preview stays within the viewport
- const previewWidth = 160; // Width of preview container
- let position = (offsetX - previewWidth / 2) / rect.width * 100;
-
- // Prevent preview from going off-screen
- const minPosition = (previewWidth / 2) / rect.width * 100;
- const maxPosition = 100 - (previewWidth / 2) / rect.width * 100;
- position = Math.max(minPosition, Math.min(maxPosition, position));
-
- setPreviewPosition(position);
- setShowPreview(true);
- };
-
- // Handle timeline hover end
- const handleTimelineLeave = () => {
- setShowPreview(false);
- };
-
- // Update buffer progress
- const updateBufferProgress = () => {
- const video = videoRef.current;
- if (!video || !video.buffered || !video.buffered.length) return;
-
- const buffered = video.buffered;
- const currentTime = video.currentTime;
-
- // Find the appropriate buffered range
- for (let i = 0; i < buffered.length; i++) {
- if (buffered.start(i) <= currentTime && currentTime <= buffered.end(i)) {
- setBufferedProgress((buffered.end(i) / video.duration) * 100);
- break;
- }
- }
- };
-
- // Add buffer progress listener
- useEffect(() => {
- const video = videoRef.current;
- if (!video) return;
-
- const handleProgress = () => {
- updateBufferProgress();
- };
-
- video.addEventListener('progress', handleProgress);
- video.addEventListener('timeupdate', handleProgress);
-
- return () => {
- video.removeEventListener('progress', handleProgress);
- video.removeEventListener('timeupdate', handleProgress);
- };
- }, []);
-
- // Update audio boost function to handle percentage
- const changeAudioBoost = (boost) => {
- const video = videoRef.current;
- if (!video) return;
- const multiplier = boost / 100;
- video.volume = Math.min(volume * multiplier, 1);
- video.volume = Math.min(multiplier, 1); // Set volume directly based on boost
- setAudioBoost(boost);
- };
-
- // Add Google Cast functionality
- const initializeCast = () => {
- if (!window.chrome || !window.chrome.cast) {
- console.log('Chrome Cast API not available');
- return;
- }
-
- // Initialize Cast API
- const initCastApi = () => {
- const sessionRequest = new chrome.cast.SessionRequest(chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID);
- const apiConfig = new chrome.cast.ApiConfig(
- sessionRequest,
- (session) => {
- console.log('Cast session success:', session);
- },
- (availability) => {
- setIsCastAvailable(availability === chrome.cast.ReceiverAvailability.AVAILABLE);
- },
- chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
- );
-
- chrome.cast.initialize(apiConfig,
- () => {
- console.log('Cast API initialized successfully');
- setIsCastAvailable(true);
- },
- (error) => {
- console.error('Cast API initialization error:', error);
- }
- );
- };
-
- // Load Cast Framework
- window['__onGCastApiAvailable'] = (isAvailable) => {
- if (isAvailable) {
- // Initialize Cast Framework
- const context = cast.framework.CastContext.getInstance();
- context.setOptions({
- receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
- autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
- });
-
- // Listen for cast state changes
- context.addEventListener(cast.framework.CastContextEventType.CAST_STATE_CHANGED, (event) => {
- setCastState(event.castState);
- });
-
- // Initialize the old Cast API as well
- initCastApi();
- }
- };
-
- // Load the Cast SDK if not already loaded
- if (!document.querySelector('#cast-script')) {
- const script = document.createElement('script');
- script.id = 'cast-script';
- script.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1';
- document.head.appendChild(script);
- }
- };
-
- // Function to start casting
- const startCasting = async () => {
- try {
- if (!window.chrome || !window.chrome.cast) {
- console.log('Cast API not available');
- return;
- }
-
- const context = cast.framework.CastContext.getInstance();
-
- // If not connected, request a session
- if (context.getCastState() === cast.framework.CastState.NOT_CONNECTED) {
- try {
- await context.requestSession();
- console.log('Cast session requested');
- } catch (error) {
- console.error('Error requesting cast session:', error);
- return;
- }
- }
-
- // Get current session
- const session = context.getCurrentSession();
- if (!session) {
- console.log('No cast session available');
- return;
- }
-
- // Prepare media info
- const mediaInfo = new chrome.cast.media.MediaInfo(src, 'application/x-mpegURL');
- mediaInfo.customData = null;
- mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata();
-
- // Add current time and duration
- const currentTime = videoRef.current?.currentTime || 0;
- const duration = videoRef.current?.duration || 0;
-
- const request = new chrome.cast.media.LoadRequest(mediaInfo);
- request.currentTime = currentTime;
- request.autoplay = true;
-
- try {
- await session.loadMedia(request);
- console.log('Media loaded successfully');
-
- // Pause the video player when casting starts
- if (videoRef.current) {
- videoRef.current.pause();
- }
- } catch (error) {
- console.error('Error loading media:', error);
- }
- } catch (error) {
- console.error('Error starting cast:', error);
- }
- };
-
- // Initialize Cast when component mounts
- useEffect(() => {
- initializeCast();
- }, []);
-
- // Load and verify thumbnails
- useEffect(() => {
- if (thumbnails) {
- const img = new window.Image();
- img.onload = () => {
- setThumbnailsLoaded(true);
- console.log('Thumbnails loaded successfully');
- };
- img.onerror = (err) => {
- console.error('Error loading thumbnails:', err);
- };
- img.src = thumbnails;
- }
- }, [thumbnails]);
-
- // Update isDubbed when category changes
- useEffect(() => {
- setIsDubbed(category === 'dub');
- }, [category]);
-
- // Add skip intro/outro functions
- const skipIntro = () => {
- if (videoRef.current && intro?.end) {
- videoRef.current.currentTime = intro.end;
- }
- };
-
- const skipOutro = () => {
- if (videoRef.current && outro?.end) {
- videoRef.current.currentTime = outro.end;
- }
- };
-
- return (
- !isMobile && setShowControls(true)}
- onMouseLeave={() => !isMobile && isPlaying && setShowControls(false)}
- onMouseMove={handleMouseMove}
- >
- {/* Mobile Controls (Only visible on small screens) */}
-
-
- {/* Settings Button */}
-
-
{
- setShowSettings(!showSettings);
- if (!showSettings) {
- setSettingsView('main');
- }
- setShowSubtitleOptions(false);
- }}
- className={`p-1.5 text-white hover:text-white/80 transition-colors hover:bg-white/10 rounded-lg ${
- showSettings ? 'bg-white/20' : ''
- }`}
- >
-
-
-
-
-
-
-
-
- {/* Video Element */}
-
{
- onLoadedData();
- // Only apply subtitles if it's not a dub and subtitles are enabled
- if (activeSubtitle && showSubtitles && !isDubbed) {
- setTimeout(() => {
- applySubtitle(activeSubtitle);
- }, 100);
- }
- }}
- onError={onError}
- onEnded={onEnded}
- onPlay={() => setIsPlaying(true)}
- onPause={() => setIsPlaying(false)}
- >
-
- Your browser does not support the video tag.
-
-
- {/* Pause Overlay with Blur */}
- {!isPlaying && !isLoading && !error && (
-
- )}
-
- {/* Loading Spinner */}
- {isLoading && (
-
-
- {/* Outer ring with fade effect */}
-
-
- {/* Inner spinning ring */}
-
-
- {/* Core circle with pulse */}
-
-
-
- )}
-
- {/* Error Message */}
- {error && (
-
-
-
-
Error: {error}
-
Please try a different server or check your connection.
-
-
- )}
-
- {/* Center Play Button (only shown briefly on state change) */}
-
-
- {/* Skip Intro Button */}
- {showSkipIntro && (
-
- )}
-
- {/* Skip Outro Button */}
- {showSkipOutro && (
-
- )}
-
- {/* Video Controls */}
-
{
- setShowControls(true);
- if (hideControlsTimer.current) {
- clearTimeout(hideControlsTimer.current);
- }
- }}
- >
- {/* Progress Bar Container */}
-
- {/* Preview Thumbnail */}
- {showPreview && thumbnailsLoaded && (
-
-
-
-
- {formatPreviewTime(previewTime)}
-
-
-
- )}
-
- {/* Progress Track */}
-
- {/* Background */}
-
-
- {/* Buffered Progress */}
-
-
- {/* Intro Marker */}
- {intro && (
-
- )}
-
- {/* Outro Marker */}
- {outro && (
-
- )}
-
- {/* Played Progress */}
-
-
- {/* Progress Handle */}
-
-
-
-
- {/* Controls Bar */}
-
-
- {/* Left Controls */}
-
- {/* Play/Pause */}
-
- {isPlaying ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
- {/* Volume and Time Controls */}
-
- {/* Volume */}
-
-
-
- {volume === 0 ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
-
-
-
- {/* Time */}
-
- {formatTime(currentTime)}
- /
- {formatTime(duration)}
-
-
-
-
- {/* Right Controls */}
-
- {/* Skip Backward/Forward */}
-
-
- {/* Hide cast and settings buttons on mobile since they're at the top */}
-
- {/* Settings */}
-
-
{
- setShowSettings(!showSettings);
- if (!showSettings) {
- setSettingsView('main');
- }
- setShowSubtitleOptions(false);
- }}
- className={`p-1.5 text-white hover:text-white/80 transition-colors hover:bg-white/10 rounded-lg ${
- showSettings ? 'bg-white/20' : ''
- }`}
- >
-
-
-
-
- {/* Settings menu content remains the same */}
- {showSettings && (
-
- {/* Main Settings View */}
-
-
- Settings
-
-
- {/* Quality Option */}
-
setSettingsView('quality')}
- className="w-full px-3 py-2 text-left text-xs hover:bg-white/10 rounded flex items-center justify-between text-white"
- >
- Quality
-
-
{currentQuality?.name || 'Auto'}
-
-
-
-
-
-
- {/* Playback Speed Option */}
-
setSettingsView('speed')}
- className="w-full px-3 py-2 text-left text-xs hover:bg-white/10 rounded flex items-center justify-between text-white"
- >
- Speed
-
-
{playbackSpeed}x
-
-
-
-
-
-
- {/* Audio Boost Option */}
-
setSettingsView('audio')}
- className="w-full px-3 py-2 text-left text-xs hover:bg-white/10 rounded flex items-center justify-between text-white"
- >
- Volume Boost
-
-
-
-
-
- {/* Quality Settings View */}
-
-
-
setSettingsView('main')}
- className="hover:bg-white/10 rounded p-1 mr-1"
- >
-
-
-
-
- Quality
-
-
-
{
- changeQuality({ id: -1, name: 'Auto' });
- setSettingsView('main');
- }}
- className={`w-full px-3 py-2 text-left text-xs hover:bg-white/10 rounded flex items-center justify-between ${
- currentQuality?.id === -1 ? 'text-white' : 'text-white/60'
- }`}
- >
- Auto {currentQuality?.id !== -1 ? `(${currentQuality?.name})` : ''}
- {currentQuality?.id === -1 && (
-
-
-
- )}
-
- {qualities
- .sort((a, b) => (b.height || 0) - (a.height || 0))
- .map((quality) => (
-
{
- changeQuality(quality);
- setSettingsView('main');
- }}
- className={`w-full px-3 py-2 text-left text-xs hover:bg-white/10 rounded flex items-center justify-between ${
- currentQuality?.id === quality.id ? 'text-white' : 'text-white/60'
- }`}
- >
- {quality.name}
- {currentQuality?.id === quality.id && (
-
-
-
- )}
-
- ))}
-
-
-
- )}
-
-
-
- {/* Picture in Picture */}
-
{
- try {
- if (document.pictureInPictureElement) {
- await document.exitPictureInPicture();
- } else if (document.pictureInPictureEnabled) {
- await videoRef.current.requestPictureInPicture();
- }
- } catch (error) {
- console.error('PiP error:', error);
- }
- }}
- className="p-1.5 text-white hover:text-white/80 transition-colors hover:bg-white/10 rounded-lg"
- >
-
-
-
-
-
- {/* Fullscreen */}
-
-
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/banner/Banner.css b/src/components/banner/Banner.css
new file mode 100644
index 0000000..599a1b4
--- /dev/null
+++ b/src/components/banner/Banner.css
@@ -0,0 +1,133 @@
+.spotlight {
+ overflow: hidden;
+}
+
+.spotlight-overlay {
+ width: 100.1%;
+ height: 100.1%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom:0;
+ background: radial-gradient(
+ circle at 130% center,
+ rgba(32, 31, 49, 0) 50%,
+ rgba(32, 31, 49, 0.5) 60%,
+ rgba(32, 31, 49, 1) 80%,
+ rgba(32, 31, 49, 1) 100%
+ ),
+ linear-gradient(
+ to top,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 20%,
+ rgba(32, 31, 49, 0) 100%
+ ),
+ linear-gradient(
+ to left,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 20%,
+ rgba(32, 31, 49, 0) 100%
+ ),
+ linear-gradient(
+ to bottom,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 20%,
+ rgba(32, 31, 49, 0) 100%
+ );
+
+ z-index: 1;
+}
+@media only screen and (max-width: 1300px) {
+ .spotlight-overlay {
+ background: radial-gradient(
+ circle at 130% center,
+ rgba(32, 31, 49, 0) 50%,
+ rgba(32, 31, 49, 0.5) 60%,
+ rgba(32, 31, 49, 1) 80%,
+ rgba(32, 31, 49, 1) 100%
+ ),
+ linear-gradient(
+ to top,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 20%,
+ rgba(32, 31, 49, 0) 100%
+ ),
+ linear-gradient(
+ to left,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 20%,
+ rgba(32, 31, 49, 0) 100%
+ ),
+ linear-gradient(
+ to bottom,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 50%,
+ rgba(32, 31, 49, 0) 100%
+ );
+ }
+}
+@media only screen and (max-width: 1200px) {
+ .spotlight-overlay {
+ background: radial-gradient(
+ circle at 100% center,
+ rgba(32, 31, 49, 0) 50%,
+ rgba(32, 31, 49, 0.5) 60%,
+ rgba(32, 31, 49, 1) 95%,
+ rgba(32, 31, 49, 1) 100%
+ ),
+ linear-gradient(
+ to top,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 20%,
+ rgba(32, 31, 49, 0) 100%
+ ),
+ linear-gradient(
+ to left,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 20%,
+ rgba(32, 31, 49, 0) 100%
+ ),
+ linear-gradient(
+ to bottom,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 70%,
+ rgba(32, 31, 49, 0) 100%
+ );
+ }
+}
+@media only screen and (max-width: 900px) {
+ .spotlight-overlay {
+ background: radial-gradient(
+ circle at 60% center,
+ rgba(32, 31, 49, 0) 50%,
+ rgba(32, 31, 49, 0.5) 85%,
+ rgba(32, 31, 49, 1) 95%,
+ rgba(32, 31, 49, 1) 100%
+ ),
+ linear-gradient(
+ to top,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 70%,
+ rgba(32, 31, 49, 0) 100%
+ ),
+ linear-gradient(
+ to left,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 20%,
+ rgba(32, 31, 49, 0) 100%
+ ),
+ linear-gradient(
+ to bottom,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 70%,
+ rgba(32, 31, 49, 0) 100%
+ ),
+ linear-gradient(
+ to right,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 15%,
+ rgba(32, 31, 49, 0) 100%
+ );
+ }
+}
diff --git a/src/components/banner/Banner.jsx b/src/components/banner/Banner.jsx
new file mode 100644
index 0000000..aa9aaf5
--- /dev/null
+++ b/src/components/banner/Banner.jsx
@@ -0,0 +1,136 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faPlay,
+ faClosedCaptioning,
+ faMicrophone,
+ faCalendar,
+ faClock,
+} from "@fortawesome/free-solid-svg-icons";
+import { FaChevronRight } from "react-icons/fa";
+import { Link } from "react-router-dom";
+import { useLanguage } from "@/src/context/LanguageContext";
+import "./Banner.css";
+
+function Banner({ item, index }) {
+ const { language } = useLanguage();
+ return (
+
+
+
+
+
+ #{index + 1} Spotlight
+
+
+ {language === "EN" ? item.title : item.japanese_title}
+
+
+ {item.tvInfo && (
+ <>
+ {item.tvInfo.showType && (
+
+
+
+ {item.tvInfo.showType}
+
+
+ )}
+
+ {item.tvInfo.duration && (
+
+
+
+ {item.tvInfo.duration}
+
+
+ )}
+
+ {item.tvInfo.releaseDate && (
+
+
+
+ {item.tvInfo.releaseDate}
+
+
+ )}
+
+
+ {item.tvInfo.quality && (
+
+ {item.tvInfo.quality}
+
+ )}
+
+ {item.tvInfo.episodeInfo?.sub && (
+
+
+
+ {item.tvInfo.episodeInfo.sub}
+
+
+ )}
+
+ {item.tvInfo.episodeInfo?.dub && (
+
+
+
+ {item.tvInfo.episodeInfo.dub}
+
+
+ )}
+
+
+ >
+ )}
+
+
+ {item.description}
+
+
+
+
+
+ Watch Now
+
+
+
+
+ Detail
+
+
+
+
+
+
+ );
+}
+
+export default Banner;
diff --git a/src/components/cart/Cart.css b/src/components/cart/Cart.css
new file mode 100644
index 0000000..ff2b750
--- /dev/null
+++ b/src/components/cart/Cart.css
@@ -0,0 +1,10 @@
+.dot {
+ width: 4px;
+ height: 4px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, .3);
+ display: inline-block;
+}
diff --git a/src/components/cart/Cart.jsx b/src/components/cart/Cart.jsx
new file mode 100644
index 0000000..0ceefe7
--- /dev/null
+++ b/src/components/cart/Cart.jsx
@@ -0,0 +1,132 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faClosedCaptioning,
+ faMicrophone,
+} from "@fortawesome/free-solid-svg-icons";
+import { FaChevronRight } from "react-icons/fa";
+import { useLanguage } from "@/src/context/LanguageContext";
+import "./Cart.css";
+import { Link, useNavigate } from "react-router-dom";
+import { useState } from "react";
+import useToolTipPosition from "@/src/hooks/useToolTipPosition";
+import Qtip from "../qtip/Qtip";
+
+function Cart({ label, data, path }) {
+ const { language } = useLanguage();
+ const navigate = useNavigate();
+ const [hoveredItem, setHoveredItem] = useState(null);
+ const [hoverTimeout, setHoverTimeout] = useState(null);
+ const { tooltipPosition, tooltipHorizontalPosition, cardRefs } =
+ useToolTipPosition(hoveredItem, data);
+
+ const handleImageEnter = (item, index) => {
+ if (hoverTimeout) clearTimeout(hoverTimeout);
+ setHoveredItem(item.id + index);
+ };
+
+ const handleImageLeave = () => {
+ setHoverTimeout(
+ setTimeout(() => {
+ setHoveredItem(null);
+ }, 300)
+ );
+ };
+
+ return (
+
+
+ {label}
+
+
+ {data &&
+ data.slice(0, 5).map((item, index) => (
+
(cardRefs.current[index] = el)}
+ >
+
navigate(`/watch/${item.id}`)}
+ onMouseEnter={() => handleImageEnter(item, index)}
+ onMouseLeave={handleImageLeave}
+ />
+
+ {hoveredItem === item.id + index && window.innerWidth > 1024 && (
+
{
+ if (hoverTimeout) clearTimeout(hoverTimeout);
+ }}
+ onMouseLeave={handleImageLeave}
+ >
+
+
+ )}
+
+
+
+ {language === "EN" ? item.title : item.japanese_title}
+
+
+ {item.tvInfo?.sub && (
+
+ )}
+
+ {item.tvInfo?.dub && (
+
+ )}
+
+
+
+ {item.tvInfo.showType}
+
+
+
+
+
+ ))}
+
+
+ View more
+
+
+
+
+
+ );
+}
+
+export default Cart;
diff --git a/src/components/categorycard/CategoryCard.css b/src/components/categorycard/CategoryCard.css
new file mode 100644
index 0000000..04411bc
--- /dev/null
+++ b/src/components/categorycard/CategoryCard.css
@@ -0,0 +1,27 @@
+.overlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(
+ to top,
+ rgba(32, 31, 49, 1) 0%,
+ rgba(32, 31, 49, 0) 20%,
+ rgba(32, 31, 49, 0) 100%
+ );
+
+ z-index: 50;
+}
+.dot {
+ width: 5px;
+ height: 5px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.3);
+ display: inline-block;
+}
diff --git a/src/components/categorycard/CategoryCard.jsx b/src/components/categorycard/CategoryCard.jsx
new file mode 100644
index 0000000..00bd560
--- /dev/null
+++ b/src/components/categorycard/CategoryCard.jsx
@@ -0,0 +1,340 @@
+import React, { useCallback, useEffect, useState } from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faClosedCaptioning,
+ faMicrophone,
+ faPlay,
+} from "@fortawesome/free-solid-svg-icons";
+import { FaChevronRight } from "react-icons/fa";
+import "./CategoryCard.css";
+import { useLanguage } from "@/src/context/LanguageContext";
+import { Link, useNavigate } from "react-router-dom";
+import Qtip from "../qtip/Qtip";
+import useToolTipPosition from "@/src/hooks/useToolTipPosition";
+
+const CategoryCard = React.memo(
+ ({
+ label,
+ data,
+ showViewMore = true,
+ className,
+ categoryPage = false,
+ cardStyle,
+ path,
+ limit,
+ }) => {
+ const { language } = useLanguage();
+ const navigate = useNavigate();
+ const [showPlay, setShowPlay] = useState(false);
+ if (limit) {
+ data = data.slice(0, limit);
+ }
+
+ const [itemsToRender, setItemsToRender] = useState({
+ firstRow: [],
+ remainingItems: [],
+ });
+
+ const getItemsToRender = useCallback(() => {
+ if (categoryPage) {
+ const firstRow =
+ window.innerWidth > 758 && data.length > 4 ? data.slice(0, 4) : [];
+ const remainingItems =
+ window.innerWidth > 758 && data.length > 4
+ ? data.slice(4)
+ : data.slice(0);
+ return { firstRow, remainingItems };
+ }
+ return { firstRow: [], remainingItems: data.slice(0) };
+ }, [categoryPage, data]);
+
+ useEffect(() => {
+ const handleResize = () => {
+ setItemsToRender(getItemsToRender());
+ };
+ const newItems = getItemsToRender();
+ setItemsToRender((prev) => {
+ if (
+ JSON.stringify(prev.firstRow) !== JSON.stringify(newItems.firstRow) ||
+ JSON.stringify(prev.remainingItems) !==
+ JSON.stringify(newItems.remainingItems)
+ ) {
+ return newItems;
+ }
+ return prev;
+ });
+
+ window.addEventListener("resize", handleResize);
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, [getItemsToRender]);
+ const [hoveredItem, setHoveredItem] = useState(null);
+ const [hoverTimeout, setHoverTimeout] = useState(null);
+ const { tooltipPosition, tooltipHorizontalPosition, cardRefs } =
+ useToolTipPosition(hoveredItem, data);
+ const handleMouseEnter = (item, index) => {
+ const timeout = setTimeout(() => {
+ setHoveredItem(item.id + index);
+ setShowPlay(true);
+ }, 400);
+ setHoverTimeout(timeout);
+ };
+ const handleMouseLeave = () => {
+ clearTimeout(hoverTimeout);
+ setHoveredItem(null);
+ setShowPlay(false);
+ };
+ return (
+
+
+
+ {label}
+
+ {showViewMore && (
+
+
+ View more
+
+
+
+ )}
+
+ <>
+ {categoryPage && (
+
0
+ ? "mt-8 max-[758px]:hidden"
+ : ""
+ }`}
+ >
+ {itemsToRender.firstRow.map((item, index) => (
+
(cardRefs.current[index] = el)}
+ >
+
+ navigate(
+ `${
+ path === "top-upcoming"
+ ? `/${item.id}`
+ : `/watch/${item.id}`
+ }`
+ )
+ }
+ onMouseEnter={() => handleMouseEnter(item, index)}
+ onMouseLeave={handleMouseLeave}
+ >
+ {hoveredItem === item.id + index && showPlay && (
+
+ )}
+
+
+
+
+
+ {(item.tvInfo?.rating === "18+" ||
+ item?.adultContent === true) && (
+
+ 18+
+
+ )}
+
+ {item.tvInfo?.sub && (
+
+
+
+ {item.tvInfo.sub}
+
+
+ )}
+ {item.tvInfo?.dub && (
+
+
+
+ {item.tvInfo.dub}
+
+
+ )}
+ {item.tvInfo?.eps && (
+
+
+ {item.tvInfo.eps}
+
+
+ )}
+
+ {hoveredItem === item.id + index &&
+ window.innerWidth > 1024 && (
+
+
+
+ )}
+
+
+ {language === "EN" ? item.title : item.japanese_title}
+
+ {item.description && (
+
+ {item.description}
+
+ )}
+
+
+ {item.tvInfo.showType.split(" ").shift()}
+
+
+
+ {item.tvInfo?.duration === "m" ||
+ item.tvInfo?.duration === "?" ||
+ item.duration === "m" ||
+ item.duration === "?"
+ ? "N/A"
+ : item.tvInfo?.duration || item.duration || "N/A"}
+
+
+
+ ))}
+
+ )}
+
+ {itemsToRender.remainingItems.map((item, index) => (
+
(cardRefs.current[index] = el)}
+ >
+
+ navigate(
+ `${
+ path === "top-upcoming"
+ ? `/${item.id}`
+ : `/watch/${item.id}`
+ }`
+ )
+ }
+ onMouseEnter={() => handleMouseEnter(item, index)}
+ onMouseLeave={handleMouseLeave}
+ >
+ {hoveredItem === item.id + index && showPlay && (
+
+ )}
+
+
+
+
+ {(item.tvInfo?.rating === "18+" ||
+ item?.adultContent === true) && (
+
+ 18+
+
+ )}
+
+ {item.tvInfo?.sub && (
+
+
+
+ {item.tvInfo.sub}
+
+
+ )}
+ {item.tvInfo?.dub && (
+
+
+
+ {item.tvInfo.dub}
+
+
+ )}
+
+ {hoveredItem === item.id + index &&
+ window.innerWidth > 1024 && (
+
+
+
+ )}
+
+
+ {language === "EN" ? item.title : item.japanese_title}
+
+
+
+ {item.tvInfo.showType.split(" ").shift()}
+
+
+
+ {item.tvInfo?.duration === "m" ||
+ item.tvInfo?.duration === "?" ||
+ item.duration === "m" ||
+ item.duration === "?"
+ ? "N/A"
+ : item.tvInfo?.duration || item.duration || "N/A"}
+
+
+
+ ))}
+
+ >
+
+ );
+ }
+);
+
+CategoryCard.displayName = "CategoryCard";
+
+export default CategoryCard;
diff --git a/src/components/continue/ContinueWatching.jsx b/src/components/continue/ContinueWatching.jsx
new file mode 100644
index 0000000..da8d369
--- /dev/null
+++ b/src/components/continue/ContinueWatching.jsx
@@ -0,0 +1,132 @@
+import { Navigation } from "swiper/modules";
+import { Swiper, SwiperSlide } from "swiper/react";
+import { Link } from "react-router-dom";
+import { useEffect, useState, useRef, useMemo } from "react";
+import "swiper/css";
+import "swiper/css/pagination";
+import "swiper/css/navigation";
+import { FaHistory, FaChevronLeft, FaChevronRight } from "react-icons/fa";
+import { useLanguage } from "@/src/context/LanguageContext";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faPlay } from "@fortawesome/free-solid-svg-icons";
+
+const ContinueWatching = () => {
+ const [watchList, setWatchList] = useState([]);
+ const { language } = useLanguage();
+ const swiperRef = useRef(null);
+
+ useEffect(() => {
+ const data = JSON.parse(localStorage.getItem("continueWatching") || "[]");
+ setWatchList(data);
+ }, []);
+
+ // Memoize watchList to avoid unnecessary re-renders
+ const memoizedWatchList = useMemo(() => watchList, [watchList]);
+
+ const removeFromWatchList = (episodeId) => {
+ setWatchList((prevList) => {
+ const updatedList = prevList.filter(
+ (item) => item.episodeId !== episodeId
+ );
+ localStorage.setItem("continueWatching", JSON.stringify(updatedList));
+ return updatedList;
+ });
+ };
+
+ if (memoizedWatchList.length === 0) return null;
+
+ return (
+
+
+
+
+
+ Continue Watching
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {memoizedWatchList.map((item, index) => (
+
+
+
removeFromWatchList(item.episodeId)}
+ >
+ ✖
+
+
+
+
+
+
+
+
+ {item?.adultContent === true && (
+
+ 18+
+
+ )}
+
+
+ {language === "EN"
+ ? item?.title
+ : item?.japanese_title}
+
+
+ Episode {item.episodeNum}
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default ContinueWatching;
diff --git a/src/components/episodelist/Episodelist.css b/src/components/episodelist/Episodelist.css
new file mode 100644
index 0000000..8fd6b62
--- /dev/null
+++ b/src/components/episodelist/Episodelist.css
@@ -0,0 +1,15 @@
+@keyframes glow {
+ 0% {
+ box-shadow: 0 0 7px #ffbade;
+ }
+ 50% {
+ box-shadow: 0 0 20px #ffbade;
+ }
+ 100% {
+ box-shadow: 0 0 7px #ffbade;
+ }
+}
+
+.glow-animation {
+ animation: glow 1.5s infinite;
+}
diff --git a/src/components/episodelist/Episodelist.jsx b/src/components/episodelist/Episodelist.jsx
new file mode 100644
index 0000000..cc86327
--- /dev/null
+++ b/src/components/episodelist/Episodelist.jsx
@@ -0,0 +1,303 @@
+import { useLanguage } from "@/src/context/LanguageContext";
+import {
+ faAngleDown,
+ faCirclePlay,
+ faList,
+ faCheck,
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
+import { useState, useEffect, useRef } from "react";
+import "./Episodelist.css";
+
+function Episodelist({
+ episodes,
+ onEpisodeClick,
+ currentEpisode,
+ totalEpisodes,
+}) {
+ const [activeEpisodeId, setActiveEpisodeId] = useState(currentEpisode);
+ const { language } = useLanguage();
+ const listContainerRef = useRef(null);
+ const activeEpisodeRef = useRef(null);
+ const [showDropDown, setShowDropDown] = useState(false);
+ const [selectedRange, setSelectedRange] = useState([1, 100]);
+ const [activeRange, setActiveRange] = useState("1-100");
+ const [episodeNum, setEpisodeNum] = useState(currentEpisode);
+ const dropDownRef = useRef(null);
+ const [searchedEpisode, setSearchedEpisode] = useState(null);
+
+ const scrollToActiveEpisode = () => {
+ if (activeEpisodeRef.current && listContainerRef.current) {
+ const container = listContainerRef.current;
+ const activeEpisode = activeEpisodeRef.current;
+ const containerTop = container.getBoundingClientRect().top;
+ const containerHeight = container.clientHeight;
+ const activeEpisodeTop = activeEpisode.getBoundingClientRect().top;
+ const activeEpisodeHeight = activeEpisode.clientHeight;
+ const offset = activeEpisodeTop - containerTop;
+ container.scrollTop =
+ container.scrollTop +
+ offset -
+ containerHeight / 2 +
+ activeEpisodeHeight / 2;
+ }
+ };
+ useEffect(() => {
+ setActiveEpisodeId(episodeNum);
+ }, [episodeNum]);
+ useEffect(() => {
+ scrollToActiveEpisode();
+ }, [activeEpisodeId]);
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (dropDownRef.current && !dropDownRef.current.contains(event.target)) {
+ setShowDropDown(false);
+ }
+ };
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, []);
+
+ function handleChange(e) {
+ const value = e.target.value;
+ if (value.trim() === "") {
+ const newRange = findRangeForEpisode(1);
+ setSelectedRange(newRange);
+ setActiveRange(`${newRange[0]}-${newRange[1]}`);
+ setSearchedEpisode(null);
+ } else if (!value || isNaN(value)) {
+ setSearchedEpisode(null);
+ } else if (
+ !isNaN(value) &&
+ parseInt(value, 10) > totalEpisodes &&
+ episodeNum !== null
+ ) {
+ const newRange = findRangeForEpisode(episodeNum);
+ setSelectedRange(newRange);
+ setActiveRange(`${newRange[0]}-${newRange[1]}`);
+ setSearchedEpisode(null);
+ } else if (!isNaN(value) && value.trim() !== "") {
+ const num = parseInt(value, 10);
+ const foundEpisode = episodes.find((item) => item?.episode_no === num);
+ if (foundEpisode) {
+ const newRange = findRangeForEpisode(num);
+ setSelectedRange(newRange);
+ setActiveRange(`${newRange[0]}-${newRange[1]}`);
+ setSearchedEpisode(foundEpisode?.id);
+ }
+ } else {
+ setSearchedEpisode(null);
+ }
+ }
+
+ function findRangeForEpisode(episodeNumber) {
+ const step = 100;
+ const start = Math.floor((episodeNumber - 1) / step) * step + 1;
+ const end = Math.min(start + step - 1, totalEpisodes);
+ return [start, end];
+ }
+
+ function generateRangeOptions(totalEpisodes) {
+ const ranges = [];
+ const step = 100;
+
+ for (let i = 0; i < totalEpisodes; i += step) {
+ const start = i + 1;
+ const end = Math.min(i + step, totalEpisodes);
+ ranges.push(`${start}-${end}`);
+ }
+ return ranges;
+ }
+ useEffect(() => {
+ if (currentEpisode && episodeNum) {
+ if (episodeNum < selectedRange[0] || episodeNum > selectedRange[1]) {
+ const newRange = findRangeForEpisode(episodeNum);
+ setSelectedRange(newRange);
+ setActiveRange(`${newRange[0]}-${newRange[1]}`);
+ }
+ }
+ }, [currentEpisode, totalEpisodes, episodeNum]);
+
+ const handleRangeSelect = (range) => {
+ const [start, end] = range.split("-").map(Number);
+ setSelectedRange([start, end]);
+ };
+
+ useEffect(() => {
+ const activeEpisode = episodes.find(
+ (item) => item?.id.match(/ep=(\d+)/)?.[1] === activeEpisodeId
+ );
+ if (activeEpisode) {
+ setEpisodeNum(activeEpisode?.episode_no);
+ }
+ }, [activeEpisodeId, episodes]);
+
+ return (
+
+
+
List of episodes:
+ {totalEpisodes > 100 && (
+
+
+
setShowDropDown((prev) => !prev)}
+ className="text-white w-fit mt-1 text-[13px] relative cursor-pointer bg-[#0D0D15] flex justify-center items-center"
+ ref={dropDownRef}
+ >
+
+
+
+ EPS: {selectedRange[0]}-{selectedRange[1]}
+
+
+
+ {showDropDown && (
+
+ {generateRangeOptions(totalEpisodes).map((item, index) => (
+
{
+ handleRangeSelect(item);
+ setActiveRange(item);
+ }}
+ className={`hover:bg-gray-200 cursor-pointer text-black ${
+ item === activeRange ? "bg-[#EFF0F4]" : ""
+ }`}
+ >
+
+ EPS: {item}
+ {item === activeRange ? (
+
+ ) : null}
+
+
+ ))}
+
+ )}
+
+
+
+
+
+
+
+ )}
+
+
+
30
+ ? "p-3 grid grid-cols-5 gap-1 max-[1200px]:grid-cols-12 max-[860px]:grid-cols-10 max-[575px]:grid-cols-8 max-[478px]:grid-cols-6 max-[350px]:grid-cols-5"
+ : ""
+ }`}
+ >
+ {totalEpisodes > 30
+ ? episodes
+ .slice(selectedRange[0] - 1, selectedRange[1])
+ .map((item, index) => {
+ const episodeNumber = item?.id.match(/ep=(\d+)/)?.[1];
+ const isActive =
+ activeEpisodeId === episodeNumber ||
+ currentEpisode === episodeNumber;
+ const isSearched = searchedEpisode === item?.id;
+
+ return (
+
{
+ if (episodeNumber) {
+ onEpisodeClick(episodeNumber);
+ setActiveEpisodeId(episodeNumber);
+ setSearchedEpisode(null);
+ }
+ }}
+ >
+
+ {index + selectedRange[0]}
+
+
+ );
+ })
+ : episodes?.map((item, index) => {
+ const episodeNumber = item?.id.match(/ep=(\d+)/)?.[1];
+ const isActive =
+ activeEpisodeId === episodeNumber ||
+ currentEpisode === episodeNumber;
+ const isSearched = searchedEpisode === item?.id;
+
+ return (
+
{
+ if (episodeNumber) {
+ onEpisodeClick(episodeNumber);
+ setActiveEpisodeId(episodeNumber);
+ setSearchedEpisode(null);
+ }
+ }}
+ >
+
{index + 1}
+
+
+ {language === "EN" ? item?.title : item?.japanese_title}
+
+ {isActive && (
+
+ )}
+
+
+ );
+ })}
+
+
+
+ );
+}
+
+export default Episodelist;
diff --git a/src/components/error/Error.jsx b/src/components/error/Error.jsx
new file mode 100644
index 0000000..6776274
--- /dev/null
+++ b/src/components/error/Error.jsx
@@ -0,0 +1,21 @@
+import { FaChevronLeft } from "react-icons/fa"
+import { useNavigate } from "react-router-dom"
+
+function Error({ error }) {
+ const navigate = useNavigate();
+ return (
+
+
+
+
{error === "404" ? "404 Error" : "Error"}
+
Oops! We couldn't find this page.
+
+
+ navigate('/home')} className="text-[18px]">Back to homepage
+
+
+
+ )
+}
+
+export default Error
\ No newline at end of file
diff --git a/src/components/footer/Footer.jsx b/src/components/footer/Footer.jsx
new file mode 100644
index 0000000..43404af
--- /dev/null
+++ b/src/components/footer/Footer.jsx
@@ -0,0 +1,62 @@
+import logoTitle from "@/src/config/logoTitle.js";
+import website_name from "@/src/config/website.js";
+import { Link } from "react-router-dom";
+
+function Footer() {
+ return (
+
+
+
+
+
+
+
A-Z LIST
+
+ Searching anime order by alphabet name A to Z
+
+
+
+ {[
+ "All",
+ "#",
+ "0-9",
+ ...Array.from({ length: 26 }, (_, i) =>
+ String.fromCharCode(65 + i)
+ ),
+ ].map((item, index) => (
+
+ {item}
+
+ ))}
+
+
+
+ {website_name} does not host any files, it merely pulls streams from
+ 3rd party services. Legal issues should be taken up with the file
+ hosts and providers. {website_name} is not responsible for any media
+ files shown by the video providers.
+
+
+ © {website_name}. All rights reserved.
+
+
+
+
+ );
+}
+
+export default Footer;
diff --git a/src/components/genres/Genre.jsx b/src/components/genres/Genre.jsx
new file mode 100644
index 0000000..c74424d
--- /dev/null
+++ b/src/components/genres/Genre.jsx
@@ -0,0 +1,54 @@
+import React, { useState } from "react";
+import { Link } from "react-router-dom";
+
+function Genre({ data }) {
+ const colors = [
+ "#A4B389",
+ "#FFBADE",
+ "#935C5F",
+ "#AD92BC",
+ "#ABCCD8",
+ "#D8B2AB",
+ "#85E1CD",
+ "#B7C996",
+ ];
+
+ const [showAll, setShowAll] = useState(false);
+ const toggleGenres = () => {
+ setShowAll((prev) => !prev);
+ };
+
+ return (
+
+
Genres
+
+
+ {data &&
+ (showAll ? data : data.slice(0, 24)).map((item, index) => {
+ const textColor = colors[index % colors.length];
+ return (
+
+
+ {item.charAt(0).toUpperCase() + item.slice(1)}
+
+
+ );
+ })}
+
+
+ {showAll ? "Show less" : "Show more"}
+
+
+
+ );
+}
+
+export default React.memo(Genre);
diff --git a/src/components/navbar/Navbar.jsx b/src/components/navbar/Navbar.jsx
new file mode 100644
index 0000000..f6f1215
--- /dev/null
+++ b/src/components/navbar/Navbar.jsx
@@ -0,0 +1,147 @@
+import { useState, useEffect } from "react";
+import logoTitle from "@/src/config/logoTitle";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faBars,
+ faFilm,
+ faRandom,
+ faStar,
+} from "@fortawesome/free-solid-svg-icons";
+import { useLanguage } from "@/src/context/LanguageContext";
+import { Link, useLocation } from "react-router-dom";
+import Sidebar from "../sidebar/Sidebar";
+import { SearchProvider } from "@/src/context/SearchContext";
+import WebSearch from "../searchbar/WebSearch";
+import MobileSearch from "../searchbar/MobileSearch";
+import { FaTelegramPlane } from "react-icons/fa";
+
+function Navbar() {
+ const location = useLocation();
+ const { language, toggleLanguage } = useLanguage();
+ const [isNotHomePage, setIsNotHomePage] = useState(
+ location.pathname !== "/" && location.pathname !== "/home"
+ );
+ const [isScrolled, setIsScrolled] = useState(false);
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setIsScrolled(window.scrollY > 0);
+ };
+ window.addEventListener("scroll", handleScroll);
+ return () => {
+ window.removeEventListener("scroll", handleScroll);
+ };
+ }, []);
+
+ const handleHamburgerClick = () => {
+ setIsSidebarOpen(true);
+ };
+
+ const handleCloseSidebar = () => {
+ setIsSidebarOpen(false);
+ };
+ const handleRandomClick = () => {
+ if (location.pathname === "/random") {
+ window.location.reload();
+ }
+ };
+ useEffect(() => {
+ setIsNotHomePage(
+ location.pathname !== "/" && location.pathname !== "/home"
+ );
+ }, [location.pathname]);
+
+ return (
+
+
+
+
+
+
+ {logoTitle.slice(0, 3)}
+ {logoTitle.slice(3, 4)}
+ {logoTitle.slice(4)}
+
+
+
+
+
+ {[
+ { icon: faRandom, label: "Random", path: "/random" },
+ { icon: faFilm, label: "Movie", path: "/movie" },
+ { icon: faStar, label: "Popular", path: "/most-popular" },
+ ].map((item) => (
+
+
+
{item.label}
+
+ ))}
+
+
+ {["EN", "JP"].map((lang, index) => (
+ toggleLanguage(lang)}
+ className={`px-1 py-[1px] text-xs font-bold ${
+ index === 0 ? "rounded-l-[3px]" : "rounded-r-[3px]"
+ } ${
+ language === lang
+ ? "bg-[#ffbade] text-black"
+ : "bg-gray-600 text-white"
+ }`}
+ >
+ {lang}
+
+ ))}
+
+
+
+
+
+
Join Telegram
+
+
+
+
+
+
+ );
+}
+
+export default Navbar;
diff --git a/src/components/pageslider/PageSlider.jsx b/src/components/pageslider/PageSlider.jsx
new file mode 100644
index 0000000..c42ebe2
--- /dev/null
+++ b/src/components/pageslider/PageSlider.jsx
@@ -0,0 +1,76 @@
+import { faAngleDoubleLeft, faAngleDoubleRight, faChevronLeft, faChevronRight } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+function PageSlider({ page, totalPages, handlePageChange, start = false, style }) {
+ const renderPageNumbers = () => {
+ const pages = [];
+ if (totalPages === 1) return null;
+ if (totalPages <= 3) {
+ for (let i = 1; i <= totalPages; i++) {
+ pages.push(i);
+ }
+ } else {
+ if (page === 1) {
+ pages.push(1, 2, 3);
+ } else if (page === 2) {
+ pages.push(1, 2, 3, 4);
+ } else if (page === totalPages) {
+ pages.push(totalPages - 2, totalPages - 1, totalPages);
+ } else if (page === totalPages - 1) {
+ pages.push(totalPages - 3, totalPages - 2, totalPages - 1, totalPages);
+ } else {
+ pages.push(page - 2, page - 1, page, page + 1, page + 2);
+ }
+ }
+ return pages.map((p) => (
+ handlePageChange(p)}
+ className={`w-[40px] text-[15px] mx-1 flex justify-center items-center p-2 rounded-full font-bold ${page === p ? 'bg-[#ffbade] text-[#2B2A3C] cursor-default' : 'bg-[#2B2A3C] text-[#999] hover:text-[#ffbade]'} ${start ? "bg-[#353537]" : "bg-[#2B2A3C]"} `}
+ >
+ {p}
+
+ ));
+ };
+ return (
+
+
+ {page > 1 && totalPages > 2 && (
+ handlePageChange(1)}
+ className={`w-[40px] mx-1 p-2 ${start ? "bg-[#353537]" : "bg-[#2B2A3C]"} rounded-full text-[#999] text-[8px] hover:text-[#ffbade]`}
+ >
+
+
+ )}
+ {page > 1 && (
+ { if (page > 0) handlePageChange(page - 1) }}
+ className={`w-[40px] mx-1 p-2 ${start ? "bg-[#353537]" : "bg-[#2B2A3C]"} rounded-full text-[#999] text-[8px] hover:text-[#ffbade]`}
+ >
+
+
+ )}
+ {renderPageNumbers()}
+ {page < totalPages && (
+ { if (page < totalPages) handlePageChange(page + 1) }}
+ className={`w-[40px] mx-1 p-2 ${start ? "bg-[#353537]" : "bg-[#2B2A3C]"} rounded-full text-[#999] text-[8px] hover:text-[#ffbade]`}
+ >
+
+
+ )}
+ {page < totalPages && totalPages > 2 && (
+ handlePageChange(totalPages)}
+ className={`w-[40px] mx-1 p-2 ${start ? "bg-[#353537]" : "bg-[#2B2A3C]"} rounded-full text-[#999] text-[8px] hover:text-[#ffbade]`}
+ >
+
+
+ )}
+
+
+ )
+}
+
+export default PageSlider
\ No newline at end of file
diff --git a/src/components/player/IframePlayer.jsx b/src/components/player/IframePlayer.jsx
new file mode 100644
index 0000000..efbaab1
--- /dev/null
+++ b/src/components/player/IframePlayer.jsx
@@ -0,0 +1,148 @@
+/* eslint-disable react/prop-types */
+import { useEffect, useState } from "react";
+import BouncingLoader from "../ui/bouncingloader/Bouncingloader";
+import axios from "axios";
+
+export default function IframePlayer({
+ animeId,
+ episodeId,
+ serverName,
+ servertype,
+ animeInfo,
+ episodeNum,
+ episodes,
+ playNext,
+ autoNext,
+}) {
+ const api_url=import.meta.env.VITE_API_URL;
+ const baseURL =
+ serverName.toLowerCase() === "hd-1"
+ ? import.meta.env.VITE_BASE_IFRAME_URL
+ : serverName.toLowerCase() === "hd-4"
+ ? import.meta.env.VITE_BASE_IFRAME_URL_2
+ : undefined;
+
+ const [loading, setLoading] = useState(true);
+ const [iframeLoaded, setIframeLoaded] = useState(false);
+ const [iframeSrc, setIframeSrc] = useState("");
+ const [currentEpisodeIndex, setCurrentEpisodeIndex] = useState(
+ episodes?.findIndex(
+ (episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId
+ )
+ );
+
+ useEffect(() => {
+ const loadIframeUrl = async () => {
+ setLoading(true);
+ setIframeLoaded(false);
+ setIframeSrc("");
+
+ const lowerName = serverName.toLowerCase();
+
+ if (lowerName === "hd-1" || lowerName === "hd-4") {
+ setIframeSrc(`${baseURL}/${episodeId}/${servertype}`);
+ } else if (lowerName === "hd-2" || lowerName === "hd-3") {
+ try {
+ const res = await axios.get(
+ `${api_url}/stream?id=${animeId}?ep=${episodeId}&server=${serverName}&type=${servertype}`
+ );
+
+ const link = res?.data?.results?.streamingLink?.link;
+ if (link) {
+ setIframeSrc(`${link}&_debug=true`);
+ } else {
+ console.error("Streaming link not found in response");
+ }
+ } catch (err) {
+ console.error("Failed to fetch streaming link:", err);
+ }
+ }
+ };
+
+ loadIframeUrl();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [episodeId, servertype, serverName, animeInfo]);
+
+ useEffect(() => {
+ if (episodes?.length > 0) {
+ const newIndex = episodes.findIndex(
+ (episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId
+ );
+ setCurrentEpisodeIndex(newIndex);
+ }
+ }, [episodeId, episodes]);
+
+ useEffect(() => {
+ const handleMessage = (event) => {
+ const { currentTime, duration } = event.data;
+ if (typeof currentTime === "number" && typeof duration === "number") {
+ if (
+ currentTime >= duration &&
+ currentEpisodeIndex < episodes?.length - 1 &&
+ autoNext
+ ) {
+ playNext(episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]);
+ }
+ }
+ };
+ window.addEventListener("message", handleMessage);
+ return () => {
+ window.removeEventListener("message", handleMessage);
+ };
+ }, [autoNext, currentEpisodeIndex, episodes, playNext]);
+
+ useEffect(() => {
+ setLoading(true);
+ setIframeLoaded(false);
+ return () => {
+ const continueWatching = JSON.parse(localStorage.getItem("continueWatching")) || [];
+ const newEntry = {
+ id: animeInfo?.id,
+ data_id: animeInfo?.data_id,
+ episodeId,
+ episodeNum,
+ adultContent: animeInfo?.adultContent,
+ poster: animeInfo?.poster,
+ title: animeInfo?.title,
+ japanese_title: animeInfo?.japanese_title,
+ };
+ if (!newEntry.data_id) return;
+ const existingIndex = continueWatching.findIndex(
+ (item) => item.data_id === newEntry.data_id
+ );
+ if (existingIndex !== -1) {
+ continueWatching[existingIndex] = newEntry;
+ } else {
+ continueWatching.push(newEntry);
+ }
+ localStorage.setItem("continueWatching", JSON.stringify(continueWatching));
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [episodeId, servertype]);
+
+ return (
+
+ {/* Loader Overlay */}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/player/Player.css b/src/components/player/Player.css
new file mode 100644
index 0000000..87bd52b
--- /dev/null
+++ b/src/components/player/Player.css
@@ -0,0 +1,59 @@
+.art-subtitle {
+ padding-inline: 0px !important;
+ gap: 2px !important;
+}
+.art-volume-panel {
+ padding-bottom: 20px !important;
+}
+.art-settings {
+ margin-bottom: 20px !important;
+}
+.art-subtitle {
+ margin-bottom: 1rem !important;
+}
+.art-subtitle-line {
+ min-width: fit-content;
+ background-color: rgba(0, 0, 0, 0.479) !important;
+ padding-inline: 3px !important;
+}
+.art-subtitle-line,
+.art-subtitle-line * {
+ font-size: inherit !important;
+ color: inherit !important;
+ line-height: inherit !important;
+ font-weight: inherit !important;
+ white-space: inherit !important;
+}
+@media screen and (max-width: 370px) {
+ .art-progress {
+ padding-bottom: 5px !important;
+ }
+ .art-controls-left .art-control {
+ justify-content: flex-start !important;
+ }
+ .art-controls-right .art-control {
+ justify-content: flex-end !important;
+ }
+ .art-controls-right .art-control svg {
+ width: 22px;
+ height: 22px;
+ }
+ .art-controls-left .art-control svg {
+ width: 22px;
+ height: 22px;
+ }
+ .art-state .art-icon svg {
+ width: 50px;
+ height: 50px;
+ }
+}
+@media screen and (max-width: 350px) {
+ .art-controls-right .art-control svg {
+ width: 20px;
+ height: 20px;
+ }
+ .art-controls-left .art-control svg {
+ width: 20px;
+ height: 20px;
+ }
+}
diff --git a/src/components/player/Player.jsx b/src/components/player/Player.jsx
new file mode 100644
index 0000000..bcf29bb
--- /dev/null
+++ b/src/components/player/Player.jsx
@@ -0,0 +1,494 @@
+/* eslint-disable react/prop-types */
+import Hls from "hls.js";
+import { useEffect, useRef, useState } from "react";
+import Artplayer from "artplayer";
+import artplayerPluginChapter from "./artPlayerPluinChaper";
+import autoSkip from "./autoSkip";
+import artplayerPluginVttThumbnail from "./artPlayerPluginVttThumbnail";
+import {
+ backward10Icon,
+ backwardIcon,
+ captionIcon,
+ forward10Icon,
+ forwardIcon,
+ fullScreenOffIcon,
+ fullScreenOnIcon,
+ loadingIcon,
+ logo,
+ muteIcon,
+ pauseIcon,
+ pipIcon,
+ playIcon,
+ playIconLg,
+ settingsIcon,
+ volumeIcon,
+} from "./PlayerIcons";
+import "./Player.css";
+import website_name from "@/src/config/website";
+import getChapterStyles from "./getChapterStyle";
+import artplayerPluginHlsControl from "artplayer-plugin-hls-control";
+import artplayerPluginUploadSubtitle from "./artplayerPluginUploadSubtitle";
+
+Artplayer.LOG_VERSION = false;
+Artplayer.CONTEXTMENU = false;
+
+const KEY_CODES = {
+ M: "m",
+ I: "i",
+ F: "f",
+ V: "v",
+ SPACE: " ",
+ ARROW_UP: "arrowup",
+ ARROW_DOWN: "arrowdown",
+ ARROW_RIGHT: "arrowright",
+ ARROW_LEFT: "arrowleft",
+};
+
+export default function Player({
+ streamUrl,
+ subtitles,
+ thumbnail,
+ intro,
+ outro,
+ serverName,
+ autoSkipIntro,
+ autoPlay,
+ autoNext,
+ episodeId,
+ episodes,
+ playNext,
+ animeInfo,
+ episodeNum,
+ streamInfo,
+}) {
+ const artRef = useRef(null);
+ const leftAtRef = useRef(0);
+ const proxy = import.meta.env.VITE_PROXY_URL;
+ const m3u8proxy = import.meta.env.VITE_M3U8_PROXY_URL?.split(",") || [];
+ const [currentEpisodeIndex, setCurrentEpisodeIndex] = useState(
+ episodes?.findIndex(
+ (episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId
+ )
+ );
+
+ useEffect(() => {
+ if (episodes?.length > 0) {
+ const newIndex = episodes.findIndex(
+ (episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId
+ );
+ setCurrentEpisodeIndex(newIndex);
+ }
+ }, [episodeId, episodes]);
+ useEffect(() => {
+ const applyChapterStyles = () => {
+ const existingStyles = document.querySelectorAll(
+ "style[data-chapter-styles]"
+ );
+ existingStyles.forEach((style) => style.remove());
+ const styleElement = document.createElement("style");
+ styleElement.setAttribute("data-chapter-styles", "true");
+ const styles = getChapterStyles(intro, outro);
+ styleElement.textContent = styles;
+ document.head.appendChild(styleElement);
+ return () => {
+ styleElement.remove();
+ };
+ };
+
+ if (streamUrl || intro || outro) {
+ const cleanup = applyChapterStyles();
+ return cleanup;
+ }
+ }, [streamUrl, intro, outro]);
+
+ const playM3u8 = (video, url, art) => {
+ if (Hls.isSupported()) {
+ if (art.hls) art.hls.destroy();
+ const hls = new Hls();
+ hls.loadSource(url);
+ hls.attachMedia(video);
+ art.hls = hls;
+
+ art.on("destroy", () => hls.destroy());
+
+ // hls.on(Hls.Events.ERROR, (event, data) => {
+ // console.error("HLS.js error:", data);
+ // });
+ video.addEventListener("timeupdate", () => {
+ const currentTime = Math.round(video.currentTime);
+ const duration = Math.round(video.duration);
+ if (duration > 0 && currentTime >= duration) {
+ art.pause();
+ if (currentEpisodeIndex < episodes?.length - 1 && autoNext) {
+ playNext(
+ episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]
+ );
+ }
+ }
+ });
+ } else if (video.canPlayType("application/vnd.apple.mpegurl")) {
+ video.src = url;
+ video.addEventListener("timeupdate", () => {
+ const currentTime = Math.round(video.currentTime);
+ const duration = Math.round(video.duration);
+ if (duration > 0 && currentTime >= duration) {
+ art.pause();
+ if (currentEpisodeIndex < episodes?.length - 1 && autoNext) {
+ playNext(
+ episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]
+ );
+ }
+ }
+ });
+ } else {
+ console.log("Unsupported playback format: m3u8");
+ }
+ };
+
+ const createChapters = () => {
+ const chapters = [];
+ if (intro?.start !== 0 || intro?.end !== 0) {
+ chapters.push({ start: intro.start, end: intro.end, title: "intro" });
+ }
+ if (outro?.start !== 0 || outro?.end !== 0) {
+ chapters.push({ start: outro.start, end: outro.end, title: "outro" });
+ }
+ return chapters;
+ };
+
+ const handleKeydown = (event, art) => {
+ const tagName = event.target.tagName.toLowerCase();
+
+ if (tagName === "input" || tagName === "textarea") return;
+
+ switch (event.key.toLowerCase()) {
+ case KEY_CODES.M:
+ art.muted = !art.muted;
+ break;
+ case KEY_CODES.I:
+ art.pip = !art.pip;
+ break;
+ case KEY_CODES.F:
+ event.preventDefault();
+ event.stopPropagation();
+ art.fullscreen = !art.fullscreen;
+ break;
+ case KEY_CODES.V:
+ event.preventDefault();
+ event.stopPropagation();
+ art.subtitle.show = !art.subtitle.show;
+ break;
+ case KEY_CODES.SPACE:
+ event.preventDefault();
+ event.stopPropagation();
+ art.playing ? art.pause() : art.play();
+ break;
+ case KEY_CODES.ARROW_UP:
+ event.preventDefault();
+ event.stopPropagation();
+ art.volume = Math.min(art.volume + 0.1, 1);
+ break;
+ case KEY_CODES.ARROW_DOWN:
+ event.preventDefault();
+ event.stopPropagation();
+ art.volume = Math.max(art.volume - 0.1, 0);
+ break;
+ case KEY_CODES.ARROW_RIGHT:
+ event.preventDefault();
+ event.stopPropagation();
+ art.currentTime = Math.min(art.currentTime + 10, art.duration);
+ break;
+ case KEY_CODES.ARROW_LEFT:
+ event.preventDefault();
+ event.stopPropagation();
+ art.currentTime = Math.max(art.currentTime - 10, 0);
+ break;
+ default:
+ break;
+ }
+ };
+
+ useEffect(() => {
+ if (!streamUrl || !artRef.current) return;
+ const iframeUrl = streamInfo?.streamingLink?.iframe;
+ const headers = {};
+ headers.referer=new URL(iframeUrl).origin+"/";
+ const art = new Artplayer({
+ url:
+ m3u8proxy[Math.floor(Math.random() * m3u8proxy?.length)] +
+ encodeURIComponent(streamUrl) +
+ "&headers=" +
+ encodeURIComponent(JSON.stringify(headers)),
+ container: artRef.current,
+ type: "m3u8",
+ autoplay: autoPlay,
+ volume: 1,
+ setting: true,
+ playbackRate: true,
+ pip: true,
+ hotkey: false,
+ fullscreen: true,
+ mutex: true,
+ playsInline: true,
+ lock: true,
+ airplay: true,
+ autoOrientation: true,
+ fastForward: true,
+ aspectRatio: true,
+ plugins: [
+ artplayerPluginHlsControl({
+ quality: {
+ setting: true,
+ getName: (level) => level.height + "P",
+ title: "Quality",
+ auto: "Auto",
+ },
+ }),
+ artplayerPluginUploadSubtitle(),
+ artplayerPluginChapter({ chapters: createChapters() }),
+ ],
+ subtitle: {
+ style: {
+ color: "#fff",
+ "font-weight": "400",
+ left: "50%",
+ transform: "translateX(-50%)",
+ "margin-bottom": "2rem",
+ },
+ escape: false,
+ },
+ layers: [
+ {
+ name: website_name,
+ html: logo,
+ tooltip: website_name,
+ style: {
+ opacity: 1,
+ position: "absolute",
+ top: "5px",
+ right: "5px",
+ transition: "opacity 0.5s ease-out",
+ },
+ },
+ {
+ html: "",
+ style: {
+ position: "absolute",
+ left: "50%",
+ top: 0,
+ width: "20%",
+ height: "100%",
+ transform: "translateX(-50%)",
+ },
+ disable: !Artplayer.utils.isMobile,
+ click: () => art.toggle(),
+ },
+ {
+ name: "rewind",
+ html: "",
+ style: { position: "absolute", left: 0, top: 0, width: "40%", height: "100%" },
+ disable: !Artplayer.utils.isMobile,
+ click: () => {
+ art.controls.show = !art.controls.show;
+ },
+ },
+ {
+ name: "forward",
+ html: "",
+ style: { position: "absolute", right: 0, top: 0, width: "40%", height: "100%" },
+ disable: !Artplayer.utils.isMobile,
+ click: () => {
+ art.controls.show = !art.controls.show;
+ },
+ },
+ {
+ name: "backwardIcon",
+ html: backwardIcon,
+ style: {
+ position: "absolute",
+ left: "25%",
+ top: "50%",
+ transform: "translate(50%,-50%)",
+ opacity: 0,
+ transition: "opacity 0.5s ease-in-out",
+ },
+ disable: !Artplayer.utils.isMobile,
+ },
+ {
+ name: "forwardIcon",
+ html: forwardIcon,
+ style: {
+ position: "absolute",
+ right: "25%",
+ top: "50%",
+ transform: "translate(50%, -50%)",
+ opacity: 0,
+ transition: "opacity 0.5s ease-in-out",
+ },
+ disable: !Artplayer.utils.isMobile,
+ },
+ ],
+ controls: [
+ {
+ html: backward10Icon,
+ position: "right",
+ tooltip: "Backward 10s",
+ click: () => {
+ art.currentTime = Math.max(art.currentTime - 10, 0);
+ },
+ },
+ {
+ html: forward10Icon,
+ position: "right",
+ tooltip: "Forward 10s",
+ click: () => {
+ art.currentTime = Math.min(art.currentTime + 10, art.duration);
+ },
+ },
+ ],
+ icons: {
+ play: playIcon,
+ pause: pauseIcon,
+ setting: settingsIcon,
+ volume: volumeIcon,
+ pip: pipIcon,
+ volumeClose: muteIcon,
+ state: playIconLg,
+ loading: loadingIcon,
+ fullscreenOn: fullScreenOnIcon,
+ fullscreenOff: fullScreenOffIcon,
+ },
+ customType: { m3u8: playM3u8 },
+ });
+ art.on("resize", () => {
+ art.subtitle.style({
+ fontSize:
+ (art.width > 500 ? art.width * 0.02 : art.width * 0.03) + "px",
+ });
+ });
+ art.on("ready", () => {
+ const continueWatchingList = JSON.parse(localStorage.getItem("continueWatching")) || [];
+ const currentEntry = continueWatchingList.find((item) => item.episodeId === episodeId);
+ if (currentEntry?.leftAt) art.currentTime = currentEntry.leftAt;
+
+ art.on("video:timeupdate", () => {
+ leftAtRef.current = Math.floor(art.currentTime);
+ });
+
+ setTimeout(() => {
+ art.layers[website_name].style.opacity = 0;
+ }, 2000);
+
+ const defaultSubtitle = subtitles?.find((sub) => sub.label.toLowerCase() === "english");
+ if (defaultSubtitle) {
+ art.subtitle.switch(defaultSubtitle.file, {
+ name: defaultSubtitle.label,
+ default: true,
+ });
+ }
+
+ const skipRanges = [
+ ...(intro.start != null && intro.end != null ? [[intro.start + 1, intro.end - 1]] : []),
+ ...(outro.start != null && outro.end != null ? [[outro.start + 1, outro.end]] : []),
+ ];
+ autoSkipIntro && art.plugins.add(autoSkip(skipRanges));
+
+ document.addEventListener("keydown", (event) => handleKeydown(event, art));
+
+ art.subtitle.style({
+ fontSize: (art.width > 500 ? art.width * 0.02 : art.width * 0.03) + "px",
+ });
+
+ if (thumbnail) {
+ art.plugins.add(
+ artplayerPluginVttThumbnail({
+ vtt: `${proxy}${thumbnail}`,
+ })
+ );
+ }
+ const $rewind = art.layers["rewind"];
+ const $forward = art.layers["forward"];
+ Artplayer.utils.isMobile &&
+ art.proxy($rewind, "dblclick", () => {
+ art.currentTime = Math.max(0, art.currentTime - 10);
+ art.layers["backwardIcon"].style.opacity = 1;
+ setTimeout(() => {
+ art.layers["backwardIcon"].style.opacity = 0;
+ }, 300);
+ });
+ Artplayer.utils.isMobile &&
+ art.proxy($forward, "dblclick", () => {
+ art.currentTime = Math.max(0, art.currentTime + 10);
+ art.layers["forwardIcon"].style.opacity = 1;
+ setTimeout(() => {
+ art.layers["forwardIcon"].style.opacity = 0;
+ }, 300);
+ });
+ if (subtitles?.length > 0) {
+ const defaultEnglishSub =
+ subtitles.find((sub) => sub.label.toLowerCase() === "english" && sub.default) ||
+ subtitles.find((sub) => sub.label.toLowerCase() === "english");
+
+ art.setting.add({
+ name: "captions",
+ icon: captionIcon,
+ html: "Subtitle",
+ tooltip: defaultEnglishSub?.label || "default",
+ position: "right",
+ selector: [
+ {
+ html: "Display",
+ switch: true,
+ onSwitch: (item) => {
+ item.tooltip = item.switch ? "Hide" : "Show";
+ art.subtitle.show = !item.switch;
+ return !item.switch;
+ },
+ },
+ ...subtitles.map((sub) => ({
+ default: sub.label.toLowerCase() === "english" && sub === defaultEnglishSub,
+ html: sub.label,
+ url: sub.file,
+ })),
+ ],
+ onSelect: (item) => {
+ art.subtitle.switch(item.url, { name: item.html });
+ return item.html;
+ },
+ });
+ }
+ });
+
+ return () => {
+ if (art && art.destroy) {
+ art.destroy(false);
+ }
+ document.removeEventListener("keydown", handleKeydown);
+ const continueWatching = JSON.parse(localStorage.getItem("continueWatching")) || [];
+ const newEntry = {
+ id: animeInfo?.id,
+ data_id: animeInfo?.data_id,
+ episodeId,
+ episodeNum,
+ adultContent: animeInfo?.adultContent,
+ poster: animeInfo?.poster,
+ title: animeInfo?.title,
+ japanese_title: animeInfo?.japanese_title,
+ leftAt: leftAtRef.current,
+ };
+
+ if (!newEntry.data_id) return;
+
+ const existingIndex = continueWatching.findIndex((item) => item.data_id === newEntry.data_id);
+ if (existingIndex !== -1) {
+ continueWatching[existingIndex] = newEntry;
+ } else {
+ continueWatching.push(newEntry);
+ }
+ localStorage.setItem("continueWatching", JSON.stringify(continueWatching));
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [streamUrl, subtitles, intro, outro]);
+
+ return
;
+}
\ No newline at end of file
diff --git a/src/components/player/PlayerIcons.jsx b/src/components/player/PlayerIcons.jsx
new file mode 100644
index 0000000..2410fa3
--- /dev/null
+++ b/src/components/player/PlayerIcons.jsx
@@ -0,0 +1,103 @@
+const backward10Icon = `
+
+
+ `;
+
+const forward10Icon = `
+
+
+
+ `;
+
+const forwardIcon = `
+
+ `;
+
+const backwardIcon = `
+
+ `;
+
+const volumeIcon = ` `;
+
+const muteIcon = `
+
+
+`;
+
+const captionIcon = `
+
+
+`;
+const captionOffIcon = ` `;
+
+const pipOffIcon = `
+ `;
+
+const loadingIcon = ` `;
+
+const pipIcon = `
+
+ `;
+
+const playIconLg = ` `;
+
+const playIcon = ` `;
+
+const pauseIcon = ` `;
+
+const uploadIcon = `
+
+
+ `;
+
+const settingsIcon = `
+
+
+ `;
+
+const fullScreenOnIcon = ` `;
+
+const fullScreenOffIcon = ` `;
+
+const logo = `
+ Powered by
+
+ Zen! me
+
+
+`;
+
+export {
+ backward10Icon,
+ forward10Icon,
+ backwardIcon,
+ forwardIcon,
+ playIcon,
+ playIconLg,
+ pauseIcon,
+ loadingIcon,
+ uploadIcon,
+ settingsIcon,
+ pipIcon,
+ pipOffIcon,
+ volumeIcon,
+ muteIcon,
+ captionIcon,
+ captionOffIcon,
+ fullScreenOnIcon,
+ fullScreenOffIcon,
+ logo,
+};
diff --git a/src/components/player/artPlayerPluginVttThumbnail.js b/src/components/player/artPlayerPluginVttThumbnail.js
new file mode 100644
index 0000000..4cb851c
--- /dev/null
+++ b/src/components/player/artPlayerPluginVttThumbnail.js
@@ -0,0 +1,72 @@
+import getVttArray from "./getVttArray";
+
+export default function artplayerPluginVttThumbnail(option) {
+ return async (art) => {
+ const {
+ constructor: {
+ utils: { setStyle, isMobile, addClass },
+ },
+ template: { $progress },
+ } = art;
+
+ let timer = null;
+ const thumbnails = await getVttArray(option.vtt);
+
+ function showThumbnails($control, find, width) {
+ setStyle($control, "backgroundImage", `url(${find.url})`);
+ setStyle($control, "height", `${find.h}px`);
+ setStyle($control, "width", `${find.w}px`);
+ setStyle($control, "backgroundPosition", `-${find.x}px -${find.y}px`);
+ if (width <= find.w / 2) {
+ setStyle($control, "left", 0);
+ } else if (width > $progress.clientWidth - find.w / 2) {
+ setStyle($control, "left", `${$progress.clientWidth - find.w}px`);
+ } else {
+ setStyle($control, "left", `${width - find.w / 2}px`);
+ }
+ }
+
+ art.controls.add({
+ name: "vtt-thumbnail",
+ position: "top",
+ index: 20,
+ style: option.style || {},
+ mounted($control) {
+ addClass($control, "art-control-thumbnails");
+ art.on("setBar", async (type, percentage, event) => {
+ const isMobileDroging = type === "played" && event && isMobile;
+
+ if (type === "hover" || isMobileDroging) {
+ const width = $progress.clientWidth * percentage;
+ const second = percentage * art.duration;
+ setStyle($control, "display", "flex");
+
+ const find = thumbnails.find(
+ (item) => second >= item.start && second <= item.end
+ );
+ if (!find) return setStyle($control, "display", "none");
+
+ if (width > 0 && width < $progress.clientWidth) {
+ showThumbnails($control, find, width);
+ } else {
+ if (!isMobile) {
+ setStyle($control, "display", "none");
+ }
+ }
+
+ if (isMobileDroging) {
+ clearTimeout(timer);
+ timer = setTimeout(() => {
+ setStyle($control, "display", "none");
+ }, 500);
+ }
+ }
+ });
+ },
+ });
+
+ return {
+ name: "artplayerPluginVttThumbnail",
+ };
+ };
+}
diff --git a/src/components/player/artPlayerPluinChaper.js b/src/components/player/artPlayerPluinChaper.js
new file mode 100644
index 0000000..ba681d8
--- /dev/null
+++ b/src/components/player/artPlayerPluinChaper.js
@@ -0,0 +1,211 @@
+import style from "./pluginChapterStyle.js";
+
+export default function artplayerPluginChapter(option = {}) {
+ return (art) => {
+ const { $player } = art.template;
+ const { setStyle, append, clamp, query, isMobile, addClass, removeClass } =
+ art.constructor.utils;
+
+ const html = `
+
+ `;
+
+ let titleTimer = null;
+ let $chapters = [];
+
+ const $progress = art.query(".art-control-progress");
+ const $inner = art.query(".art-control-progress-inner");
+ const $control = append($inner, '
');
+ const $title = append($inner, '
');
+
+ function showTitle({ $chapter, width }) {
+ const title = $chapter.dataset.title.trim();
+ if (title) {
+ setStyle($title, "display", "flex");
+ $title.innerText = title;
+ const titleWidth = $title.clientWidth;
+ if (width <= titleWidth / 2) {
+ setStyle($title, "left", 0);
+ } else if (width > $inner.clientWidth - titleWidth / 2) {
+ setStyle($title, "left", `${$inner.clientWidth - titleWidth}px`);
+ } else {
+ setStyle($title, "left", `${width - titleWidth / 2}px`);
+ }
+ } else {
+ setStyle($title, "display", "none");
+ }
+ }
+
+ function update(chapters = []) {
+ $chapters = [];
+ $control.innerText = "";
+ removeClass($player, "artplayer-plugin-chapter");
+
+ if (!Array.isArray(chapters)) return;
+ if (!chapters.length) return;
+ if (!art.duration) return;
+
+ chapters = chapters.sort((a, b) => a.start - b.start);
+
+ for (let i = 0; i < chapters.length; i++) {
+ const chapter = chapters[i];
+ const nextChapter = chapters[i + 1];
+
+ if (chapter.end === Infinity) {
+ chapter.end = art.duration;
+ }
+
+ if (
+ typeof chapter.start !== "number" ||
+ typeof chapter.end !== "number" ||
+ typeof chapter.title !== "string"
+ ) {
+ throw new Error("Illegal chapter data type");
+ }
+
+ if (
+ chapter.start < 0 ||
+ chapter.end > Math.ceil(art.duration) ||
+ chapter.start >= chapter.end
+ ) {
+ throw new Error("Illegal chapter time point");
+ }
+
+ if (nextChapter && chapter.end > nextChapter.start) {
+ throw new Error("Illegal chapter time point");
+ }
+ }
+
+ if (chapters[0].start > 0) {
+ chapters.unshift({ start: 0, end: chapters[0].start, title: "" });
+ }
+
+ if (chapters[chapters.length - 1].end < art.duration) {
+ chapters.push({
+ start: chapters[chapters.length - 1].end,
+ end: art.duration,
+ title: "",
+ });
+ }
+
+ for (let i = 0; i < chapters.length - 1; i++) {
+ if (chapters[i].end !== chapters[i + 1].start) {
+ chapters.splice(i + 1, 0, {
+ start: chapters[i].end,
+ end: chapters[i + 1].start,
+ title: "",
+ });
+ }
+ }
+
+ $chapters = chapters.map((chapter) => {
+ const $chapter = append($control, html);
+ const start = clamp(chapter.start, 0, art.duration);
+ const end = clamp(chapter.end, 0, art.duration);
+ const duration = end - start;
+ const percentage = duration / art.duration;
+ $chapter.dataset.start = start;
+ $chapter.dataset.end = end;
+ $chapter.dataset.duration = duration;
+ $chapter.dataset.title = chapter.title.trim();
+ $chapter.style.width = `${percentage * 100}%`;
+
+ return {
+ $chapter,
+ $hover: query(".art-progress-hover", $chapter),
+ $loaded: query(".art-progress-loaded", $chapter),
+ $played: query(".art-progress-played", $chapter),
+ };
+ });
+
+ addClass($player, "artplayer-plugin-chapter");
+ art.emit("setBar", "loaded", art.loaded || 0);
+ }
+
+ art.on("setBar", (type, percentage, event) => {
+ if (!$chapters.length) return;
+
+ for (let i = 0; i < $chapters.length; i++) {
+ const { $chapter, $loaded, $played, $hover } = $chapters[i];
+
+ const $target = {
+ hover: $hover,
+ loaded: $loaded,
+ played: $played,
+ }[type];
+
+ if (!$target) return;
+
+ const width = $control.clientWidth * percentage;
+ const currentTime = art.duration * percentage;
+ const duration = parseFloat($chapter.dataset.duration);
+ const start = parseFloat($chapter.dataset.start);
+ const end = parseFloat($chapter.dataset.end);
+
+ if (currentTime < start) {
+ setStyle($target, "width", 0);
+ }
+
+ if (currentTime > end) {
+ setStyle($target, "width", "100%");
+ }
+
+ if (currentTime >= start && currentTime <= end) {
+ const percentage = (currentTime - start) / duration;
+ setStyle($target, "width", `${percentage * 100}%`);
+
+ if (isMobile) {
+ if (type === "played" && event) {
+ showTitle({ $chapter, width });
+ clearTimeout(titleTimer);
+ titleTimer = setTimeout(() => {
+ setStyle($title, "display", "none");
+ }, 500);
+ }
+ } else {
+ if (type === "hover") {
+ showTitle({ $chapter, width });
+ }
+ }
+ }
+ }
+ });
+
+ if (!isMobile) {
+ art.proxy($progress, "mouseleave", () => {
+ if (!$chapters.length) return;
+ setStyle($title, "display", "none");
+ });
+ }
+
+ art.once("video:loadedmetadata", () => update(option.chapters));
+
+ return {
+ name: "artplayerPluginChapter",
+ update: ({ chapters }) => update(chapters),
+ };
+ };
+}
+
+if (typeof document !== "undefined") {
+ const id = "artplayer-plugin-chapter";
+ const $style = document.getElementById(id);
+ if ($style) {
+ $style.textContent = style;
+ } else {
+ const $style = document.createElement("style");
+ $style.id = id;
+ $style.textContent = style;
+ document.head.appendChild($style);
+ }
+}
+
+if (typeof window !== "undefined") {
+ window["artplayerPluginChapter"] = artplayerPluginChapter;
+}
diff --git a/src/components/player/artplayerPluginUploadSubtitle.js b/src/components/player/artplayerPluginUploadSubtitle.js
new file mode 100644
index 0000000..20a362d
--- /dev/null
+++ b/src/components/player/artplayerPluginUploadSubtitle.js
@@ -0,0 +1,49 @@
+import { uploadIcon } from "./PlayerIcons";
+
+export default function artplayerPluginUploadSubtitle() {
+ return (art) => {
+ const { getExt } = art.constructor.utils;
+
+ art.setting.add({
+ html: `
+
+
+
+ Upload Subtitle
+
+
+ `,
+ icon: uploadIcon,
+ onClick(setting, $setting) {
+ const $input = $setting.querySelector("input[name='subtitle-upload']");
+ const $label = $setting.querySelector(".subtitle-upload-label");
+
+ art.proxy($input, "change", (event) => {
+ const file = event.target?.files?.[0];
+ if (!file) return;
+
+ const url = URL.createObjectURL(file);
+ art.subtitle.switch(url, {
+ type: getExt(file.name),
+ });
+
+ event.target.value = null;
+
+ // Update UI
+ $label.textContent = file.name;
+ art.notice.show = `Upload Subtitle :${file.name}`;
+ setting.tooltip = file.name;
+ });
+ },
+ });
+ };
+}
diff --git a/src/components/player/autoSkip.js b/src/components/player/autoSkip.js
new file mode 100644
index 0000000..38588b9
--- /dev/null
+++ b/src/components/player/autoSkip.js
@@ -0,0 +1,74 @@
+export default function autoSkip(option) {
+ function validateRanges(ranges) {
+ if (!Array.isArray(ranges)) {
+ throw new TypeError("Option must be an array of time ranges");
+ }
+
+ ranges.forEach((range, index) => {
+ if (!Array.isArray(range) || range.length !== 2) {
+ throw new TypeError(
+ `Range at index ${index} must be an array of two numbers`
+ );
+ }
+
+ const [start, end] = range;
+ if (
+ typeof start !== "number" ||
+ (typeof end !== "number" && end !== Infinity)
+ ) {
+ throw new TypeError(
+ `Range at index ${index} must contain valid numbers or Infinity`
+ );
+ }
+
+ if (start > end && end !== Infinity) {
+ throw new RangeError(
+ `In range at index ${index}, start time must be less than end time`
+ );
+ }
+
+ if (index > 0) {
+ const prevEnd = ranges[index - 1][1];
+ if (prevEnd !== Infinity && start <= prevEnd) {
+ throw new RangeError(
+ `Range at index ${index} overlaps with the previous range`
+ );
+ }
+ }
+ });
+ }
+ validateRanges(option);
+ return (art) => {
+ let skipRanges = option;
+
+ function updateRanges() {
+ const duration = art.duration;
+ skipRanges = skipRanges.map(([start, end]) => [
+ start,
+ end === Infinity ? duration : end,
+ ]);
+ }
+
+ function checkAndSkip() {
+ const currentTime = art.currentTime;
+ for (const [start, end] of skipRanges) {
+ if (currentTime >= start && currentTime < end) {
+ art.seek = end;
+ break;
+ }
+ }
+ }
+
+ art.on("video:timeupdate", checkAndSkip);
+ art.on("video:loadedmetadata", updateRanges);
+
+ return {
+ name: "autoSkip",
+ update(newOption = []) {
+ validateRanges(newOption);
+ skipRanges = newOption;
+ updateRanges();
+ },
+ };
+ };
+}
diff --git a/src/components/player/getChapterStyle.js b/src/components/player/getChapterStyle.js
new file mode 100644
index 0000000..c558d89
--- /dev/null
+++ b/src/components/player/getChapterStyle.js
@@ -0,0 +1,82 @@
+export default function getChapterStyles(intro, outro) {
+ let styles = `
+ .art-chapters {
+ gap: 0px !important;
+ }
+ `;
+
+ if (intro && outro) {
+ if (
+ intro.start === 0 &&
+ intro.end === 0 &&
+ outro.start === 0 &&
+ outro.end === 0
+ ) {
+ styles += ``;
+ } else if (
+ intro.start === 0 &&
+ intro.end === 0 &&
+ outro.start !== 0 &&
+ outro.end !== 0
+ ) {
+ styles += `
+ .art-chapter:nth-child(2) {
+ background-color: #fdd253;
+ transform: scaleY(0.6);
+ }
+ `;
+ } else if (
+ intro.start === 0 &&
+ intro.end !== 0 &&
+ outro.start === 0 &&
+ outro.end === 0
+ ) {
+ styles += `
+ .art-chapter:nth-child(1){
+ background-color: #fdd253;
+ transform: scaleY(0.6);
+ }
+ `;
+ } else if (
+ intro.start === 0 &&
+ intro.end !== 0 &&
+ outro.start !== 0 &&
+ outro.end !== 0
+ ) {
+ styles += `
+ .art-chapter:nth-child(1),
+ .art-chapter:nth-child(3) {
+ background-color: #fdd253;
+ transform: scaleY(0.6);
+ }
+ `;
+ } else if (
+ intro.start !== 0 &&
+ intro.end !== 0 &&
+ outro.start === 0 &&
+ outro.end === 0
+ ) {
+ styles += `
+ .art-chapter:nth-child(2) {
+ background-color: #fdd253;
+ transform: scaleY(0.6);
+ }
+ `;
+ } else if (
+ intro.start !== 0 &&
+ intro.end !== 0 &&
+ outro.start !== 0 &&
+ outro.end !== 0
+ ) {
+ styles += `
+ .art-chapter:nth-child(2),
+ .art-chapter:nth-child(4) {
+ background-color: #fdd253;
+ transform: scaleY(0.6);
+ }
+ `;
+ }
+ }
+
+ return styles;
+}
diff --git a/src/components/player/getVttArray.js b/src/components/player/getVttArray.js
new file mode 100644
index 0000000..6df8d10
--- /dev/null
+++ b/src/components/player/getVttArray.js
@@ -0,0 +1,101 @@
+function padEnd(str, targetLength, padString) {
+ if (str.length > targetLength) {
+ return String(str);
+ } else {
+ targetLength = targetLength - str.length;
+ if (targetLength > padString.length) {
+ padString += padString.repeat(targetLength / padString.length);
+ }
+ return String(str) + padString.slice(0, targetLength);
+ }
+}
+
+function t2d(time) {
+ var arr = time.split(".");
+ var left = arr[0].split(":") || [];
+ var right = padEnd(arr[1] || "0", 3, "0");
+ var ms = Number(right) / 1000;
+
+ var h = Number(left[left.length - 3] || 0) * 3600;
+ var m = Number(left[left.length - 2] || 0) * 60;
+ var s = Number(left[left.length - 1] || 0);
+ return h + m + s + ms;
+}
+
+export default async function getVttArray(vttUrl = "") {
+ const vttString = await (await fetch(vttUrl)).text();
+ let lines = vttString.split(/\r?\n/).filter((item) => item.trim());
+ const vttArray = [];
+
+ //checking if the WEBVTT header is present
+ const isWebVTTHeader = lines[0].trim().toUpperCase() === "WEBVTT";
+
+ let startIndex = 0;
+ let increment = 2;
+
+ // Check if the first line is an index line
+ const indexLineReg = /^\d+$/; // Regex to match lines containing only digits
+
+ if (!isWebVTTHeader && indexLineReg.test(lines[0].trim())) {
+ // console.log("WEBVTT not present but index line is present");
+ increment = 3; // Set increment to 3 if an index line is present
+ startIndex = 1; // Start from the second line
+ } else if (isWebVTTHeader) {
+ // If WEBVTT is present, check the next line
+ // console.log("WEBVTT lines is present checking if index line is present...");
+ const indexLine = lines[1];
+ if (indexLine && indexLineReg.test(indexLine.trim())) {
+ // console.log("Index line is present");
+ increment = 3; // Set increment to 3 if an index line is present
+ startIndex = 2; // Start from the line after the index
+ } else {
+ // console.log("Index line is not present");
+ startIndex = 1; // If no index line, start from the line after WEBVTT
+ increment = 2; // Set increment to 2
+ }
+ }
+
+ for (let i = startIndex; i < lines.length; i += increment) {
+ const time = lines[i];
+ const text = lines[i + 1];
+ if (!text.trim()) continue;
+
+ // console.log(`Processing time line: ${time}`); // Logging processing timestamps
+
+ const timeReg =
+ /((?:[0-9]{2}:)?(?:[0-9]{2}:)?[0-9]{2}(?:.[0-9]{3})?)(?: ?--> ?)((?:[0-9]{2}:)?(?:[0-9]{2}:)?[0-9]{2}(?:.[0-9]{3})?)/;
+ const timeMatch = time.match(timeReg);
+
+ if (!timeMatch) {
+ // console.warn(`Failed to match time: ${time}`); // Log failed matches
+ continue; // Skip to the next loop iteration if match fails
+ }
+
+ const textReg = /(.*)#(\w{4})=(.*)/i;
+ const textMatch = text.match(textReg);
+ const start = Math.floor(t2d(timeMatch[1]));
+ const end = Math.floor(t2d(timeMatch[2]));
+
+ let url = textMatch[1];
+ const isAbsoluteUrl = /^\/|((https?|ftp|file):\/\/)/i.test(url);
+ if (!isAbsoluteUrl) {
+ const urlArr = vttUrl.split("/");
+ urlArr.pop();
+ urlArr.push(url);
+ url = urlArr.join("/");
+ }
+
+ const result = { start, end, url };
+
+ const keys = textMatch[2].split("");
+ const values = textMatch[3].split(",");
+
+ for (let j = 0; j < keys.length; j++) {
+ result[keys[j]] = values[j];
+ }
+
+ vttArray.push(result);
+ }
+
+ return vttArray;
+}
diff --git a/src/components/player/pluginChapterStyle.js b/src/components/player/pluginChapterStyle.js
new file mode 100644
index 0000000..1e945ce
--- /dev/null
+++ b/src/components/player/pluginChapterStyle.js
@@ -0,0 +1,55 @@
+export default `
+.artplayer-plugin-chapter .art-control-progress-inner {
+ height: 100% !important;
+ background-color: transparent !important;
+}
+.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-hover,
+.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-loaded,
+.artplayer-plugin-chapter .art-control-progress-inner > .art-progress-played {
+ display: none !important;
+}
+.artplayer-plugin-chapter .art-control-thumbnails {
+ bottom: calc(var(--art-bottom-gap) + 64px) !important;
+}
+.artplayer-plugin-chapter .art-chapters {
+ position: absolute;
+ z-index: 0;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ height: 100%;
+ transform: scaleY(1.25);
+}
+.artplayer-plugin-chapter .art-chapters .art-chapter {
+ display: flex;
+ align-items: center;
+ height: 100%;
+}
+.artplayer-plugin-chapter .art-chapters .art-chapter .art-chapter-inner {
+ position: relative;
+ cursor: pointer;
+ width: 100%;
+ height: 50%;
+ border-radius: 10px;
+ overflow: hidden;
+ transition: height var(--art-transition-duration) ease;
+ background-color: var(--art-progress-color);
+}
+.artplayer-plugin-chapter .art-chapters .art-chapter:hover .art-chapter-inner {
+ height: 100%;
+}
+.artplayer-plugin-chapter .art-chapter-title {
+ display: none;
+ position: absolute;
+ z-index: 70;
+ top: -50px;
+ left: 0;
+ padding: 3px 5px;
+ line-height: 1;
+ font-size: 14px;
+ border-radius: var(--art-border-radius);
+ white-space: nowrap;
+ background-color: var(--art-tip-background);
+}
+`;
diff --git a/src/components/producer/Producer.jsx b/src/components/producer/Producer.jsx
new file mode 100644
index 0000000..90b7c10
--- /dev/null
+++ b/src/components/producer/Producer.jsx
@@ -0,0 +1,102 @@
+import { useNavigate, useParams, useSearchParams } from "react-router-dom";
+import Error from "../error/Error";
+import Topten from "../topten/Topten";
+import Genre from "../genres/Genre";
+import SidecardLoader from "../Loader/Sidecard.loader";
+import PageSlider from "../pageslider/PageSlider";
+import CategoryCard from "../categorycard/CategoryCard";
+import { useEffect, useState } from "react";
+import { useHomeInfo } from "@/src/context/HomeInfoContext";
+import getProducer from "@/src/utils/getProducer.utils";
+import Loader from "../Loader/Loader";
+
+function Producer() {
+ const { id } = useParams();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [producerInfo, setProducerInfo] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [totalPages, setTotalPages] = useState(0);
+ const page = parseInt(searchParams.get("page")) || 1;
+ const { homeInfo, homeInfoLoading } = useHomeInfo();
+ const navigate = useNavigate();
+ useEffect(() => {
+ const fetchProducerInfo = async () => {
+ setLoading(true);
+ try {
+ const data = await getProducer(id, page);
+ setProducerInfo(data.data);
+ setTotalPages(data.totalPages);
+ setLoading(false);
+ } catch (err) {
+ setError(err);
+ console.error("Error fetching category info:", err);
+ }
+ };
+ fetchProducerInfo();
+ window.scrollTo(0, 0);
+ }, [id, page]);
+ if (loading) return ;
+ if (error) {
+ navigate("/error-page");
+ return ;
+ }
+ if (!producerInfo) {
+ navigate("/404-not-found-page");
+ return null;
+ }
+ const handlePageChange = (newPage) => {
+ setSearchParams({ page: newPage });
+ };
+
+ return (
+
+ {producerInfo ? (
+
+ {page > totalPages ? (
+
+ You came a long way, go back
+ nothing is here
+
+ ) : (
+
+ {producerInfo && (
+
+ )}
+
+
+ )}
+
+ {homeInfoLoading ? (
+
+ ) : (
+ <>
+ {homeInfo && homeInfo.topten && (
+
+ )}
+ {homeInfo?.genres && }
+ >
+ )}
+
+
+ ) : (
+
+ )}
+
+ );
+}
+export default Producer;
diff --git a/src/components/qtip/Qtip.jsx b/src/components/qtip/Qtip.jsx
new file mode 100644
index 0000000..a3c4efe
--- /dev/null
+++ b/src/components/qtip/Qtip.jsx
@@ -0,0 +1,159 @@
+import BouncingLoader from "../ui/bouncingloader/Bouncingloader";
+import getQtip from "@/src/utils/getQtip.utils";
+import { useState, useEffect } from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faPlay,
+ faStar,
+ faClosedCaptioning,
+ faMicrophone,
+} from "@fortawesome/free-solid-svg-icons";
+import { Link } from "react-router-dom";
+
+function Qtip({ id }) {
+ const [qtip, setQtip] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchQtipInfo = async () => {
+ setLoading(true);
+ try {
+ const data = await getQtip(id);
+ setQtip(data);
+ } catch (err) {
+ console.error("Error fetching anime info:", err);
+ setError(err);
+ } finally {
+ setLoading(false);
+ }
+ };
+ fetchQtipInfo();
+ }, [id]);
+
+ return (
+
+ {loading || error || !qtip ? (
+
+ ) : (
+
+
+ {qtip.title}
+
+
+ {qtip?.rating && (
+
+ )}
+
+ {qtip?.quality && (
+
+ )}
+
+ {qtip?.subCount && (
+
+ )}
+ {qtip?.dubCount && (
+
+ )}
+ {qtip?.episodeCount && (
+
+
+ {qtip.episodeCount}
+
+
+ )}
+
+ {qtip?.type && (
+
+ )}
+
+
+ {qtip?.description && (
+
+ {qtip.description}
+
+ )}
+
+ {qtip?.japaneseTitle && (
+
+
+ Japanese:
+
+ {qtip.japaneseTitle}
+
+ )}
+ {qtip?.Synonyms && (
+
+
+ Synonyms:
+
+ {qtip.Synonyms}
+
+ )}
+ {qtip?.airedDate && (
+
+ Aired:
+ {qtip.airedDate}
+
+ )}
+ {qtip?.status && (
+
+
+ Status:
+
+ {qtip.status}
+
+ )}
+ {qtip?.genres && (
+
+
+ Genres:
+
+ {qtip.genres.map((genre, index) => (
+
+
+ {genre}
+ {index === qtip.genres.length - 1 ? "" : ","}
+
+
+ ))}
+
+ )}
+
+
+
+
Watch Now
+
+
+ )}
+
+ );
+}
+
+export default Qtip;
diff --git a/src/components/schedule/Schedule.jsx b/src/components/schedule/Schedule.jsx
new file mode 100644
index 0000000..200f6a6
--- /dev/null
+++ b/src/components/schedule/Schedule.jsx
@@ -0,0 +1,241 @@
+import { useState, useEffect, useRef } from "react";
+import getSchedInfo from "../../utils/getScheduleInfo.utils";
+import { Pagination, Navigation } from "swiper/modules";
+import { Swiper, SwiperSlide } from "swiper/react";
+import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
+import BouncingLoader from "../ui/bouncingloader/Bouncingloader";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faPlay } from "@fortawesome/free-solid-svg-icons";
+import "./schedule.css";
+import { Link } from "react-router-dom";
+
+const Schedule = () => {
+ const [dates, setDates] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [showAll, setShowAll] = useState(false);
+ const [currentActiveIndex, setCurrentActiveIndex] = useState(null);
+ const [scheduleData, setscheduleData] = useState([]);
+ const [currentTime, setCurrentTime] = useState(new Date());
+ const cardRefs = useRef([]);
+ const swiperRef = useRef(null);
+ const currentDate = new Date();
+ const year = currentDate.getFullYear();
+ const month = currentDate.getMonth();
+ const monthName = currentDate.toLocaleString("default", { month: "short" });
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
+ const GMTOffset = `GMT ${
+ new Date().getTimezoneOffset() > 0 ? "-" : "+"
+ }${String(Math.floor(Math.abs(new Date().getTimezoneOffset()) / 60)).padStart(
+ 2,
+ "0"
+ )}:${String(Math.abs(new Date().getTimezoneOffset()) % 60).padStart(2, "0")}`;
+ const months = [];
+
+ useEffect(() => {
+ for (let day = 1; day <= daysInMonth; day++) {
+ const date = new Date(year, month, day);
+ const dayname = date.toLocaleString("default", { weekday: "short" });
+ const yearr = date.getFullYear();
+ const monthh = String(date.getMonth() + 1).padStart(2, "0");
+ const dayy = String(date.getDate()).padStart(2, "0");
+ const fulldate = `${yearr}-${monthh}-${dayy}`;
+ months.push({ day, monthName, dayname, fulldate });
+ }
+ setDates(months);
+ const timer = setInterval(() => {
+ setCurrentTime(new Date());
+ }, 1000);
+ return () => clearInterval(timer);
+ }, []);
+
+ useEffect(() => {
+ const todayIndex = dates.findIndex(
+ (date) =>
+ date.fulldate ===
+ `${currentDate.getFullYear()}-${String(
+ currentDate.getMonth() + 1
+ ).padStart(2, "0")}-${String(currentDate.getDate()).padStart(2, "0")}`
+ );
+
+ if (todayIndex !== -1) {
+ setCurrentActiveIndex(todayIndex);
+ toggleActive(todayIndex);
+ }
+ }, [dates]);
+
+ const fetchSched = async (date) => {
+ try {
+ setLoading(true);
+
+ // Check if cached data exists
+ const cachedData = localStorage.getItem(`schedule-${date}`);
+ if (cachedData) {
+ const parsedData = JSON.parse(cachedData);
+ setscheduleData(Array.isArray(parsedData) ? parsedData : []);
+ } else {
+ const data = await getSchedInfo(date);
+ setscheduleData(Array.isArray(data) ? data : []);
+ localStorage.setItem(`schedule-${date}`, JSON.stringify(data || []));
+ }
+ } catch (err) {
+ console.error("Error fetching schedule info:", err);
+ setError(err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const toggleActive = (index) => {
+ cardRefs.current.forEach((card) => {
+ if (card) {
+ card.classList.remove("active");
+ }
+ });
+ if (cardRefs.current[index]) {
+ cardRefs.current[index].classList.add("active");
+ if (dates[index] && dates[index].fulldate) {
+ fetchSched(dates[index].fulldate);
+ }
+ setCurrentActiveIndex(index);
+ }
+ };
+
+ const toggleShowAll = () => {
+ setShowAll(!showAll);
+ };
+
+ useEffect(() => {
+ setShowAll(false);
+ if (currentActiveIndex !== null && swiperRef.current) {
+ swiperRef.current.slideTo(currentActiveIndex);
+ }
+ }, [currentActiveIndex]);
+
+ return (
+ <>
+
+
+
+ Estimated Schedule
+
+
+ ({GMTOffset}) {currentTime.toLocaleDateString()}{" "}
+ {currentTime.toLocaleTimeString()}
+
+
+
+
+
+
(swiperRef.current = swiper)}
+ >
+ {dates &&
+ dates.map((date, index) => (
+
+ (cardRefs.current[index] = el)}
+ onClick={() => toggleActive(index)}
+ className={`h-[70px] flex flex-col justify-center items-center w-full text-center rounded-xl shadow-lg cursor-pointer ${
+ currentActiveIndex === index
+ ? "bg-[#ffbade] text-black"
+ : "bg-white bg-opacity-5 text-[#ffffff] hover:bg-[#373646] transition-all duration-300 ease-in-out"
+ }`}
+ >
+
+ {date.dayname}
+
+
+ {date.monthName} {date.day}
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ {loading ? (
+
+
+
+ ) : !scheduleData || scheduleData.length === 0 ? (
+
+ No data to display
+
+ ) : error ? (
+
+ Something went wrong
+
+ ) : (
+
+ {(showAll
+ ? scheduleData
+ : Array.isArray(scheduleData)
+ ? scheduleData.slice(0, 7)
+ : []
+ ).map((item, idx) => (
+
+
+
+ {item.time || "N/A"}
+
+
+ {item.title || "N/A"}
+
+
+
+
+
+ Episode {item.episode_no || "N/A"}
+
+
+
+ ))}
+ {scheduleData.length > 7 && (
+
+ {showAll ? "Show Less" : "Show More"}
+
+ )}
+
+ )}
+ >
+ );
+};
+
+export default Schedule;
diff --git a/src/components/schedule/schedule.css b/src/components/schedule/schedule.css
new file mode 100644
index 0000000..440906f
--- /dev/null
+++ b/src/components/schedule/schedule.css
@@ -0,0 +1,11 @@
+.next,
+.prev {
+ width: 30px;
+ height: 30px;
+ border-radius: 100%;
+ background-color: white;
+ color: black;
+ font-size: 13px;
+ padding: 10px;
+ z-index: 10;
+}
diff --git a/src/components/searchbar/MobileSearch.jsx b/src/components/searchbar/MobileSearch.jsx
new file mode 100644
index 0000000..80a56d7
--- /dev/null
+++ b/src/components/searchbar/MobileSearch.jsx
@@ -0,0 +1,73 @@
+import Suggestion from '../suggestion/Suggestion';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons';
+import useSearch from '@/src/hooks/useSearch';
+import { useNavigate } from 'react-router-dom';
+
+function MobileSearch() {
+ const navigate = useNavigate();
+ const {
+ isSearchVisible,
+ searchValue,
+ setSearchValue,
+ isFocused,
+ setIsFocused,
+ debouncedValue,
+ suggestionRefs,
+ addSuggestionRef,
+ } = useSearch();
+ const handleSearchClick = () => {
+ if (searchValue.trim() && window.innerWidth <= 600) {
+ navigate(`/search?keyword=${encodeURIComponent(searchValue)}`);
+ }
+ };
+ return (
+ <>
+ {isSearchVisible && (
+
+
setSearchValue(e.target.value)}
+ onFocus={() => setIsFocused(true)}
+ onBlur={() => {
+ setTimeout(() => {
+ const isInsideSuggestionBox = suggestionRefs.current.some(
+ (ref) => ref && ref.contains(document.activeElement),
+ );
+ if (!isInsideSuggestionBox) {
+ setIsFocused(false);
+ }
+ }, 100);
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleSearchClick();
+ }
+ }}
+ />
+
+
+
+ {searchValue.trim() && isFocused && (
+
+
+
+ )}
+
+ )}
+ >
+ );
+}
+
+export default MobileSearch;
diff --git a/src/components/searchbar/WebSearch.jsx b/src/components/searchbar/WebSearch.jsx
new file mode 100644
index 0000000..8f36cd6
--- /dev/null
+++ b/src/components/searchbar/WebSearch.jsx
@@ -0,0 +1,77 @@
+import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import Suggestion from "../suggestion/Suggestion";
+import useSearch from "@/src/hooks/useSearch";
+import { useNavigate } from "react-router-dom";
+
+function WebSearch() {
+ const navigate = useNavigate();
+ const {
+ setIsSearchVisible,
+ searchValue,
+ setSearchValue,
+ isFocused,
+ setIsFocused,
+ debouncedValue,
+ suggestionRefs,
+ addSuggestionRef,
+ } = useSearch();
+
+ const handleSearchClick = () => {
+ if (window.innerWidth <= 600) {
+ setIsSearchVisible((prev) => !prev);
+ }
+ if (searchValue.trim() && window.innerWidth > 600) {
+ navigate(`/search?keyword=${encodeURIComponent(searchValue)}`);
+ }
+ };
+
+ return (
+
+
setSearchValue(e.target.value)}
+ onFocus={() => setIsFocused(true)}
+ onBlur={() => {
+ setTimeout(() => {
+ const isInsideSuggestionBox = suggestionRefs.current.some(
+ (ref) => ref && ref.contains(document.activeElement),
+ );
+ if (!isInsideSuggestionBox) {
+ setIsFocused(false);
+ }
+ }, 100);
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ if (searchValue.trim()) {
+ navigate(`/search?keyword=${encodeURIComponent(searchValue)}`);
+ }
+ }
+ }}
+ />
+
+
+
+ {searchValue.trim() && isFocused && (
+
+
+
+ )}
+
+ );
+}
+
+export default WebSearch;
diff --git a/src/components/servers/Servers.css b/src/components/servers/Servers.css
new file mode 100644
index 0000000..10bf0e4
--- /dev/null
+++ b/src/components/servers/Servers.css
@@ -0,0 +1,9 @@
+.servers {
+ border-bottom: 1px dashed #35373d;
+}
+.servers:only-child {
+ border-bottom: none;
+}
+.servers:last-child {
+ border-bottom: none;
+}
diff --git a/src/components/servers/Servers.jsx b/src/components/servers/Servers.jsx
new file mode 100644
index 0000000..39f82bd
--- /dev/null
+++ b/src/components/servers/Servers.jsx
@@ -0,0 +1,187 @@
+/* eslint-disable react/prop-types */
+import {
+ faClosedCaptioning,
+ faFile,
+ faMicrophone,
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import BouncingLoader from "../ui/bouncingloader/Bouncingloader";
+import "./Servers.css";
+import { useEffect } from "react";
+
+function Servers({
+ servers,
+ activeEpisodeNum,
+ activeServerId,
+ setActiveServerId,
+ serverLoading,
+ setActiveServerType,
+ setActiveServerName,
+}) {
+ const subServers =
+ servers?.filter((server) => server.type === "sub") || [];
+ const dubServers =
+ servers?.filter((server) => server.type === "dub") || [];
+ const rawServers =
+ servers?.filter((server) => server.type === "raw") || [];
+
+ useEffect(() => {
+ const savedServerName = localStorage.getItem("server_name");
+ if (savedServerName) {
+ const matchingServer = servers?.find(
+ (server) => server.serverName === savedServerName,
+ );
+
+ if (matchingServer) {
+ setActiveServerId(matchingServer.data_id);
+ setActiveServerType(matchingServer.type);
+ } else if (servers && servers.length > 0) {
+ setActiveServerId(servers[0].data_id);
+ setActiveServerType(servers[0].type);
+ }
+ } else if (servers && servers.length > 0) {
+ setActiveServerId(servers[0].data_id);
+ setActiveServerType(servers[0].type);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [servers]);
+
+ const handleServerSelect = (server) => {
+ setActiveServerId(server.data_id);
+ setActiveServerType(server.type);
+ setActiveServerName(server.serverName);
+ localStorage.setItem("server_name", server.serverName);
+ localStorage.setItem("server_type", server.type);
+ };
+ return (
+
+ {serverLoading ? (
+
+
+
+ ) : servers ? (
+
+
+
+ You are watching
+
+ Episode {activeEpisodeNum}
+
+
+
+ If the current server doesn't work, please try other servers
+ beside.
+
+
+
+ {rawServers.length > 0 && (
+
+
+
+ {rawServers.map((item, index) => (
+
handleServerSelect(item)}
+ >
+
+ {item.serverName}
+
+
+ ))}
+
+
+ )}
+ {subServers.length > 0 && (
+
+
+
+ {subServers.map((item, index) => (
+
handleServerSelect(item)}
+ >
+
+ {item.serverName}
+
+
+ ))}
+
+
+ )}
+ {dubServers.length > 0 && (
+
+
+
+ {dubServers.map((item, index) => (
+
handleServerSelect(item)}
+ >
+
+ {item.serverName}
+
+
+ ))}
+
+
+ )}
+
+
+ ) : (
+
+ Could not load servers
+ Either reload or try again after sometime
+
+ )}
+
+ );
+}
+
+export default Servers;
diff --git a/src/components/sidebar/Sidebar.jsx b/src/components/sidebar/Sidebar.jsx
new file mode 100644
index 0000000..61a61c9
--- /dev/null
+++ b/src/components/sidebar/Sidebar.jsx
@@ -0,0 +1,141 @@
+import { FaChevronLeft } from "react-icons/fa";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faFilm, faRandom } from "@fortawesome/free-solid-svg-icons";
+import { useLanguage } from "@/src/context/LanguageContext";
+import { useEffect } from "react";
+import { Link, useLocation } from "react-router-dom";
+import {
+ cleanupScrollbar,
+ toggleScrollbar,
+} from "@/src/helper/toggleScrollbar";
+
+const Sidebar = ({ isOpen, onClose }) => {
+ const { language, toggleLanguage } = useLanguage();
+ const location = useLocation();
+
+ useEffect(() => {
+ toggleScrollbar(isOpen);
+ return () => {
+ cleanupScrollbar();
+ };
+ }, [isOpen]);
+
+ useEffect(() => {
+ onClose();
+ }, [location]);
+
+ return (
+ <>
+ {isOpen && (
+
+ )}
+
+
+
+
+
+ {[
+ { icon: faRandom, label: "Random" },
+ { icon: faFilm, label: "Movie" },
+ ].map((item, index) => (
+
+
+
+ {item.label}
+
+
+ ))}
+
+
+ {["EN", "JP"].map((lang, index) => (
+ toggleLanguage(lang)}
+ className={`px-1 py-[1px] text-xs font-bold ${
+ index === 0 ? "rounded-l-[3px]" : "rounded-r-[3px]"
+ } ${
+ language === lang
+ ? "bg-[#ffbade] text-black"
+ : "bg-gray-600 text-white"
+ } max-[575px]:text-[9px] max-[575px]:py-0`}
+ >
+ {lang}
+
+ ))}
+
+
+
+
+
+ {[
+ { name: "Home", path: "/home" },
+ { name: "Subbed Anime", path: "/subbed-anime" },
+ { name: "Dubbed Anime", path: "/dubbed-anime" },
+ { name: "Most Popular", path: "/most-popular" },
+ { name: "Movies", path: "/movie" },
+ { name: "TV Series", path: "/tv" },
+ { name: "OVAs", path: "/ova" },
+ { name: "ONAs", path: "/ona" },
+ { name: "Specials", path: "/special" },
+ {
+ name: "Join Telegram",
+ path: "https://t.me/zenime_discussion",
+ },
+ ].map((item, index) => (
+
+
+ {item.name}
+
+
+ ))}
+
+
+
+ >
+ );
+};
+
+export default Sidebar;
diff --git a/src/components/sidecard/Sidecard.jsx b/src/components/sidecard/Sidecard.jsx
new file mode 100644
index 0000000..fb539da
--- /dev/null
+++ b/src/components/sidecard/Sidecard.jsx
@@ -0,0 +1,142 @@
+import React, { useState } from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faClosedCaptioning,
+ faMicrophone,
+} from "@fortawesome/free-solid-svg-icons";
+import { useLanguage } from "@/src/context/LanguageContext";
+import { Link, useNavigate } from "react-router-dom";
+import useToolTipPosition from "@/src/hooks/useToolTipPosition";
+import Qtip from "../qtip/Qtip";
+
+function Sidecard({ data, label, className, limit }) {
+ const { language } = useLanguage();
+ const navigate = useNavigate();
+ const [showAll, setShowAll] = useState(false);
+ const [hoverTimeout, setHoverTimeout] = useState(null);
+ const handleMouseEnter = (item, index) => {
+ const timeout = setTimeout(() => {
+ setHoveredItem(item.id + index);
+ }, 400);
+ setHoverTimeout(timeout);
+ };
+ const handleMouseLeave = () => {
+ clearTimeout(hoverTimeout);
+ setHoveredItem(null);
+ };
+ const toggleShowAll = () => {
+ setShowAll((prev) => !prev);
+ };
+
+ const displayedData = limit
+ ? data.slice(0, limit)
+ : showAll
+ ? data
+ : data.slice(0, 6);
+ const [hoveredItem, setHoveredItem] = useState(null);
+ const { tooltipPosition, tooltipHorizontalPosition, cardRefs } =
+ useToolTipPosition(hoveredItem, data);
+ return (
+
+
{label}
+
+ {data &&
+ displayedData.map((item, index) => (
+
(cardRefs.current[index] = el)}
+ >
+
+ {hoveredItem === item.id + index &&
+ window.innerWidth > 1024 && (
+
+
+
+ )}
+
navigate(`/watch/${item.id}`)}
+ onMouseEnter={() => handleMouseEnter(item, index)}
+ onMouseLeave={handleMouseLeave}
+ />
+
+
+ window.scrollTo({ top: 0, behavior: "smooth" })
+ }
+ >
+ {language === "EN" ? item.title : item.japanese_title}
+
+
+ {item.tvInfo?.sub && (
+
+
+
+ {item.tvInfo.sub}
+
+
+ )}
+ {item.tvInfo?.dub && (
+
+
+
+ {item.tvInfo.dub}
+
+
+ )}
+ {item.tvInfo?.showType && (
+
+
+
+ {item.tvInfo.showType}
+
+
+ )}
+
+
+
+
+ ))}
+ {!limit && data.length > 6 && (
+
+ {showAll ? "Show less" : "Show more"}
+
+ )}
+
+
+ );
+}
+
+export default React.memo(Sidecard);
diff --git a/src/components/splashscreen/SplashScreen.css b/src/components/splashscreen/SplashScreen.css
new file mode 100644
index 0000000..7401cba
--- /dev/null
+++ b/src/components/splashscreen/SplashScreen.css
@@ -0,0 +1,227 @@
+/* Base styles */
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+ color: white;
+}
+
+/* Container and background */
+.splash-container {
+ min-height: 100vh;
+ width: 100%;
+ position: relative;
+ background: url('/splash.jpg') no-repeat center center fixed;
+ background-size: cover;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ padding: 0 30px;
+}
+
+.splash-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.7);
+ z-index: 1;
+}
+
+.content-wrapper {
+ position: relative;
+ z-index: 2;
+ width: 100%;
+ max-width: 800px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding-top: 140px;
+}
+
+/* Logo */
+.logo-container {
+ margin-bottom: 30px;
+}
+
+.logo {
+ height: 75px;
+ width: auto;
+}
+
+/* Search */
+.search-container {
+ width: 100%;
+ max-width: 500px;
+ position: relative;
+ margin-bottom: 24px;
+}
+
+.search-input {
+ width: 100%;
+ padding: 14px 48px 14px 20px;
+ background: rgba(17, 17, 17, 0.75);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 8px;
+ color: white;
+ font-size: 16px;
+ outline: none;
+ transition: border-color 0.2s;
+}
+
+.search-input:focus {
+ border-color: rgba(255, 255, 255, 0.3);
+}
+
+.search-input::placeholder {
+ color: rgba(255, 255, 255, 0.5);
+}
+
+.search-button {
+ position: absolute;
+ right: 16px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ color: rgba(255, 255, 255, 0.5);
+ cursor: pointer;
+ padding: 0;
+ font-size: 18px;
+ transition: color 0.2s;
+}
+
+.search-button:hover {
+ color: white;
+}
+
+/* Enter button */
+.enter-button {
+ background: white;
+ color: black;
+ padding: 12px 24px;
+ border-radius: 8px;
+ text-decoration: none;
+ font-weight: 500;
+ margin: 8px 0 60px;
+ transition: background-color 0.2s;
+}
+
+.enter-button:hover {
+ background: #ffbade;
+}
+
+/* FAQ Section */
+.faq-section {
+ width: 100%;
+ max-width: 700px;
+}
+
+.faq-title {
+ font-size: 32px;
+ font-weight: 700;
+ text-align: center;
+ margin-bottom: 40px;
+ color: white;
+}
+
+.faq-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.faq-item {
+ background: #141414;
+ border-radius: 12px;
+ overflow: hidden;
+ border: 1px solid #1a1a1a;
+}
+
+.faq-question {
+ width: 100%;
+ padding: 18px 24px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: none;
+ border: none;
+ color: white;
+ font-size: 17px;
+ text-align: left;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.faq-question:hover {
+ background: #1a1a1a;
+}
+
+.faq-toggle {
+ font-size: 16px;
+ color: white;
+ opacity: 0.8;
+ transition: transform 0.2s ease;
+}
+
+.faq-toggle.rotate {
+ transform: rotate(180deg);
+}
+
+.faq-answer {
+ padding: 0 24px 18px;
+ color: rgba(255, 255, 255, 0.7);
+ line-height: 1.6;
+ font-size: 15px;
+}
+
+/* Responsive adjustments */
+@media (max-width: 768px) {
+ .content-wrapper {
+ padding-top: 100px;
+ }
+
+ .logo {
+ height: 60px;
+ }
+
+ .search-input {
+ padding: 12px 40px 12px 16px;
+ font-size: 15px;
+ }
+
+ .faq-title {
+ font-size: 24px;
+ margin-bottom: 24px;
+ }
+}
+
+@media (max-width: 480px) {
+ .content-wrapper {
+ padding-top: 80px;
+ }
+
+ .logo {
+ height: 50px;
+ }
+
+ .search-input {
+ padding: 12px 36px 12px 14px;
+ font-size: 14px;
+ }
+
+ .enter-button {
+ padding: 10px 20px;
+ font-size: 14px;
+ }
+
+ .faq-question {
+ padding: 16px;
+ font-size: 15px;
+ }
+
+ .faq-answer {
+ padding: 0 16px 16px;
+ font-size: 14px;
+ }
+}
diff --git a/src/components/splashscreen/SplashScreen.jsx b/src/components/splashscreen/SplashScreen.jsx
new file mode 100644
index 0000000..b060ae0
--- /dev/null
+++ b/src/components/splashscreen/SplashScreen.jsx
@@ -0,0 +1,107 @@
+import { useState, useCallback } from "react";
+import { Link, useNavigate } from "react-router-dom";
+import "./SplashScreen.css";
+import logoTitle from "@/src/config/logoTitle";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faMagnifyingGlass, faChevronDown } from "@fortawesome/free-solid-svg-icons";
+
+const FAQ_ITEMS = [
+ {
+ question: "Is JustAnime safe?",
+ answer: "Yes, JustAnime is completely safe to use. We ensure all content is properly scanned and secured for our users."
+ },
+ {
+ question: "What makes JustAnime the best site to watch anime free online?",
+ answer: "JustAnime offers high-quality streaming, a vast library of anime, no intrusive ads, and a user-friendly interface - all completely free."
+ },
+ {
+ question: "How do I request an anime?",
+ answer: "You can submit anime requests through our contact form or by reaching out to our support team."
+ }
+];
+
+function SplashScreen() {
+ const navigate = useNavigate();
+ const [search, setSearch] = useState("");
+ const [expandedFaq, setExpandedFaq] = useState(null);
+
+ const handleSearchSubmit = useCallback(() => {
+ const trimmedSearch = search.trim();
+ if (!trimmedSearch) return;
+ const queryParam = encodeURIComponent(trimmedSearch);
+ navigate(`/search?keyword=${queryParam}`);
+ }, [search, navigate]);
+
+ const handleKeyDown = useCallback(
+ (e) => {
+ if (e.key === "Enter") {
+ handleSearchSubmit();
+ }
+ },
+ [handleSearchSubmit]
+ );
+
+ const toggleFaq = (index) => {
+ setExpandedFaq(expandedFaq === index ? null : index);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ setSearch(e.target.value)}
+ onKeyDown={handleKeyDown}
+ />
+
+
+
+
+
+
+ Enter Homepage →
+
+
+
+
Frequently Asked Questions
+
+ {FAQ_ITEMS.map((item, index) => (
+
+
toggleFaq(index)}
+ >
+ {item.question}
+
+
+ {expandedFaq === index && (
+
+ {item.answer}
+
+ )}
+
+ ))}
+
+
+
+
+ );
+}
+
+export default SplashScreen;
diff --git a/src/components/spotlight/Spotlight.css b/src/components/spotlight/Spotlight.css
new file mode 100644
index 0000000..01d867e
--- /dev/null
+++ b/src/components/spotlight/Spotlight.css
@@ -0,0 +1,68 @@
+.swiper {
+ width: 100%;
+}
+.swiper-slide {
+ font-size: 18px;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: -webkit-flex;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+}
+.button-prev,
+.button-next {
+ width: 40px;
+ height: 40px;
+ color: white;
+ background-color: #383747;
+ border-radius: 7px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ transition: all 0.3s ease-out;
+}
+.button-prev:hover,
+.button-next:hover {
+ background-color: #ffbade;
+ color: #383747;
+}
+
+.button-prev::after {
+ font-family: "Font Awesome 5 Free";
+ content: "\f053";
+ font-weight: 900;
+ font-size: 14px;
+}
+
+.button-next::after {
+ font-family: "Font Awesome 5 Free";
+ content: "\f054";
+ font-weight: 900;
+ font-size: 14px;
+}
+
+.swiper-horizontal > .swiper-pagination-bullets {
+ display: none;
+}
+.swiper-pagination-bullet-active {
+ background-color: rgb(239, 213, 22) !important;
+}
+@media only screen and (max-width: 575px) {
+ .swiper-horizontal > .swiper-pagination-bullets {
+ /* bottom: var(--swiper-pagination-bottom, 8px); */
+ bottom: 0;
+ right: 10px !important ;
+ left: auto !important;
+ width: 20px !important;
+ bottom: 5px !important;
+ display: flex !important;
+ gap: 18px;
+ align-items: center;
+ justify-content: center;
+ height: 80%;
+ flex-direction: column;
+ }
+}
diff --git a/src/components/spotlight/Spotlight.jsx b/src/components/spotlight/Spotlight.jsx
new file mode 100644
index 0000000..94e2e7a
--- /dev/null
+++ b/src/components/spotlight/Spotlight.jsx
@@ -0,0 +1,54 @@
+import { Swiper, SwiperSlide } from "swiper/react";
+import { Navigation, Autoplay } from "swiper/modules";
+import "swiper/css";
+import "swiper/css/autoplay";
+import "swiper/css/navigation";
+import "./Spotlight.css";
+import Banner from "../banner/Banner";
+
+const Spotlight = ({ spotlights }) => {
+ return (
+ <>
+
+
+ {spotlights && spotlights.length > 0 ? (
+ <>
+
+ {spotlights.map((item, index) => (
+
+
+
+ ))}
+
+ >
+ ) : (
+
No spotlights to show.
+ )}
+
+ >
+ );
+};
+
+export default Spotlight;
diff --git a/src/components/suggestion/Suggestion.jsx b/src/components/suggestion/Suggestion.jsx
new file mode 100644
index 0000000..5cacb94
--- /dev/null
+++ b/src/components/suggestion/Suggestion.jsx
@@ -0,0 +1,115 @@
+import getSearchSuggestion from "@/src/utils/getSearchSuggestion.utils";
+import { useEffect, useState } from "react";
+import BouncingLoader from "../ui/bouncingloader/Bouncingloader";
+import { FaChevronRight } from "react-icons/fa";
+import { Link } from "react-router-dom";
+
+function Suggestion({ keyword, className }) {
+ const [suggestion, setSuggestion] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [hasFetched, setHasFetched] = useState(false);
+
+ useEffect(() => {
+ const fetchSearchSuggestion = async () => {
+ if (!keyword) return;
+ setLoading(true);
+ setHasFetched(false);
+ try {
+ const data = await getSearchSuggestion(keyword);
+ setSuggestion(data);
+ setHasFetched(true);
+ } catch (err) {
+ console.error("Error fetching search suggestion info:", err);
+ setError(err);
+ } finally {
+ setLoading(false);
+ }
+ };
+ fetchSearchSuggestion();
+ }, [keyword]);
+
+ return (
+
+ {loading ? (
+
+ ) : error && !suggestion ? (
+
Error loading suggestions
+ ) : suggestion && hasFetched ? (
+
+ {suggestion.map((item, index) => (
+
+
{
+ e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
+ }}
+ />
+
+ {item?.title && (
+
+ {item.title || "N/A"}
+
+ )}
+ {item?.japanese_title && (
+
+ {item.japanese_title || "N/A"}
+
+ )}
+ {(item?.releaseDate || item?.showType || item?.duration) && (
+
+
+ {item.releaseDate || "N/A"}
+
+
+
+ {item.showType || "N/A"}
+
+
+
+ {item.duration || "N/A"}
+
+
+ )}
+
+
+ ))}
+ {!loading && hasFetched && (
+
+
+
+ View all results
+
+
+
+
+ )}
+
+ ) : hasFetched ? (
+
No results found!
+ ) : null}
+
+ );
+}
+
+export default Suggestion;
diff --git a/src/components/topten/Topten.jsx b/src/components/topten/Topten.jsx
new file mode 100644
index 0000000..c0a6758
--- /dev/null
+++ b/src/components/topten/Topten.jsx
@@ -0,0 +1,176 @@
+import React, { useState } from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faClosedCaptioning,
+ faMicrophone,
+} from "@fortawesome/free-solid-svg-icons";
+import { useLanguage } from "@/src/context/LanguageContext";
+import { Link, useNavigate } from "react-router-dom";
+import useToolTipPosition from "@/src/hooks/useToolTipPosition";
+import Qtip from "../qtip/Qtip";
+
+function Topten({ data, className }) {
+ const { language } = useLanguage();
+ const [activePeriod, setActivePeriod] = useState("today");
+ const [hoveredItem, setHoveredItem] = useState(null);
+ const [hoverTimeout, setHoverTimeout] = useState(null);
+ const navigate = useNavigate();
+
+ const handlePeriodChange = (period) => {
+ setActivePeriod(period);
+ };
+
+ const handleNavigate = (id) => {
+ navigate(`/${id}`);
+ window.scrollTo({ top: 0, behavior: "smooth" });
+ };
+
+ const currentData =
+ activePeriod === "today"
+ ? data.today
+ : activePeriod === "week"
+ ? data.week
+ : data.month;
+
+ const { tooltipPosition, tooltipHorizontalPosition, cardRefs } =
+ useToolTipPosition(hoveredItem, currentData);
+
+ const handleMouseEnter = (item, index) => {
+ if (hoverTimeout) clearTimeout(hoverTimeout);
+ setHoveredItem(item.id + index);
+ };
+
+ const handleMouseLeave = () => {
+ setHoverTimeout(
+ setTimeout(() => {
+ setHoveredItem(null);
+ }, 300) // Small delay to prevent flickering
+ );
+ };
+
+ return (
+
+
+
Top 10
+
+ {["today", "week", "month"].map((period) => (
+ handlePeriodChange(period)}
+ >
+ {period.charAt(0).toUpperCase() + period.slice(1)}
+
+ ))}
+
+
+
+
+ {currentData &&
+ currentData.map((item, index) => (
+
(cardRefs.current[index] = el)}
+ >
+
+ {`${index + 1 < 10 ? "0" : ""}${index + 1}`}
+
+
+ {/* Image with tooltip behavior */}
+
navigate(`/watch/${item.id}`)}
+ onMouseEnter={() => handleMouseEnter(item, index)}
+ onMouseLeave={handleMouseLeave}
+ />
+
+ {/* Tooltip positioned near image */}
+ {hoveredItem === item.id + index &&
+ window.innerWidth > 1024 && (
+
{
+ if (hoverTimeout) clearTimeout(hoverTimeout);
+ }}
+ onMouseLeave={handleMouseLeave}
+ >
+
+
+ )}
+
+
+
handleNavigate(item.id)}
+ >
+ {language === "EN" ? item.title : item.japanese_title}
+
+
+ {item.tvInfo?.sub && (
+
+
+
+ {item.tvInfo.sub}
+
+
+ )}
+ {item.tvInfo?.dub && (
+
+
+
+ {item.tvInfo.dub}
+
+
+ )}
+
+
+
+
+ ))}
+
+
+ );
+}
+
+export default React.memo(Topten);
diff --git a/src/components/trending/Trending.jsx b/src/components/trending/Trending.jsx
new file mode 100644
index 0000000..002ad54
--- /dev/null
+++ b/src/components/trending/Trending.jsx
@@ -0,0 +1,77 @@
+import { Pagination, Navigation } from "swiper/modules";
+import { Swiper, SwiperSlide } from "swiper/react";
+import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
+import { useLanguage } from "@/src/context/LanguageContext";
+import { Link, useNavigate } from "react-router-dom";
+
+const Trending = ({ trending }) => {
+ const { language } = useLanguage();
+ const navigate = useNavigate();
+ return (
+
+
+ Trending
+
+
+
+ {trending &&
+ trending.map((item, idx) => (
+ navigate(`/watch/${item.id}`)}
+ >
+
+
+
+ {item.number}
+
+
+ {language === "EN" ? item.title : item.japanese_title}
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+ );
+};
+
+export default Trending;
diff --git a/src/components/ui/Skeleton/Skeleton.css b/src/components/ui/Skeleton/Skeleton.css
new file mode 100644
index 0000000..d51e7a0
--- /dev/null
+++ b/src/components/ui/Skeleton/Skeleton.css
@@ -0,0 +1,23 @@
+@keyframes shimmer {
+ 0% {
+ background-position: 100% 0;
+ }
+ 100% {
+ background-position: -100% 0;
+ }
+ }
+
+ .shimmer-effect {
+ background: linear-gradient(
+ to right,
+ rgba(255, 255, 255, 0.1) 0%,
+ rgba(255, 255, 255, 0.2) 20%,
+ rgba(255, 255, 255, 0.3) 40%,
+ rgba(255, 255, 255, 0.2) 60%,
+ rgba(255, 255, 255, 0.1) 80%,
+ rgba(0, 0, 0, 0.03) 100%
+ );
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite linear;
+ }
+
\ No newline at end of file
diff --git a/src/components/ui/Skeleton/Skeleton.jsx b/src/components/ui/Skeleton/Skeleton.jsx
new file mode 100644
index 0000000..eab2eaa
--- /dev/null
+++ b/src/components/ui/Skeleton/Skeleton.jsx
@@ -0,0 +1,16 @@
+import { cn } from "@/lib/utils";
+import './Skeleton.css';
+
+function Skeleton({ className, animation=true, ...props }) {
+ return (
+
+ );
+}
+
+export { Skeleton };
diff --git a/src/components/ui/bouncingloader/Bouncingloader.css b/src/components/ui/bouncingloader/Bouncingloader.css
new file mode 100644
index 0000000..c5b1723
--- /dev/null
+++ b/src/components/ui/bouncingloader/Bouncingloader.css
@@ -0,0 +1,45 @@
+.bouncing-loading > div {
+ width: 18px;
+ height: 18px;
+ background-color: #858490;
+ border-radius: 100%;
+ display: inline-block;
+ -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
+ animation: sk-bouncedelay 1.4s infinite ease-in-out both;
+}
+
+.bouncing-loading .span1 {
+ -webkit-animation-delay: -0.32s;
+ animation-delay: -0.32s;
+}
+
+.bouncing-loading .span2 {
+ -webkit-animation-delay: -0.16s;
+ animation-delay: -0.16s;
+}
+
+@-webkit-keyframes sk-bouncedelay {
+ 0%,
+ 100%,
+ 80% {
+ -webkit-transform: scale(0);
+ }
+
+ 40% {
+ -webkit-transform: scale(1);
+ }
+}
+
+@keyframes sk-bouncedelay {
+ 0%,
+ 100%,
+ 80% {
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ }
+
+ 40% {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ }
+}
diff --git a/src/components/ui/bouncingloader/Bouncingloader.jsx b/src/components/ui/bouncingloader/Bouncingloader.jsx
new file mode 100644
index 0000000..9c81444
--- /dev/null
+++ b/src/components/ui/bouncingloader/Bouncingloader.jsx
@@ -0,0 +1,12 @@
+import "./Bouncingloader.css"
+const BouncingLoader = () => {
+ return (
+
+ );
+};
+
+export default BouncingLoader;
\ No newline at end of file
diff --git a/src/components/voiceactor/Voiceactor.jsx b/src/components/voiceactor/Voiceactor.jsx
new file mode 100644
index 0000000..bcc588e
--- /dev/null
+++ b/src/components/voiceactor/Voiceactor.jsx
@@ -0,0 +1,100 @@
+import { useState } from "react";
+import { FaChevronRight } from "react-icons/fa";
+import VoiceactorList from "../voiceactorlist/VoiceactorList";
+
+function Voiceactor({ animeInfo, className }) {
+ const [showVoiceActors, setShowVoiceActors] = useState(false);
+ return (
+
+
+
+ Characters & Voice Actors
+
+
+ {
+ setShowVoiceActors(true);
+ }}
+ >
+ View more
+
+
+
+
+
+ {animeInfo.charactersVoiceActors.slice(0, 6).map((character, index) => (
+
+ {character.character && (
+
+
+ {character.character.poster && (
+
{
+ e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
+ }}
+ className="w-[45px] h-[45px] flex-shrink-0 rounded-full object-cover"
+ loading="lazy"
+ />
+ )}
+
+ {character.character.name && (
+
+ {character.character.name}
+
+ )}
+ {character.character.cast && (
+
+ {character.character.cast}
+
+ )}
+
+
+
+ )}
+ {character.voiceActors.length > 0 && character.voiceActors[0] && (
+
+
+
+ {character.voiceActors[0].name && (
+
+ {character.voiceActors[0].name}
+
+ )}
+
+ {character.voiceActors[0].poster && (
+
{
+ e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
+ }}
+ className="w-[45px] h-[45px] rounded-full object-cover grayscale hover:grayscale-0 hover:cursor-pointer flex-shrink-0 transition-all duration-300 ease-in-out"
+ />
+ )}
+
+
+ )}
+
+ ))}
+
+ {showVoiceActors && (
+
setShowVoiceActors(false)}
+ />
+ )}
+
+ );
+}
+
+export default Voiceactor;
diff --git a/src/components/voiceactorlist/VoiceactorList.jsx b/src/components/voiceactorlist/VoiceactorList.jsx
new file mode 100644
index 0000000..43f951d
--- /dev/null
+++ b/src/components/voiceactorlist/VoiceactorList.jsx
@@ -0,0 +1,175 @@
+import { useState, useEffect } from "react";
+import {
+ faAngleDoubleLeft,
+ faAngleDoubleRight,
+ faChevronLeft,
+ faChevronRight,
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import fetchVoiceActorInfo from "@/src/utils/getVoiceActor.utils";
+import VoiceActorlistLoader from "../Loader/VoiceActorlist.loader";
+import { useNavigate } from "react-router-dom";
+import Error from "../error/Error";
+import {
+ cleanupScrollbar,
+ toggleScrollbar,
+} from "@/src/helper/toggleScrollbar";
+import PageSlider from "../pageslider/PageSlider";
+
+function VoiceactorList({ id, isOpen, onClose }) {
+ const [loading, setLoading] = useState(true);
+ const [page, setPage] = useState(1);
+ const [totalPages, setTotalPages] = useState(1);
+ const [error, setError] = useState(null);
+ const [VoiceactorList, setVoiceactorList] = useState([]);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ toggleScrollbar(isOpen);
+ return () => {
+ cleanupScrollbar();
+ };
+ }, [isOpen]);
+
+ useEffect(() => {
+ const fetchCategoryInfo = async () => {
+ setLoading(true);
+ try {
+ const data = await fetchVoiceActorInfo(id, page);
+ setVoiceactorList(data.data);
+ setTotalPages(data.totalPages);
+ setLoading(false);
+ } catch (err) {
+ setError(err);
+ console.error("Error fetching category info:", err);
+ }
+ };
+ fetchCategoryInfo();
+ }, [page]);
+ if (error) {
+ navigate("/error-page");
+ return ;
+ }
+ if (!VoiceactorList) {
+ navigate("/404-not-found-page");
+ return null;
+ }
+ return (
+
+
+ {!loading && (
+
+ Characters & Voice Actors
+
+ )}
+
+ {loading ? (
+
+ ) : (
+
+ {VoiceactorList.map((item, index) => (
+
+
+
{
+ e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
+ }}
+ />
+
+ {item.character.name && (
+
+ {item.character.name}
+
+ )}
+ {item.character.cast && (
+
+ {item.character.cast}
+
+ )}
+
+
+
+ {item.voiceActors &&
+ item.voiceActors.length > 0 &&
+ (item.voiceActors.length > 1 ? (
+
+ {item.voiceActors.map((data, index) => (
+
{
+ e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
+ }}
+ />
+ ))}
+
+ ) : (
+
+ {item?.voiceActors[0]?.name && (
+
+ {item.voiceActors[0].name}
+
+ )}
+
{
+ e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
+ }}
+ />
+
+ ))}
+
+ ))}
+
+ )}
+
+
+ ×
+
+
+
+
+
+ );
+}
+
+export default VoiceactorList;
diff --git a/src/components/watchcontrols/Watchcontrols.jsx b/src/components/watchcontrols/Watchcontrols.jsx
new file mode 100644
index 0000000..c5ca17e
--- /dev/null
+++ b/src/components/watchcontrols/Watchcontrols.jsx
@@ -0,0 +1,97 @@
+import { faBackward, faForward } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { useEffect, useState } from "react";
+
+const ToggleButton = ({ label, isActive, onClick }) => (
+
+ {label}
+
+ {isActive ? "on" : "off"}
+
+
+);
+
+export default function WatchControls({
+ autoPlay,
+ setAutoPlay,
+ autoSkipIntro,
+ setAutoSkipIntro,
+ autoNext,
+ setAutoNext,
+ episodeId,
+ episodes = [],
+ onButtonClick,
+}) {
+ const [currentEpisodeIndex, setCurrentEpisodeIndex] = useState(
+ episodes?.findIndex(
+ (episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId
+ )
+ );
+
+ useEffect(() => {
+ if (episodes?.length > 0) {
+ const newIndex = episodes.findIndex(
+ (episode) => episode.id.match(/ep=(\d+)/)?.[1] === episodeId
+ );
+ setCurrentEpisodeIndex(newIndex);
+ }
+ }, [episodeId, episodes]);
+
+ return (
+
+
+ setAutoPlay((prev) => !prev)}
+ />
+ setAutoSkipIntro((prev) => !prev)}
+ />
+ setAutoNext((prev) => !prev)}
+ />
+
+
+ {
+ if (currentEpisodeIndex > 0) {
+ onButtonClick(
+ episodes[currentEpisodeIndex - 1].id.match(/ep=(\d+)/)?.[1]
+ );
+ }
+ }}
+ disabled={currentEpisodeIndex <= 0}
+ >
+
+
+ {
+ if (currentEpisodeIndex < episodes?.length - 1) {
+ onButtonClick(
+ episodes[currentEpisodeIndex + 1].id.match(/ep=(\d+)/)?.[1]
+ );
+ }
+ }}
+ disabled={currentEpisodeIndex >= episodes?.length - 1}
+ >
+
+
+
+
+ );
+}
diff --git a/src/config/logoTitle.js b/src/config/logoTitle.js
new file mode 100644
index 0000000..95b3110
--- /dev/null
+++ b/src/config/logoTitle.js
@@ -0,0 +1,3 @@
+const logoTitle="Zen!me"
+
+export default logoTitle;
\ No newline at end of file
diff --git a/src/config/website.js b/src/config/website.js
new file mode 100644
index 0000000..d7cdf5d
--- /dev/null
+++ b/src/config/website.js
@@ -0,0 +1,3 @@
+const website_name = "JustAnime";
+
+export default website_name;
\ No newline at end of file
diff --git a/src/context/HomeInfoContext.jsx b/src/context/HomeInfoContext.jsx
new file mode 100644
index 0000000..814e8b0
--- /dev/null
+++ b/src/context/HomeInfoContext.jsx
@@ -0,0 +1,31 @@
+import { createContext, useContext, useState, useEffect } from 'react';
+import getHomeInfo from '../utils/getHomeInfo.utils.js';
+
+const HomeInfoContext = createContext();
+
+export const HomeInfoProvider = ({ children }) => {
+ const [homeInfo, setHomeInfo] = useState(null);
+ const [homeInfoLoading, setHomeInfoLoading] = useState(true);
+ const [error, setError] = useState(null);
+ useEffect(() => {
+ const fetchHomeInfo = async () => {
+ try {
+ const data = await getHomeInfo();
+ setHomeInfo(data);
+ } catch (err) {
+ console.error("Error fetching home info:", err);
+ setError(err);
+ } finally {
+ setHomeInfoLoading(false);
+ }
+ };
+ fetchHomeInfo();
+ }, []);
+ return (
+
+ {children}
+
+ );
+};
+
+export const useHomeInfo = () => useContext(HomeInfoContext);
diff --git a/src/context/LanguageContext.jsx b/src/context/LanguageContext.jsx
new file mode 100644
index 0000000..cec8067
--- /dev/null
+++ b/src/context/LanguageContext.jsx
@@ -0,0 +1,27 @@
+import { createContext, useContext, useState, useEffect } from 'react';
+
+const LanguageContext = createContext();
+
+export const LanguageProvider = ({ children }) => {
+ const [language, setLanguage] = useState(() => {
+ const storedLanguage = localStorage.getItem('language');
+ return storedLanguage ? storedLanguage : 'EN';
+ });
+ useEffect(() => {
+ localStorage.setItem('language', language);
+ }, [language]);
+
+ const toggleLanguage = (lang) => {
+ setLanguage(lang);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useLanguage = () => {
+ return useContext(LanguageContext);
+};
diff --git a/src/context/SearchContext.jsx b/src/context/SearchContext.jsx
new file mode 100644
index 0000000..36e738e
--- /dev/null
+++ b/src/context/SearchContext.jsx
@@ -0,0 +1,13 @@
+import { createContext, useContext, useState } from 'react';
+
+const SearchContext = createContext();
+export function SearchProvider({ children }) {
+ const [isSearchVisible, setIsSearchVisible] = useState(false);
+
+ return (
+
+ {children}
+
+ );
+}
+export const useSearchContext = () => useContext(SearchContext);
\ No newline at end of file
diff --git a/src/helper/toggleScrollbar.js b/src/helper/toggleScrollbar.js
new file mode 100644
index 0000000..77e0bdd
--- /dev/null
+++ b/src/helper/toggleScrollbar.js
@@ -0,0 +1,32 @@
+export function toggleScrollbar(isOpen) {
+ const getScrollbarWidth = () => {
+ return window.innerWidth - document.documentElement.clientWidth;
+ };
+ const body = document.body;
+ if (isOpen) {
+ const scrollbarWidth = getScrollbarWidth();
+ body.style.paddingRight = `${scrollbarWidth}px`;
+ body.classList.add("overflow-y-hidden");
+
+ const style = document.createElement("style");
+ style.id = "hide-scrollbar";
+ style.innerHTML = `::-webkit-scrollbar { display: none; }`;
+ document.head.appendChild(style);
+ } else {
+ body.style.paddingRight = "0";
+ body.classList.remove("overflow-y-hidden");
+ const styleElement = document.getElementById("hide-scrollbar");
+ if (styleElement) {
+ styleElement.remove();
+ }
+ }
+}
+export function cleanupScrollbar() {
+ const body = document.body;
+ body.style.paddingRight = "0";
+ body.classList.remove("overflow-y-hidden");
+ const styleElement = document.getElementById("hide-scrollbar");
+ if (styleElement) {
+ styleElement.remove();
+ }
+}
diff --git a/src/hooks/useSearch.js b/src/hooks/useSearch.js
new file mode 100644
index 0000000..f094b86
--- /dev/null
+++ b/src/hooks/useSearch.js
@@ -0,0 +1,64 @@
+import { useState, useEffect, useRef, useCallback } from "react";
+import { useLocation } from "react-router-dom";
+import { useSearchContext } from "@/src/context/SearchContext";
+
+const useSearch = () => {
+ const { isSearchVisible, setIsSearchVisible } = useSearchContext();
+ const [searchValue, setSearchValue] = useState("");
+ const [isFocused, setIsFocused] = useState(false);
+ const [debouncedValue, setDebouncedValue] = useState("");
+ const suggestionRefs = useRef([]);
+ const location = useLocation();
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setDebouncedValue(searchValue);
+ }, 500);
+ return () => {
+ clearTimeout(timer);
+ };
+ }, [searchValue]);
+
+ useEffect(() => {
+ setIsSearchVisible(false);
+ setSearchValue("");
+ setDebouncedValue("");
+ // setIsFocused(false);
+ }, [location, setIsSearchVisible]);
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ const isInsideSuggestionBox = suggestionRefs.current.some(
+ (ref) => ref && ref.contains(event.target)
+ );
+ const isInsideInput = document.activeElement === event.target;
+ if (!isInsideSuggestionBox && !isInsideInput) {
+ setIsFocused(false);
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, []);
+ const addSuggestionRef = useCallback((ref) => {
+ if (ref && !suggestionRefs.current.includes(ref)) {
+ suggestionRefs.current.push(ref);
+ }
+ }, []);
+
+ return {
+ isSearchVisible,
+ setIsSearchVisible,
+ searchValue,
+ setSearchValue,
+ isFocused,
+ setIsFocused,
+ debouncedValue,
+ suggestionRefs,
+ addSuggestionRef,
+ };
+};
+
+export default useSearch;
diff --git a/src/hooks/useToolTipPosition.js b/src/hooks/useToolTipPosition.js
new file mode 100644
index 0000000..ae74331
--- /dev/null
+++ b/src/hooks/useToolTipPosition.js
@@ -0,0 +1,49 @@
+import { useEffect, useRef, useState } from "react";
+
+const useToolTipPosition = (hoveredItem, data) => {
+ const cardRefs = useRef([]);
+ const [tooltipPosition, setTooltipPosition] = useState("top-1/2");
+ const [tooltipHorizontalPosition, setTooltipHorizontalPosition] =
+ useState("left-1/2");
+
+ const updateToolTipPosition = () => {
+ if (hoveredItem !== null) {
+ const refIndex = data.findIndex(
+ (item, index) => item.id + index === hoveredItem
+ );
+ const ref = cardRefs.current[refIndex];
+ if (ref) {
+ const { top, height, left, width } = ref.getBoundingClientRect();
+ const adjustedTop = top + height / 2 - 64;
+ const bottomY = window.innerHeight - adjustedTop;
+ if (adjustedTop < bottomY) {
+ setTooltipPosition("top-1/2");
+ } else {
+ setTooltipPosition("bottom-1/2");
+ }
+ const adjustedLeft = left + width / 2;
+ const spaceRight = window.innerWidth - adjustedLeft;
+ if (spaceRight > 320) {
+ setTooltipHorizontalPosition("left-1/2");
+ } else {
+ setTooltipHorizontalPosition("right-1/2");
+ }
+ }
+ }
+ };
+
+ useEffect(() => {
+ updateToolTipPosition();
+ const handleScroll = () => {
+ updateToolTipPosition();
+ };
+ window.addEventListener("scroll", handleScroll);
+ return () => {
+ window.removeEventListener("scroll", handleScroll);
+ };
+ }, [hoveredItem, data]);
+
+ return { tooltipPosition, tooltipHorizontalPosition, cardRefs };
+};
+
+export default useToolTipPosition;
diff --git a/src/hooks/useWatch.js b/src/hooks/useWatch.js
new file mode 100644
index 0000000..cc416bb
--- /dev/null
+++ b/src/hooks/useWatch.js
@@ -0,0 +1,269 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+import { useState, useEffect, useRef } from "react";
+import getAnimeInfo from "@/src/utils/getAnimeInfo.utils";
+import getEpisodes from "@/src/utils/getEpisodes.utils";
+import getNextEpisodeSchedule from "../utils/getNextEpisodeSchedule.utils";
+import getServers from "../utils/getServers.utils";
+import getStreamInfo from "../utils/getStreamInfo.utils";
+
+export const useWatch = (animeId, initialEpisodeId) => {
+ const [error, setError] = useState(null);
+ const [buffering, setBuffering] = useState(true);
+ const [streamInfo, setStreamInfo] = useState(null);
+ const [animeInfo, setAnimeInfo] = useState(null);
+ const [episodes, setEpisodes] = useState(null);
+ const [animeInfoLoading, setAnimeInfoLoading] = useState(false);
+ const [totalEpisodes, setTotalEpisodes] = useState(null);
+ const [seasons, setSeasons] = useState(null);
+ const [servers, setServers] = useState(null);
+ const [streamUrl, setStreamUrl] = useState(null);
+ const [isFullOverview, setIsFullOverview] = useState(false);
+ const [subtitles, setSubtitles] = useState([]);
+ const [thumbnail, setThumbnail] = useState(null);
+ const [intro, setIntro] = useState(null);
+ const [outro, setOutro] = useState(null);
+ const [episodeId, setEpisodeId] = useState(null);
+ const [activeEpisodeNum, setActiveEpisodeNum] = useState(null);
+ const [activeServerId, setActiveServerId] = useState(null);
+ const [activeServerType, setActiveServerType] = useState(null);
+ const [activeServerName, setActiveServerName] = useState(null);
+ const [serverLoading, setServerLoading] = useState(true);
+ const [nextEpisodeSchedule, setNextEpisodeSchedule] = useState(null);
+ const isServerFetchInProgress = useRef(false);
+ const isStreamFetchInProgress = useRef(false);
+
+ useEffect(() => {
+ setEpisodes(null);
+ setEpisodeId(null);
+ setActiveEpisodeNum(null);
+ setServers(null);
+ setActiveServerId(null);
+ setStreamInfo(null);
+ setStreamUrl(null);
+ setSubtitles([]);
+ setThumbnail(null);
+ setIntro(null);
+ setOutro(null);
+ setBuffering(true);
+ setServerLoading(true);
+ setError(null);
+ setAnimeInfo(null);
+ setSeasons(null);
+ setTotalEpisodes(null);
+ setAnimeInfoLoading(true);
+ isServerFetchInProgress.current = false;
+ isStreamFetchInProgress.current = false;
+ }, [animeId]);
+
+ useEffect(() => {
+ const fetchInitialData = async () => {
+ try {
+ setAnimeInfoLoading(true);
+ const [animeData, episodesData] = await Promise.all([
+ getAnimeInfo(animeId, false),
+ getEpisodes(animeId),
+ ]);
+ setAnimeInfo(animeData?.data);
+ setSeasons(animeData?.seasons);
+ setEpisodes(episodesData?.episodes);
+ setTotalEpisodes(episodesData?.totalEpisodes);
+ const newEpisodeId =
+ initialEpisodeId ||
+ (episodesData?.episodes?.length > 0
+ ? episodesData.episodes[0].id.match(/ep=(\d+)/)?.[1]
+ : null);
+ setEpisodeId(newEpisodeId);
+ } catch (err) {
+ console.error("Error fetching initial data:", err);
+ setError(err.message || "An error occurred.");
+ } finally {
+ setAnimeInfoLoading(false);
+ }
+ };
+ fetchInitialData();
+ }, [animeId]);
+
+ useEffect(() => {
+ const fetchNextEpisodeSchedule = async () => {
+ try {
+ const data = await getNextEpisodeSchedule(animeId);
+ setNextEpisodeSchedule(data);
+ } catch (err) {
+ console.error("Error fetching next episode schedule:", err);
+ }
+ };
+ fetchNextEpisodeSchedule();
+ }, [animeId]);
+
+ useEffect(() => {
+ if (!episodes || !episodeId) {
+ setActiveEpisodeNum(null);
+ return;
+ }
+ const activeEpisode = episodes.find((episode) => {
+ const match = episode.id.match(/ep=(\d+)/);
+ return match && match[1] === episodeId;
+ });
+ const newActiveEpisodeNum = activeEpisode ? activeEpisode.episode_no : null;
+ if (activeEpisodeNum !== newActiveEpisodeNum) {
+ setActiveEpisodeNum(newActiveEpisodeNum);
+ }
+ }, [episodeId, episodes]);
+
+ useEffect(() => {
+ if (!episodeId || !episodes || isServerFetchInProgress.current) return;
+
+ const fetchServers = async () => {
+ isServerFetchInProgress.current = true;
+ setServerLoading(true);
+ try {
+ const data = await getServers(animeId, episodeId);
+ console.log(data);
+
+ const filteredServers = data?.filter(
+ (server) =>
+ server.serverName === "HD-1" ||
+ server.serverName === "HD-2" ||
+ server.serverName === "HD-3"
+ );
+ if (filteredServers.some((s) => s.type === "sub")) {
+ filteredServers.push({
+ type: "sub",
+ data_id: "69696969",
+ server_id: "41",
+ serverName: "HD-4",
+ });
+ }
+ if (filteredServers.some((s) => s.type === "dub")) {
+ filteredServers.push({
+ type: "dub",
+ data_id: "96969696",
+ server_id: "42",
+ serverName: "HD-4",
+ });
+ }
+ const savedServerName = localStorage.getItem("server_name");
+ const savedServerType = localStorage.getItem("server_type");
+ let initialServer =
+ data.find(
+ (s) =>
+ s.serverName === savedServerName && s.type === savedServerType
+ ) ||
+ data.find((s) => s.serverName === savedServerName) ||
+ data.find((s) => s.type === savedServerType) ||
+ data.find(
+ (s) => s.serverName === "HD-1" && s.type === savedServerType
+ ) ||
+ data.find(
+ (s) => s.serverName === "HD-2" && s.type === savedServerType
+ ) ||
+ data.find(
+ (s) => s.serverName === "HD-3" && s.type === savedServerType
+ ) ||
+ data.find(
+ (s) => s.serverName === "HD-4" && s.type === savedServerType
+ ) ||
+ filteredServers[0];
+ setServers(filteredServers);
+ setActiveServerType(initialServer?.type);
+ setActiveServerName(initialServer?.serverName);
+ setActiveServerId(initialServer?.data_id);
+ } catch (error) {
+ console.error("Error fetching servers:", error);
+ setError(error.message || "An error occurred.");
+ } finally {
+ setServerLoading(false);
+ isServerFetchInProgress.current = false;
+ }
+ };
+ fetchServers();
+ }, [episodeId, episodes]);
+ // Fetch stream info only when episodeId, activeServerId, and servers are ready
+ useEffect(() => {
+ if (
+ !episodeId ||
+ !activeServerId ||
+ !servers ||
+ isServerFetchInProgress.current ||
+ isStreamFetchInProgress.current
+ )
+ return;
+ if (
+ (activeServerName?.toLowerCase() === "hd-1"
+ || activeServerName?.toLowerCase() === "hd-2"|| activeServerName?.toLowerCase() === "hd-3"|| activeServerName?.toLowerCase() === "hd-4")
+ &&
+ !serverLoading
+ ) {
+ setBuffering(false);
+ return;
+ }
+ const fetchStreamInfo = async () => {
+ isStreamFetchInProgress.current = true;
+ setBuffering(true);
+ try {
+ const server = servers.find((srv) => srv.data_id === activeServerId);
+ if (server) {
+ const data = await getStreamInfo(
+ animeId,
+ episodeId,
+ server.serverName.toLowerCase(),
+ server.type.toLowerCase()
+ );
+ setStreamInfo(data);
+ setStreamUrl(data?.streamingLink?.link?.file || null);
+ setIntro(data?.streamingLink?.intro || null);
+ setOutro(data?.streamingLink?.outro || null);
+ const subtitles =
+ data?.streamingLink?.tracks
+ ?.filter((track) => track.kind === "captions")
+ .map(({ file, label }) => ({ file, label })) || [];
+ setSubtitles(subtitles);
+ const thumbnailTrack = data?.streamingLink?.tracks?.find(
+ (track) => track.kind === "thumbnails" && track.file
+ );
+ if (thumbnailTrack) setThumbnail(thumbnailTrack.file);
+ } else {
+ setError("No server found with the activeServerId.");
+ }
+ } catch (err) {
+ console.error("Error fetching stream info:", err);
+ setError(err.message || "An error occurred.");
+ } finally {
+ setBuffering(false);
+ isStreamFetchInProgress.current = false;
+ }
+ };
+ fetchStreamInfo();
+ }, [episodeId, activeServerId, servers]);
+
+ return {
+ error,
+ buffering,
+ serverLoading,
+ streamInfo,
+ animeInfo,
+ episodes,
+ nextEpisodeSchedule,
+ animeInfoLoading,
+ totalEpisodes,
+ seasons,
+ servers,
+ streamUrl,
+ isFullOverview,
+ setIsFullOverview,
+ subtitles,
+ thumbnail,
+ intro,
+ outro,
+ episodeId,
+ setEpisodeId,
+ activeEpisodeNum,
+ setActiveEpisodeNum,
+ activeServerId,
+ setActiveServerId,
+ activeServerType,
+ setActiveServerType,
+ activeServerName,
+ setActiveServerName,
+ };
+};
diff --git a/src/hooks/useWatchControl.js b/src/hooks/useWatchControl.js
new file mode 100644
index 0000000..6ae19f9
--- /dev/null
+++ b/src/hooks/useWatchControl.js
@@ -0,0 +1,34 @@
+import { useState, useEffect } from "react";
+
+export default function useWatchControl() {
+ const [autoPlay, setAutoPlay] = useState(
+ () => JSON.parse(localStorage.getItem("autoPlay")) || false
+ );
+ const [autoSkipIntro, setAutoSkipIntro] = useState(
+ () => JSON.parse(localStorage.getItem("autoSkipIntro")) || false
+ );
+ const [autoNext, setAutoNext] = useState(
+ () => JSON.parse(localStorage.getItem("autoNext")) || false
+ );
+
+ useEffect(() => {
+ localStorage.setItem("autoPlay", JSON.stringify(autoPlay));
+ }, [autoPlay]);
+
+ useEffect(() => {
+ localStorage.setItem("autoSkipIntro", JSON.stringify(autoSkipIntro));
+ }, [autoSkipIntro]);
+
+ useEffect(() => {
+ localStorage.setItem("autoNext", JSON.stringify(autoNext));
+ }, [autoNext]);
+
+ return {
+ autoPlay,
+ setAutoPlay,
+ autoSkipIntro,
+ setAutoSkipIntro,
+ autoNext,
+ setAutoNext,
+ };
+}
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..765f332
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,126 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #201f31;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+body {
+ overflow-y: scroll;
+}
+.scrollbar-visible {
+ scrollbar-width: auto;
+ scrollbar-color: #888 #333;
+}
+.scrollbar-visible::-webkit-scrollbar {
+ width: 20px;
+}
+
+.scrollbar-visible::-webkit-scrollbar-thumb {
+ background-color: #888;
+}
+
+.scrollbar-visible::-webkit-scrollbar-track {
+ background: black;
+}
+.scrollbar-hide {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
+::-webkit-scrollbar {
+ width: 16px;
+}
+
+::-webkit-scrollbar-track {
+ background: #23222c;
+}
+
+::-webkit-scrollbar-thumb {
+ background: #65646a;
+}
+.scrollbar-hide::-webkit-scrollbar {
+ display: none;
+}
+
+.is-visible {
+ opacity: 1;
+}
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 240 10% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 240 10% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 240 10% 3.9%;
+ --primary: 240 5.9% 10%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 240 4.8% 95.9%;
+ --secondary-foreground: 240 5.9% 10%;
+ --muted: 240 4.8% 95.9%;
+ --muted-foreground: 240 3.8% 46.1%;
+ --accent: 240 4.8% 95.9%;
+ --accent-foreground: 240 5.9% 10%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 240 5.9% 90%;
+ --input: 240 5.9% 90%;
+ --ring: 240 10% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+ }
+ .dark {
+ --background: 240 10% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 240 10% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 240 10% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 240 5.9% 10%;
+ --secondary: 240 3.7% 15.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 240 3.7% 15.9%;
+ --muted-foreground: 240 5% 64.9%;
+ --accent: 240 3.7% 15.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 240 3.7% 15.9%;
+ --input: 240 3.7% 15.9%;
+ --ring: 240 4.9% 83.9%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
+}
+.dot {
+ width: 4px;
+ height: 4px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.3);
+ display: inline-block;
+}
diff --git a/src/lib/api.js b/src/lib/api.js
deleted file mode 100644
index 1a6081f..0000000
--- a/src/lib/api.js
+++ /dev/null
@@ -1,858 +0,0 @@
-// Use absolute URL for server components and relative URL for client components
-const isServer = typeof window === 'undefined';
-const API_BASE_URL = isServer
- ? process.env.ANIWATCH_API // Use environment variable with fallback
- : "/api/v2/hianime"; // Use relative URL for client-side
-
-// Common headers for all API requests
-const API_HEADERS = {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- 'Origin': 'https://hianime.to',
- 'Referer': 'https://hianime.to/',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
-};
-
-export const fetchRecentEpisodes = async (page = 1) => {
- try {
- const response = await fetch(`${API_BASE_URL}/category/recently-updated?page=${page}`, {
- headers: API_HEADERS,
- credentials: 'omit'
- });
- if (!response.ok) throw new Error('Failed to fetch recent episodes');
- const data = await response.json();
- return {
- results: data.data.animes.map(anime => ({
- id: anime.id,
- name: anime.name,
- poster: anime.poster,
- type: anime.type,
- episodes: {
- sub: anime.episodes?.sub || 0,
- dub: anime.episodes?.dub || 0
- }
- })) || [],
- currentPage: data.data.currentPage,
- hasNextPage: data.data.hasNextPage
- };
- } catch (error) {
- console.error('Error fetching recent episodes:', error);
- return { results: [] };
- }
-};
-
-export const fetchTopAiring = async (page = 1) => {
- try {
- const response = await fetch(`${API_BASE_URL}/category/top-airing?page=${page}`);
- if (!response.ok) throw new Error('Failed to fetch top airing');
- const data = await response.json();
- return {
- results: data.data.animes.map(anime => ({
- id: anime.id,
- name: anime.name,
- poster: anime.poster,
- type: anime.type,
- episodes: {
- sub: anime.episodes?.sub || 0,
- dub: anime.episodes?.dub || 0
- }
- })) || [],
- currentPage: data.data.currentPage,
- hasNextPage: data.data.hasNextPage
- };
- } catch (error) {
- console.error('Error fetching top airing:', error);
- return { results: [] };
- }
-};
-
-export const fetchMostPopular = async (page = 1) => {
- try {
- const response = await fetch(`${API_BASE_URL}/category/most-popular?page=${page}`);
- if (!response.ok) throw new Error('Failed to fetch most popular');
- const data = await response.json();
- return {
- results: data.data.animes.map(anime => ({
- id: anime.id,
- name: anime.name,
- poster: anime.poster,
- type: anime.type,
- episodes: {
- sub: anime.episodes?.sub || 0,
- dub: anime.episodes?.dub || 0
- }
- })) || [],
- currentPage: data.data.currentPage,
- hasNextPage: data.data.hasNextPage
- };
- } catch (error) {
- console.error('Error fetching most popular:', error);
- return { results: [] };
- }
-};
-
-export const fetchMostFavorite = async (page = 1) => {
- try {
- const response = await fetch(`${API_BASE_URL}/category/most-favorite?page=${page}`);
- if (!response.ok) throw new Error('Failed to fetch most favorite');
- const data = await response.json();
- return {
- results: data.data.animes || [],
- currentPage: data.data.currentPage,
- hasNextPage: data.data.hasNextPage
- };
- } catch (error) {
- console.error('Error fetching most favorite:', error);
- return { results: [] };
- }
-};
-
-export const fetchLatestCompleted = async (page = 1) => {
- try {
- const response = await fetch(`${API_BASE_URL}/category/completed?page=${page}`);
- if (!response.ok) throw new Error('Failed to fetch latest completed');
- const data = await response.json();
- return {
- results: data.data.animes.map(anime => ({
- id: anime.id,
- name: anime.name,
- poster: anime.poster,
- type: anime.type,
- episodes: {
- sub: anime.episodes?.sub || 0,
- dub: anime.episodes?.dub || 0
- }
- })) || [],
- currentPage: data.data.currentPage,
- hasNextPage: data.data.hasNextPage
- };
- } catch (error) {
- console.error('Error fetching latest completed:', error);
- return { results: [] };
- }
-};
-
-export const fetchTopUpcoming = async (page = 1) => {
- try {
- const response = await fetch(`${API_BASE_URL}/category/top-upcoming?page=${page}`);
- if (!response.ok) throw new Error('Failed to fetch top upcoming');
- const data = await response.json();
- return {
- results: data.data.animes || [],
- currentPage: data.data.currentPage,
- hasNextPage: data.data.hasNextPage
- };
- } catch (error) {
- console.error('Error fetching top upcoming:', error);
- return { results: [] };
- }
-};
-
-export const fetchTrending = async () => {
- try {
- const response = await fetch(`${API_BASE_URL}/home`);
- if (!response.ok) throw new Error('Failed to fetch trending anime');
- const data = await response.json();
-
- // Map the trending animes to match the TrendingList component's expected format
- const trendingAnimes = (data.data.trendingAnimes || []).map(anime => ({
- id: anime.id,
- title: anime.name,
- image: anime.poster,
- rank: anime.rank
- }));
-
- return {
- results: trendingAnimes
- };
- } catch (error) {
- console.error('Error fetching trending anime:', error);
- return { results: [] };
- }
-};
-
-export const fetchAnimeInfo = async (id) => {
- try {
- if (!id) {
- return null;
- }
-
- const encodedId = encodeURIComponent(id);
- const url = `${API_BASE_URL}/anime/${encodedId}`;
-
- // Server-side fetch doesn't need credentials or mode settings
- const requestOptions = {
- method: 'GET',
- headers: API_HEADERS,
- };
-
- const response = await fetch(url, requestOptions);
-
- // Handle failed requests gracefully
- if (!response.ok) {
- return createFallbackAnimeData(id);
- }
-
- // Parse the JSON response
- const data = await response.json();
-
- // Check if the response is successful
- if (!data.success && data.status !== 200) {
- return createFallbackAnimeData(id);
- }
-
- // The data structure might be nested in different ways depending on the API
- const responseData = data.data || data;
-
- // Extract the anime data from the response
- const animeData = responseData.anime;
-
- if (!animeData) {
- return createFallbackAnimeData(id);
- }
-
- // Create mock characterVoiceActor data if missing
- if (!animeData.info?.characterVoiceActor || !Array.isArray(animeData.info?.characterVoiceActor) || animeData.info?.characterVoiceActor.length === 0) {
-
- // Ensure the info object exists
- if (!animeData.info) animeData.info = {};
-
- // Add mock data for the characters and voice actors
- animeData.info.characterVoiceActor = [
- {
- character: {
- id: "character-1",
- name: animeData.info?.name ? `${animeData.info.name} Main Character` : "Main Character",
- poster: animeData.info?.poster || "https://via.placeholder.com/150",
- cast: "Main"
- },
- voiceActor: {
- id: "voice-actor-1",
- name: "Voice Actor",
- poster: "https://via.placeholder.com/150",
- cast: "Japanese"
- }
- },
- {
- character: {
- id: "character-2",
- name: "Supporting Character",
- poster: "https://via.placeholder.com/150",
- cast: "Supporting"
- },
- voiceActor: {
- id: "voice-actor-2",
- name: "Voice Actor 2",
- poster: "https://via.placeholder.com/150",
- cast: "Japanese"
- }
- }
- ];
- }
-
- // Check for characterVoiceActor data
- console.log('[API Debug] charactersVoiceActors:',
- animeData.info?.charactersVoiceActors
- ? `Found ${animeData.info.charactersVoiceActors.length} characters`
- : 'Missing charactersVoiceActors data'
- );
-
- // Check the raw API response structure for characterVoiceActor
- if (animeData.info) {
- console.log('[API Debug] Raw charactersVoiceActors type:',
- animeData.info.charactersVoiceActors ?
- typeof animeData.info.charactersVoiceActors + ' ' +
- (Array.isArray(animeData.info.charactersVoiceActors) ? 'is Array' : 'not Array') :
- 'undefined'
- );
- }
-
- // Return the complete data structure as expected by the components
- return {
- info: {
- id: id,
- name: animeData.info?.name || '',
- jname: animeData.info?.jname || '',
- poster: animeData.info?.poster || '',
- description: animeData.info?.description || '',
- stats: {
- rating: animeData.info?.stats?.rating || '0',
- quality: animeData.info?.stats?.quality || 'HD',
- episodes: animeData.info?.stats?.episodes || { sub: 0, dub: 0 },
- type: animeData.info?.stats?.type || 'TV',
- duration: animeData.info?.stats?.duration || 'Unknown'
- },
- promotionalVideos: Array.isArray(animeData.info?.promotionalVideos)
- ? animeData.info.promotionalVideos
- : [],
- characterVoiceActor: (() => {
- // Explicit validation of charactersVoiceActors data (note the "s" in characters)
- const charData = animeData.info?.charactersVoiceActors;
- if (!charData) {
- return [];
- }
- if (!Array.isArray(charData)) {
- return [];
- }
-
- // Validate each item in the array to ensure it has the required structure
- return charData.filter(item => {
- if (!item) return false;
- if (!item.character || !item.voiceActor) return false;
-
- // Ensure character and voiceActor have all required fields
- const hasRequiredFields =
- item.character.id &&
- item.character.name &&
- item.character.poster &&
- item.voiceActor.id &&
- item.voiceActor.name &&
- item.voiceActor.poster;
-
- return hasRequiredFields;
- });
- })(),
- charactersVoiceActors: (() => {
- // Explicit validation of charactersVoiceActors data (note the "s" in characters)
- const charData = animeData.info?.charactersVoiceActors;
- if (!charData) {
- return [];
- }
- if (!Array.isArray(charData)) {
- return [];
- }
-
- // Validate each item in the array to ensure it has the required structure
- return charData.filter(item => {
- if (!item) return false;
- if (!item.character || !item.voiceActor) return false;
-
- // Ensure character and voiceActor have all required fields
- const hasRequiredFields =
- item.character.id &&
- item.character.name &&
- item.character.poster &&
- item.voiceActor.id &&
- item.voiceActor.name &&
- item.voiceActor.poster;
-
- return hasRequiredFields;
- });
- })()
- },
- moreInfo: animeData.moreInfo || {
- aired: '',
- genres: [],
- status: 'Unknown',
- studios: '',
- duration: ''
- },
- relatedAnime: Array.isArray(responseData.relatedAnimes)
- ? responseData.relatedAnimes
- : [],
- recommendations: Array.isArray(responseData.recommendedAnimes)
- ? responseData.recommendedAnimes
- : [],
- mostPopular: Array.isArray(responseData.mostPopularAnimes)
- ? responseData.mostPopularAnimes
- : [],
- seasons: Array.isArray(responseData.seasons)
- ? responseData.seasons
- : []
- };
- } catch (error) {
- return createFallbackAnimeData(id);
- }
-};
-
-// Helper function to create fallback anime data when the API fails
-function createFallbackAnimeData(id) {
- // Create the mock character data to be reused
- const mockCharacterData = [
- {
- character: {
- id: "character-1",
- name: "Main Character",
- poster: "https://via.placeholder.com/150",
- cast: "Main"
- },
- voiceActor: {
- id: "voice-actor-1",
- name: "Voice Actor",
- poster: "https://via.placeholder.com/150",
- cast: "Japanese"
- }
- },
- {
- character: {
- id: "character-2",
- name: "Supporting Character",
- poster: "https://via.placeholder.com/150",
- cast: "Supporting"
- },
- voiceActor: {
- id: "voice-actor-2",
- name: "Voice Actor 2",
- poster: "https://via.placeholder.com/150",
- cast: "Japanese"
- }
- }
- ];
-
- return {
- info: {
- id: id,
- name: 'Anime Information Temporarily Unavailable',
- jname: '',
- poster: 'https://via.placeholder.com/225x318?text=Anime',
- description: 'The anime data could not be loaded at this time. Please try again later.',
- stats: {
- rating: '0',
- quality: 'HD',
- episodes: {
- sub: 0,
- dub: 0
- },
- type: 'Unknown',
- duration: 'Unknown'
- },
- promotionalVideos: [],
- // Use same property name as in fetchAnimeInfo to match what the frontend expects
- characterVoiceActor: mockCharacterData,
- // Also include the API property name for compatibility
- charactersVoiceActors: mockCharacterData
- },
- moreInfo: {
- aired: '',
- genres: ['Action', 'Adventure'],
- status: 'Unknown',
- studios: '',
- duration: ''
- },
- relatedAnime: [],
- recommendations: [],
- mostPopular: [],
- seasons: []
- };
-}
-
-export const fetchAnimeEpisodes = async (animeId) => {
- try {
- if (!animeId) {
- console.error('Invalid anime ID provided');
- return { episodes: [] };
- }
-
- const apiUrl = `${API_BASE_URL}/anime/${encodeURIComponent(animeId)}/episodes`;
- console.log(`[API Call] Fetching episodes for anime: ${animeId}`);
-
- const response = await fetch(apiUrl, {
- headers: API_HEADERS,
- credentials: 'omit'
- });
-
- if (!response.ok) {
- throw new Error(`Failed to fetch episodes: ${response.status} ${response.statusText}`);
- }
-
- const data = await response.json();
- console.log('[API Response] Episodes count:', data?.data?.episodes?.length || 0);
-
- if (!data || !data.data) {
- console.error('[API Error] Empty response received for episodes');
- return { episodes: [] };
- }
-
- // API returns episodes with episode.id in the format (animeId?ep=episodeNumber)
- // We use this id directly for all episode operations
-
- return {
- episodes: data.data.episodes || [],
- totalEpisodes: data.data.totalEpisodes || 0
- };
- } catch (error) {
- console.error('Error fetching anime episodes:', error);
- return { episodes: [] };
- }
-};
-
-export const fetchEpisodeServers = async (episodeId) => {
- try {
- if (!episodeId || episodeId === 'undefined') {
- console.error('Invalid episode ID provided');
- return { servers: [] };
- }
-
- console.log(`[API] Processing episode ID: ${episodeId}`);
-
- // Use the episode.id directly - it's in the format "animeId?ep=episodeNumber"
- const apiUrl = `${API_BASE_URL}/episode/servers?animeEpisodeId=${encodeURIComponent(episodeId)}`;
- console.log(`[API Call] Fetching servers from: ${apiUrl}`);
-
- const response = await fetch(apiUrl, {
- headers: API_HEADERS,
- credentials: 'omit'
- });
-
- if (!response.ok) {
- throw new Error(`Failed to fetch episode servers: ${response.status} ${response.statusText}`);
- }
-
- const data = await response.json();
- console.log('[API Response] Episode servers:', data);
-
- if (!data || !data.success || !data.data) {
- console.error('[API Error] Empty response received for episode servers');
- return { servers: [] };
- }
-
- // Get all servers from the response (sub, dub, raw)
- // The response has separate arrays for sub, dub, and raw servers
- const subServers = data.data.sub || [];
- const dubServers = data.data.dub || [];
- const rawServers = data.data.raw || [];
-
- // Combine all servers into a single array for easier handling
- const allServers = [
- ...subServers.map(s => ({ ...s, category: 'sub' })),
- ...dubServers.map(s => ({ ...s, category: 'dub' })),
- ...rawServers.map(s => ({ ...s, category: 'raw' }))
- ];
-
- return {
- servers: allServers,
- episodeId: data.data.episodeId,
- episodeNo: data.data.episodeNo,
- hasSubServers: subServers.length > 0,
- hasDubServers: dubServers.length > 0,
- hasRawServers: rawServers.length > 0
- };
- } catch (error) {
- console.error('Error fetching episode servers:', error);
- return { servers: [] };
- }
-};
-
-export const fetchEpisodeSources = async (episodeId, dub = false, server = 'hd-2') => {
- try {
- if (!episodeId || episodeId === 'undefined') {
- console.error('Invalid episode ID provided');
- return { sources: [] };
- }
-
- console.log(`[API] Processing episode ID for sources: ${episodeId}`);
-
- // Use the episode.id directly - it's in the format "animeId?ep=episodeNumber"
- const category = dub ? 'dub' : 'sub';
- const serverName = server || 'hd-2'; // Default to hd-2 if server is null or empty
- const apiUrl = `${API_BASE_URL}/episode/sources?animeEpisodeId=${encodeURIComponent(episodeId)}&category=${category}&server=${serverName}`;
- console.log(`[API Call] Fetching sources from: ${apiUrl}`);
-
- const response = await fetch(apiUrl, {
- headers: API_HEADERS,
- credentials: 'omit'
- });
-
- // Log raw response details for debugging
- console.log('[API Response] Status:', response.status, response.statusText);
- console.log('[API Response] Headers:', [...response.headers.entries()]);
-
- if (!response.ok) {
- throw new Error(`Failed to fetch episode sources: ${response.status} ${response.statusText}`);
- }
-
- const data = await response.json();
- console.log('[API Response] Raw data:', JSON.stringify(data, null, 2));
-
- if (!data) {
- console.error('[API Error] No data received - response was null or undefined');
- return { sources: [] };
- }
-
- // Check if response is valid (status 200 or success flag)
- // Some API responses use success flag, others use status code
- const isValidResponse = (data.success === true) || (data.status === 200);
- if (!isValidResponse) {
- console.error('[API Error] Response indicates failure - invalid status or success flag');
- return { sources: [] };
- }
-
- // Get the data object from either data.data (new format) or data (old format)
- const responseData = data.data || data;
-
- if (!responseData) {
- console.error('[API Error] Empty data object in response');
- return { sources: [] };
- }
-
- if (!responseData.sources || responseData.sources.length === 0) {
- console.error('[API Error] No sources found in response data');
- return { sources: [] };
- }
-
- console.log('[API Success] Found sources:', responseData.sources.map(s => ({
- url: s.url.substring(0, 50) + '...',
- quality: s.quality,
- isM3U8: s.isM3U8
- })));
-
- return {
- sources: responseData.sources || [],
- headers: responseData.headers || { "Referer": "https://hianime.to/" },
- subtitles: responseData.tracks || responseData.subtitles || [],
- anilistID: responseData.anilistID || null,
- malID: responseData.malID || null,
- intro: responseData.intro || null,
- outro: responseData.outro || null
- };
- } catch (error) {
- console.error('Error fetching episode sources:', error);
- return { sources: [] };
- }
-};
-
-export const searchAnime = async (query, page = 1, filters = {}) => {
- try {
- // Build the URL with query and page parameters
- let url = `${API_BASE_URL}/search?q=${encodeURIComponent(query)}&page=${page}`;
-
- // Add any additional filters to the URL
- if (filters && Object.keys(filters).length > 0) {
- Object.entries(filters).forEach(([key, value]) => {
- if (value) {
- url += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
- }
- });
- }
-
- console.log("[API] Searching anime at:", url);
-
- // Make the request
- const response = await fetch(url, {
- headers: API_HEADERS,
- next: { revalidate: 60 }, // Cache for 60 seconds
- cache: 'no-cache' // Don't use browser cache
- });
-
- if (!response.ok) {
- console.error(`[API] Search error: ${response.status} ${response.statusText}`);
- throw new Error(`Failed to search anime: ${response.status} ${response.statusText}`);
- }
-
- const data = await response.json();
- console.log("[API] Search response:", data);
-
- // Check if the response is valid and matches expected format
- if (!data.data) {
- console.error('[API] Invalid response format from search API:', data);
- return {
- results: [],
- mostPopularResults: [],
- currentPage: page,
- hasNextPage: false,
- searchQuery: query,
- searchFilters: filters
- };
- }
-
- return {
- results: data.data.animes || [],
- mostPopularResults: data.data.mostPopularAnimes || [],
- currentPage: data.data.currentPage || page,
- hasNextPage: data.data.hasNextPage || false,
- totalPages: data.data.totalPages || 1,
- searchQuery: data.data.searchQuery || query,
- searchFilters: data.data.searchFilters || filters
- };
- } catch (error) {
- console.error('[API] Error searching anime:', error);
- return {
- results: [],
- mostPopularResults: [],
- currentPage: page,
- hasNextPage: false,
- searchQuery: query,
- searchFilters: filters
- };
- }
-};
-
-export const fetchGenres = async () => {
- try {
- const response = await fetch(`${API_BASE_URL}/home`);
- if (!response.ok) throw new Error('Failed to fetch genres');
- const data = await response.json();
- return data.data.genres || [];
- } catch (error) {
- console.error('Error fetching genres:', error);
- return [];
- }
-};
-
-export const fetchGenreAnime = async (genre, page = 1) => {
- try {
- const response = await fetch(`${API_BASE_URL}/genre/${encodeURIComponent(genre)}?page=${page}`);
- if (!response.ok) throw new Error('Failed to fetch genre anime');
- const data = await response.json();
- return {
- results: data.data.animes || [],
- currentPage: data.data.currentPage,
- hasNextPage: data.data.hasNextPage
- };
- } catch (error) {
- console.error('Error fetching genre anime:', error);
- return { results: [] };
- }
-};
-
-export const fetchSearchSuggestions = async (query) => {
- try {
- console.log("[API] Fetching search suggestions for:", query);
- const response = await fetch(`${API_BASE_URL}/search/suggestion?q=${encodeURIComponent(query)}`, {
- headers: API_HEADERS,
- next: { revalidate: 60 }, // Cache for 60 seconds
- cache: 'no-cache' // Don't use browser cache
- });
-
- if (!response.ok) {
- console.error(`[API] Search suggestions error: ${response.status} ${response.statusText}`);
- throw new Error(`Failed to fetch search suggestions: ${response.status} ${response.statusText}`);
- }
-
- const data = await response.json();
- console.log("[API] Search suggestions response:", data);
-
- if (!data.data) {
- console.error('[API] Invalid response format from search suggestions API:', data);
- return [];
- }
-
- // Map the suggestions to include required fields
- return (data.data.suggestions || []).map(suggestion => ({
- id: suggestion.id,
- title: suggestion.name || suggestion.title,
- image: suggestion.poster || suggestion.image,
- // Include additional fields that might be useful for display
- type: suggestion.type || 'ANIME',
- jname: suggestion.jname || ''
- }));
- } catch (error) {
- console.error('[API] Error fetching search suggestions:', error);
- return [];
- }
-};
-
-export const fetchSchedule = async () => {
- try {
- const today = new Date().toISOString().split('T')[0];
- const response = await fetch(`${API_BASE_URL}/schedule?date=${today}`);
- if (!response.ok) throw new Error('Failed to fetch schedule');
- const data = await response.json();
-
- // Map the scheduled animes to include all required fields
- const scheduledAnimes = data.data.scheduledAnimes || [];
- return {
- scheduledAnimes: scheduledAnimes.map(anime => ({
- id: anime.id,
- time: anime.time,
- name: anime.name,
- jname: anime.jname,
- airingTimestamp: anime.airingTimestamp,
- secondsUntilAiring: anime.secondsUntilAiring
- }))
- };
- } catch (error) {
- console.error('Error fetching schedule:', error);
- return { scheduledAnimes: [] };
- }
-};
-
-export const fetchSpotlightAnime = async (limit = 8) => {
- try {
- const response = await fetch(`${API_BASE_URL}/home`);
- if (!response.ok) throw new Error('Failed to fetch spotlight anime');
- const data = await response.json();
-
- // Map the spotlight animes to match the exact schema from the API
- const spotlightAnimes = (data.data.spotlightAnimes || []).map(anime => ({
- id: anime.id,
- name: anime.name,
- jname: anime.jname,
- poster: anime.poster,
- banner: anime.banner,
- description: anime.description,
- rank: anime.rank,
- otherInfo: anime.otherInfo || [],
- episodes: {
- sub: anime.episodes?.sub || 0,
- dub: anime.episodes?.dub || 0
- }
- }));
-
- return spotlightAnimes.slice(0, limit);
- } catch (error) {
- console.error('Error fetching spotlight anime:', error);
- return [];
- }
-};
-
-// Top 10 sections with proper data mapping
-export const fetchTopToday = async () => {
- try {
- const response = await fetch(`${API_BASE_URL}/home`);
- if (!response.ok) throw new Error('Failed to fetch top today');
- const data = await response.json();
-
- // Map the top 10 animes to include all required fields
- return (data.data.top10Animes?.today || []).map(anime => ({
- id: anime.id,
- name: anime.name,
- poster: anime.poster,
- rank: anime.rank,
- episodes: anime.episodes || { sub: 0, dub: 0 }
- }));
- } catch (error) {
- console.error('Error fetching top today:', error);
- return [];
- }
-};
-
-export const fetchTopWeek = async () => {
- try {
- const response = await fetch(`${API_BASE_URL}/home`);
- if (!response.ok) throw new Error('Failed to fetch top week');
- const data = await response.json();
-
- // Map the top 10 animes to include all required fields
- return (data.data.top10Animes?.week || []).map(anime => ({
- id: anime.id,
- name: anime.name,
- poster: anime.poster,
- rank: anime.rank,
- episodes: anime.episodes || { sub: 0, dub: 0 }
- }));
- } catch (error) {
- console.error('Error fetching top week:', error);
- return [];
- }
-};
-
-export const fetchTopMonth = async () => {
- try {
- const response = await fetch(`${API_BASE_URL}/home`);
- if (!response.ok) throw new Error('Failed to fetch top month');
- const data = await response.json();
-
- // Map the top 10 animes to include all required fields
- return (data.data.top10Animes?.month || []).map(anime => ({
- id: anime.id,
- name: anime.name,
- poster: anime.poster,
- rank: anime.rank,
- episodes: anime.episodes || { sub: 0, dub: 0 }
- }));
- } catch (error) {
- console.error('Error fetching top month:', error);
- return [];
- }
-};
\ No newline at end of file
diff --git a/src/main.jsx b/src/main.jsx
new file mode 100644
index 0000000..e5bd98c
--- /dev/null
+++ b/src/main.jsx
@@ -0,0 +1,13 @@
+import { LanguageProvider } from './context/LanguageContext';
+import { createRoot } from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import App from './App.jsx';
+import './index.css';
+
+createRoot(document.getElementById('root')).render(
+
+
+
+
+
+);
diff --git a/src/pages/Home/Home.jsx b/src/pages/Home/Home.jsx
new file mode 100644
index 0000000..789fb6a
--- /dev/null
+++ b/src/pages/Home/Home.jsx
@@ -0,0 +1,82 @@
+import website_name from "@/src/config/website.js";
+import Spotlight from "@/src/components/spotlight/Spotlight.jsx";
+import Trending from "@/src/components/trending/Trending.jsx";
+import Cart from "@/src/components/cart/Cart.jsx";
+import CategoryCard from "@/src/components/categorycard/CategoryCard.jsx";
+import Genre from "@/src/components/genres/Genre.jsx";
+import Topten from "@/src/components/topten/Topten.jsx";
+import Loader from "@/src/components/Loader/Loader.jsx";
+import Error from "@/src/components/error/Error.jsx";
+import { useHomeInfo } from "@/src/context/HomeInfoContext.jsx";
+import Schedule from "@/src/components/schedule/Schedule";
+import ContinueWatching from "@/src/components/continue/ContinueWatching";
+
+function Home() {
+ const { homeInfo, homeInfoLoading, error } = useHomeInfo();
+ if (homeInfoLoading) return ;
+ if (error) return ;
+ if (!homeInfo) return ;
+ return (
+ <>
+
+ >
+ );
+}
+
+export default Home;
diff --git a/src/pages/a2z/AtoZ.jsx b/src/pages/a2z/AtoZ.jsx
new file mode 100644
index 0000000..f2717e6
--- /dev/null
+++ b/src/pages/a2z/AtoZ.jsx
@@ -0,0 +1,118 @@
+import { useEffect, useState } from "react";
+import { useSearchParams, Link } from "react-router-dom";
+import getCategoryInfo from "@/src/utils/getCategoryInfo.utils";
+import CategoryCard from "@/src/components/categorycard/CategoryCard";
+import Loader from "@/src/components/Loader/Loader";
+import Error from "@/src/components/error/Error";
+import PageSlider from "@/src/components/pageslider/PageSlider";
+
+function AtoZ({ path }) {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [categoryInfo, setCategoryInfo] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [totalPages, setTotalPages] = useState(0);
+ const page = parseInt(searchParams.get("page")) || 1;
+ const currentLetter = path.split("/").pop() || "";
+
+ useEffect(() => {
+ const fetchAtoZInfo = async () => {
+ setLoading(true);
+ try {
+ const data = await getCategoryInfo(path, page);
+ setCategoryInfo(data.data);
+ setTotalPages(data.totalPages);
+ setLoading(false);
+ } catch (err) {
+ setError(err);
+ setLoading(false);
+ console.error("Error fetching category info:", err);
+ }
+ };
+ fetchAtoZInfo();
+ window.scrollTo(0, 0);
+ }, [path, page]);
+
+ if (loading) return ;
+ if (error) {
+ return ;
+ }
+ if (!categoryInfo) {
+ return null;
+ }
+ const handlePageChange = (newPage) => {
+ setSearchParams({ page: newPage });
+ };
+
+ return (
+
+
+
+
+ Home
+
+
+
+ A-Z List
+
+
+
+ Sort By Letters
+
+
+ {[
+ "All",
+ "#",
+ "0-9",
+ ...Array.from({ length: 26 }, (_, i) =>
+ String.fromCharCode(65 + i)
+ ),
+ ].map((item, index) => {
+ const linkPath =
+ item.toLowerCase() === "all"
+ ? ""
+ : item === "#"
+ ? "other"
+ : item;
+ const isActive =
+ (currentLetter === "az-list" && item.toLowerCase() === "all") ||
+ (currentLetter === "other" && item === "#") ||
+ currentLetter === item.toLowerCase();
+
+ return (
+
+ {item}
+
+ );
+ })}
+
+
+
+
+ {categoryInfo && categoryInfo.length > 0 && (
+
+ )}
+
+
+
+
+ );
+}
+
+export default AtoZ;
diff --git a/src/pages/animeInfo/AnimeInfo.jsx b/src/pages/animeInfo/AnimeInfo.jsx
new file mode 100644
index 0000000..60b1e4f
--- /dev/null
+++ b/src/pages/animeInfo/AnimeInfo.jsx
@@ -0,0 +1,416 @@
+import getAnimeInfo from "@/src/utils/getAnimeInfo.utils";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faPlay,
+ faClosedCaptioning,
+ faMicrophone,
+} from "@fortawesome/free-solid-svg-icons";
+import { useEffect, useState } from "react";
+import { Link, useNavigate, useParams } from "react-router-dom";
+import website_name from "@/src/config/website";
+import CategoryCard from "@/src/components/categorycard/CategoryCard";
+import Sidecard from "@/src/components/sidecard/Sidecard";
+import Loader from "@/src/components/Loader/Loader";
+import Error from "@/src/components/error/Error";
+import { useLanguage } from "@/src/context/LanguageContext";
+import { useHomeInfo } from "@/src/context/HomeInfoContext";
+import Voiceactor from "@/src/components/voiceactor/Voiceactor";
+
+function InfoItem({ label, value, isProducer = true }) {
+ return (
+ value && (
+
+ {`${label}: `}
+
+ {Array.isArray(value) ? (
+ value.map((item, index) =>
+ isProducer ? (
+ :;,.?/\\|{}[\]`~*_]/g, "")
+ .split(" ")
+ .join("-")
+ .replace(/-+/g, "-")}`}
+ key={index}
+ className="cursor-pointer hover:text-[#ffbade]"
+ >
+ {item}
+ {index < value.length - 1 && ", "}
+
+ ) : (
+
+ {item}
+
+ )
+ )
+ ) : isProducer ? (
+ :;,.?/\\|{}[\]`~*_]/g, "")
+ .split(" ")
+ .join("-")
+ .replace(/-+/g, "-")}`}
+ className="cursor-pointer hover:text-[#ffbade]"
+ >
+ {value}
+
+ ) : (
+ {value}
+ )}
+
+
+ )
+ );
+}
+
+function Tag({ bgColor, index, icon, text }) {
+ return (
+
+ );
+}
+
+function AnimeInfo({ random = false }) {
+ const { language } = useLanguage();
+ const { id: paramId } = useParams();
+ const id = random ? null : paramId;
+ const [isFull, setIsFull] = useState(false);
+ const [animeInfo, setAnimeInfo] = useState(null);
+ const [seasons, setSeasons] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const { homeInfo } = useHomeInfo();
+ const { id: currentId } = useParams();
+ const navigate = useNavigate();
+ useEffect(() => {
+ if (id === "404-not-found-page") {
+ console.log("404 got!");
+ return null;
+ } else {
+ const fetchAnimeInfo = async () => {
+ setLoading(true);
+ try {
+ const data = await getAnimeInfo(id, random);
+ setSeasons(data?.seasons);
+ setAnimeInfo(data.data);
+ } catch (err) {
+ console.error("Error fetching anime info:", err);
+ setError(err);
+ } finally {
+ setLoading(false);
+ }
+ };
+ fetchAnimeInfo();
+ window.scrollTo({ top: 0, behavior: "smooth" });
+ }
+ }, [id, random]);
+ useEffect(() => {
+ if (animeInfo && location.pathname === `/${animeInfo.id}`) {
+ document.title = `Watch ${animeInfo.title} English Sub/Dub online Free on ${website_name}`;
+ }
+ return () => {
+ document.title = `${website_name} | Free anime streaming platform`;
+ };
+ }, [animeInfo]);
+ if (loading) return ;
+ if (error) {
+ return ;
+ }
+ if (!animeInfo) {
+ navigate("/404-not-found-page");
+ return undefined;
+ }
+ const { title, japanese_title, poster, animeInfo: info } = animeInfo;
+ const tags = [
+ {
+ condition: info.tvInfo?.rating,
+ bgColor: "#ffffff",
+ text: info.tvInfo.rating,
+ },
+ {
+ condition: info.tvInfo?.quality,
+ bgColor: "#FFBADE",
+ text: info.tvInfo.quality,
+ },
+ {
+ condition: info.tvInfo?.sub,
+ icon: faClosedCaptioning,
+ bgColor: "#B0E3AF",
+ text: info.tvInfo.sub,
+ },
+ {
+ condition: info.tvInfo?.dub,
+ icon: faMicrophone,
+ bgColor: "#B9E7FF",
+ text: info.tvInfo.dub,
+ },
+ ];
+
+ return (
+ <>
+
+
+
+
+
+ {animeInfo.adultContent && (
+
+ 18+
+
+ )}
+
+
+
+
+ {language === "EN" ? title : japanese_title}
+
+
+ {tags.map(
+ ({ condition, icon, bgColor, text }, index) =>
+ condition && (
+
+ )
+ )}
+
+ {[info.tvInfo?.showType, info.tvInfo?.duration].map(
+ (item, index) =>
+ item && (
+
+ )
+ )}
+
+
+ {animeInfo?.animeInfo?.Status?.toLowerCase() !== "not-yet-aired" ? (
+
+
+
Watch Now
+
+ ) : (
+
+ )}
+ {info?.Overview && (
+
+ {info.Overview.length > 270 ? (
+ <>
+ {isFull
+ ? info.Overview
+ : `${info.Overview.slice(0, 270)}...`}
+ setIsFull(!isFull)}
+ >
+ {isFull ? "- Less" : "+ More"}
+
+ >
+ ) : (
+ info.Overview
+ )}
+
+ )}
+
+ {`${website_name} is the best site to watch `}
+ {title}
+ {` SUB online, or you can even watch `}
+ {title}
+ {` DUB in HD quality.`}
+
+
+
+
+
+ Share Anime
+
+
to your friends
+
+
+
+
+
+
+ {info?.Overview && (
+
+ )}
+ {[
+ { label: "Japanese", value: info?.Japanese },
+ { label: "Synonyms", value: info?.Synonyms },
+ { label: "Aired", value: info?.Aired },
+ { label: "Premiered", value: info?.Premiered },
+ { label: "Duration", value: info?.Duration },
+ { label: "Status", value: info?.Status },
+ { label: "MAL Score", value: info?.["MAL Score"] },
+ ].map(({ label, value }, index) => (
+
+ ))}
+ {info?.Genres && (
+
+
Genres:
+
+ {info.Genres.map((genre, index) => (
+
+ {genre}
+
+ ))}
+
+
+ )}
+ {[
+ { label: "Studios", value: info?.Studios },
+ { label: "Producers", value: info?.Producers },
+ ].map(({ label, value }, index) => (
+
+ ))}
+
+ {`${website_name} is the best site to watch `}
+ {title}
+ {` SUB online, or you can even watch `}
+ {title}
+ {` DUB in HD quality.`}
+
+
+
+
+
+
+ {seasons?.length > 0 && (
+
+
+ More Seasons
+
+
+ {seasons.map((season, index) => (
+
+
+ {season.season}
+
+
+
+
+ ))}
+
+
+ )}
+ {animeInfo?.charactersVoiceActors.length > 0 && (
+
+ )}
+ {animeInfo.recommended_data.length > 0 && (
+
+ )}
+
+
+ {animeInfo.related_data.length > 0 && (
+
+ )}
+ {homeInfo && homeInfo.most_popular && (
+
+ )}
+
+
+ >
+ );
+}
+
+export default AnimeInfo;
diff --git a/src/pages/category/Category.jsx b/src/pages/category/Category.jsx
new file mode 100644
index 0000000..326c61e
--- /dev/null
+++ b/src/pages/category/Category.jsx
@@ -0,0 +1,111 @@
+import { useEffect, useState } from "react";
+import { useSearchParams } from "react-router-dom";
+import getCategoryInfo from "@/src/utils/getCategoryInfo.utils";
+import CategoryCard from "@/src/components/categorycard/CategoryCard";
+import Genre from "@/src/components/genres/Genre";
+import Topten from "@/src/components/topten/Topten";
+import Loader from "@/src/components/Loader/Loader";
+import Error from "@/src/components/error/Error";
+import { useNavigate } from "react-router-dom";
+import { useHomeInfo } from "@/src/context/HomeInfoContext";
+import PageSlider from "@/src/components/pageslider/PageSlider";
+import SidecardLoader from "@/src/components/Loader/Sidecard.loader";
+
+function Category({ path, label }) {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [categoryInfo, setCategoryInfo] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [totalPages, setTotalPages] = useState(0);
+ const page = parseInt(searchParams.get("page")) || 1;
+ const { homeInfo, homeInfoLoading } = useHomeInfo();
+ const navigate = useNavigate();
+ useEffect(() => {
+ const fetchCategoryInfo = async () => {
+ setLoading(true);
+ try {
+ const data = await getCategoryInfo(path, page);
+ setCategoryInfo(data.data);
+ setTotalPages(data.totalPages);
+ setLoading(false);
+ } catch (err) {
+ setError(err);
+ console.error("Error fetching category info:", err);
+ }
+ };
+ fetchCategoryInfo();
+ window.scrollTo(0, 0);
+ }, [path, page]);
+ if (loading) return ;
+ if (error) {
+ navigate("/error-page");
+ return ;
+ }
+ if (!categoryInfo) {
+ navigate("/404-not-found-page");
+ return null;
+ }
+ const handlePageChange = (newPage) => {
+ setSearchParams({ page: newPage });
+ };
+
+ return (
+
+
+
+
+
Share Anime
+
to your friends
+
+
+ {categoryInfo ? (
+
+ {page > totalPages ? (
+
+ You came a long way, go back
+ nothing is here
+
+ ) : (
+
+ {categoryInfo && categoryInfo.length > 0 && (
+
+ )}
+
+
+ )}
+
+ {homeInfoLoading ? (
+
+ ) : (
+ <>
+ {homeInfo && homeInfo.topten && (
+
+ )}
+ {homeInfo?.genres && }
+ >
+ )}
+
+
+ ) : (
+
+ )}
+
+ );
+}
+
+export default Category;
diff --git a/src/pages/search/Search.jsx b/src/pages/search/Search.jsx
new file mode 100644
index 0000000..f52033c
--- /dev/null
+++ b/src/pages/search/Search.jsx
@@ -0,0 +1,74 @@
+import CategoryCard from '@/src/components/categorycard/CategoryCard';
+import Genre from '@/src/components/genres/Genre';
+import CategoryCardLoader from '@/src/components/Loader/CategoryCard.loader';
+import SidecardLoader from '@/src/components/Loader/Sidecard.loader';
+import PageSlider from '@/src/components/pageslider/PageSlider';
+import Sidecard from '@/src/components/sidecard/Sidecard';
+import { useHomeInfo } from '@/src/context/HomeInfoContext';
+import getSearch from '@/src/utils/getSearch.utils';
+import { useEffect, useState } from 'react';
+import { useSearchParams } from 'react-router-dom';
+
+function Search() {
+ const { homeInfo, homeInfoLoading } = useHomeInfo();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const keyword = searchParams.get("keyword");
+ const page = parseInt(searchParams.get("page"), 10) || 1;
+ const [searchData, setSearchData] = useState(null);
+ const [totalPages, setTotalPages] = useState(0);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchSearch = async () => {
+ setLoading(true);
+ try {
+ const data = await getSearch(keyword,page);
+ setSearchData(data.data);
+ setTotalPages(data.totalPage);
+ setLoading(false);
+ } catch (err) {
+ console.error("Error fetching anime info:", err);
+ setError(err);
+ setLoading(false);
+ }
+ };
+ fetchSearch();
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ }, [keyword, page]);
+
+ const handlePageChange = (newPage) => {
+ setSearchParams({ keyword, page: newPage });
+ };
+ return (
+
+ {loading ? (
+
+ ) : page > totalPages ?
You came a long way, go back nothing is here
: searchData && searchData.length > 0 ? (
+
+ ) : error ?
Couldn't get search result please try again
: (
+
{`Search results for: ${keyword}`}
+ )}
+
+ {homeInfoLoading ? (
+
+ ) : (
+ <>
+ {homeInfo?.most_popular && }
+ {homeInfo?.genres && }
+ >
+ )}
+
+
+ );
+}
+
+export default Search;
diff --git a/src/pages/watch/Watch.jsx b/src/pages/watch/Watch.jsx
new file mode 100644
index 0000000..e9ee8c0
--- /dev/null
+++ b/src/pages/watch/Watch.jsx
@@ -0,0 +1,541 @@
+/* eslint-disable react/prop-types */
+import { useEffect, useRef, useState } from "react";
+import { useLocation, useParams, Link, useNavigate } from "react-router-dom";
+import { useLanguage } from "@/src/context/LanguageContext";
+import { useHomeInfo } from "@/src/context/HomeInfoContext";
+import { useWatch } from "@/src/hooks/useWatch";
+import BouncingLoader from "@/src/components/ui/bouncingloader/Bouncingloader";
+import IframePlayer from "@/src/components/player/IframePlayer";
+import Episodelist from "@/src/components/episodelist/Episodelist";
+import website_name from "@/src/config/website";
+import Sidecard from "@/src/components/sidecard/Sidecard";
+import CategoryCard from "@/src/components/categorycard/CategoryCard";
+import {
+ faClosedCaptioning,
+ faMicrophone,
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import Servers from "@/src/components/servers/Servers";
+import CategoryCardLoader from "@/src/components/Loader/CategoryCard.loader";
+import { Skeleton } from "@/src/components/ui/Skeleton/Skeleton";
+import SidecardLoader from "@/src/components/Loader/Sidecard.loader";
+import Voiceactor from "@/src/components/voiceactor/Voiceactor";
+import Watchcontrols from "@/src/components/watchcontrols/Watchcontrols";
+import useWatchControl from "@/src/hooks/useWatchControl";
+import Player from "@/src/components/player/Player";
+
+export default function Watch() {
+ const location = useLocation();
+ const navigate = useNavigate();
+ const { id: animeId } = useParams();
+ const queryParams = new URLSearchParams(location.search);
+ let initialEpisodeId = queryParams.get("ep");
+ const [tags, setTags] = useState([]);
+ const { language } = useLanguage();
+ const { homeInfo } = useHomeInfo();
+ const isFirstSet = useRef(true);
+ const [showNextEpisodeSchedule, setShowNextEpisodeSchedule] = useState(true);
+ const {
+ // error,
+ buffering,
+ streamInfo,
+ streamUrl,
+ animeInfo,
+ episodes,
+ nextEpisodeSchedule,
+ animeInfoLoading,
+ totalEpisodes,
+ isFullOverview,
+ intro,
+ outro,
+ subtitles,
+ thumbnail,
+ setIsFullOverview,
+ activeEpisodeNum,
+ seasons,
+ episodeId,
+ setEpisodeId,
+ activeServerId,
+ setActiveServerId,
+ servers,
+ serverLoading,
+ activeServerType,
+ setActiveServerType,
+ activeServerName,
+ setActiveServerName
+ } = useWatch(animeId, initialEpisodeId);
+ const {
+ autoPlay,
+ setAutoPlay,
+ autoSkipIntro,
+ setAutoSkipIntro,
+ autoNext,
+ setAutoNext,
+ } = useWatchControl();
+
+ useEffect(() => {
+ if (!episodes || episodes.length === 0) return;
+
+ const isValidEpisode = episodes.some(ep => {
+ const epNumber = ep.id.split('ep=')[1];
+ return epNumber === episodeId;
+ });
+
+ // If missing or invalid episodeId, fallback to first
+ if (!episodeId || !isValidEpisode) {
+ const fallbackId = episodes[0].id.match(/ep=(\d+)/)?.[1];
+ if (fallbackId && fallbackId !== episodeId) {
+ setEpisodeId(fallbackId);
+ }
+ return;
+ }
+
+ const newUrl = `/watch/${animeId}?ep=${episodeId}`;
+ if (isFirstSet.current) {
+ navigate(newUrl, { replace: true });
+ isFirstSet.current = false;
+ } else {
+ navigate(newUrl);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [episodeId, animeId, navigate, episodes]);
+
+ // Update document title
+ useEffect(() => {
+ if (animeInfo) {
+ document.title = `Watch ${animeInfo.title} English Sub/Dub online Free on ${website_name}`;
+ }
+ return () => {
+ document.title = `${website_name} | Free anime streaming platform`;
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [animeId]);
+
+ // Redirect if no episodes
+ useEffect(() => {
+ if (totalEpisodes !== null && totalEpisodes === 0) {
+ navigate(`/${animeId}`);
+ }
+ }, [streamInfo, episodeId, animeId, totalEpisodes, navigate]);
+
+ useEffect(() => {
+ const adjustHeight = () => {
+ if (window.innerWidth > 1200) {
+ const player = document.querySelector(".player");
+ const episodes = document.querySelector(".episodes");
+ if (player && episodes) {
+ episodes.style.height = `${player.clientHeight}px`;
+ }
+ } else {
+ const episodes = document.querySelector(".episodes");
+ if (episodes) {
+ episodes.style.height = "auto";
+ }
+ }
+ };
+ adjustHeight();
+ window.addEventListener("resize", adjustHeight);
+ return () => {
+ window.removeEventListener("resize", adjustHeight);
+ };
+ });
+
+ function Tag({ bgColor, index, icon, text }) {
+ return (
+
+ );
+ }
+
+ useEffect(() => {
+ setTags([
+ {
+ condition: animeInfo?.animeInfo?.tvInfo?.rating,
+ bgColor: "#ffffff",
+ text: animeInfo?.animeInfo?.tvInfo?.rating,
+ },
+ {
+ condition: animeInfo?.animeInfo?.tvInfo?.quality,
+ bgColor: "#FFBADE",
+ text: animeInfo?.animeInfo?.tvInfo?.quality,
+ },
+ {
+ condition: animeInfo?.animeInfo?.tvInfo?.sub,
+ icon: faClosedCaptioning,
+ bgColor: "#B0E3AF",
+ text: animeInfo?.animeInfo?.tvInfo?.sub,
+ },
+ {
+ condition: animeInfo?.animeInfo?.tvInfo?.dub,
+ icon: faMicrophone,
+ bgColor: "#B9E7FF",
+ text: animeInfo?.animeInfo?.tvInfo?.dub,
+ },
+ ]);
+ }, [animeId, animeInfo]);
+ return (
+
+
+
+
+
+ {animeInfo && (
+
+ )}
+
+
+ {!episodes ? (
+
+ ) : (
+ setEpisodeId(id)}
+ totalEpisodes={totalEpisodes}
+ />
+ )}
+
+
+
+ {!buffering ? (( activeServerName.toLowerCase()==="hd-1" || activeServerName.toLowerCase()==="hd-2" || activeServerName.toLowerCase()==="hd-3" || activeServerName.toLowerCase()==="hd-4") ?
+
setEpisodeId(id)}
+ autoNext={autoNext}
+ />: setEpisodeId(id)}
+ animeInfo={animeInfo}
+ episodeNum={activeEpisodeNum}
+ streamInfo={streamInfo}
+ />
+ ) : (
+
+
+
+ )}
+
+ {!buffering && !activeServerType ? (
+ servers ? (
+ <>
+ Probably this server is down, try other servers
+
+ Either reload or try again after sometime
+ >
+ ) : (
+ <>
+ Probably streaming server is down
+
+ Either reload or try again after sometime
+ >
+ )
+ ) : null}
+
+
+
+ {!buffering && (
+
setEpisodeId(id)}
+ />
+ )}
+
+ {seasons?.length > 0 && (
+
+
+ Watch more seasons of this anime
+
+
+ {seasons.map((season, index) => (
+
+
+ {season.season}
+
+
+
+
+ ))}
+
+
+ )}
+ {nextEpisodeSchedule?.nextEpisodeSchedule &&
+ showNextEpisodeSchedule && (
+
+
+
+ 🚀
+ {" Estimated the next episode will come at "}
+
+ {new Date(
+ new Date(
+ nextEpisodeSchedule.nextEpisodeSchedule
+ ).getTime() -
+ new Date().getTimezoneOffset() * 60000
+ ).toLocaleDateString("en-GB", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: true,
+ })}
+
+
+
setShowNextEpisodeSchedule(false)}
+ >
+ ×
+
+
+
+ )}
+
+
+
+ {animeInfo && animeInfo?.poster ? (
+
+ ) : (
+
+ )}
+
+ {animeInfo && animeInfo?.title ? (
+
+ {language ? animeInfo?.title : animeInfo?.japanese_title}
+
+ ) : (
+
+ )}
+
+ {animeInfo ? (
+ tags.map(
+ ({ condition, icon, bgColor, text }, index) =>
+ condition && (
+
+ )
+ )
+ ) : (
+
+ )}
+
+ {[
+ animeInfo?.animeInfo?.tvInfo?.showType,
+ animeInfo?.animeInfo?.tvInfo?.duration,
+ ].map(
+ (item, index) =>
+ item && (
+
+ )
+ )}
+
+
+ {animeInfo ? (
+ animeInfo?.animeInfo?.Overview && (
+
+
+
+ {animeInfo?.animeInfo?.Overview.length > 270 ? (
+ <>
+ {isFullOverview
+ ? animeInfo?.animeInfo?.Overview
+ : `${animeInfo?.animeInfo?.Overview.slice(
+ 0,
+ 270
+ )}...`}
+ setIsFullOverview(!isFullOverview)}
+ >
+ {isFullOverview ? "- Less" : "+ More"}
+
+ >
+ ) : (
+ animeInfo?.animeInfo?.Overview
+ )}
+
+
+
+ )
+ ) : (
+
+
+
+
+
+
+ )}
+
+ {`${website_name} is the best site to watch `}
+
+ {language ? animeInfo?.title : animeInfo?.japanese_title}
+
+ {` SUB online, or you can even watch `}
+
+ {language ? animeInfo?.title : animeInfo?.japanese_title}
+
+ {` DUB in HD quality.`}
+
+
+ View detail
+
+
+
+
+
+
+
+
+
Share Anime
+
to your friends
+
+
+
+
+ {animeInfo?.charactersVoiceActors.length > 0 && (
+
+ )}
+ {animeInfo?.recommended_data.length > 0 ? (
+
+ ) : (
+
+ )}
+
+
+ {animeInfo && animeInfo.related_data ? (
+
+ ) : (
+
+ )}
+ {homeInfo && homeInfo.most_popular && (
+
+ )}
+
+
+
+ );
+}
diff --git a/src/utils/category.utils.js b/src/utils/category.utils.js
new file mode 100644
index 0000000..dfc5d69
--- /dev/null
+++ b/src/utils/category.utils.js
@@ -0,0 +1,89 @@
+export const categoryRoutes = [
+ "genre/action",
+ "genre/adventure",
+ "genre/cars",
+ "genre/comedy",
+ "genre/dementia",
+ "genre/demons",
+ "genre/drama",
+ "genre/ecchi",
+ "genre/fantasy",
+ "genre/game",
+ "genre/harem",
+ "genre/historical",
+ "genre/horror",
+ "genre/isekai",
+ "genre/josei",
+ "genre/kids",
+ "genre/magic",
+ "genre/martial-arts",
+ "genre/mecha",
+ "genre/military",
+ "genre/music",
+ "genre/mystery",
+ "genre/parody",
+ "genre/police",
+ "genre/psychological",
+ "genre/romance",
+ "genre/samurai",
+ "genre/school",
+ "genre/sci-fi",
+ "genre/seinen",
+ "genre/shoujo",
+ "genre/shoujo-ai",
+ "genre/shounen",
+ "genre/shounen-ai",
+ "genre/slice-of-life",
+ "genre/space",
+ "genre/sports",
+ "genre/super-power",
+ "genre/supernatural",
+ "genre/thriller",
+ "genre/vampire",
+ "top-airing",
+ "most-popular",
+ "most-favorite",
+ "completed",
+ "recently-updated",
+ "recently-added",
+ "top-upcoming",
+ "subbed-anime",
+ "dubbed-anime",
+ "movie",
+ "special",
+ "ova",
+ "ona",
+ "tv",
+];
+
+export const azRoute = [
+ "az-list",
+ "az-list/other",
+ "az-list/0-9",
+ "az-list/a",
+ "az-list/b",
+ "az-list/c",
+ "az-list/d",
+ "az-list/e",
+ "az-list/f",
+ "az-list/g",
+ "az-list/h",
+ "az-list/i",
+ "az-list/j",
+ "az-list/k",
+ "az-list/l",
+ "az-list/m",
+ "az-list/n",
+ "az-list/o",
+ "az-list/p",
+ "az-list/q",
+ "az-list/r",
+ "az-list/s",
+ "az-list/t",
+ "az-list/u",
+ "az-list/v",
+ "az-list/w",
+ "az-list/x",
+ "az-list/y",
+ "az-list/z",
+];
diff --git a/src/utils/getAnimeInfo.utils.js b/src/utils/getAnimeInfo.utils.js
new file mode 100644
index 0000000..8736365
--- /dev/null
+++ b/src/utils/getAnimeInfo.utils.js
@@ -0,0 +1,18 @@
+import axios from "axios";
+
+export default async function fetchAnimeInfo(id, random = false) {
+ const api_url = import.meta.env.VITE_API_URL;
+ try {
+ if (random) {
+ const id = await axios.get(`${api_url}/random/id`);
+ const response = await axios.get(`${api_url}/info?id=${id.data.results}`);
+ return response.data.results;
+ } else {
+ const response = await axios.get(`${api_url}/info?id=${id}`);
+ return response.data.results;
+ }
+ } catch (error) {
+ console.error("Error fetching anime info:", error);
+ return error;
+ }
+}
diff --git a/src/utils/getCategoryInfo.utils.js b/src/utils/getCategoryInfo.utils.js
new file mode 100644
index 0000000..409631e
--- /dev/null
+++ b/src/utils/getCategoryInfo.utils.js
@@ -0,0 +1,14 @@
+import axios from "axios";
+
+const getCategoryInfo = async (path,page) => {
+ const api_url = import.meta.env.VITE_API_URL;
+ try {
+ const response = await axios.get(`${api_url}/${path}?page=${page}`);
+ return response.data.results;
+ } catch (err) {
+ console.error("Error fetching genre info:", err);
+ return err;
+ }
+};
+
+export default getCategoryInfo;
diff --git a/src/utils/getEpisodes.utils.js b/src/utils/getEpisodes.utils.js
new file mode 100644
index 0000000..d98c5f6
--- /dev/null
+++ b/src/utils/getEpisodes.utils.js
@@ -0,0 +1,12 @@
+import axios from "axios";
+
+export default async function getEpisodes(id) {
+ const api_url = import.meta.env.VITE_API_URL;
+ try {
+ const response = await axios.get(`${api_url}/episodes/${id}`);
+ return response.data.results;
+ } catch (error) {
+ console.error("Error fetching anime info:", error);
+ return error;
+ }
+}
diff --git a/src/utils/getHomeInfo.utils.js b/src/utils/getHomeInfo.utils.js
new file mode 100644
index 0000000..655182b
--- /dev/null
+++ b/src/utils/getHomeInfo.utils.js
@@ -0,0 +1,58 @@
+import axios from "axios";
+
+const CACHE_KEY = "homeInfoCache";
+const CACHE_DURATION = 24 * 60 * 60 * 1000;
+
+export default async function getHomeInfo() {
+ const api_url = import.meta.env.VITE_API_URL;
+
+ const currentTime = Date.now();
+ const cachedData = JSON.parse(localStorage.getItem(CACHE_KEY));
+
+ if (cachedData && currentTime - cachedData.timestamp < CACHE_DURATION) {
+ return cachedData.data;
+ }
+ const response = await axios.get(`${api_url}`);
+ if (
+ !response.data.results ||
+ Object.keys(response.data.results).length === 0
+ ) {
+ return null;
+ }
+ const {
+ spotlights,
+ trending,
+ topTen: topten,
+ today: todaySchedule,
+ topAiring: top_airing,
+ mostPopular: most_popular,
+ mostFavorite: most_favorite,
+ latestCompleted: latest_completed,
+ latestEpisode: latest_episode,
+ topUpcoming: top_upcoming,
+ recentlyAdded: recently_added,
+ genres,
+ } = response.data.results;
+
+ const dataToCache = {
+ data: {
+ spotlights,
+ trending,
+ topten,
+ todaySchedule,
+ top_airing,
+ most_popular,
+ most_favorite,
+ latest_completed,
+ latest_episode,
+ top_upcoming,
+ recently_added,
+ genres,
+ },
+ timestamp: currentTime,
+ };
+
+ localStorage.setItem(CACHE_KEY, JSON.stringify(dataToCache));
+
+ return dataToCache.data;
+}
diff --git a/src/utils/getNextEpisodeSchedule.utils.js b/src/utils/getNextEpisodeSchedule.utils.js
new file mode 100644
index 0000000..e01cb5b
--- /dev/null
+++ b/src/utils/getNextEpisodeSchedule.utils.js
@@ -0,0 +1,14 @@
+import axios from "axios";
+
+const getNextEpisodeSchedule = async (id) => {
+ const api_url = import.meta.env.VITE_API_URL;
+ try {
+ const response = await axios.get(`${api_url}/schedule/${id}`);
+ return response.data.results;
+ } catch (err) {
+ console.error("Error fetching next episode schedule:", err);
+ return err;
+ }
+};
+
+export default getNextEpisodeSchedule;
diff --git a/src/utils/getProducer.utils.js b/src/utils/getProducer.utils.js
new file mode 100644
index 0000000..8f4e5bc
--- /dev/null
+++ b/src/utils/getProducer.utils.js
@@ -0,0 +1,14 @@
+import axios from "axios";
+
+const getProducer = async (producer, page) => {
+ const api_url = import.meta.env.VITE_API_URL;
+ try {
+ const response = await axios.get(`${api_url}/producer/${producer}?page=${page}`);
+ return response.data.results;
+ } catch (err) {
+ console.error("Error fetching genre info:", err);
+ return err;
+ }
+};
+
+export default getProducer;
diff --git a/src/utils/getQtip.utils.js b/src/utils/getQtip.utils.js
new file mode 100644
index 0000000..19c8026
--- /dev/null
+++ b/src/utils/getQtip.utils.js
@@ -0,0 +1,18 @@
+import axios from "axios";
+
+const getQtip = async (id) => {
+ try {
+ let workerUrls = import.meta.env.VITE_WORKER_URL?.split(",");
+ let baseUrl = workerUrls?.length
+ ? workerUrls[Math.floor(Math.random() * workerUrls.length)]
+ : import.meta.env.VITE_API_URL;
+ if (!baseUrl) throw new Error("No API endpoint defined.");
+ const response = await axios.get(`${baseUrl}/qtip/${id.split("-").pop()}`);
+ return response.data.results;
+ } catch (err) {
+ console.error("Error fetching genre info:", err);
+ return null;
+ }
+};
+
+export default getQtip;
diff --git a/src/utils/getScheduleInfo.utils.js b/src/utils/getScheduleInfo.utils.js
new file mode 100644
index 0000000..1095c23
--- /dev/null
+++ b/src/utils/getScheduleInfo.utils.js
@@ -0,0 +1,12 @@
+import axios from "axios";
+
+export default async function getSchedInfo(date) {
+ try {
+ const api_url = import.meta.env.VITE_API_URL;
+ const response = await axios.get(`${api_url}/schedule?date=${date}`);
+ return response.data.results;
+ } catch (error) {
+ console.error(error);
+ return error;
+ }
+}
diff --git a/src/utils/getSearch.utils.js b/src/utils/getSearch.utils.js
new file mode 100644
index 0000000..48aafb9
--- /dev/null
+++ b/src/utils/getSearch.utils.js
@@ -0,0 +1,17 @@
+import axios from "axios";
+
+const getSearch = async (keyword, page) => {
+ const api_url = import.meta.env.VITE_API_URL;
+ if (!page) page = 1;
+ try {
+ const response = await axios.get(
+ `${api_url}/search?keyword=${keyword}&page=${page}`
+ );
+ return response.data.results;
+ } catch (err) {
+ console.error("Error fetching genre info:", err);
+ return err;
+ }
+};
+
+export default getSearch;
diff --git a/src/utils/getSearchSuggestion.utils.js b/src/utils/getSearchSuggestion.utils.js
new file mode 100644
index 0000000..0d1173d
--- /dev/null
+++ b/src/utils/getSearchSuggestion.utils.js
@@ -0,0 +1,16 @@
+import axios from "axios";
+
+const getSearchSuggestion = async (keyword) => {
+ const api_url = import.meta.env.VITE_API_URL;
+ try {
+ const response = await axios.get(
+ `${api_url}/search/suggest?keyword=${keyword}`
+ );
+ return response.data.results;
+ } catch (err) {
+ console.error("Error fetching genre info:", err);
+ return err;
+ }
+};
+
+export default getSearchSuggestion;
diff --git a/src/utils/getServers.utils.js b/src/utils/getServers.utils.js
new file mode 100644
index 0000000..0ed3e14
--- /dev/null
+++ b/src/utils/getServers.utils.js
@@ -0,0 +1,14 @@
+import axios from "axios";
+
+export default async function getServers(animeId, episodeId) {
+ try {
+ const api_url = import.meta.env.VITE_API_URL;
+ const response = await axios.get(
+ `${api_url}/servers/${animeId}?ep=${episodeId}`
+ );
+ return response.data.results;
+ } catch (error) {
+ console.error(error);
+ return error;
+ }
+}
diff --git a/src/utils/getStreamInfo.utils.js b/src/utils/getStreamInfo.utils.js
new file mode 100644
index 0000000..d044e5c
--- /dev/null
+++ b/src/utils/getStreamInfo.utils.js
@@ -0,0 +1,12 @@
+import axios from "axios";
+
+export default async function getStreamInfo(animeId,episodeId,serverName,type) {
+ const api_url = import.meta.env.VITE_API_URL;
+ try {
+ const response = await axios.get(`${api_url}/stream?id=${animeId}?ep=${episodeId}&server=${serverName}&type=${type}`);
+ return response.data.results;
+ } catch (error) {
+ console.error("Error fetching stream info:", error);
+ return error;
+ }
+}
diff --git a/src/utils/getTopSearch.utils.js b/src/utils/getTopSearch.utils.js
new file mode 100644
index 0000000..a1d1282
--- /dev/null
+++ b/src/utils/getTopSearch.utils.js
@@ -0,0 +1,32 @@
+import axios from "axios";
+
+const getTopSearch = async () => {
+ try {
+ let workerUrls = import.meta.env.VITE_WORKER_URL?.split(",");
+ let baseUrl = workerUrls?.length
+ ? workerUrls[Math.floor(Math.random() * workerUrls.length)]
+ : import.meta.env.VITE_API_URL;
+ const storedData = localStorage.getItem("topSearch");
+ if (storedData) {
+ const { data, timestamp } = JSON.parse(storedData);
+ if (Date.now() - timestamp <= 7 * 24 * 60 * 60 * 1000) {
+ return data;
+ }
+ }
+ const { data } = await axios.get(`${baseUrl}/top-search`);
+ const results = data?.results || [];
+ if (results.length) {
+ localStorage.setItem(
+ "topSearch",
+ JSON.stringify({ data: results, timestamp: Date.now() })
+ );
+ return results;
+ }
+ return [];
+ } catch (error) {
+ console.error("Error fetching top search data:", error);
+ return null;
+ }
+};
+
+export default getTopSearch;
diff --git a/src/utils/getVoiceActor.utils.js b/src/utils/getVoiceActor.utils.js
new file mode 100644
index 0000000..57afecf
--- /dev/null
+++ b/src/utils/getVoiceActor.utils.js
@@ -0,0 +1,14 @@
+import axios from "axios";
+
+export default async function fetchVoiceActorInfo(id, page) {
+ const api_url = import.meta.env.VITE_API_URL;
+ try {
+ const response = await axios.get(
+ `${api_url}/character/list/${id}?page=${page}`
+ );
+ return response.data.results;
+ } catch (error) {
+ console.error("Error fetching anime info:", error);
+ return error;
+ }
+}
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..2725950
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,62 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ darkMode: ["class"],
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
+ theme: {
+ extend: {
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ screens: {
+ "custom-md": "600px",
+ "custom-xl": "1200px",
+ "ultra-wide":"1660px",
+ },
+ colors: {
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ chart: {
+ 1: "hsl(var(--chart-1))",
+ 2: "hsl(var(--chart-2))",
+ 3: "hsl(var(--chart-3))",
+ 4: "hsl(var(--chart-4))",
+ 5: "hsl(var(--chart-5))",
+ },
+ },
+ },
+ },
+ plugins: [require("tailwindcss-animate")],
+};
diff --git a/vercel.json b/vercel.json
new file mode 100644
index 0000000..2e3d156
--- /dev/null
+++ b/vercel.json
@@ -0,0 +1,3 @@
+{
+ "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }]
+}
\ No newline at end of file
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..454ab3e
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,12 @@
+import path from "path"
+import react from "@vitejs/plugin-react"
+import { defineConfig } from "vite"
+
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./"),
+ },
+ },
+})